diff --git a/lib/common/src/version.h b/lib/common/src/version.h index 45c3d106..ceaadf36 100644 --- a/lib/common/src/version.h +++ b/lib/common/src/version.h @@ -7,4 +7,4 @@ #pragma once -#define NCHAT_VERSION "4.02" +#define NCHAT_VERSION "4.03" diff --git a/lib/tgchat/ext/td/CMake/GetGitRevisionDescription.cmake b/lib/tgchat/ext/td/CMake/GetGitRevisionDescription.cmake index 42077296..724d0782 100644 --- a/lib/tgchat/ext/td/CMake/GetGitRevisionDescription.cmake +++ b/lib/tgchat/ext/td/CMake/GetGitRevisionDescription.cmake @@ -71,7 +71,7 @@ function(get_git_head_revision _refspecvar _hashvar) return() endif() - find_package(Git) + find_package(Git QUIET) # Check if the current source dir is a git submodule or a worktree. # In both cases .git is a file instead of a directory. diff --git a/lib/tgchat/ext/td/CMakeLists.txt b/lib/tgchat/ext/td/CMakeLists.txt index c56420f3..4c6ef2de 100644 --- a/lib/tgchat/ext/td/CMakeLists.txt +++ b/lib/tgchat/ext/td/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.19 LANGUAGES CXX C) +project(TDLib VERSION 1.8.21 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -96,9 +96,9 @@ if (EMSCRIPTEN) set(ZLIB_INCLUDE_DIR) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s MEMFS_APPEND_TO_TYPED_ARRAYS=1 -s USE_ZLIB=1 -s MODULARIZE=1 \ - -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js") + -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s MEMFS_APPEND_TO_TYPED_ARRAYS=1 -s USE_ZLIB=1 -s MODULARIZE=1 \ - -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js") + -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1") @@ -296,6 +296,7 @@ set(TDLIB_SOURCE td/telegram/BackgroundInfo.cpp td/telegram/BackgroundManager.cpp td/telegram/BackgroundType.cpp + td/telegram/BoostManager.cpp td/telegram/BotCommand.cpp td/telegram/BotCommandScope.cpp td/telegram/BotInfoManager.cpp @@ -368,6 +369,7 @@ set(TDLIB_SOURCE td/telegram/ForumTopicManager.cpp td/telegram/Game.cpp td/telegram/GameManager.cpp + td/telegram/GiveawayParameters.cpp td/telegram/Global.cpp td/telegram/GlobalPrivacySettings.cpp td/telegram/GroupCallManager.cpp @@ -395,6 +397,7 @@ set(TDLIB_SOURCE td/telegram/MessageExtendedMedia.cpp td/telegram/MessageId.cpp td/telegram/MessageInputReplyTo.cpp + td/telegram/MessageOrigin.cpp td/telegram/MessageReaction.cpp td/telegram/MessageReplyHeader.cpp td/telegram/MessageReplyInfo.cpp @@ -451,6 +454,7 @@ set(TDLIB_SOURCE td/telegram/ReactionManager.cpp td/telegram/ReactionType.cpp td/telegram/RecentDialogList.cpp + td/telegram/RepliedMessageInfo.cpp td/telegram/ReplyMarkup.cpp td/telegram/ReportReason.cpp td/telegram/RequestedDialogType.cpp @@ -471,6 +475,7 @@ set(TDLIB_SOURCE td/telegram/StateManager.cpp td/telegram/StatisticsManager.cpp td/telegram/StickerFormat.cpp + td/telegram/StickerListType.cpp td/telegram/StickerMaskPosition.cpp td/telegram/StickerPhotoSize.cpp td/telegram/StickerSetId.cpp @@ -536,6 +541,7 @@ set(TDLIB_SOURCE td/mtproto/TransportType.h td/mtproto/utils.h + td/telegram/AccentColorId.h td/telegram/AccessRights.h td/telegram/AccountManager.h td/telegram/AffectedHistory.h @@ -551,6 +557,7 @@ set(TDLIB_SOURCE td/telegram/BackgroundManager.h td/telegram/BackgroundType.h td/telegram/BlockListId.h + td/telegram/BoostManager.h td/telegram/BotCommand.h td/telegram/BotCommandScope.h td/telegram/BotInfoManager.h @@ -645,6 +652,7 @@ set(TDLIB_SOURCE td/telegram/Game.h td/telegram/GameManager.h td/telegram/GitCommitHash.h + td/telegram/GiveawayParameters.h td/telegram/Global.h td/telegram/GlobalPrivacySettings.h td/telegram/GroupCallId.h @@ -679,6 +687,7 @@ set(TDLIB_SOURCE td/telegram/MessageId.h td/telegram/MessageInputReplyTo.h td/telegram/MessageLinkInfo.h + td/telegram/MessageOrigin.h td/telegram/MessageReaction.h td/telegram/MessageReplyHeader.h td/telegram/MessageReplyInfo.h @@ -755,6 +764,7 @@ set(TDLIB_SOURCE td/telegram/ReactionManager.h td/telegram/ReactionType.h td/telegram/RecentDialogList.h + td/telegram/RepliedMessageInfo.h td/telegram/ReplyMarkup.h td/telegram/ReportReason.h td/telegram/RequestActor.h @@ -781,6 +791,7 @@ set(TDLIB_SOURCE td/telegram/StateManager.h td/telegram/StatisticsManager.h td/telegram/StickerFormat.h + td/telegram/StickerListType.h td/telegram/StickerMaskPosition.h td/telegram/StickerPhotoSize.h td/telegram/StickerSetId.h @@ -849,12 +860,15 @@ set(TDLIB_SOURCE td/telegram/ForumTopicIcon.hpp td/telegram/ForumTopicInfo.hpp td/telegram/Game.hpp + td/telegram/GiveawayParameters.hpp td/telegram/InputInvoice.hpp td/telegram/InputMessageText.hpp td/telegram/MediaArea.hpp td/telegram/MediaAreaCoordinates.hpp td/telegram/MessageEntity.hpp td/telegram/MessageExtendedMedia.hpp + td/telegram/MessageInputReplyTo.hpp + td/telegram/MessageOrigin.hpp td/telegram/MessageReaction.hpp td/telegram/MessageReplyInfo.hpp td/telegram/MinChannel.hpp @@ -868,6 +882,7 @@ set(TDLIB_SOURCE td/telegram/PremiumGiftOption.hpp td/telegram/ReactionManager.hpp td/telegram/ReactionType.hpp + td/telegram/RepliedMessageInfo.hpp td/telegram/ReplyMarkup.hpp td/telegram/RequestedDialogType.hpp td/telegram/ScopeNotificationSettings.hpp @@ -1048,11 +1063,9 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(tg_cli td/telegram/cli.cpp ${TL_TD_JSON_SOURCE}) if (NOT READLINE_FOUND) - find_package(Readline) + find_package(Readline QUIET) endif() - if (NOT READLINE_FOUND) - message(STATUS "Could NOT find Readline (this is NOT an error)") - else() + if (READLINE_FOUND) message(STATUS "Found Readline: ${READLINE_INCLUDE_DIR} ${READLINE_LIBRARY}") if (NOT USABLE_READLINE_FOUND) set(CMAKE_REQUIRED_INCLUDES "${READLINE_INCLUDE_DIR}") @@ -1090,13 +1103,27 @@ add_library(Td::TdStatic ALIAS TdStatic) add_library(Td::TdJson ALIAS TdJson) add_library(Td::TdJsonStatic ALIAS TdJsonStatic) -install(TARGETS tdjson TdJson tdjson_static TdJsonStatic tdjson_private tdclient tdcore tdapi TdStatic EXPORT TdTargets +set(INSTALL_TARGETS tdjson TdJson) +set(INSTALL_STATIC_TARGETS tdjson_static TdJsonStatic tdjson_private tdcore) +if (BUILD_SHARED_LIBS) + set(INSTALL_TARGETS ${INSTALL_TARGETS} tdclient TdStatic tdapi) +else() + set(INSTALL_STATIC_TARGETS ${INSTALL_STATIC_TARGETS} tdclient TdStatic tdapi) +endif() + +install(TARGETS ${INSTALL_TARGETS} EXPORT TdTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" ) +install(TARGETS ${INSTALL_STATIC_TARGETS} EXPORT TdStaticTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + # generate pkg-config files include(GeneratePkgConfig) @@ -1124,6 +1151,11 @@ install(EXPORT TdTargets NAMESPACE Td:: DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Td" ) +install(EXPORT TdStaticTargets + FILE TdStaticTargets.cmake + NAMESPACE Td:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Td" +) # Install tdjson/tdjson_static: install(FILES ${TD_JSON_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/td/telegram/tdjson_export.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/telegram") diff --git a/lib/tgchat/ext/td/README.md b/lib/tgchat/ext/td/README.md index 8045bbc9..9d6c3dd2 100644 --- a/lib/tgchat/ext/td/README.md +++ b/lib/tgchat/ext/td/README.md @@ -103,7 +103,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.19 REQUIRED) +find_package(Td 1.8.21 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt). diff --git a/lib/tgchat/ext/td/SplitSource.php b/lib/tgchat/ext/td/SplitSource.php index 9a4522d5..36859e4c 100644 --- a/lib/tgchat/ext/td/SplitSource.php +++ b/lib/tgchat/ext/td/SplitSource.php @@ -283,6 +283,7 @@ function ($matches) use ($needed_std_headers) { 'auth_manager[_(-][^.]|AuthManager' => 'AuthManager', 'autosave_manager[_(-][^.]|AutosaveManager' => 'AutosaveManager', 'background_manager[_(-][^.]|BackgroundManager' => "BackgroundManager", + 'boost_manager[_(-][^.]|BoostManager' => "BoostManager", 'bot_info_manager[_(-][^.]|BotInfoManager' => "BotInfoManager", 'contacts_manager[_(-][^.]|ContactsManager([^ ;.]| [^*])' => 'ContactsManager', 'country_info_manager[_(-][^.]|CountryInfoManager' => 'CountryInfoManager', diff --git a/lib/tgchat/ext/td/TdConfig.cmake b/lib/tgchat/ext/td/TdConfig.cmake index 3cdff38c..cbe7a034 100644 --- a/lib/tgchat/ext/td/TdConfig.cmake +++ b/lib/tgchat/ext/td/TdConfig.cmake @@ -1,3 +1,6 @@ include(CMakeFindDependencyMacro) #TODO: write all external dependencies include("${CMAKE_CURRENT_LIST_DIR}/TdTargets.cmake") +if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/TdStaticTargets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/TdStaticTargets.cmake") +endif() diff --git a/lib/tgchat/ext/td/benchmark/bench_misc.cpp b/lib/tgchat/ext/td/benchmark/bench_misc.cpp index ed400fb4..2142a8e5 100644 --- a/lib/tgchat/ext/td/benchmark/bench_misc.cpp +++ b/lib/tgchat/ext/td/benchmark/bench_misc.cpp @@ -4,9 +4,11 @@ // 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/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/telegram_api.hpp" +#include "td/utils/algorithm.h" #include "td/utils/benchmark.h" #include "td/utils/common.h" #include "td/utils/logging.h" @@ -17,9 +19,12 @@ #include "td/utils/port/RwMutex.h" #include "td/utils/port/Stat.h" #include "td/utils/port/thread.h" +#include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" +#include "td/utils/StackAllocator.h" #include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" #include "td/utils/ThreadSafeCounter.h" #if !TD_WINDOWS @@ -50,7 +55,7 @@ class F { } }; -BENCH(Call, "TL Call") { +BENCH(TlCall, "TL Call") { td::tl_object_ptr x = td::make_tl_object(0); td::uint32 res = 0; F f(res); @@ -60,6 +65,67 @@ BENCH(Call, "TL Call") { td::do_not_optimize_away(res); } +static td::td_api::object_ptr get_file_object() { + return td::td_api::make_object( + 12345, 123456, 123456, + td::td_api::make_object( + "/android/data/0/data/org.telegram.data/files/photos/12345678901234567890_123.jpg", true, true, false, true, + 0, 123456, 123456), + td::td_api::make_object("abacabadabacabaeabacabadabacabafabacabadabacabaeabacabadabacaba", + "abacabadabacabaeabacabadabacaba", false, true, 123456)); +} + +BENCH(ToStringIntSmall, "to_string small") { + auto buf = td::StackAllocator::alloc(1000); + td::StringBuilder sb(buf.as_slice()); + for (int i = 0; i < n; i++) { + sb << td::Random::fast(0, 100); + sb.clear(); + } +} + +BENCH(ToStringIntBig, "to_string big") { + auto buf = td::StackAllocator::alloc(1000); + td::StringBuilder sb(buf.as_slice()); + for (int i = 0; i < n; i++) { + sb << 1234567890; + sb.clear(); + } +} + +BENCH(TlToStringUpdateFile, "TL to_string updateFile") { + auto x = td::td_api::make_object(get_file_object()); + + std::size_t res = 0; + for (int i = 0; i < n; i++) { + res += to_string(x).size(); + } + td::do_not_optimize_away(res); +} + +BENCH(TlToStringMessage, "TL to_string message") { + auto x = td::td_api::make_object(); + x->id_ = 123456000111; + x->sender_id_ = td::td_api::make_object(123456000112); + x->chat_id_ = 123456000112; + x->sending_state_ = td::td_api::make_object(0); + x->date_ = 1699999999; + auto photo = td::td_api::make_object(); + for (int i = 0; i < 4; i++) { + photo->sizes_.push_back(td::td_api::make_object( + "a", get_file_object(), 160, 160, + td::vector{10000, 20000, 30000, 50000, 70000, 90000, 120000, 150000, 180000, 220000})); + } + x->content_ = td::td_api::make_object( + std::move(photo), td::td_api::make_object(), false, false); + + std::size_t res = 0; + for (int i = 0; i < n; i++) { + res += to_string(x).size(); + } + td::do_not_optimize_away(res); +} + #if !TD_EVENTFD_UNSUPPORTED BENCH(EventFd, "EventFd") { td::EventFd fd; @@ -632,9 +698,75 @@ class DuplicateCheckerBenchEvenOdd final : public td::Benchmark { } }; +BENCH(AddToTopStd, "add_to_top std") { + td::vector v; + for (int i = 0; i < n; i++) { + for (size_t j = 0; j < 10; j++) { + auto value = td::Random::fast(0, 9); + auto it = std::find(v.begin(), v.end(), value); + if (it == v.end()) { + if (v.size() == 8) { + v.back() = value; + } else { + v.push_back(value); + } + it = v.end() - 1; + } + std::rotate(v.begin(), it, it + 1); + } + } +} + +BENCH(AddToTopTd, "add_to_top td") { + td::vector v; + for (int i = 0; i < n; i++) { + for (size_t j = 0; j < 10; j++) { + td::add_to_top(v, 8, td::Random::fast(0, 9)); + } + } +} + +BENCH(AnyOfStd, "any_of std") { + td::vector v; + for (int i = 0; i < 100; i++) { + v.push_back(i); + } + int res = 0; + for (int i = 0; i < n; i++) { + int rem = td::Random::fast(0, 127); + res += static_cast(std::any_of(v.begin(), v.end(), [rem](int x) { return (x & 127) == rem; })); + } + td::do_not_optimize_away(res); +} + +BENCH(AnyOfTd, "any_of td") { + td::vector v; + for (int i = 0; i < 100; i++) { + v.push_back(i); + } + int res = 0; + for (int i = 0; i < n; i++) { + int rem = td::Random::fast(0, 127); + res += static_cast(td::any_of(v, [rem](int x) { return (x & 127) == rem; })); + } + td::do_not_optimize_away(res); +} + int main() { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG)); + td::bench(AnyOfStdBench()); + td::bench(AnyOfTdBench()); + + td::bench(ToStringIntSmallBench()); + td::bench(ToStringIntBigBench()); + + td::bench(AddToTopStdBench()); + td::bench(AddToTopTdBench()); + + td::bench(TlToStringUpdateFileBench()); + td::bench(TlToStringMessageBench()); + td::bench(DuplicateCheckerBenchEvenOdd>()); td::bench(DuplicateCheckerBenchEvenOdd>()); td::bench(DuplicateCheckerBenchEvenOdd>()); @@ -691,7 +823,7 @@ int main() { td::bench(CreateFileBench()); td::bench(PwriteBench()); - td::bench(CallBench()); + td::bench(TlCallBench()); #if !TD_THREAD_UNSUPPORTED td::bench(ThreadNewBench()); #endif diff --git a/lib/tgchat/ext/td/example/README.md b/lib/tgchat/ext/td/example/README.md index 99efe9df..7a29c3d1 100644 --- a/lib/tgchat/ext/td/example/README.md +++ b/lib/tgchat/ext/td/example/README.md @@ -63,7 +63,7 @@ You can also check out [example/python/tdjson_example.py](https://github.com/tdl TDLib can be compiled to WebAssembly or asm.js and used in a browser from JavaScript. See [tdweb](https://github.com/tdlib/td/tree/master/example/web) as a convenient wrapper for TDLib in a browser and [telegram-react](https://github.com/evgeny-nadymov/telegram-react) as an example of a TDLib-based Telegram client. -See also [Svelte-tdweb-starter](https://github.com/gennadypolakov/svelte-tdweb-starter) - Svelte wrapper for tdweb, and [Telegram-Photoframe](https://github.com/lukefx/telegram-photoframe) - a web application that displays your prefered group or channel as Photoframe. +See also [Svelte-tdweb-starter](https://github.com/gennadypolakov/svelte-tdweb-starter) - Svelte wrapper for tdweb, and [Telegram-Photoframe](https://github.com/lukefx/telegram-photoframe) - a web application that displays your preferred group or channel as Photoframe. TDLib can be used from Node.js through the [JSON](https://github.com/tdlib/td#using-json) interface. diff --git a/lib/tgchat/ext/td/example/cpp/CMakeLists.txt b/lib/tgchat/ext/td/example/cpp/CMakeLists.txt index 18074d1b..704d5be8 100644 --- a/lib/tgchat/ext/td/example/cpp/CMakeLists.txt +++ b/lib/tgchat/ext/td/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.19 REQUIRED) +find_package(Td 1.8.21 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/lib/tgchat/ext/td/example/csharp/TdExample.cs b/lib/tgchat/ext/td/example/csharp/TdExample.cs index c09b0fb3..0268af55 100644 --- a/lib/tgchat/ext/td/example/csharp/TdExample.cs +++ b/lib/tgchat/ext/td/example/csharp/TdExample.cs @@ -210,7 +210,7 @@ private static void sendMessage(long chatId, string message) TdApi.InlineKeyboardButton[] row = { new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl()) }; TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][] { row, row, row }); - TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true); + TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), null, true); _client.Send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), _defaultHandler); } diff --git a/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec b/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec index 1418bf75..e43d19d4 100644 --- a/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec +++ b/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec @@ -2,7 +2,7 @@ Telegram.Td.UWP - 1.8.19 + 1.8.21 TDLib for Universal Windows Platform Telegram Telegram diff --git a/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest b/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest index 184cefbf..e39d67b2 100644 --- a/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest +++ b/lib/tgchat/ext/td/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/lib/tgchat/ext/td/sqlite/CMakeLists.txt b/lib/tgchat/ext/td/sqlite/CMakeLists.txt index cc698b2b..82ceb04c 100644 --- a/lib/tgchat/ext/td/sqlite/CMakeLists.txt +++ b/lib/tgchat/ext/td/sqlite/CMakeLists.txt @@ -70,7 +70,7 @@ elseif (MSVC) target_compile_options(tdsqlite PRIVATE /wd4996) endif() -install(TARGETS tdsqlite EXPORT TdTargets +install(TARGETS tdsqlite EXPORT TdStaticTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) diff --git a/lib/tgchat/ext/td/td/generate/DoxygenTlDocumentationGenerator.php b/lib/tgchat/ext/td/td/generate/DoxygenTlDocumentationGenerator.php index 95475e64..468bd18d 100644 --- a/lib/tgchat/ext/td/td/generate/DoxygenTlDocumentationGenerator.php +++ b/lib/tgchat/ext/td/td/generate/DoxygenTlDocumentationGenerator.php @@ -236,7 +236,7 @@ protected function addGlobalDocumentation() * auto message_text = td::td_api::make_object("Hello, world!!!", * td::td_api::array>()); * auto send_message_request = td::td_api::make_object(chat_id, 0, nullptr, nullptr, nullptr, - * td::td_api::make_object(std::move(message_text), false, true)); + * td::td_api::make_object(std::move(message_text), nullptr, true)); * \\endcode * * \\tparam Type Type of an object to construct. diff --git a/lib/tgchat/ext/td/td/generate/scheme/td_api.tl b/lib/tgchat/ext/td/td/generate/scheme/td_api.tl index ecfc9fb0..c2b106b8 100644 --- a/lib/tgchat/ext/td/td/generate/scheme/td_api.tl +++ b/lib/tgchat/ext/td/td/generate/scheme/td_api.tl @@ -105,7 +105,7 @@ textEntity offset:int32 length:int32 type:TextEntityType = TextEntity; textEntities entities:vector = TextEntities; //@description A text with some entities @text The text @entities Entities contained in the text. Entities can be nested, but must not mutually intersect with each other. -//-Pre, Code and PreCode entities can't contain other entities. Bold, Italic, Underline, Strikethrough, and Spoiler entities can contain and can be part of any other entities. All other entities can't contain each other +//-Pre, Code and PreCode entities can't contain other entities. BlockQuote entities can't contain other BlockQuote entities. Bold, Italic, Underline, Strikethrough, and Spoiler entities can contain and can be part of any other entities. All other entities can't contain each other formattedText text:string entities:vector = FormattedText; @@ -632,11 +632,11 @@ inputChatPhotoSticker sticker:chatPhotoSticker = InputChatPhoto; //@description Describes actions that a user is allowed to take in a chat -//@can_send_basic_messages True, if the user can send text messages, contacts, invoices, locations, and venues +//@can_send_basic_messages True, if the user can send text messages, contacts, giveaways, invoices, locations, and venues //@can_send_audios True, if the user can send music files //@can_send_documents True, if the user can send documents -//@can_send_photos True, if the user can send audio photos -//@can_send_videos True, if the user can send audio videos +//@can_send_photos True, if the user can send photos +//@can_send_videos True, if the user can send videos //@can_send_video_notes True, if the user can send video notes //@can_send_voice_notes True, if the user can send voice notes //@can_send_polls True, if the user can send polls @@ -649,14 +649,13 @@ inputChatPhotoSticker sticker:chatPhotoSticker = InputChatPhoto; chatPermissions can_send_basic_messages:Bool can_send_audios:Bool can_send_documents:Bool can_send_photos:Bool can_send_videos:Bool can_send_video_notes:Bool can_send_voice_notes:Bool can_send_polls:Bool can_send_other_messages:Bool can_add_web_page_previews:Bool can_change_info:Bool can_invite_users:Bool can_pin_messages:Bool can_manage_topics:Bool = ChatPermissions; //@description Describes rights of the administrator -//@can_manage_chat True, if the administrator can get chat event log, get chat statistics, get chat boosts in channels, get message statistics in channels, get channel members, -//-see anonymous administrators in supergroups and ignore slow mode. Implied by any other privilege; applicable to supergroups and channels only +//@can_manage_chat True, if the administrator can get chat event log, get chat boosts in channels, get channel members, report supergroup spam messages, see anonymous administrators in supergroups and ignore slow mode. Implied by any other privilege; applicable to supergroups and channels only //@can_change_info True, if the administrator can change the chat title, photo, and other settings -//@can_post_messages True, if the administrator can create channel posts; applicable to channels only +//@can_post_messages True, if the administrator can create channel posts or view channel statistics; applicable to channels only //@can_edit_messages True, if the administrator can edit messages of other users and pin messages; applicable to channels only //@can_delete_messages True, if the administrator can delete messages of other users //@can_invite_users True, if the administrator can invite new users to the chat -//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members; always true for channels +//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members or view supergroup statistics; always true for channels //@can_pin_messages True, if the administrator can pin messages; applicable to basic groups and supergroups only //@can_manage_topics True, if the administrator can manage topics; applicable to forum supergroups only //@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them @@ -684,6 +683,72 @@ premiumPaymentOption currency:string amount:int53 discount_percentage:int32 mont //@last_transaction_id Identifier of the last in-store transaction for the currently used option premiumStatePaymentOption payment_option:premiumPaymentOption is_current:Bool is_upgrade:Bool last_transaction_id:string = PremiumStatePaymentOption; +//@description Describes an option for creating Telegram Premium gift codes +//@currency ISO 4217 currency code for Telegram Premium gift code payment +//@amount The amount to pay, in the smallest units of the currency +//@user_count Number of users which will be able to activate the gift codes +//@month_count Number of month the Telegram Premium subscription will be active +//@store_product_id Identifier of the store product associated with the option; may be empty if none +//@store_product_quantity Number of times the store product must be paid +premiumGiftCodePaymentOption currency:string amount:int53 user_count:int32 month_count:int32 store_product_id:string store_product_quantity:int32 = PremiumGiftCodePaymentOption; + +//@description Contains a list of options for creating Telegram Premium gift codes @options The list of options +premiumGiftCodePaymentOptions options:vector = PremiumGiftCodePaymentOptions; + +//@description Contains information about a Telegram Premium gift code +//@creator_id Identifier of a chat or a user that created the gift code +//@creation_date Point in time (Unix timestamp) when the code was created +//@is_from_giveaway True, if the gift code was created for a giveaway +//@giveaway_message_id Identifier of the corresponding giveaway message; can be 0 or an identifier of a deleted message +//@month_count Number of month the Telegram Premium subscription will be active after code activation +//@user_id Identifier of a user for which the code was created; 0 if none +//@use_date Point in time (Unix timestamp) when the code was activated; 0 if none +premiumGiftCodeInfo creator_id:MessageSender creation_date:int32 is_from_giveaway:Bool giveaway_message_id:int53 month_count:int32 user_id:int53 use_date:int32 = PremiumGiftCodeInfo; + + +//@class PremiumGiveawayParticipantStatus @description Contains information about status of a user in a Telegram Premium giveaway + +//@description The user is eligible for the giveaway +premiumGiveawayParticipantStatusEligible = PremiumGiveawayParticipantStatus; + +//@description The user participates in the giveaway +premiumGiveawayParticipantStatusParticipating = PremiumGiveawayParticipantStatus; + +//@description The user can't participate in the giveaway, because they have already been member of the chat +//@joined_chat_date Point in time (Unix timestamp) when the user joined the chat +premiumGiveawayParticipantStatusAlreadyWasMember joined_chat_date:int32 = PremiumGiveawayParticipantStatus; + +//@description The user can't participate in the giveaway, because they are an administrator in one of the chats that created the giveaway @chat_id Identifier of the chat administered by the user +premiumGiveawayParticipantStatusAdministrator chat_id:int53 = PremiumGiveawayParticipantStatus; + +//@description The user can't participate in the giveaway, because they phone number is from a disallowed country @user_country_code A two-letter ISO 3166-1 alpha-2 country code of the user's country +premiumGiveawayParticipantStatusDisallowedCountry user_country_code:string = PremiumGiveawayParticipantStatus; + + +//@class PremiumGiveawayInfo @description Contains information about Telegram Premium giveaway + +//@description Describes an ongoing giveaway +//@creation_date Point in time (Unix timestamp) when the giveaway was created +//@status Status of the current user in the giveaway +//@is_ended True, if the giveaway has ended and results are being prepared +premiumGiveawayInfoOngoing creation_date:int32 status:PremiumGiveawayParticipantStatus is_ended:Bool = PremiumGiveawayInfo; + +//@description Describes a completed giveaway +//@creation_date Point in time (Unix timestamp) when the giveaway was created +//@actual_winners_selection_date Point in time (Unix timestamp) when the winners were selected. May be bigger than winners selection date specified in parameters of the giveaway +//@was_refunded True, if the giveaway was canceled and was fully refunded +//@winner_count Number of winners in the giveaway +//@activation_count Number of winners, which activated their gift codes +//@gift_code Telegram Premium gift code that was received by the current user; empty if the user isn't a winner in the giveaway +premiumGiveawayInfoCompleted creation_date:int32 actual_winners_selection_date:int32 was_refunded:Bool winner_count:int32 activation_count:int32 gift_code:string = PremiumGiveawayInfo; + + +//@description Contains information about supported accent color for user/chat name, background of empty chat photo, replies to messages and link previews +//@id Accent color identifier +//@built_in_accent_color_id Identifier of a built-in color to use in places, where only one color is needed; 0-6 +//@light_theme_colors The list of 1-3 colors in RGB format, describing the accent color, as expected to be shown in light themes +//@dark_theme_colors The list of 1-3 colors in RGB format, describing the accent color, as expected to be shown in dark themes +accentColor id:int32 built_in_accent_color_id:int32 light_theme_colors:vector dark_theme_colors:vector = AccentColor; //@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 @@ -709,6 +774,8 @@ usernames active_usernames:vector disabled_usernames:vector edit //@phone_number Phone number of the user //@status Current online status of the user //@profile_photo Profile photo of the user; may be null +//@accent_color_id Identifier of the accent color for name, and backgrounds of profile photo, reply header, and link preview +//@background_custom_emoji_id Identifier of a custom emoji to be shown on the reply header background; 0 if none. For Telegram Premium users only //@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 @@ -725,7 +792,7 @@ usernames active_usernames:vector disabled_usernames:vector edit //@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 usernames:usernames phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_close_friend:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool has_active_stories:Bool has_unread_active_stories:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; +user id:int53 first_name:string last_name:string usernames:usernames phone_number:string status:UserStatus profile_photo:profilePhoto accent_color_id:int32 background_custom_emoji_id:int64 emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_close_friend:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool has_active_stories:Bool has_unread_active_stories:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; //@description Contains information about a bot @@ -925,6 +992,7 @@ inviteLinkChatTypeChannel = InviteLinkChatType; //@type Type of the chat //@title Title of the chat //@photo Chat photo; may be null +//@accent_color_id Identifier of the accent color for chat title and background of chat photo //@param_description Chat description //@member_count Number of members in the chat //@member_user_ids User identifiers of some chat members that may be known to the current user @@ -933,7 +1001,7 @@ inviteLinkChatTypeChannel = InviteLinkChatType; //@is_verified True, if the chat is verified //@is_scam True, if many users reported this chat as a scam //@is_fake True, if many users reported this chat as a fake account -chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:InviteLinkChatType title:string photo:chatPhotoInfo description:string member_count:int32 member_user_ids:vector creates_join_request:Bool is_public:Bool is_verified:Bool is_scam:Bool is_fake:Bool = ChatInviteLinkInfo; +chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:InviteLinkChatType title:string photo:chatPhotoInfo accent_color_id:int32 description:string member_count:int32 member_user_ids:vector creates_join_request:Bool is_public:Bool is_verified:Bool is_scam:Bool is_fake:Bool = ChatInviteLinkInfo; //@description Describes a user that sent a join request and waits for administrator approval @user_id User identifier @date Point in time (Unix timestamp) when the user sent the join request @bio A short bio of the user @@ -1073,27 +1141,24 @@ messageViewer user_id:int53 view_date:int32 = MessageViewer; messageViewers viewers:vector = MessageViewers; -//@class MessageForwardOrigin @description Contains information about the origin of a forwarded message +//@class MessageOrigin @description Contains information about the origin of a message //@description The message was originally sent by a known user @sender_user_id Identifier of the user that originally sent the message -messageForwardOriginUser sender_user_id:int53 = MessageForwardOrigin; +messageOriginUser sender_user_id:int53 = MessageOrigin; + +//@description The message was originally sent by a user, which is hidden by their privacy settings @sender_name Name of the sender +messageOriginHiddenUser sender_name:string = MessageOrigin; //@description The message was originally sent on behalf of a chat //@sender_chat_id Identifier of the chat that originally sent the message //@author_signature For messages originally sent by an anonymous chat administrator, original message author signature -messageForwardOriginChat sender_chat_id:int53 author_signature:string = MessageForwardOrigin; - -//@description The message was originally sent by a user, which is hidden by their privacy settings @sender_name Name of the sender -messageForwardOriginHiddenUser sender_name:string = MessageForwardOrigin; +messageOriginChat sender_chat_id:int53 author_signature:string = MessageOrigin; //@description The message was originally a post in a channel -//@chat_id Identifier of the chat from which the message was originally forwarded +//@chat_id Identifier of the channel chat to which the message was originally sent //@message_id Message identifier of the original message //@author_signature Original post author signature -messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:string = MessageForwardOrigin; - -//@description The message was imported from an exported message history @sender_name Name of the sender -messageForwardOriginMessageImport sender_name:string = MessageForwardOrigin; +messageOriginChannel chat_id:int53 message_id:int53 author_signature:string = MessageOrigin; //@class ReactionType @description Describes type of message reaction @@ -1106,12 +1171,17 @@ reactionTypeCustomEmoji custom_emoji_id:int64 = ReactionType; //@description Contains information about a forwarded message -//@origin Origin of a forwarded message +//@origin Origin of the forwarded message //@date Point in time (Unix timestamp) when the message was originally sent //@public_service_announcement_type The type of a public service announcement for the forwarded message //@from_chat_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the chat from which the message was forwarded last time; 0 if unknown //@from_message_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the original message from which the new message was forwarded last time; 0 if unknown -messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo; +messageForwardInfo origin:MessageOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo; + +//@description Contains information about a message created with importMessages +//@sender_name Name of the original sender +//@date Point in time (Unix timestamp) when the message was originally sent +messageImportInfo sender_name:string date:int32 = MessageImportInfo; //@description Contains information about replies to a message //@reply_count Number of times the message was directly or indirectly replied @@ -1125,8 +1195,10 @@ messageReplyInfo reply_count:int32 recent_replier_ids:vector last //@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 +//@used_sender_id Identifier of the message sender used by the current user to add the reaction; null if unknown or the reaction isn't chosen //@recent_sender_ids Identifiers of at most 3 recent message senders, added the reaction; available in private, basic group and supergroup chats -messageReaction type:ReactionType total_count:int32 is_chosen:Bool recent_sender_ids:vector = MessageReaction; +messageReaction type:ReactionType total_count:int32 is_chosen:Bool used_sender_id:MessageSender recent_sender_ids:vector = MessageReaction; + //@description Contains information about interactions with a message //@view_count Number of times the message was viewed @@ -1151,21 +1223,43 @@ messageSendingStatePending sending_id:int32 = MessageSendingState; //@error The cause of the message sending failure //@can_retry True, if the message can be re-sent //@need_another_sender True, if the message can be re-sent only on behalf of a different sender +//@need_another_reply_quote True, if the message can be re-sent only if another quote is chosen in the message that is replied by the given message +//@need_drop_reply True, if the message can be re-sent only if the message to be replied is removed. This will be done automatically by resendMessages //@retry_after Time left before the message can be re-sent, in seconds. No update is sent when this field changes -messageSendingStateFailed error:error can_retry:Bool need_another_sender:Bool retry_after:double = MessageSendingState; +messageSendingStateFailed error:error can_retry:Bool need_another_sender:Bool need_another_reply_quote:Bool need_drop_reply:Bool retry_after:double = MessageSendingState; //@class MessageReplyTo @description Contains information about the message or the story a message is replying to -//@description Describes a replied message -//@chat_id The identifier of the chat to which the replied message belongs; ignored for outgoing replies. For example, messages in the Replies chat are replies to messages in different chats -//@message_id The identifier of the replied message -messageReplyToMessage chat_id:int53 message_id:int53 = MessageReplyTo; - -//@description Describes a replied story @story_sender_chat_id The identifier of the sender of the replied story. Currently, stories can be replied only in the sender's chat @story_id The identifier of the replied story +//@description Describes a message replied by a given message +//@chat_id The identifier of the chat to which the message belongs; may be 0 if the replied message is in unknown chat +//@message_id The identifier of the message; may be 0 if the replied message is in unknown chat +//@quote Manually or automatically chosen quote from the replied message; may be null if none. Only Bold, Italic, Underline, Strikethrough, Spoiler, and CustomEmoji entities can be present in the quote +//@is_quote_manual True, if the quote was manually chosen by the message sender +//@origin Information about origin of the message if the message was replied from another chat; may be null for messages from the same chat +//@origin_send_date Point in time (Unix timestamp) when the message was sent if the message was replied from another chat; 0 for messages from the same chat +//@content Media content of the message if the message was replied from another chat; may be null for messages from the same chat and messages without media. +//-Can be only one of the following types: messageAnimation, messageAudio, messageContact, messageDice, messageDocument, messageGame, messageInvoice, messageLocation, +//-messagePhoto, messagePoll, messagePremiumGiveaway, messageSticker, messageStory, messageText (for link preview), messageVenue, messageVideo, messageVideoNote, or messageVoiceNote +messageReplyToMessage chat_id:int53 message_id:int53 quote:formattedText is_quote_manual:Bool origin:MessageOrigin origin_send_date:int32 content:MessageContent = MessageReplyTo; + +//@description Describes a story replied by a given message @story_sender_chat_id The identifier of the sender of the story @story_id The identifier of the story messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; +//@class InputMessageReplyTo @description Contains information about the message or the story to be replied + +//@description Describes a message to be replied +//@chat_id The identifier of the chat to which the message to be replied belongs; pass 0 if the message to be replied is in the same chat. Must always be 0 for replies in secret chats. A message can be replied in another chat only if message.can_be_replied_in_another_chat +//@message_id The identifier of the message to be replied in the same or the specified chat +//@quote Manually chosen quote from the message to be replied; pass null if none; 0-getOption("message_reply_quote_length_max") characters. Must always be null for replies in secret chats. +//-Only Bold, Italic, Underline, Strikethrough, Spoiler, and CustomEmoji entities are allowed to be kept and must be kept in the quote +inputMessageReplyToMessage chat_id:int53 message_id:int53 quote:formattedText = InputMessageReplyTo; + +//@description Describes a story to be replied @story_sender_chat_id The identifier of the sender of the story. Currently, stories can be replied only in the sender's chat @story_id The identifier of the story +inputMessageReplyToStory story_sender_chat_id:int53 story_id:int32 = InputMessageReplyTo; + + //@description Describes a message //@id Message identifier; unique for the chat to which the message belongs //@sender_id Identifier of the sender of the message @@ -1176,6 +1270,7 @@ messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; //@is_pinned True, if the message is pinned //@can_be_edited True, if the message can be edited. For live location and poll messages this fields shows whether editMessageLiveLocation or stopPoll can be used with this message by the application //@can_be_forwarded True, if the message can be forwarded +//@can_be_replied_in_another_chat True, if the message can be replied in another chat //@can_be_saved True, if content of the message can be saved locally or copied //@can_be_deleted_only_for_self True, if the message can be deleted only for the current user while other users will continue to see it //@can_be_deleted_for_all_users True, if the message can be deleted for all users @@ -1192,12 +1287,13 @@ messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; //@date Point in time (Unix timestamp) when the message was sent //@edit_date Point in time (Unix timestamp) when the message was last edited //@forward_info Information about the initial message sender; may be null if none or unknown +//@import_info Information about the initial message for messages created with importMessages; may be null if the message isn't imported //@interaction_info Information about interactions with the message; may be null if none //@unread_reactions Information about unread reactions added to the message //@reply_to Information about the message or the story this message is replying to; may be null if none //@message_thread_id If non-zero, the identifier of the message thread the message belongs to; unique within the chat to which the message belongs //@self_destruct_type The message's self-destruct type; may be null if none -//@self_destruct_in Time left before the message self-destruct timer expires, in seconds; 0 if self-desctruction isn't scheduled yet +//@self_destruct_in Time left before the message self-destruct timer expires, in seconds; 0 if self-destruction isn't scheduled yet //@auto_delete_in Time left before the message will be automatically deleted by message_auto_delete_time setting of the chat, in seconds; 0 if never //@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent //@author_signature For channel posts and anonymous group messages, optional author signature @@ -1205,7 +1301,7 @@ messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; //@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 if none -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 is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo unread_reactions:vector reply_to:MessageReplyTo message_thread_id:int53 self_destruct_type:MessageSelfDestructType self_destruct_in:double auto_delete_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_replied_in_another_chat: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 is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo import_info:messageImportInfo interaction_info:messageInteractionInfo unread_reactions:vector reply_to:MessageReplyTo message_thread_id:int53 self_destruct_type:MessageSelfDestructType self_destruct_in:double auto_delete_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; @@ -1361,10 +1457,10 @@ scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool use_de //@description Contains information about a message draft -//@reply_to_message_id Identifier of the replied message; 0 if none +//@reply_to Information about the message to be replied; must be of the type inputMessageReplyToMessage; may be null if none //@date Point in time (Unix timestamp) when the draft was created //@input_message_text Content of the message draft; must be of the type inputMessageText -draftMessage reply_to_message_id:int53 date:int32 input_message_text:InputMessageContent = DraftMessage; +draftMessage reply_to:InputMessageReplyTo date:int32 input_message_text:InputMessageContent = DraftMessage; //@class ChatType @description Describes the type of a chat @@ -1492,6 +1588,8 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@type Type of the chat //@title Chat title //@photo Chat photo; may be null +//@accent_color_id Identifier of the accent color for message sender name, and backgrounds of chat photo, reply header, and link preview +//@background_custom_emoji_id Identifier of a custom emoji to be shown on the reply header background in replies to messages sent by the chat; 0 if none //@permissions Actions that non-administrator chat members are allowed to take in the chat //@last_message Last message in the chat; may be null if none or unknown //@positions Positions of the chat in chat lists @@ -1521,7 +1619,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 if none //@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 block_list:BlockList has_protected_content:Bool is_translatable:Bool is_marked_as_unread: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_auto_delete_time:int32 background:chatBackground 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 accent_color_id:int32 background_custom_emoji_id:int64 permissions:chatPermissions last_message:message positions:vector message_sender_id:MessageSender block_list:BlockList has_protected_content:Bool is_translatable:Bool is_marked_as_unread: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_auto_delete_time:int32 background:chatBackground 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; @@ -1691,10 +1789,9 @@ loginUrlInfoRequestConfirmation url:string domain:string bot_user_id:int53 reque //@description Contains information about a Web App found by its short name //@web_app The Web App -//@supports_settings True, if the app supports "settings_button_pressed" event //@request_write_access True, if the user must be asked for the permission to the bot to send them messages //@skip_confirmation True, if there is no need to show an ordinary open URL confirmation before opening the Web App. The field must be ignored and confirmation must be shown anyway if the Web App link was hidden -foundWebApp web_app:webApp supports_settings:Bool request_write_access:Bool skip_confirmation:Bool = FoundWebApp; +foundWebApp web_app:webApp request_write_access:Bool skip_confirmation:Bool = FoundWebApp; //@description Contains information about a Web App @launch_id Unique identifier for the Web App launch @url A Web App URL to open in a web view webAppInfo launch_id:int64 url:string = WebAppInfo; @@ -1747,6 +1844,15 @@ forumTopic info:forumTopicInfo last_message:message is_pinned:Bool unread_count: forumTopics total_count:int32 topics:vector next_offset_date:int32 next_offset_message_id:int53 next_offset_message_thread_id:int53 = ForumTopics; +//@description Options to be used for generation of a link preview +//@is_disabled True, if link preview must be disabled +//@url URL to use for link preview. If empty, then the first URL found in the message text will be used +//@force_small_media True, if shown media preview must be small; ignored in secret chats or if the URL isn't explicitly specified +//@force_large_media True, if shown media preview must be large; ignored in secret chats or if the URL isn't explicitly specified +//@show_above_text True, if link preview must be shown above message text; otherwise, the link preview will be shown below the message text; ignored in secret chats +linkPreviewOptions is_disabled:Bool url:string force_small_media:Bool force_large_media:Bool show_above_text:Bool = LinkPreviewOptions; + + //@class RichText @description Describes a text object inside an instant-view web page //@description A plain text @text Text @@ -1965,8 +2071,9 @@ pageBlockSlideshow page_blocks:vector caption:pageBlockCaption = Page //@description A link to a chat //@title Chat title //@photo Chat photo; may be null +//@accent_color_id Identifier of the accent color for chat title and background of chat photo //@username Chat username by which all other information about the chat can be resolved -pageBlockChatLink title:string photo:chatPhotoInfo username:string = PageBlock; +pageBlockChatLink title:string photo:chatPhotoInfo accent_color_id:int32 username:string = PageBlock; //@description A table //@caption Table caption @@ -2005,7 +2112,7 @@ pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageB webPageInstantView page_blocks:vector view_count:int32 version:int32 is_rtl:Bool is_full:Bool feedback_link:InternalLinkType = WebPageInstantView; -//@description Describes a web page preview +//@description Describes a link preview //@url Original URL of the link //@display_url URL to display //@type Type of the web page. Can be: article, photo, audio, video, document, profile, app, or something else @@ -2019,6 +2126,10 @@ webPageInstantView page_blocks:vector view_count:int32 version:int32 //@embed_height Height of the embedded preview //@duration Duration of the content, in seconds //@author Author of the content +//@has_large_media True, if the preview has large media and its appearance can be changed +//@show_large_media True, if large media preview must be shown +//@skip_confirmation True, if there is no need to show an ordinary open URL confirmation, when opening the URL from the preview, because the URL is shown in the message text in clear +//@show_above_text True, if the link preview must be shown above message text; otherwise, the link preview must be shown below the message text //@animation Preview of the content as an animation, if available; may be null //@audio Preview of the content as an audio file, if available; may be null //@document Preview of the content as a document, if available; may be null @@ -2029,7 +2140,7 @@ webPageInstantView page_blocks:vector view_count:int32 version:int32 //@story_sender_chat_id The identifier of the sender of the previewed story; 0 if none //@story_id The identifier of the previewed story; 0 if none //@instant_view_version Version of web page instant view (currently, can be 1 or 2); 0 if none -webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote story_sender_chat_id:int53 story_id:int32 instant_view_version:int32 = WebPage; +webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string has_large_media:Bool show_large_media:Bool skip_confirmation:Bool show_above_text:Bool animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote story_sender_chat_id:int53 story_id:int32 instant_view_version:int32 = WebPage; //@description Contains information about a country @@ -2071,12 +2182,18 @@ address country_code:string state:string city:string street_line1:string street_ //@description Contains parameters of the application theme //@background_color A color of the background in the RGB24 format //@secondary_background_color A secondary color for the background in the RGB24 format +//@header_background_color A color of the header background in the RGB24 format +//@section_background_color A color of the section background in the RGB24 format //@text_color A color of text in the RGB24 format +//@accent_text_color An accent color of the text in the RGB24 format +//@section_header_text_color A color of text on the section headers in the RGB24 format +//@subtitle_text_color A color of the subtitle text in the RGB24 format +//@destructive_text_color A color of the text for destructive actions in the RGB24 format //@hint_color A color of hints in the RGB24 format //@link_color A color of links in the RGB24 format //@button_color A color of the buttons in the RGB24 format //@button_text_color A color of text on the buttons in the RGB24 format -themeParameters background_color:int32 secondary_background_color:int32 text_color:int32 hint_color:int32 link_color:int32 button_color:int32 button_text_color:int32 = ThemeParameters; +themeParameters background_color:int32 secondary_background_color:int32 header_background_color:int32 section_background_color:int32 text_color:int32 accent_text_color:int32 section_header_text_color:int32 subtitle_text_color:int32 destructive_text_color:int32 hint_color:int32 link_color:int32 button_color:int32 button_text_color:int32 = ThemeParameters; //@description Portion of the price of a product (e.g., "delivery cost", "tax amount") @label Label for this portion of the product price @amount Currency amount in the smallest units of the currency @@ -2196,6 +2313,9 @@ inputInvoiceMessage chat_id:int53 message_id:int53 = InputInvoice; //@description An invoice from a link of the type internalLinkTypeInvoice @name Name of the invoice inputInvoiceName name:string = InputInvoice; +//@description An invoice for a payment toward Telegram; must not be used in the in-store apps @purpose Transaction purpose +inputInvoiceTelegram purpose:TelegramPaymentPurpose = InputInvoice; + //@class MessageExtendedMedia @description Describes a media, which is attached to an invoice @@ -2217,6 +2337,15 @@ messageExtendedMediaVideo video:video caption:formattedText = MessageExtendedMed messageExtendedMediaUnsupported caption:formattedText = MessageExtendedMedia; +//@description Describes parameters of a Telegram Premium giveaway +//@boosted_chat_id Identifier of the channel chat, which will be automatically boosted by the winners of the giveaway for duration of the Premium subscription +//@additional_chat_ids Identifiers of other channel chats that must be subscribed by the users to be eligible for the giveaway. There can be up to getOption("giveaway_additional_chat_count_max") additional chats +//@winners_selection_date Point in time (Unix timestamp) when the giveaway is expected to be performed; must be 60-getOption("giveaway_duration_max") seconds in the future in scheduled giveaways +//@only_new_members True, if only new subscribers of the chats will be eligible for the giveaway +//@country_codes The list of two-letter ISO 3166-1 alpha-2 codes of countries, users from which will be eligible for the giveaway. If empty, then all users can participate in the giveaway. +//-There can be up to getOption("giveaway_country_count_max") chosen countries. Users with phone number that was bought on Fragment can participate in any giveaway and the country code "FT" must not be specified in the list +premiumGiveawayParameters boosted_chat_id:int53 additional_chat_ids:vector winners_selection_date:int32 only_new_members:Bool country_codes:vector = PremiumGiveawayParameters; + //@description File with the date it was uploaded @file The file @date Point in time (Unix timestamp) when the file was uploaded datedFile file:file date:int32 = DatedFile; @@ -2500,8 +2629,11 @@ inputPassportElementError type:PassportElementType message:string source:InputPa //@class MessageContent @description Contains the content of a message -//@description A text message @text Text of the message @web_page A preview of the web page that's mentioned in the text; may be null -messageText text:formattedText web_page:webPage = MessageContent; +//@description A text message +//@text Text of the message +//@web_page A link preview attached to the message; may be null +//@link_preview_options Options which was used for generation of the link preview; may be null if default options were used +messageText text:formattedText web_page:webPage link_preview_options:linkPreviewOptions = MessageContent; //@description An animation message (GIF-style). //@animation The animation description @@ -2714,6 +2846,25 @@ messagePaymentSuccessfulBot currency:string total_amount:int53 is_recurring:Bool //@sticker A sticker to be shown in the message; may be null if unknown messageGiftedPremium gifter_user_id:int53 currency:string amount:int53 cryptocurrency:string cryptocurrency_amount:int64 month_count:int32 sticker:sticker = MessageContent; +//@description A Telegram Premium gift code was created for the user +//@creator_id Identifier of a chat or a user that created the gift code +//@is_from_giveaway True, if the gift code was created for a giveaway +//@is_unclaimed True, if the winner for the corresponding Telegram Premium subscription wasn't chosen +//@month_count Number of month the Telegram Premium subscription will be active after code activation +//@sticker A sticker to be shown in the message; may be null if unknown +//@code The gift code +messagePremiumGiftCode creator_id:MessageSender is_from_giveaway:Bool is_unclaimed:Bool month_count:int32 sticker:sticker code:string = MessageContent; + +//@description A Telegram Premium giveaway was created for the chat +messagePremiumGiveawayCreated = MessageContent; + +//@description A Telegram Premium giveaway +//@parameters Giveaway parameters +//@winner_count Number of users which will receive Telegram Premium subscription gift codes +//@month_count Number of month the Telegram Premium subscription will be active after code activation +//@sticker A sticker to be shown in the message; may be null if unknown +messagePremiumGiveaway parameters:premiumGiveawayParameters winner_count:int32 month_count:int32 sticker:sticker = MessageContent; + //@description A contact has registered with Telegram messageContactRegistered = MessageContent; @@ -2723,13 +2874,8 @@ messageUserShared user_id:int53 button_id:int32 = MessageContent; //@description The current user shared a chat, which was requested by the bot @chat_id Identifier of the shared chat @button_id Identifier of the keyboard button with the request messageChatShared chat_id:int53 button_id:int32 = MessageContent; -//@description The current user has connected a website by logging in using Telegram Login Widget on it @domain_name Domain name of the connected website -messageWebsiteConnected domain_name:string = MessageContent; - -//@description The user allowed the bot to send messages -//@web_app Information about the Web App, which requested the access; may be null if none or the Web App was opened from the attachment menu -//@by_request True, if user allowed the bot to send messages by an explicit call to allowBotToSendMessages -messageBotWriteAccessAllowed web_app:webApp by_request:Bool = MessageContent; +//@description The user allowed the bot to send messages @reason The reason why the bot was allowed to write messages +messageBotWriteAccessAllowed reason:BotWriteAccessAllowReason = MessageContent; //@description Data from a Web App has been sent to a bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App messageWebAppDataSent button_text:string = MessageContent; @@ -2800,6 +2946,9 @@ textEntityTypePre = TextEntityType; //@description Text that must be formatted as if inside pre, and code HTML tags @language Programming language of the code; as defined by the sender textEntityTypePreCode language:string = TextEntityType; +//@description Text that must be formatted as if inside a blockquote HTML tag +textEntityTypeBlockQuote = TextEntityType; + //@description A text description shown instead of a raw URL @url HTTP or tg:// URL to be opened when the link is clicked textEntityTypeTextUrl url:string = TextEntityType; @@ -2809,7 +2958,7 @@ textEntityTypeMentionName user_id:int53 = TextEntityType; //@description A custom emoji. The text behind a custom emoji must be an emoji. Only premium users can use premium custom emoji @custom_emoji_id Unique identifier of the custom emoji textEntityTypeCustomEmoji custom_emoji_id:int64 = TextEntityType; -//@description A media timestamp @media_timestamp Timestamp from which a video/audio/video note/voice note playing must start, in seconds. The media can be in the content or the web page preview of the current message, or in the same places in the replied message +//@description A media timestamp @media_timestamp Timestamp from which a video/audio/video note/voice note/story playing must start, in seconds. The media can be in the content or the web page preview of the current message, or in the same places in the replied message textEntityTypeMediaTimestamp media_timestamp:int32 = TextEntityType; @@ -2845,9 +2994,10 @@ messageSelfDestructTypeImmediately = MessageSelfDestructType; //@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 //@sending_id Non-persistent identifier, which will be returned back in messageSendingStatePending object and can be used to match sent messages and corresponding updateNewMessage updates -messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState sending_id:int32 = MessageSendOptions; +//@only_preview Pass true to get a fake message instead of actually sending them +messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState sending_id:int32 only_preview:Bool = 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 +//@description Options to be used when a message content is copied without reference to the original sender. Service messages, and messages with messageInvoice or messagePremiumGiveaway content 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 //@replace_caption True, if media caption of the message copy needs to be replaced. Ignored if send_copy is false //@new_caption New message caption; pass null to copy message without caption. Ignored if replace_caption is false @@ -2857,10 +3007,10 @@ messageCopyOptions send_copy:Bool replace_caption:Bool new_caption:formattedText //@class InputMessageContent @description The content of a message to send //@description A text message -//@text Formatted text to be sent; 1-getOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually -//@disable_web_page_preview True, if rich web page previews for URLs in the message text must be disabled +//@text Formatted text to be sent; 0-getOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually +//@link_preview_options Options to be used for generation of a link preview; pass null to use default link preview options //@clear_draft True, if a chat message draft must be deleted -inputMessageText text:formattedText disable_web_page_preview:Bool clear_draft:Bool = InputMessageContent; +inputMessageText text:formattedText link_preview_options:linkPreviewOptions clear_draft:Bool = InputMessageContent; //@description An animation message (GIF-style). //@animation Animation file to be sent @@ -2987,7 +3137,7 @@ inputMessageStory story_sender_chat_id:int53 story_id:int32 = InputMessageConten //@description A forwarded message //@from_chat_id Identifier for the chat this forwarded message came from -//@message_id Identifier of the message to forward +//@message_id Identifier of the message to forward. A message can be forwarded only if message.can_be_forwarded //@in_game_share True, if a game message is being shared from a launched game; applies only to game messages //@copy_options Options to be used to copy content of the message without reference to the original sender; pass null to forward the message as usual inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool copy_options:messageCopyOptions = InputMessageContent; @@ -3133,10 +3283,11 @@ emojis emojis:vector = Emojis; //@is_official True, if the sticker set is official //@sticker_format Format of the stickers in the set //@sticker_type Type of the stickers in the set +//@needs_repainting True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only //@is_viewed True for already viewed trending sticker sets //@stickers List of stickers in this set //@emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object -stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType is_viewed:Bool stickers:vector emojis:vector = StickerSet; +stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType needs_repainting:Bool is_viewed:Bool stickers:vector emojis:vector = StickerSet; //@description Represents short information about a sticker set //@id Identifier of the sticker set @@ -3149,10 +3300,11 @@ stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outli //@is_official True, if the sticker set is official //@sticker_format Format of the stickers in the set //@sticker_type Type of the stickers in the set +//@needs_repainting True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only //@is_viewed True for already viewed trending sticker sets //@size Total number of stickers in the set //@covers Up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full sticker set needs to be requested -stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType is_viewed:Bool size:int32 covers:vector = StickerSetInfo; +stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType needs_repainting:Bool is_viewed:Bool size:int32 covers:vector = StickerSetInfo; //@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets stickerSets total_count:int32 sets:vector = StickerSets; @@ -3202,7 +3354,7 @@ storyViewers total_count:int32 total_reaction_count:int32 viewers:vector = ChatActiveStories; +//@class ChatBoostSource @description Describes source of a chat boost + +//@description The chat created a Telegram Premium gift code for a user +//@user_id Identifier of a user, for which the gift code was created +//@gift_code The created Telegram Premium gift code, which is known only if this is a gift code for the current user, or it has already been claimed +chatBoostSourceGiftCode user_id:int53 gift_code:string = ChatBoostSource; + +//@description The chat created a Telegram Premium giveaway +//@user_id Identifier of a user that won in the giveaway; 0 if none +//@gift_code The created Telegram Premium gift code if it was used by the user or can be claimed by the current user; an empty string otherwise +//@giveaway_message_id Identifier of the corresponding giveaway message; can be an identifier of a deleted message +//@is_unclaimed True, if the winner for the corresponding Telegram Premium subscription wasn't chosen, because there were not enough participants +chatBoostSourceGiveaway user_id:int53 gift_code:string giveaway_message_id:int53 is_unclaimed:Bool = ChatBoostSource; + +//@description A user with Telegram Premium subscription or gifted Telegram Premium boosted the chat +//@user_id Identifier of the user +chatBoostSourcePremium user_id:int53 = ChatBoostSource; + + +//@description Describes a prepaid Telegram Premium giveaway +//@id Unique identifier of the prepaid giveaway +//@winner_count Number of users which will receive Telegram Premium subscription gift codes +//@month_count Number of month the Telegram Premium subscription will be active after code activation +//@payment_date Point in time (Unix timestamp) when the giveaway was paid +prepaidPremiumGiveaway id:int64 winner_count:int32 month_count:int32 payment_date:int32 = PrepaidPremiumGiveaway; + //@description Describes current boost status of a chat -//@is_boosted True, if the current user has already boosted the chat +//@boost_url An HTTP URL, which can be used to boost the chat +//@applied_slot_ids Identifiers of boost slots of the current user applied to the chat //@level Current boost level of the chat -//@boost_count The number of times the chat was boosted +//@gift_code_boost_count The number of boosts received by the chat from created Telegram Premium gift codes and giveaways; always 0 if the current user isn't an administrator in the chat +//@boost_count The number of boosts received by the chat //@current_level_boost_count The number of boosts added to reach the current level //@next_level_boost_count The number of boosts needed to reach the next level; 0 if the next level isn't available //@premium_member_count Approximate number of Telegram Premium subscribers joined the chat; always 0 if the current user isn't an administrator in the chat //@premium_member_percentage A percentage of Telegram Premium subscribers joined the chat; always 0 if the current user isn't an administrator in the chat -chatBoostStatus is_boosted:Bool level:int32 boost_count:int32 current_level_boost_count:int32 next_level_boost_count:int32 premium_member_count:int32 premium_member_percentage:double = ChatBoostStatus; +//@prepaid_giveaways The list of prepaid giveaways available for the chat; only for chat administrators +chatBoostStatus boost_url:string applied_slot_ids:vector level:int32 gift_code_boost_count:int32 boost_count:int32 current_level_boost_count:int32 next_level_boost_count:int32 premium_member_count:int32 premium_member_percentage:double prepaid_giveaways:vector = ChatBoostStatus; -//@description Describes a boost of a chat @user_id Identifier of a user that boosted the chat @expiration_date Point in time (Unix timestamp) when the boost will automatically expire if the user will not prolongate their Telegram Premium subscription -chatBoost user_id:int53 expiration_date:int32 = ChatBoost; +//@description Describes a boost applied to a chat +//@id Unique identifier of the boost +//@count The number of identical boosts applied +//@source Source of the boost +//@start_date Point in time (Unix timestamp) when the chat was boosted +//@expiration_date Point in time (Unix timestamp) when the boost will expire +chatBoost id:string count:int32 source:ChatBoostSource start_date:int32 expiration_date:int32 = ChatBoost; //@description Contains a list of boosts applied to a chat @total_count Total number of boosts applied to the chat @boosts List of boosts @next_offset The offset for the next request. If empty, there are no more results foundChatBoosts total_count:int32 boosts:vector next_offset:string = FoundChatBoosts; +//@description Describes a slot for chat boost +//@slot_id Unique identifier of the slot +//@currently_boosted_chat_id Identifier of the currently boosted chat; 0 if none +//@start_date Point in time (Unix timestamp) when the chat was boosted; 0 if none +//@expiration_date Point in time (Unix timestamp) when the boost will expire +//@cooldown_until_date Point in time (Unix timestamp) after which the boost can be used for another chat +chatBoostSlot slot_id:int32 currently_boosted_chat_id:int53 start_date:int32 expiration_date:int32 cooldown_until_date:int32 = ChatBoostSlot; + +//@description Contains a list of chat boost slots @slots List of boost slots +chatBoostSlots slots:vector = ChatBoostSlots; + //@class CallDiscardReason @description Describes the reason why a call was discarded @@ -3448,7 +3645,7 @@ callStateReady protocol:callProtocol servers:vector config:string en callStateHangingUp = CallState; //@description The call has ended successfully -//@reason The reason, why the call has ended +//@reason The reason why the call has ended //@need_rating True, if the call rating must be sent to the server //@need_debug_information True, if the call debug information must be sent to the server //@need_log True, if the call log must be sent to the server @@ -3599,8 +3796,12 @@ firebaseAuthenticationSettingsIos device_token:string is_app_sandbox:Bool = Fire phoneNumberAuthenticationSettings allow_flash_call:Bool allow_missed_call:Bool is_current_phone_number:Bool allow_sms_retriever_api:Bool firebase_authentication_settings:FirebaseAuthenticationSettings authentication_tokens:vector = PhoneNumberAuthenticationSettings; -//@description Represents a reaction applied to a message @type Type of the reaction @sender_id Identifier of the chat member, applied the reaction @date Point in time (Unix timestamp) when the reaction was added -addedReaction type:ReactionType sender_id:MessageSender date:int32 = AddedReaction; +//@description Represents a reaction applied to a message +//@type Type of the reaction +//@sender_id Identifier of the chat member, applied the reaction +//@is_outgoing True, if the reaction was added by the current user +//@date Point in time (Unix timestamp) when the reaction was added +addedReaction type:ReactionType sender_id:MessageSender is_outgoing:Bool date:int32 = 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; @@ -3675,7 +3876,6 @@ attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotCol //@supports_bot_chats True, if the bot supports opening from attachment menu in private chats with other bots //@supports_group_chats True, if the bot supports opening from attachment menu in basic group and supergroup chats //@supports_channel_chats True, if the bot supports opening from attachment menu in channel chats -//@supports_settings True, if the bot supports "settings_button_pressed" event //@request_write_access True, if the user must be asked for the permission to send messages to the bot //@is_added True, if the bot was explicitly added by the user. If the bot isn't added, then on the first bot launch toggleBotIsAddedToAttachmentMenu must be called and the bot must be added or removed //@show_in_attachment_menu True, if the bot must be shown in the attachment menu @@ -3693,12 +3893,27 @@ attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotCol //@macos_side_menu_icon Icon for the bot in PNG format for the official macOS app side menu; may be null //@icon_color Color to highlight selected icon of the bot if appropriate; may be null //@web_app_placeholder Default placeholder for opened Web Apps in SVG format; may be null -attachmentMenuBot bot_user_id:int53 supports_self_chat:Bool supports_user_chats:Bool supports_bot_chats:Bool supports_group_chats:Bool supports_channel_chats:Bool supports_settings:Bool request_write_access:Bool is_added:Bool show_in_attachment_menu:Bool show_in_side_menu:Bool show_disclaimer_in_side_menu:Bool name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file ios_side_menu_icon:file android_icon:file android_side_menu_icon:file macos_icon:file macos_side_menu_icon:file icon_color:attachmentMenuBotColor web_app_placeholder:file = AttachmentMenuBot; +attachmentMenuBot bot_user_id:int53 supports_self_chat:Bool supports_user_chats:Bool supports_bot_chats:Bool supports_group_chats:Bool supports_channel_chats:Bool request_write_access:Bool is_added:Bool show_in_attachment_menu:Bool show_in_side_menu:Bool show_disclaimer_in_side_menu:Bool name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file ios_side_menu_icon:file android_icon:file android_side_menu_icon:file macos_icon:file macos_side_menu_icon:file icon_color:attachmentMenuBotColor web_app_placeholder:file = AttachmentMenuBot; //@description Information about the message sent by answerWebAppQuery @inline_message_id Identifier of the sent inline message, if known sentWebAppMessage inline_message_id:string = SentWebAppMessage; +//@class BotWriteAccessAllowReason @description Describes a reason why a bot was allowed to write messages to the current user + +//@description The user connected a website by logging in using Telegram Login Widget on it @domain_name Domain name of the connected website +botWriteAccessAllowReasonConnectedWebsite domain_name:string = BotWriteAccessAllowReason; + +//@description The user added the bot to attachment or side menu using toggleBotIsAddedToAttachmentMenu +botWriteAccessAllowReasonAddedToAttachmentMenu = BotWriteAccessAllowReason; + +//@description The user launched a Web App using getWebAppLinkUrl @web_app Information about the Web App +botWriteAccessAllowReasonLaunchedWebApp web_app:webApp = BotWriteAccessAllowReason; + +//@description The user accepted bot's request to send messages with allowBotToSendMessages +botWriteAccessAllowReasonAcceptedRequest = BotWriteAccessAllowReason; + + //@description Contains an HTTP URL @url The URL httpUrl url:string = HttpUrl; @@ -4044,6 +4259,12 @@ chatEventUsernameChanged old_username:string new_username:string = ChatEventActi //@description The chat active usernames were changed @old_usernames Previous list of active usernames @new_usernames New list of active usernames chatEventActiveUsernamesChanged old_usernames:vector new_usernames:vector = ChatEventAction; +//@description The chat accent color was changed @old_accent_color_id Previous identifier of chat accent color @new_accent_color_id New identifier of chat accent color +chatEventAccentColorChanged old_accent_color_id:int32 new_accent_color_id:int32 = ChatEventAction; + +//@description The chat's custom emoji for reply background was changed @old_background_custom_emoji_id Previous identifier of the custom emoji; 0 if none @new_background_custom_emoji_id New identifier of the custom emoji; 0 if none +chatEventBackgroundCustomEmojiChanged old_background_custom_emoji_id:int64 new_background_custom_emoji_id:int64 = ChatEventAction; + //@description The has_protected_content setting of a channel was toggled @has_protected_content New value of has_protected_content chatEventHasProtectedContentToggled has_protected_content:Bool = ChatEventAction; @@ -4282,6 +4503,9 @@ premiumFeatureUpgradedStories = PremiumFeature; //@description The ability to boost chats premiumFeatureChatBoost = PremiumFeature; +//@description The ability to choose accent color +premiumFeatureAccentColor = PremiumFeature; + //@class PremiumStoryFeature @description Describes a story feature available to Premium users @@ -4344,12 +4568,44 @@ premiumState state:formattedText payment_options:vector = StorePaymentPurpose; + +//@description The user creating a Telegram Premium giveaway for subscribers of channel chats; requires can_post_messages rights in the channels +//@parameters Giveaway parameters +//@currency ISO 4217 currency code of the payment currency +//@amount Paid amount, in the smallest units of the currency +storePaymentPurposePremiumGiveaway parameters:premiumGiveawayParameters currency:string amount:int53 = StorePaymentPurpose; + + +//@class TelegramPaymentPurpose @description Describes a purpose of a payment toward Telegram + +//@description The user creating Telegram Premium gift codes for other users +//@boosted_chat_id Identifier of the channel chat, which will be automatically boosted by the users for duration of the Premium subscription and which is administered by the user; 0 if none +//@currency ISO 4217 currency code of the payment currency +//@amount Paid amount, in the smallest units of the currency +//@user_ids Identifiers of the users which can activate the gift codes +//@month_count Number of month the Telegram Premium subscription will be active for the users +telegramPaymentPurposePremiumGiftCodes boosted_chat_id:int53 currency:string amount:int53 user_ids:vector month_count:int32 = TelegramPaymentPurpose; + +//@description The user creating a Telegram Premium giveaway for subscribers of channel chats; requires can_post_messages rights in the channels +//@parameters Giveaway parameters +//@currency ISO 4217 currency code of the payment currency +//@amount Paid amount, in the smallest units of the currency +//@winner_count Number of users which will be able to activate the gift codes +//@month_count Number of month the Telegram Premium subscription will be active for the users +telegramPaymentPurposePremiumGiveaway parameters:premiumGiveawayParameters currency:string amount:int53 winner_count:int32 month_count:int32 = TelegramPaymentPurpose; + //@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. //-To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org @@ -4485,27 +4741,6 @@ canSendStoryResultWeeklyLimitExceeded retry_after:int32 = CanSendStoryResult; canSendStoryResultMonthlyLimitExceeded retry_after:int32 = CanSendStoryResult; -//@class CanBoostChatResult @description Represents result of checking whether the current user can boost the specific chat - -//@description The chat can be boosted @currently_boosted_chat_id Identifier of the currently boosted chat from which boost will be removed; 0 if none -canBoostChatResultOk currently_boosted_chat_id:int53 = CanBoostChatResult; - -//@description The chat can't be boosted -canBoostChatResultInvalidChat = CanBoostChatResult; - -//@description The chat is already boosted by the user -canBoostChatResultAlreadyBoosted = CanBoostChatResult; - -//@description The user must subscribe to Telegram Premium to be able to boost chats -canBoostChatResultPremiumNeeded = CanBoostChatResult; - -//@description The user must have Telegram Premium subscription instead of a gifted Telegram Premium -canBoostChatResultPremiumSubscriptionNeeded = CanBoostChatResult; - -//@description The user must wait the specified time before the boost can be moved to another chat @retry_after Time left before the user can boost another chat -canBoostChatResultWaitNeeded retry_after:int32 = CanBoostChatResult; - - //@class CanTransferOwnershipResult @description Represents result of checking whether the current session can be used to transfer a chat ownership to another user //@description The session can be used @@ -4623,6 +4858,15 @@ pushMessageContentPhoto photo:photo caption:string is_secret:Bool is_pinned:Bool //@is_pinned True, if the message is a pinned message with the specified content pushMessageContentPoll question:string is_regular:Bool is_pinned:Bool = PushMessageContent; +//@description A message with a Telegram Premium gift code created for the user @month_count Number of month the Telegram Premium subscription will be active after code activation +pushMessageContentPremiumGiftCode month_count:int32 = PushMessageContent; + +//@description A message with a Telegram Premium giveaway +//@winner_count Number of users which will receive Telegram Premium subscription gift codes; 0 for pinned message +//@month_count Number of month the Telegram Premium subscription will be active after code activation; 0 for pinned message +//@is_pinned True, if the message is a pinned message with the specified content +pushMessageContentPremiumGiveaway winner_count:int32 month_count:int32 is_pinned:Bool = PushMessageContent; + //@description A screenshot of a message in the chat has been taken pushMessageContentScreenshotTaken = PushMessageContent; @@ -4714,7 +4958,7 @@ notificationTypeNewSecretChat = NotificationType; notificationTypeNewCall call_id:int32 = NotificationType; //@description New message was received through a push notification -//@message_id The message identifier. The message will not be available in the chat history, but the identifier can be used in viewMessages, or as a message to reply +//@message_id The message identifier. The message will not be available in the chat history, but the identifier can be used in viewMessages, or as a message to be replied in the same chat //@sender_id Identifier of the sender of the message. Corresponding user or chat may be inaccessible //@sender_name Name of the sender //@is_outgoing True, if the message is outgoing @@ -5092,7 +5336,7 @@ internalLinkTypeBotStartInGroup bot_username:string start_parameter:string admin internalLinkTypeChangePhoneNumber = InternalLinkType; //@description The link is a link to boost a Telegram chat. Call getChatBoostLinkInfo with the given URL to process the link. -//-If the chat is found, then call getChatBoostStatus and canBoostChat to get the current boost status and check whether the chat can be boosted. +//-If the chat is found, then call getChatBoostStatus and getAvailableChatBoostSlots to get the current boost status and check whether the chat can be boosted. //-If the user wants to boost the chat and the chat can be boosted, then call boostChat //@url URL to be passed to getChatBoostLinkInfo internalLinkTypeChatBoost url:string = InternalLinkType; @@ -5154,6 +5398,10 @@ internalLinkTypePhoneNumberConfirmation hash:string phone_number:string = Intern //@description The link is a link to the Premium features screen of the application from which the user can subscribe to Telegram Premium. Call getPremiumFeatures with the given referrer to process the link @referrer Referrer specified in the link internalLinkTypePremiumFeatures referrer:string = InternalLinkType; +//@description The link is a link with a Telegram Premium gift code. Call checkPremiumGiftCode with the given code to process the link. If the code is valid and the user wants to apply it, then call applyPremiumGiftCode +//@code The Telegram Premium gift code +internalLinkTypePremiumGiftCode code:string = InternalLinkType; + //@description The link is a link to the privacy and security section of the app settings internalLinkTypePrivacyAndSecuritySettings = InternalLinkType; @@ -5236,7 +5484,7 @@ messageLink link:string is_public:Bool = MessageLink; //@chat_id If found, identifier of the chat to which the link points, 0 otherwise //@message_thread_id If found, identifier of the message thread in which to open the message, or a forum topic to open if the message is missing //@message If found, the linked message; may be null -//@media_timestamp Timestamp from which the video/audio/video note/voice note playing must start, in seconds; 0 if not specified. The media can be in the message content or in its web page preview +//@media_timestamp Timestamp from which the video/audio/video note/voice note/story playing must start, in seconds; 0 if not specified. The media can be in the message content or in its web page preview //@for_album True, if the whole media album to which the message belongs is linked messageLinkInfo is_public:Bool chat_id:int53 message_thread_id:int53 message:message media_timestamp:int32 for_album:Bool = MessageLinkInfo; @@ -5244,7 +5492,7 @@ messageLinkInfo is_public:Bool chat_id:int53 message_thread_id:int53 message:mes //@description Contains an HTTPS link to boost a chat @link The link @is_public True, if the link will work for non-members of the chat chatBoostLink link:string is_public:Bool = ChatBoostLink; -//@description Contains information about a link to boost a a chat +//@description Contains information about a link to boost a chat //@is_public True, if the link will work for non-members of the chat //@chat_id Identifier of the chat to which the link points; 0 if the chat isn't found chatBoostLinkInfo is_public:Bool chat_id:int53 = ChatBoostLinkInfo; @@ -5488,6 +5736,9 @@ topChatCategoryCalls = TopChatCategory; topChatCategoryForwardChats = TopChatCategory; +//@description Contains 0-based match position @position The position of the match +foundPosition position:int32 = FoundPosition; + //@description Contains 0-based positions of matched objects @total_count Total number of matched objects @positions The positions of the matched objects foundPositions total_count:int32 positions:vector = FoundPositions; @@ -5676,7 +5927,7 @@ chatStatisticsSupergroup period:dateRange member_count:statisticalValue message_ //@member_count Number of members in the chat //@mean_view_count Mean number of times the recently sent messages was viewed //@mean_share_count Mean number of times the recently sent messages was shared -//@enabled_notifications_percentage A percentage of users with enabled notifications for the chat +//@enabled_notifications_percentage A percentage of users with enabled notifications for the chat; 0-100 //@member_count_graph A graph containing number of members in the chat //@join_graph A graph containing number of members joined and left the chat //@mute_graph A graph containing number of members muted and unmuted the chat @@ -5797,12 +6048,18 @@ updateChatTitle chat_id:int53 title:string = Update; //@description A chat photo was changed @chat_id Chat identifier @photo The new chat photo; may be null updateChatPhoto chat_id:int53 photo:chatPhotoInfo = Update; +//@description A chat accent color has changed @chat_id Chat identifier @accent_color_id The new chat accent color identifier +updateChatAccentColor chat_id:int53 accent_color_id:int32 = Update; + +//@description A chat's custom emoji for reply background has changed @chat_id Chat identifier @background_custom_emoji_id The new tdentifier of a custom emoji to be shown on the reply header background +updateChatBackgroundCustomEmoji chat_id:int53 background_custom_emoji_id:int64 = Update; + //@description Chat permissions was changed @chat_id Chat identifier @permissions The new chat permissions updateChatPermissions chat_id:int53 permissions:chatPermissions = Update; -//@description The last message of a chat was changed. If last_message is null, then the last message in the chat became unknown. Some new unknown messages might be added to the chat in this case +//@description The last message of a chat was changed //@chat_id Chat identifier -//@last_message The new last message in the chat; may be null +//@last_message The new last message in the chat; may be null if the last message became unknown. While the last message is unknown, new messages can be added to the chat without corresponding updateNewMessage update //@positions The new chat positions in the chat lists updateChatLastMessage chat_id:int53 last_message:message positions:vector = Update; @@ -6080,6 +6337,12 @@ updateSelectedBackground for_dark_theme:Bool background:background = Update; //@description The list of available chat themes has changed @chat_themes The new list of chat themes updateChatThemes chat_themes:vector = Update; +//@description The list of supported accent colors has changed +//@colors Information about supported colors; colors with identifiers 0 (red), 1 (orange), 2 (purple/violet), 3 (green), 4 (cyan), 5 (blue), 6 (pink) must always be supported +//-and aren't included in the list. The exact colors for the accent colors with identifiers 0-6 must be taken from the app theme +//@available_accent_color_ids The list of accent color identifiers, which can be set through setAccentColor and setChatAccentColor. The colors must be shown in the specififed order +updateAccentColors colors:vector available_accent_color_ids:vector = Update; + //@description Some language pack strings have been updated @localization_target Localization target to which the language pack belongs @language_pack_id Identifier of the updated language pack @strings List of changed language pack strings; empty if all strings have changed updateLanguagePackStrings localization_target:string language_pack_id:string strings:vector = Update; @@ -6211,6 +6474,11 @@ updateChatMember chat_id:int53 actor_user_id:int53 date:int32 invite_link:chatIn //@invite_link The invite link, which was used to send join request; may be null updateNewChatJoinRequest chat_id:int53 request:chatJoinRequest user_chat_id:int53 invite_link:chatInviteLink = Update; +//@description A chat boost has changed; for bots only +//@chat_id Chat identifier +//@boost New information about the boost +updateChatBoost chat_id:int53 boost:chatBoost = Update; + //@description Contains a list of updates @updates List of updates updates updates:vector = Updates; @@ -6682,13 +6950,6 @@ getChatMessagePosition chat_id:int53 message_id:int53 filter:SearchMessagesFilte //@description Returns all scheduled messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id) @chat_id Chat identifier getChatScheduledMessages chat_id:int53 = Messages; -//@description Returns forwarded copies of a channel message to different public channels. For optimal performance, the number of returned messages is chosen by TDLib -//@chat_id Chat identifier of the message -//@message_id Message identifier -//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results -//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit -getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages; - //@description Returns sponsored messages to be shown in a chat; for channel chats only @chat_id Identifier of the chat getChatSponsoredMessages chat_id:int53 = SponsoredMessages; @@ -6708,7 +6969,7 @@ removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = //@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels, or if message.can_get_media_timestamp_links and a media timestamp link is generated. This is an offline request //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message -//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing must start, in seconds. The media can be in the message content or in its web page preview +//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note/story playing must start, in seconds. The media can be in the message content or in its web page preview //@for_album Pass true to create a link for the whole media album //@in_message_thread Pass true to create a link to the message as a channel post comment, in a message thread, or a forum topic getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool in_message_thread:Bool = MessageLink; @@ -6760,20 +7021,19 @@ setChatMessageSender chat_id:int53 message_sender_id:MessageSender = Ok; //@description Sends a message. Returns the sent message //@chat_id Target chat //@message_thread_id If not 0, a message thread identifier in which the message will be sent -//@reply_to Identifier of the replied message or story; pass null if none +//@reply_to Information about the message or story to be replied; pass null if none //@options Options to be used to send the message; pass null to use default options //@reply_markup Markup for replying to the message; pass null if none; for bots only //@input_message_content The content of the message to be sent -sendMessage chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; +sendMessage chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; //@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages //@chat_id Target chat //@message_thread_id If not 0, a message thread identifier in which the messages will be sent -//@reply_to Identifier of the replied message or story; pass null if none +//@reply_to Information about the message or story to be replied; pass null if none //@options Options to be used to send the messages; pass null to use default options //@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album -//@only_preview Pass true to get fake messages instead of actually sending them -sendMessageAlbum chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo options:messageSendOptions input_message_contents:vector only_preview:Bool = Messages; +sendMessageAlbum chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions input_message_contents:vector = Messages; //@description Invites a bot to a chat (if it is not yet a member) and sends it the /start command. Bots can't be invited to a private chat other than the chat with the bot. Bots can't be invited to channels (although they can be added as admins) and secret chats. Returns the sent message //@bot_user_id Identifier of the bot @@ -6784,37 +7044,37 @@ sendBotStartMessage bot_user_id:int53 chat_id:int53 parameter:string = Message; //@description Sends the result of an inline query as a message. Returns the sent message. Always clears a chat draft message //@chat_id Target chat //@message_thread_id If not 0, a message thread identifier in which the message will be sent -//@reply_to Identifier of the replied message or story; pass null if none +//@reply_to Information about the message or story to be replied; pass null if none //@options Options to be used to send the message; pass null to use default options //@query_id Identifier of the inline query //@result_id Identifier of the inline query result //@hide_via_bot Pass true to hide the bot, via which the message is sent. Can be used only for bots getOption("animation_search_bot_username"), getOption("photo_search_bot_username"), and getOption("venue_search_bot_username") -sendInlineQueryResultMessage chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo options:messageSendOptions query_id:int64 result_id:string hide_via_bot:Bool = Message; +sendInlineQueryResultMessage chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions query_id:int64 result_id:string hide_via_bot:Bool = Message; //@description Forwards previously sent messages. Returns the forwarded messages in the same order as the message identifiers passed in message_ids. If a message can't be forwarded, null will be returned instead of the message //@chat_id Identifier of the chat to which to forward messages //@message_thread_id If not 0, a message thread identifier in which the message will be sent; for forum threads only //@from_chat_id Identifier of the chat from which to forward messages -//@message_ids Identifiers of the messages to forward. Message identifiers must be in a strictly increasing order. At most 100 messages can be forwarded simultaneously +//@message_ids Identifiers of the messages to forward. Message identifiers must be in a strictly increasing order. At most 100 messages can be forwarded simultaneously. A message can be forwarded only if message.can_be_forwarded //@options Options to be used to send the messages; pass null to use default options //@send_copy Pass true to copy content of the messages without reference to the original sender. Always true if the messages are forwarded to a secret chat or are local //@remove_caption Pass true to remove media captions of message copies. Ignored if send_copy is false -//@only_preview Pass true to get fake messages instead of actually forwarding them -forwardMessages chat_id:int53 message_thread_id:int53 from_chat_id:int53 message_ids:vector options:messageSendOptions send_copy:Bool remove_caption:Bool only_preview:Bool = Messages; +forwardMessages chat_id:int53 message_thread_id:int53 from_chat_id:int53 message_ids:vector options:messageSendOptions send_copy:Bool remove_caption:Bool = Messages; //@description Resends messages which failed to send. Can be called only for messages for which messageSendingStateFailed.can_retry is true and after specified in messageSendingStateFailed.retry_after time passed. //-If a message is re-sent, the corresponding failed to send message is deleted. Returns the sent messages in the same order as the message identifiers passed in message_ids. If a message can't be re-sent, null will be returned instead of the message //@chat_id Identifier of the chat to send messages //@message_ids Identifiers of the messages to resend. Message identifiers must be in a strictly increasing order -resendMessages chat_id:int53 message_ids:vector = Messages; +//@quote New manually chosen quote from the message to be replied; pass null if none. Ignored if more than one message is re-sent, or if messageSendingStateFailed.need_another_reply_quote == false +resendMessages chat_id:int53 message_ids:vector quote:formattedText = Messages; //@description Adds a local message to a chat. The message is persistent across application restarts only if the message database is used. Returns the added message //@chat_id Target chat //@sender_id Identifier of the sender of the message -//@reply_to Identifier of the replied message or story; pass null if none +//@reply_to Information about the message or story to be replied; pass null if none //@disable_notification Pass true to disable notification for the message //@input_message_content The content of the message to be added -addLocalMessage chat_id:int53 sender_id:MessageSender reply_to:MessageReplyTo disable_notification:Bool input_message_content:InputMessageContent = Message; +addLocalMessage chat_id:int53 sender_id:MessageSender reply_to:InputMessageReplyTo disable_notification:Bool input_message_content:InputMessageContent = Message; //@description Deletes messages @chat_id Chat identifier @message_ids Identifiers of the messages to be deleted @revoke Pass true to delete messages for all chat members. Always true for supergroups, channels and secret chats deleteMessages chat_id:int53 message_ids:vector revoke:Bool = Ok; @@ -7010,10 +7270,16 @@ getMessageAddedReactions chat_id:int53 message_id:int53 reaction_type:ReactionTy setDefaultReactionType reaction_type:ReactionType = Ok; +//@description Searches for a given quote in a text. Returns found quote start position in UTF-16 code units. Returns a 404 error if the quote is not found. Can be called synchronously +//@text Text in which to search for the quote +//@quote Quote to search for +//@quote_position Approximate quote position in UTF-16 code units +searchQuote text:formattedText quote:formattedText quote_position:int32 = FoundPosition; + //@description Returns all entities (mentions, hashtags, cashtags, bot commands, bank card numbers, URLs, and email addresses) found in the text. Can be called synchronously @text The text in which to look for entities getTextEntities text:string = TextEntities; -//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, Code, Pre, PreCode, TextUrl and MentionName entities from a marked-up text. Can be called synchronously @text The text to parse @parse_mode Text parse mode +//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, Code, Pre, PreCode, TextUrl and MentionName entities from a marked-up text. Can be called synchronously @text The text to parse @parse_mode Text parse mode parseTextEntities text:string parse_mode:TextParseMode = FormattedText; //@description Parses Markdown entities in a human-friendly format, ignoring markup errors. Can be called synchronously @@ -7162,8 +7428,8 @@ sendWebAppData bot_user_id:int53 button_text:string data:string = Ok; //@theme Preferred Web App theme; pass null to use the default theme //@application_name Short name of the application; 0-64 English letters, digits, and underscores //@message_thread_id If not 0, a message thread identifier in which the message will be sent -//@reply_to Identifier of the replied message or story for the message sent by the Web App; pass null if none -openWebApp chat_id:int53 bot_user_id:int53 url:string theme:themeParameters application_name:string message_thread_id:int53 reply_to:MessageReplyTo = WebAppInfo; +//@reply_to Information about the message or story to be replied in the message sent by the Web App; pass null if none +openWebApp chat_id:int53 bot_user_id:int53 url:string theme:themeParameters application_name:string message_thread_id:int53 reply_to:InputMessageReplyTo = WebAppInfo; //@description Informs TDLib that a previously opened Web App was closed @web_app_launch_id Identifier of Web App launch, received from openWebApp closeWebApp web_app_launch_id:int64 = Ok; @@ -7404,6 +7670,12 @@ setChatTitle chat_id:int53 title:string = Ok; //@photo New chat photo; pass null to delete the chat photo setChatPhoto chat_id:int53 photo:InputChatPhoto = Ok; +//@description Changes accent color and background custom emoji of a chat. Supported only for channels with getOption("channel_custom_accent_color_boost_level_min") boost level. Requires can_change_info administrator right +//@chat_id Chat identifier +//@accent_color_id Identifier of the accent color to use +//@background_custom_emoji_id Identifier of a custom emoji to be shown on the reply header background; 0 if none +setChatAccentColor chat_id:int53 accent_color_id:int32 background_custom_emoji_id:int64 = Ok; + //@description Changes the message auto-delete or self-destruct (for secret chats) time in a chat. Requires change_info administrator right in basic groups, supergroups and channels //-Message auto-delete time can't be changed in a chat with the current user (Saved Messages) and the chat 777000 (Telegram). //@chat_id Chat identifier @@ -7704,14 +7976,14 @@ reportStory story_sender_chat_id:int53 story_id:int32 reason:ReportReason text:s activateStoryStealthMode = Ok; +//@description Returns the list of available chat boost slots for the current user +getAvailableChatBoostSlots = ChatBoostSlots; + //@description Returns the current boost status for a channel chat @chat_id Identifier of the channel chat getChatBoostStatus chat_id:int53 = ChatBoostStatus; -//@description Checks whether the current user can boost a chat @chat_id Identifier of the chat -canBoostChat chat_id:int53 = CanBoostChatResult; - -//@description Boosts a chat @chat_id Identifier of the chat -boostChat chat_id:int53 = Ok; +//@description Boosts a chat and returns the list of available chat boost slots for the current user after the boost @chat_id Identifier of the chat @slot_ids Identifiers of boost slots of the current user from which to apply boosts to the chat +boostChat chat_id:int53 slot_ids:vector = ChatBoostSlots; //@description Returns an HTTPS link to boost the specified channel chat @chat_id Identifier of the chat getChatBoostLink chat_id:int53 = ChatBoostLink; @@ -7719,11 +7991,17 @@ getChatBoostLink chat_id:int53 = ChatBoostLink; //@description Returns information about a link to boost a chat. Can be called for any internal link of the type internalLinkTypeChatBoost @url The link to boost a chat getChatBoostLinkInfo url:string = ChatBoostLinkInfo; -//@description Returns list of boosts applied to a chat. The user must be an administrator in the channel chat to get the list of boosts +//@description Returns list of boosts applied to a chat; requires administrator rights in the channel chat //@chat_id Identifier of the chat +//@only_gift_codes Pass true to receive only boosts received from gift codes and giveaways created by the chat //@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 boosts to be returned; up to 100. For optimal performance, the number of returned boosts can be smaller than the specified limit -getChatBoosts chat_id:int53 offset:string limit:int32 = FoundChatBoosts; +getChatBoosts chat_id:int53 only_gift_codes:Bool offset:string limit:int32 = FoundChatBoosts; + +//@description Returns list of boosts applied to a chat by a given user; requires administrator rights in the channel chat; for bots only +//@chat_id Identifier of the chat +//@user_id Identifier of the user +getUserChatBoosts chat_id:int53 user_id:int53 = FoundChatBoosts; //@description Returns information about a bot that can be added to attachment or side menu @bot_user_id Bot's user identifier @@ -8285,6 +8563,9 @@ getDefaultChatPhotoCustomEmojiStickers = Stickers; //@description Returns default list of custom emoji stickers for placing on a profile photo getDefaultProfilePhotoCustomEmojiStickers = Stickers; +//@description Returns default list of custom emoji stickers for reply background +getDefaultBackgroundCustomEmojiStickers = Stickers; + //@description Returns saved animations getSavedAnimations = Animations; @@ -8308,8 +8589,10 @@ searchHashtags prefix:string limit:int32 = Hashtags; removeRecentHashtag hashtag:string = Ok; -//@description Returns a web page preview by the text of the message. Do not call this function too often. Returns a 404 error if the web page has no preview @text Message text with formatting -getWebPagePreview text:formattedText = WebPage; +//@description Returns a link preview by the text of a message. Do not call this function too often. Returns a 404 error if the text has no link preview +//@text Message text with formatting +//@link_preview_options Options to be used for generation of the link preview; pass null to use default link preview options +getWebPagePreview text:formattedText link_preview_options:linkPreviewOptions = WebPage; //@description Returns an instant view version of a web page if available. Returns a 404 error if the web page has no instant view page @url The web page URL @force_full Pass true to get full instant view for the web page getWebPageInstantView url:string force_full:Bool = WebPageInstantView; @@ -8323,6 +8606,11 @@ setProfilePhoto photo:InputChatPhoto is_public:Bool = Ok; //@description Deletes a profile photo @profile_photo_id Identifier of the profile photo to delete deleteProfilePhoto profile_photo_id:int64 = Ok; +//@description Changes accent color and background custom emoji for the current user; for Telegram Premium users only +//@accent_color_id Identifier of the accent color to use +//@background_custom_emoji_id Identifier of a custom emoji to be shown on the reply header background; 0 if none +setAccentColor accent_color_id:int32 background_custom_emoji_id:int64 = Ok; + //@description Changes the first and last name of the current user @first_name The new value of the first name for the current user; 1-64 characters @last_name The new value of the optional last name for the current user; 0-64 characters setName first_name:string last_name:string = Ok; @@ -8748,6 +9036,13 @@ getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics; //@description Returns detailed statistics about a message. Can be used only if message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application getMessageStatistics chat_id:int53 message_id:int53 is_dark:Bool = MessageStatistics; +//@description Returns forwarded copies of a channel message to different public channels. Can be used only if message.can_get_statistics == true. For optimal performance, the number of returned messages is chosen by TDLib +//@chat_id Chat identifier of the message +//@message_id Message identifier +//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit +getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages; + //@description Loads an asynchronous or a zoomed in statistical graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise getStatisticalGraph chat_id:int53 token:string x:int53 = StatisticalGraph; @@ -8988,6 +9283,26 @@ clickPremiumSubscriptionButton = Ok; //@description Returns state of Telegram Premium subscription and promotion videos for Premium features getPremiumState = PremiumState; +//@description Returns available options for Telegram Premium gift code or giveaway creation +//@boosted_chat_id Identifier of the channel chat, which will be automatically boosted by receivers of the gift codes and which is administered by the user; 0 if none +getPremiumGiftCodePaymentOptions boosted_chat_id:int53 = PremiumGiftCodePaymentOptions; + +//@description Return information about a Telegram Premium gift code @code The code to check +checkPremiumGiftCode code:string = PremiumGiftCodeInfo; + +//@description Applies a Telegram Premium gift code @code The code to apply +applyPremiumGiftCode code:string = Ok; + +//@description Launches a prepaid Telegram Premium giveaway for subscribers of channel chats; requires can_post_messages rights in the channels +//@giveaway_id Unique identifier of the prepaid giveaway +//@parameters Giveaway parameters +launchPrepaidPremiumGiveaway giveaway_id:int64 parameters:premiumGiveawayParameters = Ok; + +//@description Returns information about a Telegram Premium giveaway +//@chat_id Identifier of the channel chat which started the giveaway +//@message_id Identifier of the giveaway message in the chat +getPremiumGiveawayInfo chat_id:int53 message_id:int53 = PremiumGiveawayInfo; + //@description Checks whether Telegram Premium purchase is possible. Must be called before in-store Premium purchase @purpose Transaction purpose canPurchasePremium purpose:StorePaymentPurpose = Ok; diff --git a/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl b/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl index 3a567ade..6dc2eba4 100644 --- a/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl +++ b/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl @@ -64,6 +64,7 @@ inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia; +inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; @@ -102,7 +103,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#abb5f120 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 flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?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 usernames:flags2.0?Vector stories_max_id:flags2.5?int = User; +user#eb602f25 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 flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?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 usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.7?int background_emoji_id:flags2.6?long = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -117,7 +118,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#94f592db 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 forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?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 usernames:flags2.0?Vector stories_max_id:flags2.4?int = Chat; +channel#1981ea7e 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 forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?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 usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.6?int background_emoji_id:flags2.5?long = 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#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?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; @@ -134,7 +135,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -143,7 +144,7 @@ messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#4cf4d72d flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document alt_document:flags.5?Document ttl_seconds:flags.2?int = MessageMedia; -messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; +messageMediaWebPage#ddf10c3b flags:# force_large_media:flags.0?true force_small_media:flags.1?true manual:flags.3?true safe:flags.4?true webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia; @@ -151,6 +152,7 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; +messageMediaGiveaway#58260664 flags:# only_new_subscribers:flags.0?true channels:Vector countries_iso2:flags.1?Vector quantity:int months:int until_date:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -191,6 +193,8 @@ messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; +messageActionGiftCode#d2cfdb0e flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string = MessageAction; +messageActionGiveawayLaunch#332ba9ed = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -309,7 +313,7 @@ updateChatParticipantAdd#3dda5451 chat_id:long user_id:long inviter_id:long date updateChatParticipantDelete#e32f3d77 chat_id:long user_id:long version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; -updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; +updateServiceNotification#ebe46819 flags:# popup:flags.0?true invert_media:flags.2?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; updateUserPhone#5492a13 user_id:long phone:string = Update; updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update; @@ -409,6 +413,7 @@ updateReadStories#f74e932b peer:Peer max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; updateSentStoryReaction#7d627683 peer:Peer story_id:int reaction:Reaction = Update; +updateBotChatBoost#904dd49c peer:Peer boost:Boost qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -569,9 +574,9 @@ messages.allStickers#cdbbcebb hash:long sets:Vector = messages.AllSt messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; -webPageEmpty#eb1477e8 id:long = WebPage; -webPagePending#c586da1c id:long date:int = WebPage; -webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; +webPageEmpty#211a1788 flags:# id:long url:flags.0?string = WebPage; +webPagePending#b0d13e47 flags:# id:long url:flags.0?string date:int = WebPage; +webPage#e89c45b2 flags:# has_large_media:flags.13?true id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true unconfirmed:flags.5?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -592,7 +597,7 @@ chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true r chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvite#cde0ec40 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:int = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -606,7 +611,7 @@ inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = 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; +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 text_color:flags.9?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; messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; @@ -656,10 +661,10 @@ messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; -messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; +messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -702,25 +707,27 @@ help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#84a02a0d hash:long gifs:Vector = messages.SavedGifs; -inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaAuto#3380c786 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaWebPage#bddcc510 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true optional:flags.6?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult; -botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaAuto#764cf810 flags:# invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true invert_media:flags.3?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaWebPage#809ad9a6 flags:# invert_media:flags.3?true force_large_media:flags.4?true force_small_media:flags.5?true manual:flags.7?true safe:flags.8?true message:string entities:flags.1?Vector url:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; @@ -776,7 +783,7 @@ contacts.topPeers#70b772a8 categories:Vector chats:Vector< contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; -draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; +draftMessage#3fccf7ef flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector media:flags.5?InputMedia date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; @@ -981,6 +988,8 @@ channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:For channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction; channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction; channelAdminLogEventActionToggleAntiSpam#64f36dfc new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeColor#3c2b247b prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeBackgroundEmoji#445fc434 prev_value:long new_value:long = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1277,7 +1286,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#6eebcabd flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector = MessageReplyHeader; messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1427,6 +1436,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; +inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1436,6 +1446,8 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentPremiumGiveaway#7c9375e6 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1567,7 +1579,7 @@ stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vect stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; -inputReplyToMessage#9c5386e4 flags:# reply_to_msg_id:int top_msg_id:flags.0?int = InputReplyTo; +inputReplyToMessage#73ec805 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector = InputReplyTo; inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1585,14 +1597,26 @@ peerStories#9a35e999 flags:# peer:Peer max_read_id:flags.0?int stories:Vector users:Vector = stories.PeerStories; -stories.boostsStatus#66ea1fef flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue = stories.BoostsStatus; +messages.webPage#fd5e12bd webpage:WebPage chats:Vector users:Vector = messages.WebPage; + +premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags.0?string store_quantity:flags.1?int currency:string amount:long = PremiumGiftCodeOption; + +payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; + +payments.giveawayInfo#4367daa0 flags:# participating:flags.0?true preparing_results:flags.3?true start_date:int joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long disallowed_country:flags.4?string = payments.GiveawayInfo; +payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; -stories.canApplyBoostOk#c3173587 = stories.CanApplyBoostResult; -stories.canApplyBoostReplace#712c4655 current_boost:Peer chats:Vector = stories.CanApplyBoostResult; +prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; -booster#e9e6380 user_id:long expires:int = Booster; +boost#2a1c8c71 flags:# gift:flags.1?true giveaway:flags.2?true unclaimed:flags.3?true id:string user_id:flags.0?long giveaway_msg_id:flags.2?int date:int expires:int used_gift_slug:flags.4?string multiplier:flags.5?int = Boost; -stories.boostersList#f3dd3d1d flags:# count:int boosters:Vector next_offset:flags.0?string users:Vector = stories.BoostersList; +premium.boostsList#86f8613c flags:# count:int boosts:Vector next_offset:flags.0?string users:Vector = premium.BoostsList; + +myBoost#c448415c flags:# slot:int peer:flags.0?Peer date:int expires:int cooldown_until_date:flags.1?int = MyBoost; + +premium.myBoosts#9ae228e2 my_boosts:Vector chats:Vector users:Vector = premium.MyBoosts; + +premium.boostsStatus#4959427a flags:# my_boost:flags.2?true level:int current_level_boosts:int boosts:int gift_boosts:flags.4?int next_level_boosts:flags.0?int premium_audience:flags.1?StatsPercentValue boost_url:string prepaid_giveaways:flags.3?Vector my_boost_slots:flags.2?Vector = premium.BoostsStatus; ---functions--- @@ -1715,6 +1739,8 @@ account.getAutoSaveSettings#adcbbcda = account.AutoSaveSettings; account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?true broadcasts:flags.2?true peer:flags.3?InputPeer settings:AutoSaveSettings = Bool; account.deleteAutoSaveExceptions#53bc0020 = Bool; account.invalidateSignInCodes#ca8ae8ba codes:Vector = Bool; +account.updateColor#a001cc43 flags:# color:int background_emoji_id:flags.0?long = Bool; +account.getDefaultBackgroundEmojis#a60ab9ce hash:long = EmojiList; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1755,8 +1781,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#280d096f 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:flags.0?InputReplyTo 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#72ccc23d 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:flags.0?InputReplyTo 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#280d096f 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 invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo 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#72ccc23d flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo 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#c661bbc4 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 top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -1802,12 +1828,12 @@ messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_p messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; messages.sendInlineBotResult#f7bc68ba flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; -messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; -messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; +messages.editMessage#48f71778 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; +messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_media:flags.16?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector = Bool; +messages.saveDraft#7ff3b806 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo peer:InputPeer message:string entities:flags.3?Vector media:flags.5?InputMedia = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; @@ -1822,7 +1848,7 @@ messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:fla messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores; messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores; messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats; -messages.getWebPage#32ca8f91 url:string hash:int = WebPage; +messages.getWebPage#8d9692a3 url:string hash:int = messages.WebPage; messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool; messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int order:Vector = Bool; messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs; @@ -1835,7 +1861,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#456e8987 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:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#456e8987 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo 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; @@ -2033,6 +2059,7 @@ channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates; channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool; channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool; +channels.updateColor#621a201f flags:# channel:InputChannel color:int background_emoji_id:flags.0?long = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2062,6 +2089,11 @@ 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.getPremiumGiftCodeOptions#2757ba54 flags:# boost_peer:flags.0?InputPeer = Vector; +payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; +payments.applyGiftCode#f6e26854 slug:string = Updates; +payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; +payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?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; @@ -2155,7 +2187,9 @@ stories.getAllReadPeerStories#9b5ae7f9 = Updates; stories.getPeerMaxIDs#535983c3 id:Vector = Vector; stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; -stories.getBoostsStatus#4c449472 peer:InputPeer = stories.BoostsStatus; -stories.getBoostersList#337ef980 peer:InputPeer offset:string limit:int = stories.BoostersList; -stories.canApplyBoost#db05c1bd peer:InputPeer = stories.CanApplyBoostResult; -stories.applyBoost#f29d7c2b peer:InputPeer = Bool; + +premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; +premium.getMyBoosts#be77b4a = premium.MyBoosts; +premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = premium.MyBoosts; +premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; +premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; diff --git a/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h b/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h index c0226c4c..14b8ceb7 100644 --- a/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h +++ b/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h @@ -26,7 +26,7 @@ class TlWriterDotNet final : public TL_writer { : TL_writer(name), is_header_(is_header), prefix_(prefix) { } - int get_max_arity(void) const final { + int get_max_arity() const final { return 0; } @@ -42,7 +42,7 @@ class TlWriterDotNet final : public TL_writer { is_built_in_complex_type(t->name); } - std::vector get_parsers(void) const final { + std::vector get_parsers() const final { return {"FromUnmanaged"}; } int get_parser_type(const tl_combinator *t, const std::string &name) const final { @@ -51,10 +51,10 @@ class TlWriterDotNet final : public TL_writer { Mode get_parser_mode(int type) const final { return All; // Server; } - std::vector get_storers(void) const final { + std::vector get_storers() const final { return {"ToUnmanaged", "ToString"}; } - std::vector get_additional_functions(void) const final { + std::vector get_additional_functions() const final { return {"ToUnmanaged", "FromUnmanaged"}; } int get_storer_type(const tl_combinator *t, const std::string &name) const final { @@ -64,14 +64,14 @@ class TlWriterDotNet final : public TL_writer { return type <= 1 ? All : Server; } - std::string gen_base_tl_class_name(void) const final { + std::string gen_base_tl_class_name() const final { return "BaseObject"; } std::string gen_base_type_class_name(int arity) const final { assert(arity == 0); return "Object"; } - std::string gen_base_function_class_name(void) const final { + std::string gen_base_function_class_name() const final { return "Function"; } @@ -188,7 +188,7 @@ class TlWriterDotNet final : public TL_writer { "namespace Api {\n"; } - std::string gen_output_begin_once(void) const final { + std::string gen_output_begin_once() const final { return std::string(); } @@ -231,7 +231,7 @@ class TlWriterDotNet final : public TL_writer { << " public:\n"; return ss.str(); } - std::string gen_class_end(void) const final { + std::string gen_class_end() const final { return ""; } @@ -413,7 +413,7 @@ class TlWriterDotNet final : public TL_writer { assert(0); return std::string(); } - std::string gen_var_type_name(void) const final { + std::string gen_var_type_name() const final { assert(0); return std::string(); } @@ -504,7 +504,7 @@ class TlWriterDotNet final : public TL_writer { const tl_tree *result) const final { return ""; } - std::string gen_fetch_function_result_end(void) const final { + std::string gen_fetch_function_result_end() const final { return ""; } std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name, @@ -515,13 +515,13 @@ class TlWriterDotNet final : public TL_writer { return ""; } - std::string gen_fetch_switch_begin(void) const final { + std::string gen_fetch_switch_begin() const final { return ""; } std::string gen_fetch_switch_case(const tl_combinator *t, int arity) const final { return ""; } - std::string gen_fetch_switch_end(void) const final { + std::string gen_fetch_switch_end() const final { return ""; } diff --git a/lib/tgchat/ext/td/td/mtproto/Handshake.h b/lib/tgchat/ext/td/td/mtproto/Handshake.h index 3eb9ff8a..cc993946 100644 --- a/lib/tgchat/ext/td/td/mtproto/Handshake.h +++ b/lib/tgchat/ext/td/td/mtproto/Handshake.h @@ -16,11 +16,6 @@ #include "td/utils/UInt.h" namespace td { - -namespace mtproto_api { -class Object; -} // namespace mtproto_api - namespace mtproto { class DhCallback; diff --git a/lib/tgchat/ext/td/td/mtproto/PingConnection.cpp b/lib/tgchat/ext/td/td/mtproto/PingConnection.cpp index 53b15c2a..68f00157 100644 --- a/lib/tgchat/ext/td/td/mtproto/PingConnection.cpp +++ b/lib/tgchat/ext/td/td/mtproto/PingConnection.cpp @@ -119,12 +119,6 @@ class PingConnectionPingPong final status_ = std::move(status); } - void on_auth_key_updated() final { - } - - void on_tmp_auth_key_updated() final { - } - void on_server_salt_updated() final { } diff --git a/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp b/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp index f3cef190..d880cb0e 100644 --- a/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp +++ b/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp @@ -299,7 +299,10 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::dest } Status SessionConnection::on_destroy_auth_key(const mtproto_api::DestroyAuthKeyRes &destroy_auth_key) { - LOG_CHECK(need_destroy_auth_key_) << static_cast(mode_); + if (!need_destroy_auth_key_) { + LOG(ERROR) << "Receive unexpected " << oneline(to_string(destroy_auth_key)); + return Status::OK(); + } return callback_->on_destroy_auth_key(); } @@ -413,6 +416,13 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::pong if (info.message_id.get() < static_cast(pong.msg_id_) - (static_cast(15) << 32)) { reset_server_time_difference(info.message_id); } + + if (sent_destroy_auth_key_ && destroy_auth_key_send_time_ < Time::now() - 60) { + return Status::Error(PSLICE() << "No response for destroy_auth_key for " + << (Time::now() - destroy_auth_key_send_time_) << " seconds from auth key " + << auth_data_->get_auth_key().id()); + } + last_pong_at_ = Time::now_cached(); real_last_pong_at_ = last_pong_at_; return callback_->on_pong(); @@ -583,6 +593,7 @@ void SessionConnection::on_message_failed(MessageId message_id, Status status) { callback_->on_message_failed(message_id, std::move(status)); sent_destroy_auth_key_ = false; + destroy_auth_key_send_time_ = 0.0; if (message_id == last_ping_message_id_ || message_id == last_ping_container_message_id_) { // restart ping immediately @@ -747,6 +758,7 @@ SessionConnection::SessionConnection(Mode mode, unique_ptr raw_co , raw_connection_(std::move(raw_connection)) , auth_data_(auth_data) { CHECK(raw_connection_); + CHECK(auth_data_ != nullptr); } PollableFdInfo &SessionConnection::get_poll_info() { @@ -957,7 +969,10 @@ void SessionConnection::flush_packet() { return; } - sent_destroy_auth_key_ |= destroy_auth_key; + if (destroy_auth_key && !sent_destroy_auth_key_) { + sent_destroy_auth_key_ = true; + destroy_auth_key_send_time_ = Time::now(); + } VLOG(mtproto) << "Sent packet: " << tag("query_count", queries.size()) << tag("ack_count", to_ack_message_ids_.size()) << tag("ping", ping_id != 0) << tag("http_wait", max_delay >= 0) @@ -993,8 +1008,7 @@ void SessionConnection::flush_packet() { auto to_ack = cut_tail(to_ack_message_ids_, 8192, "ack"); MessageId ping_message_id; - bool use_quick_ack = - std::any_of(queries.begin(), queries.end(), [](const auto &query) { return query.use_quick_ack; }); + bool use_quick_ack = any_of(queries, [](const auto &query) { return query.use_quick_ack; }); { // LOG(ERROR) << (auth_data_->get_header().empty() ? '-' : '+'); diff --git a/lib/tgchat/ext/td/td/mtproto/SessionConnection.h b/lib/tgchat/ext/td/td/mtproto/SessionConnection.h index 669e38cc..50d28a16 100644 --- a/lib/tgchat/ext/td/td/mtproto/SessionConnection.h +++ b/lib/tgchat/ext/td/td/mtproto/SessionConnection.h @@ -91,8 +91,6 @@ class SessionConnection final virtual void on_connected() = 0; virtual void on_closed(Status status) = 0; - virtual void on_auth_key_updated() = 0; - virtual void on_tmp_auth_key_updated() = 0; virtual void on_server_salt_updated() = 0; virtual void on_server_time_difference_updated(bool force) = 0; @@ -194,6 +192,7 @@ class SessionConnection final bool need_destroy_auth_key_ = false; bool sent_destroy_auth_key_ = false; + double destroy_auth_key_send_time_ = 0.0; double flush_packet_at_ = 0; @@ -207,7 +206,7 @@ class SessionConnection final double created_at_ = 0; unique_ptr raw_connection_; - AuthData *auth_data_; + AuthData *const auth_data_; SessionConnection::Callback *callback_ = nullptr; BufferSlice *current_buffer_slice_ = nullptr; diff --git a/lib/tgchat/ext/td/td/telegram/AccentColorId.h b/lib/tgchat/ext/td/td/telegram/AccentColorId.h new file mode 100644 index 00000000..8faffc43 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/AccentColorId.h @@ -0,0 +1,82 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/ChannelId.h" +#include "td/telegram/ChatId.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +#include + +namespace td { + +class AccentColorId { + int32 id = -1; + + public: + AccentColorId() = default; + + explicit constexpr AccentColorId(int32 accent_color_id) : id(accent_color_id) { + } + template ::value>> + AccentColorId(T accent_color_id) = delete; + + explicit AccentColorId(UserId user_id) : id(static_cast(user_id.get() % 7)) { + } + + explicit AccentColorId(ChatId chat_id) : id(static_cast(chat_id.get() % 7)) { + } + + explicit AccentColorId(ChannelId channel_id) : id(static_cast(channel_id.get() % 7)) { + } + + bool is_valid() const { + return id >= 0; + } + + bool is_built_in() const { + return 0 <= id && id < 7; + } + + int32 get() const { + return id; + } + + bool operator==(const AccentColorId &other) const { + return id == other.id; + } + + bool operator!=(const AccentColorId &other) const { + return id != other.id; + } + + template + void store(StorerT &storer) const { + storer.store_int(id); + } + + template + void parse(ParserT &parser) { + id = parser.fetch_int(); + } +}; + +struct AccentColorIdHash { + uint32 operator()(AccentColorId accent_color_id) const { + return Hash()(accent_color_id.get()); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, AccentColorId accent_color_id) { + return string_builder << "accent color #" << accent_color_id.get(); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/AnimationsManager.cpp b/lib/tgchat/ext/td/td/telegram/AnimationsManager.cpp index a9623c1e..027567a3 100644 --- a/lib/tgchat/ext/td/td/telegram/AnimationsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AnimationsManager.cpp @@ -756,16 +756,8 @@ void AnimationsManager::add_saved_animation_impl(FileId animation_id, bool add_o return promise.set_error(Status::Error(400, "Can't save encrypted animations")); } - auto it = std::find_if(saved_animation_ids_.begin(), saved_animation_ids_.end(), is_equal); - if (it == saved_animation_ids_.end()) { - if (static_cast(saved_animation_ids_.size()) == saved_animations_limit_) { - saved_animation_ids_.back() = animation_id; - } else { - saved_animation_ids_.push_back(animation_id); - } - it = saved_animation_ids_.end() - 1; - } - std::rotate(saved_animation_ids_.begin(), it, it + 1); + add_to_top_if(saved_animation_ids_, static_cast(saved_animations_limit_), animation_id, is_equal); + CHECK(is_equal(saved_animation_ids_[0])); if (saved_animation_ids_[0].get_remote() == 0 && animation_id.get_remote() != 0) { saved_animation_ids_[0] = animation_id; diff --git a/lib/tgchat/ext/td/td/telegram/Application.cpp b/lib/tgchat/ext/td/td/telegram/Application.cpp index 0b29e352..f84aa1bd 100644 --- a/lib/tgchat/ext/td/td/telegram/Application.cpp +++ b/lib/tgchat/ext/td/td/telegram/Application.cpp @@ -150,7 +150,7 @@ static void save_app_log_impl(Td *td, telegram_api::object_ptr &&data, Promise &&promise) { CHECK(data != nullptr); - auto input_app_event = telegram_api::make_object(G()->server_time_cached(), type, + auto input_app_event = telegram_api::make_object(G()->server_time(), type, dialog_id.get(), std::move(data)); save_app_log_impl(td, std::move(input_app_event), 0, std::move(promise)); } diff --git a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp index 7b18d848..bca3de45 100644 --- a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp @@ -135,7 +135,7 @@ class RequestWebViewQuery final : public Td::ResultHandler { dialog_id_ = dialog_id; bot_user_id_ = bot_user_id; top_thread_message_id_ = top_thread_message_id; - input_reply_to_ = input_reply_to; + input_reply_to_ = std::move(input_reply_to); as_dialog_id_ = as_dialog_id; int32 flags = 0; @@ -198,7 +198,7 @@ class RequestWebViewQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); td_->attach_menu_manager_->open_web_view(ptr->query_id_, dialog_id_, bot_user_id_, top_thread_message_id_, - input_reply_to_, as_dialog_id_); + std::move(input_reply_to_), as_dialog_id_); promise_.set_value(td_api::make_object(ptr->query_id_, ptr->url_)); } @@ -217,7 +217,7 @@ class ProlongWebViewQuery final : public Td::ResultHandler { public: void send(DialogId dialog_id, UserId bot_user_id, int64 query_id, MessageId top_thread_message_id, - MessageInputReplyTo input_reply_to, bool silent, DialogId as_dialog_id) { + const MessageInputReplyTo &input_reply_to, bool silent, DialogId as_dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); @@ -414,7 +414,7 @@ bool operator==(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuMan lhs.supports_bot_dialogs_ == rhs.supports_bot_dialogs_ && lhs.supports_group_dialogs_ == rhs.supports_group_dialogs_ && lhs.supports_broadcast_dialogs_ == rhs.supports_broadcast_dialogs_ && - lhs.supports_settings_ == rhs.supports_settings_ && lhs.request_write_access_ == rhs.request_write_access_ && + lhs.request_write_access_ == rhs.request_write_access_ && lhs.show_in_attach_menu_ == rhs.show_in_attach_menu_ && lhs.show_in_side_menu_ == rhs.show_in_side_menu_ && lhs.side_menu_disclaimer_needed_ == rhs.side_menu_disclaimer_needed_ && lhs.name_ == rhs.name_ && lhs.default_icon_file_id_ == rhs.default_icon_file_id_ && @@ -460,7 +460,7 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { STORE_FLAG(supports_bot_dialogs_); STORE_FLAG(supports_group_dialogs_); STORE_FLAG(supports_broadcast_dialogs_); - STORE_FLAG(supports_settings_); + STORE_FLAG(false); STORE_FLAG(has_placeholder_file_id); STORE_FLAG(has_cache_version); STORE_FLAG(request_write_access_); @@ -523,6 +523,7 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { bool has_android_side_menu_icon_file_id; bool has_ios_side_menu_icon_file_id; bool has_macos_side_menu_icon_file_id; + bool legacy_supports_settings; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_ios_static_icon_file_id); PARSE_FLAG(has_ios_animated_icon_file_id); @@ -537,7 +538,7 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { PARSE_FLAG(supports_bot_dialogs_); PARSE_FLAG(supports_group_dialogs_); PARSE_FLAG(supports_broadcast_dialogs_); - PARSE_FLAG(supports_settings_); + PARSE_FLAG(legacy_supports_settings); PARSE_FLAG(has_placeholder_file_id); PARSE_FLAG(has_cache_version); PARSE_FLAG(request_write_access_); @@ -801,7 +802,7 @@ void AttachMenuManager::on_get_web_app(UserId bot_user_id, string web_app_short_ td_->file_manager_->add_file_source(file_id, file_source_id); } } - promise.set_value(td_api::make_object(web_app.get_web_app_object(td_), bot_app->has_settings_, + promise.set_value(td_api::make_object(web_app.get_web_app_object(td_), bot_app->request_write_access_, !bot_app->inactive_)); } @@ -834,7 +835,7 @@ void AttachMenuManager::request_app_web_view(DialogId dialog_id, UserId bot_user } void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, string &&url, + td_api::object_ptr &&reply_to, string &&url, td_api::object_ptr &&theme, string &&platform, Promise> &&promise) { TRY_STATUS_PROMISE(promise, td_->contacts_manager_->get_bot_data(bot_user_id)); @@ -875,11 +876,11 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, td_->create_handler(std::move(promise)) ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), std::move(platform), - top_thread_message_id, input_reply_to, silent, as_dialog_id); + top_thread_message_id, std::move(input_reply_to), silent, as_dialog_id); } void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, - MessageId top_thread_message_id, MessageInputReplyTo input_reply_to, + MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, DialogId as_dialog_id) { if (query_id == 0) { LOG(ERROR) << "Receive Web App query identifier == 0"; @@ -1051,7 +1052,6 @@ Result AttachMenuManager::get_attach_menu_bot( break; } } - attach_menu_bot.supports_settings_ = bot->has_settings_; attach_menu_bot.request_write_access_ = bot->request_write_access_; attach_menu_bot.show_in_attach_menu_ = bot->show_in_attach_menu_; attach_menu_bot.show_in_side_menu_ = bot->show_in_side_menu_; @@ -1305,8 +1305,8 @@ td_api::object_ptr AttachMenuManager::get_attachment_ return td_api::make_object( td_->contacts_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"), bot.supports_self_dialog_, bot.supports_user_dialogs_, bot.supports_bot_dialogs_, bot.supports_group_dialogs_, - bot.supports_broadcast_dialogs_, bot.supports_settings_, bot.request_write_access_, bot.is_added_, - bot.show_in_attach_menu_, bot.show_in_side_menu_, bot.side_menu_disclaimer_needed_, bot.name_, + bot.supports_broadcast_dialogs_, bot.request_write_access_, bot.is_added_, bot.show_in_attach_menu_, + bot.show_in_side_menu_, bot.side_menu_disclaimer_needed_, bot.name_, get_attach_menu_bot_color_object(bot.name_color_), get_file(bot.default_icon_file_id_), get_file(bot.ios_static_icon_file_id_), get_file(bot.ios_animated_icon_file_id_), get_file(bot.ios_side_menu_icon_file_id_), get_file(bot.android_icon_file_id_), diff --git a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.h b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.h index 94d94a68..0adf2403 100644 --- a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.h +++ b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.h @@ -43,12 +43,12 @@ class AttachMenuManager final : public Actor { string &&platform, bool allow_write_access, Promise &&promise); void request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, string &&url, + td_api::object_ptr &&reply_to, string &&url, td_api::object_ptr &&theme, string &&platform, Promise> &&promise); void open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id, - MessageInputReplyTo input_reply_to, DialogId as_dialog_id); + MessageInputReplyTo &&input_reply_to, DialogId as_dialog_id); void close_web_view(int64 query_id, Promise &&promise); @@ -104,7 +104,6 @@ class AttachMenuManager final : public Actor { bool supports_bot_dialogs_ = false; bool supports_group_dialogs_ = false; bool supports_broadcast_dialogs_ = false; - bool supports_settings_ = false; bool request_write_access_ = false; bool show_in_attach_menu_ = false; bool show_in_side_menu_ = false; diff --git a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp index 9939ac57..56f49ffe 100644 --- a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp @@ -778,10 +778,9 @@ void AuthManager::do_delete_account(uint64 query_id, string reason, } void AuthManager::on_closing(bool destroy_flag) { - if (destroy_flag) { - update_state(State::LoggingOut); - } else { - update_state(State::Closing); + auto new_state = destroy_flag ? State::LoggingOut : State::Closing; + if (new_state != state_) { + update_state(new_state); } } @@ -849,7 +848,7 @@ void AuthManager::on_sent_code(telegram_api::object_ptrphone_code_hash_)); allow_apple_id_ = code_type->apple_signin_allowed_; allow_google_id_ = code_type->google_signin_allowed_; - update_state(State::WaitEmailAddress, true); + update_state(State::WaitEmailAddress); } 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_)); @@ -870,10 +869,10 @@ void AuthManager::on_sent_code(telegram_api::object_ptr", code_type->length_); CHECK(!email_code_info_.is_empty()); } - update_state(State::WaitEmailCode, true); + update_state(State::WaitEmailCode); } else { send_code_helper_.on_sent_code(std::move(sent_code)); - update_state(State::WaitCode, true); + update_state(State::WaitCode); } on_current_query_ok(); } @@ -900,7 +899,7 @@ void AuthManager::on_send_email_code_result(NetQueryPtr &&net_query) { return on_current_query_error(Status::Error(500, "Receive invalid response")); } - update_state(State::WaitEmailCode, true); + update_state(State::WaitEmailCode); on_current_query_ok(); } @@ -929,7 +928,7 @@ void AuthManager::on_reset_email_address_result(NetQueryPtr &&net_query) { r_sent_code.error().message() == "TASK_ALREADY_EXISTS") { reset_pending_date_ = G()->unix_time() + reset_available_period_; reset_available_period_ = -1; - update_state(State::WaitEmailCode, true); + update_state(State::WaitEmailCode); } return on_current_query_error(r_sent_code.move_as_error()); } @@ -975,7 +974,7 @@ void AuthManager::on_get_login_token(tl_object_ptr(login_token); login_token_ = token->token_.as_slice().str(); set_login_token_expires_at(Time::now() + td::max(token->expires_ - G()->server_time(), 1.0)); - update_state(State::WaitQrCodeConfirmation, true); + update_state(State::WaitQrCodeConfirmation); on_current_query_ok(); break; } @@ -1091,7 +1090,7 @@ void AuthManager::on_request_password_recovery_result(NetQueryPtr &&net_query) { } auto email_address_pattern = r_email_address_pattern.move_as_ok(); wait_password_state_.email_address_pattern_ = std::move(email_address_pattern->email_pattern_); - update_state(State::WaitPassword, true); + update_state(State::WaitPassword); on_current_query_ok(); } @@ -1239,7 +1238,7 @@ void AuthManager::on_get_authorization(tl_object_ptrcontacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization"); - update_state(State::Ok, true); + update_state(State::Ok); if (!td_->contacts_manager_->get_my_id().is_valid()) { LOG(ERROR) << "Server didsn't send proper authorization"; on_current_query_error(Status::Error(500, "Server didn't send proper authorization")); @@ -1381,10 +1380,7 @@ void AuthManager::on_result(NetQueryPtr net_query) { } } -void AuthManager::update_state(State new_state, bool force, bool should_save_state) { - if (state_ == new_state && !force) { - return; - } +void AuthManager::update_state(State new_state, bool should_save_state) { bool skip_update = (state_ == State::LoggingOut || state_ == State::DestroyingKeys) && (new_state == State::LoggingOut || new_state == State::DestroyingKeys); state_ = new_state; @@ -1455,7 +1451,7 @@ bool AuthManager::load_state() { } else { UNREACHABLE(); } - update_state(db_state.state_, false, false); + update_state(db_state.state_, false); return true; } diff --git a/lib/tgchat/ext/td/td/telegram/AuthManager.h b/lib/tgchat/ext/td/td/telegram/AuthManager.h index 1aaa00c4..7c8a39d0 100644 --- a/lib/tgchat/ext/td/td/telegram/AuthManager.h +++ b/lib/tgchat/ext/td/td/telegram/AuthManager.h @@ -217,7 +217,7 @@ class AuthManager final : public NetActor { void on_result(NetQueryPtr net_query) final; - void update_state(State new_state, bool force = false, bool should_save_state = true); + void update_state(State new_state, bool should_save_state = true); tl_object_ptr get_authorization_state_object(State authorization_state) const; static void send_ok(uint64 query_id); diff --git a/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp b/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp index 0af47629..6a056460 100644 --- a/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp @@ -512,7 +512,7 @@ void AutosaveManager::set_autosave_settings(td_api::object_ptrsend(users, chats, broadcasts, dialog_id, new_settings.get_input_auto_save_settings()); } -void AutosaveManager::clear_autosave_settings_excpetions(Promise &&promise) { +void AutosaveManager::clear_autosave_settings_exceptions(Promise &&promise) { if (!settings_.are_inited_) { return promise.set_error(Status::Error(400, "Autosave settings must be loaded first")); } diff --git a/lib/tgchat/ext/td/td/telegram/AutosaveManager.h b/lib/tgchat/ext/td/td/telegram/AutosaveManager.h index c35cfe9b..2bbe056d 100644 --- a/lib/tgchat/ext/td/td/telegram/AutosaveManager.h +++ b/lib/tgchat/ext/td/td/telegram/AutosaveManager.h @@ -32,7 +32,7 @@ class AutosaveManager final : public Actor { void set_autosave_settings(td_api::object_ptr &&scope, td_api::object_ptr &&settings, Promise &&promise); - void clear_autosave_settings_excpetions(Promise &&promise); + void clear_autosave_settings_exceptions(Promise &&promise); void get_current_state(vector> &updates) const; diff --git a/lib/tgchat/ext/td/td/telegram/BoostManager.cpp b/lib/tgchat/ext/td/td/telegram/BoostManager.cpp new file mode 100644 index 00000000..47d0e3c4 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BoostManager.cpp @@ -0,0 +1,475 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/BoostManager.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/LinkManager.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/Td.h" +#include "td/telegram/UserId.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" + +namespace td { + +static td_api::object_ptr get_chat_boost_object( + Td *td, const telegram_api::object_ptr &boost) { + auto source = [&]() -> td_api::object_ptr { + if (boost->giveaway_) { + UserId user_id(boost->user_id_); + if (!user_id.is_valid()) { + user_id = UserId(); + } + auto giveaway_message_id = MessageId(ServerMessageId(boost->giveaway_msg_id_)); + if (!giveaway_message_id.is_valid()) { + giveaway_message_id = MessageId::min(); + } + return td_api::make_object( + td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourceGiveaway"), boost->used_gift_slug_, + giveaway_message_id.get(), boost->unclaimed_); + } + if (boost->gift_) { + UserId user_id(boost->user_id_); + if (!user_id.is_valid()) { + return nullptr; + } + return td_api::make_object( + td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourceGiftCode"), boost->used_gift_slug_); + } + + UserId user_id(boost->user_id_); + if (!user_id.is_valid()) { + return nullptr; + } + return td_api::make_object( + td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourcePremium")); + }(); + if (source == nullptr) { + LOG(ERROR) << "Receive " << to_string(boost); + return nullptr; + } + return td_api::make_object(boost->id_, max(boost->multiplier_, 1), std::move(source), boost->date_, + max(boost->expires_, 0)); +} + +static td_api::object_ptr get_chat_boost_slots_object( + Td *td, telegram_api::object_ptr &&my_boosts) { + td->contacts_manager_->on_get_users(std::move(my_boosts->users_), "GetMyBoostsQuery"); + td->contacts_manager_->on_get_chats(std::move(my_boosts->chats_), "GetMyBoostsQuery"); + vector> slots; + for (auto &my_boost : my_boosts->my_boosts_) { + auto expiration_date = my_boost->expires_; + if (expiration_date <= G()->unix_time()) { + continue; + } + + auto start_date = max(0, my_boost->date_); + auto cooldown_until_date = max(0, my_boost->cooldown_until_date_); + DialogId dialog_id; + if (my_boost->peer_ != nullptr) { + dialog_id = DialogId(my_boost->peer_); + if (!dialog_id.is_valid()) { + LOG(ERROR) << "Receive " << to_string(my_boost); + continue; + } + } + if (dialog_id.is_valid()) { + td->messages_manager_->force_create_dialog(dialog_id, "GetMyBoostsQuery", true); + } else { + start_date = 0; + cooldown_until_date = 0; + } + slots.push_back(td_api::make_object( + my_boost->slot_, td->messages_manager_->get_chat_id_object(dialog_id, "GetMyBoostsQuery"), start_date, + expiration_date, cooldown_until_date)); + } + return td_api::make_object(std::move(slots)); +} + +class GetMyBoostsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetMyBoostsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::premium_getMyBoosts(), {{"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 result = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetMyBoostsQuery: " << to_string(result); + promise_.set_value(get_chat_boost_slots_object(td_, std::move(result))); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetBoostsStatusQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetBoostsStatusQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id) { + dialog_id_ = dialog_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + send_query( + G()->net_query_creator().create(telegram_api::premium_getBoostsStatus(std::move(input_peer)), {{dialog_id}})); + } + + 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 result = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetBoostsStatusQuery: " << to_string(result); + if (result->level_ < 0 || result->current_level_boosts_ < 0 || result->boosts_ < result->current_level_boosts_ || + (result->next_level_boosts_ != 0 && result->boosts_ >= result->next_level_boosts_)) { + LOG(ERROR) << "Receive invalid " << to_string(result); + if (result->level_ < 0) { + result->level_ = 0; + } + if (result->current_level_boosts_ < 0) { + result->current_level_boosts_ = 0; + } + if (result->boosts_ < result->current_level_boosts_) { + result->boosts_ = result->current_level_boosts_; + } + if (result->next_level_boosts_ != 0 && result->boosts_ >= result->next_level_boosts_) { + result->next_level_boosts_ = result->boosts_ + 1; + } + } + int32 premium_member_count = 0; + double premium_member_percentage = 0.0; + if (result->premium_audience_ != nullptr) { + premium_member_count = max(0, static_cast(result->premium_audience_->part_)); + auto participant_count = max(static_cast(result->premium_audience_->total_), premium_member_count); + if (dialog_id_.get_type() == DialogType::Channel) { + td_->contacts_manager_->on_update_channel_participant_count(dialog_id_.get_channel_id(), participant_count); + } + if (participant_count > 0) { + premium_member_percentage = 100.0 * premium_member_count / participant_count; + } + } + auto giveaways = transform(std::move(result->prepaid_giveaways_), + [](telegram_api::object_ptr giveaway) { + return td_api::make_object( + giveaway->id_, giveaway->quantity_, giveaway->months_, giveaway->date_); + }); + auto boost_count = max(0, result->boosts_); + auto gift_code_boost_count = clamp(result->gift_boosts_, 0, boost_count); + promise_.set_value(td_api::make_object( + result->boost_url_, std::move(result->my_boost_slots_), result->level_, gift_code_boost_count, boost_count, + result->current_level_boosts_, result->next_level_boosts_, premium_member_count, premium_member_percentage, + std::move(giveaways))); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBoostsStatusQuery"); + promise_.set_error(std::move(status)); + } +}; + +class ApplyBoostQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit ApplyBoostQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, vector slot_ids) { + dialog_id_ = dialog_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + send_query( + G()->net_query_creator().create(telegram_api::premium_applyBoost(telegram_api::premium_applyBoost::SLOTS_MASK, + std::move(slot_ids), std::move(input_peer)), + {{dialog_id}, {"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 result = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for ApplyBoostQuery: " << to_string(result); + promise_.set_value(get_chat_boost_slots_object(td_, std::move(result))); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ApplyBoostQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetBoostsListQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetBoostsListQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, bool only_gift_codes, const string &offset, int32 limit) { + dialog_id_ = dialog_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + int32 flags = 0; + if (only_gift_codes) { + flags |= telegram_api::premium_getBoostsList::GIFTS_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::premium_getBoostsList(0, false /*ignored*/, std::move(input_peer), offset, limit))); + } + + 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 result = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetBoostsListQuery: " << to_string(result); + td_->contacts_manager_->on_get_users(std::move(result->users_), "GetBoostsListQuery"); + + auto total_count = result->count_; + vector> boosts; + for (auto &boost : result->boosts_) { + auto chat_boost_object = get_chat_boost_object(td_, boost); + if (chat_boost_object == nullptr || chat_boost_object->expiration_date_ <= G()->unix_time()) { + continue; + } + boosts.push_back(std::move(chat_boost_object)); + } + promise_.set_value( + td_api::make_object(total_count, std::move(boosts), result->next_offset_)); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBoostsListQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetUserBoostsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetUserBoostsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, UserId user_id) { + dialog_id_ = dialog_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + CHECK(r_input_user.is_ok()); + send_query(G()->net_query_creator().create( + telegram_api::premium_getUserBoosts(std::move(input_peer), r_input_user.move_as_ok()))); + } + + 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 result = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetUserBoostsQuery: " << to_string(result); + td_->contacts_manager_->on_get_users(std::move(result->users_), "GetUserBoostsQuery"); + + auto total_count = result->count_; + vector> boosts; + for (auto &boost : result->boosts_) { + auto chat_boost_object = get_chat_boost_object(td_, boost); + if (chat_boost_object == nullptr || chat_boost_object->expiration_date_ <= G()->unix_time()) { + continue; + } + boosts.push_back(std::move(chat_boost_object)); + } + promise_.set_value( + td_api::make_object(total_count, std::move(boosts), result->next_offset_)); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetUserBoostsQuery"); + promise_.set_error(std::move(status)); + } +}; + +BoostManager::BoostManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void BoostManager::tear_down() { + parent_.reset(); +} + +void BoostManager::get_boost_slots(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +void BoostManager::get_dialog_boost_status(DialogId dialog_id, + Promise> &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { + 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")); + } + + td_->create_handler(std::move(promise))->send(dialog_id); +} + +void BoostManager::boost_dialog(DialogId dialog_id, vector slot_ids, + Promise> &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { + 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 (slot_ids.empty()) { + return get_boost_slots(std::move(promise)); + } + + td_->create_handler(std::move(promise))->send(dialog_id, slot_ids); +} + +Result> BoostManager::get_dialog_boost_link(DialogId dialog_id) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { + return Status::Error(400, "Chat not found"); + } + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return Status::Error(400, "Can't access the chat"); + } + if (dialog_id.get_type() != DialogType::Channel || + !td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + return Status::Error(400, "Can't boost the chat"); + } + + SliceBuilder sb; + sb << LinkManager::get_t_me_url(); + + auto username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); + bool is_public = !username.empty(); + if (is_public) { + sb << username; + } else { + sb << "c/" << dialog_id.get_channel_id().get(); + } + sb << "?boost"; + + return std::make_pair(sb.as_cslice().str(), is_public); +} + +void BoostManager::get_dialog_boost_link_info(Slice url, Promise &&promise) { + auto r_dialog_boost_link_info = LinkManager::get_dialog_boost_link_info(url); + if (r_dialog_boost_link_info.is_error()) { + return promise.set_error(Status::Error(400, r_dialog_boost_link_info.error().message())); + } + + auto info = r_dialog_boost_link_info.move_as_ok(); + auto query_promise = PromiseCreator::lambda( + [info, promise = std::move(promise)](Result &&result) mutable { promise.set_value(std::move(info)); }); + td_->messages_manager_->resolve_dialog(info.username, info.channel_id, std::move(query_promise)); +} + +td_api::object_ptr BoostManager::get_chat_boost_link_info_object( + const DialogBoostLinkInfo &info) const { + CHECK(info.username.empty() == info.channel_id.is_valid()); + + bool is_public = !info.username.empty(); + DialogId dialog_id = + is_public ? td_->messages_manager_->resolve_dialog_username(info.username) : DialogId(info.channel_id); + return td_api::make_object( + is_public, td_->messages_manager_->get_chat_id_object(dialog_id, "chatBoostLinkInfo")); +} + +void BoostManager::get_dialog_boosts(DialogId dialog_id, bool only_gift_codes, const string &offset, int32 limit, + Promise> &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boosts")) { + 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 (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, only_gift_codes, offset, limit); +} + +void BoostManager::get_user_dialog_boosts(DialogId dialog_id, UserId user_id, + Promise> &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_user_dialog_boosts")) { + 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 (!user_id.is_valid()) { + return promise.set_error(Status::Error(400, "User not found")); + } + + td_->create_handler(std::move(promise))->send(dialog_id, user_id); +} + +void BoostManager::on_update_dialog_boost(DialogId dialog_id, telegram_api::object_ptr &&boost) { + if (!td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive updateBotChatBoost by a non-bot"; + return; + } + if (!dialog_id.is_valid() || !td_->messages_manager_->have_dialog_info_force(dialog_id, "on_update_dialog_boost")) { + LOG(ERROR) << "Receive updateBotChatBoost in " << dialog_id; + return; + } + auto chat_boost_object = get_chat_boost_object(td_, boost); + if (chat_boost_object == nullptr) { + LOG(ERROR) << "Receive wrong updateBotChatBoost in " << dialog_id << ": " << to_string(boost); + return; + } + td_->messages_manager_->force_create_dialog(dialog_id, "on_update_dialog_boost", true); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + td_->messages_manager_->get_chat_id_object(dialog_id, "updateChatBoost"), std::move(chat_boost_object))); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BoostManager.h b/lib/tgchat/ext/td/td/telegram/BoostManager.h new file mode 100644 index 00000000..4bac1e0f --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BoostManager.h @@ -0,0 +1,60 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/DialogBoostLinkInfo.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +#include + +namespace td { + +class Td; + +class BoostManager final : public Actor { + public: + BoostManager(Td *td, ActorShared<> parent); + + void get_boost_slots(Promise> &&promise); + + void get_dialog_boost_status(DialogId dialog_id, Promise> &&promise); + + void boost_dialog(DialogId dialog_id, vector slot_ids, + Promise> &&promise); + + Result> get_dialog_boost_link(DialogId dialog_id); + + void get_dialog_boost_link_info(Slice url, Promise &&promise); + + td_api::object_ptr get_chat_boost_link_info_object(const DialogBoostLinkInfo &info) const; + + void get_dialog_boosts(DialogId dialog_id, bool only_gift_codes, const string &offset, int32 limit, + Promise> &&promise); + + void get_user_dialog_boosts(DialogId dialog_id, UserId user_id, + Promise> &&promise); + + void on_update_dialog_boost(DialogId dialog_id, telegram_api::object_ptr &&boost); + + private: + void tear_down() final; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp index 3027f944..fb3bbc28 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/ConfigManager.h" +#include "td/telegram/AccentColorId.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ConnectionState.h" #include "td/telegram/ContactsManager.h" @@ -29,6 +30,7 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" #include "td/mtproto/AuthData.h" #include "td/mtproto/AuthKey.h" @@ -1483,6 +1485,9 @@ void ConfigManager::process_app_config(tl_object_ptr &c // bool archive_all_stories = false; int32 story_viewers_expire_period = 86400; int64 stories_changelog_user_id = ContactsManager::get_service_notifications_user_id().get(); + FlatHashMap, AccentColorIdHash> light_colors; + FlatHashMap, AccentColorIdHash> dark_colors; + vector accent_color_ids; if (config->get_id() == telegram_api::jsonObject::ID) { for (auto &key_value : static_cast(config.get())->value_) { Slice key = key_value->key_; @@ -1917,6 +1922,94 @@ void ConfigManager::process_app_config(tl_object_ptr &c get_json_value_int(std::move(key_value->value_), key)); continue; } + if (key == "giveaway_add_peers_max") { + G()->set_option_integer("giveaway_additional_chat_count_max", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "giveaway_countries_max") { + G()->set_option_integer("giveaway_country_count_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "giveaway_boosts_per_premium") { + G()->set_option_integer("giveaway_boost_count_per_premium", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "giveaway_period_max") { + G()->set_option_integer("giveaway_duration_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "channel_color_level_min") { + G()->set_option_integer("channel_custom_accent_color_boost_level_min", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "boosts_per_sent_gift") { + G()->set_option_integer("premium_gift_boost_count", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "quote_length_max") { + G()->set_option_integer("message_reply_quote_length_max", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "peer_colors" || key == "dark_peer_colors") { + auto &color_map = key == "peer_colors" ? light_colors : dark_colors; + if (value->get_id() == telegram_api::jsonObject::ID) { + auto peer_color_ids = std::move(static_cast(value)->value_); + for (auto &peer_color_id : peer_color_ids) { + CHECK(peer_color_id != nullptr); + auto r_accent_color_id = to_integer_safe(peer_color_id->key_); + if (r_accent_color_id.is_error()) { + LOG(ERROR) << "Receive " << to_string(peer_color_id); + continue; + } + auto accent_color_id = AccentColorId(r_accent_color_id.ok()); + if (!accent_color_id.is_valid() || accent_color_id.is_built_in() || + peer_color_id->value_->get_id() != telegram_api::jsonArray::ID) { + LOG(ERROR) << "Receive " << to_string(peer_color_id); + continue; + } + auto &colors = color_map[accent_color_id]; + if (!colors.empty()) { + LOG(ERROR) << "Receive duplicate " << accent_color_id; + continue; + } + auto colors_json = std::move(static_cast(peer_color_id->value_.get())->value_); + for (auto &color_json : colors_json) { + auto color_str = get_json_value_string(std::move(color_json), key); + auto r_color = hex_to_integer_safe(color_str); + if (r_color.is_ok() && r_color.ok() <= 0xFFFFFF) { + colors.push_back(static_cast(r_color.ok())); + } + } + if (colors.empty() || colors.size() != colors_json.size()) { + LOG(ERROR) << "Receive invalid colors for " << accent_color_id; + color_map.erase(accent_color_id); + } + } + } else { + LOG(ERROR) << "Receive unexpected " << key << ' ' << to_string(*value); + } + continue; + } + if (key == "peer_colors_available") { + if (value->get_id() == telegram_api::jsonArray::ID) { + auto colors = std::move(static_cast(value)->value_); + for (auto &color : colors) { + auto accent_color_id = AccentColorId(get_json_value_int(std::move(color), key)); + if (accent_color_id.is_valid() && !td::contains(accent_color_ids, accent_color_id)) { + accent_color_ids.push_back(accent_color_id); + } else { + LOG(ERROR) << "Receive an invalid accent color identifier"; + } + } + } else { + LOG(ERROR) << "Receive unexpected peer_colors_available " << to_string(*value); + } + continue; + } new_values.push_back(std::move(key_value)); } @@ -1928,6 +2021,16 @@ void ConfigManager::process_app_config(tl_object_ptr &c send_closure(G()->link_manager(), &LinkManager::update_autologin_domains, std::move(autologin_domains), std::move(url_auth_domains), std::move(whitelisted_domains)); + td::remove_if(accent_color_ids, [&light_colors](AccentColorId accent_color_id) { + if (!accent_color_id.is_built_in() && light_colors.count(accent_color_id) == 0) { + LOG(ERROR) << "Receive unknown " << accent_color_id; + return true; + } + return false; + }); + send_closure(G()->theme_manager(), &ThemeManager::on_update_accent_colors, std::move(light_colors), + std::move(dark_colors), std::move(accent_color_ids)); + Global &options = *G(); if (ignored_restriction_reasons.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.h b/lib/tgchat/ext/td/td/telegram/ConfigManager.h index 4848703c..55c67cd8 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.h +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.h @@ -102,7 +102,7 @@ class ConfigManager final : public NetQueryCallback { private: struct AppConfig { - static constexpr int32 CURRENT_VERSION = 14; + static constexpr int32 CURRENT_VERSION = 22; int32 version_ = 0; int32 hash_ = 0; telegram_api::object_ptr config_; diff --git a/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp b/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp index 2db01219..f7b31d62 100644 --- a/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp @@ -51,6 +51,7 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" @@ -772,6 +773,42 @@ class DeleteProfilePhotoQuery final : public Td::ResultHandler { } }; +class UpdateColorQuery final : public Td::ResultHandler { + Promise promise_; + AccentColorId accent_color_id_; + CustomEmojiId background_custom_emoji_id_; + + public: + explicit UpdateColorQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { + accent_color_id_ = accent_color_id; + background_custom_emoji_id_ = background_custom_emoji_id; + int32 flags = 0; + if (background_custom_emoji_id.is_valid()) { + flags |= telegram_api::account_updateColor::BACKGROUND_EMOJI_ID_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::account_updateColor(flags, accent_color_id.get(), background_custom_emoji_id.get()), {{"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 UpdateColorQuery: " << result_ptr.ok(); + td_->contacts_manager_->on_update_accent_color_success(accent_color_id_, background_custom_emoji_id_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class UpdateProfileQuery final : public Td::ResultHandler { Promise promise_; int32 flags_; @@ -1278,6 +1315,52 @@ class ReorderChannelUsernamesQuery final : public Td::ResultHandler { } }; +class UpdateChannelColorQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit UpdateChannelColorQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { + channel_id_ = channel_id; + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + int32 flags = 0; + if (background_custom_emoji_id.is_valid()) { + flags |= telegram_api::channels_updateColor::BACKGROUND_EMOJI_ID_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::channels_updateColor(flags, std::move(input_channel), accent_color_id.get(), + background_custom_emoji_id.get()), + {{channel_id}})); + } + + 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 UpdateChannelColorQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelColorQuery"); + } + promise_.set_error(std::move(status)); + } +}; + class SetChannelStickerSetQuery final : public Td::ResultHandler { Promise promise_; ChannelId channel_id_; @@ -3868,8 +3951,9 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent was_online_local_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_local")); was_online_remote_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_remote")); - if (was_online_local_ >= G()->unix_time_cached() && !td_->is_online()) { - was_online_local_ = G()->unix_time_cached() - 1; + auto unix_time = G()->unix_time(); + if (was_online_local_ >= unix_time && !td_->is_online()) { + was_online_local_ = unix_time - 1; } location_visibility_expire_date_ = @@ -3998,7 +4082,8 @@ void ContactsManager::on_user_online_timeout(UserId user_id) { LOG(INFO) << "Update " << user_id << " online status to offline"; send_closure(G()->td(), &Td::send_update, - td_api::make_object(user_id.get(), get_user_status_object(user_id, u))); + td_api::make_object(user_id.get(), + get_user_status_object(user_id, u, G()->unix_time()))); update_user_online_member_count(u); } @@ -4182,6 +4267,8 @@ void ContactsManager::User::store(StorerT &storer) const { bool has_max_active_story_id = max_active_story_id.is_valid(); bool has_max_read_story_id = max_read_story_id.is_valid(); bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); + bool has_accent_color_id = accent_color_id.is_valid(); + bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_received); STORE_FLAG(is_verified); @@ -4222,6 +4309,8 @@ void ContactsManager::User::store(StorerT &storer) const { STORE_FLAG(has_max_active_story_id); STORE_FLAG(has_max_read_story_id); STORE_FLAG(has_max_active_story_id_next_reload_time); + STORE_FLAG(has_accent_color_id); + STORE_FLAG(has_background_custom_emoji_id); END_STORE_FLAGS(); } store(first_name, storer); @@ -4266,6 +4355,12 @@ void ContactsManager::User::store(StorerT &storer) const { if (has_max_active_story_id_next_reload_time) { store_time(max_active_story_id_next_reload_time, storer); } + if (has_accent_color_id) { + store(accent_color_id, storer); + } + if (has_background_custom_emoji_id) { + store(background_custom_emoji_id, storer); + } } template @@ -4287,6 +4382,8 @@ void ContactsManager::User::parse(ParserT &parser) { bool has_max_active_story_id = false; bool has_max_read_story_id = false; bool has_max_active_story_id_next_reload_time = false; + bool has_accent_color_id = false; + bool has_background_custom_emoji_id = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_received); PARSE_FLAG(is_verified); @@ -4327,6 +4424,8 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(has_max_active_story_id); PARSE_FLAG(has_max_read_story_id); PARSE_FLAG(has_max_active_story_id_next_reload_time); + PARSE_FLAG(has_accent_color_id); + PARSE_FLAG(has_background_custom_emoji_id); END_PARSE_FLAGS(); } parse(first_name, parser); @@ -4399,6 +4498,12 @@ void ContactsManager::User::parse(ParserT &parser) { if (has_max_active_story_id_next_reload_time) { parse_time(max_active_story_id_next_reload_time, parser); } + if (has_accent_color_id) { + parse(accent_color_id, parser); + } + if (has_background_custom_emoji_id) { + parse(background_custom_emoji_id, parser); + } if (!check_utf8(first_name)) { LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; @@ -4697,9 +4802,9 @@ void ContactsManager::Chat::parse(ParserT &parser) { } else { status = DialogParticipantStatus::Member(); } - default_permissions = - RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, - everyone_is_administrator, everyone_is_administrator, everyone_is_administrator, false); + default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, + everyone_is_administrator, everyone_is_administrator, + everyone_is_administrator, false, ChannelType::Unknown); } if (has_default_permissions_version) { parse(default_permissions_version, parser); @@ -4808,6 +4913,8 @@ void ContactsManager::Channel::store(StorerT &storer) const { bool has_max_active_story_id = max_active_story_id.is_valid(); bool has_max_read_story_id = max_read_story_id.is_valid(); bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); + bool has_accent_color_id = accent_color_id.is_valid(); + bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(false); STORE_FLAG(false); @@ -4847,6 +4954,8 @@ void ContactsManager::Channel::store(StorerT &storer) const { STORE_FLAG(has_max_read_story_id); STORE_FLAG(has_max_active_story_id_next_reload_time); STORE_FLAG(stories_hidden); + STORE_FLAG(has_accent_color_id); + STORE_FLAG(has_background_custom_emoji_id); END_STORE_FLAGS(); } @@ -4881,6 +4990,12 @@ void ContactsManager::Channel::store(StorerT &storer) const { if (has_max_active_story_id_next_reload_time) { store_time(max_active_story_id_next_reload_time, storer); } + if (has_accent_color_id) { + store(accent_color_id, storer); + } + if (has_background_custom_emoji_id) { + store(background_custom_emoji_id, storer); + } } template @@ -4906,6 +5021,8 @@ void ContactsManager::Channel::parse(ParserT &parser) { bool has_max_active_story_id = false; bool has_max_read_story_id = false; bool has_max_active_story_id_next_reload_time = false; + bool has_accent_color_id = false; + bool has_background_custom_emoji_id = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(left); PARSE_FLAG(kicked); @@ -4945,6 +5062,8 @@ void ContactsManager::Channel::parse(ParserT &parser) { PARSE_FLAG(has_max_read_story_id); PARSE_FLAG(has_max_active_story_id_next_reload_time); PARSE_FLAG(stories_hidden); + PARSE_FLAG(has_accent_color_id); + PARSE_FLAG(has_background_custom_emoji_id); END_PARSE_FLAGS(); } @@ -4990,7 +5109,7 @@ void ContactsManager::Channel::parse(ParserT &parser) { parse(default_permissions, parser); } else { default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, - true, false, anyone_can_invite, false, false); + true, false, anyone_can_invite, false, false, ChannelType::Megagroup); } } if (has_cache_version) { @@ -5009,6 +5128,12 @@ void ContactsManager::Channel::parse(ParserT &parser) { if (has_max_active_story_id_next_reload_time) { parse_time(max_active_story_id_next_reload_time, parser); } + if (has_accent_color_id) { + parse(accent_color_id, parser); + } + if (has_background_custom_emoji_id) { + parse(background_custom_emoji_id, parser); + } if (!check_utf8(title)) { LOG(ERROR) << "Have invalid title \"" << title << '"'; @@ -5018,6 +5143,13 @@ void ContactsManager::Channel::parse(ParserT &parser) { if (legacy_has_active_group_call) { cache_version = 0; } + if (!is_megagroup && status.is_restricted()) { + if (status.is_member()) { + status = DialogParticipantStatus::Member(); + } else { + status = DialogParticipantStatus::Left(); + } + } } template @@ -5642,6 +5774,78 @@ const DialogPhoto *ContactsManager::get_secret_chat_dialog_photo(SecretChatId se return get_user_dialog_photo(c->user_id); } +int32 ContactsManager::get_user_accent_color_id_object(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr || !u->accent_color_id.is_valid()) { + return td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)); + } + + return td_->theme_manager_->get_accent_color_id_object(u->accent_color_id, AccentColorId(user_id)); +} + +int32 ContactsManager::get_chat_accent_color_id_object(ChatId chat_id) const { + return td_->theme_manager_->get_accent_color_id_object(AccentColorId(chat_id)); +} + +AccentColorId ContactsManager::get_channel_accent_color_id(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + auto min_channel = get_min_channel(channel_id); + if (min_channel != nullptr && min_channel->accent_color_id_.is_valid()) { + return min_channel->accent_color_id_; + } + return AccentColorId(channel_id); + } + if (!c->accent_color_id.is_valid()) { + return AccentColorId(channel_id); + } + + return c->accent_color_id; +} + +int32 ContactsManager::get_channel_accent_color_id_object(ChannelId channel_id) const { + return td_->theme_manager_->get_accent_color_id_object(get_channel_accent_color_id(channel_id), + AccentColorId(channel_id)); +} + +int32 ContactsManager::get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return 5; + } + return get_user_accent_color_id_object(c->user_id); +} + +CustomEmojiId ContactsManager::get_user_background_custom_emoji_id(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return CustomEmojiId(); + } + + return u->background_custom_emoji_id; +} + +CustomEmojiId ContactsManager::get_chat_background_custom_emoji_id(ChatId chat_id) const { + return CustomEmojiId(); +} + +CustomEmojiId ContactsManager::get_channel_background_custom_emoji_id(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return CustomEmojiId(); + } + + return c->background_custom_emoji_id; +} + +CustomEmojiId ContactsManager::get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return CustomEmojiId(); + } + return get_user_background_custom_emoji_id(c->user_id); +} + string ContactsManager::get_user_title(UserId user_id) const { auto u = get_user(user_id); if (u == nullptr) { @@ -5688,17 +5892,17 @@ RestrictedRights ContactsManager::get_user_default_permissions(UserId user_id) c auto u = get_user(user_id); if (u == nullptr || user_id == get_replies_bot_user_id()) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, u != nullptr, false); + false, false, u != nullptr, false, ChannelType::Unknown); } return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, - true, false); + true, false, ChannelType::Unknown); } RestrictedRights ContactsManager::get_chat_default_permissions(ChatId chat_id) const { auto c = get_chat(chat_id); if (c == nullptr) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false); + false, false, false, false, ChannelType::Unknown); } return c->default_permissions; } @@ -5707,7 +5911,7 @@ RestrictedRights ContactsManager::get_channel_default_permissions(ChannelId chan auto c = get_channel(channel_id); if (c == nullptr) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false); + false, false, false, false, ChannelType::Unknown); } return c->default_permissions; } @@ -5716,10 +5920,10 @@ RestrictedRights ContactsManager::get_secret_chat_default_permissions(SecretChat auto c = get_secret_chat(secret_chat_id); if (c == nullptr) { return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false); + false, false, false, false, ChannelType::Unknown); } return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, - false, false); + false, false, ChannelType::Unknown); } bool ContactsManager::get_chat_has_protected_content(ChatId chat_id) const { @@ -5929,6 +6133,17 @@ FolderId ContactsManager::get_secret_chat_initial_folder_id(SecretChatId secret_ return c->initial_folder_id; } +bool ContactsManager::can_use_premium_custom_emoji() const { + if (td_->option_manager_->get_option_boolean("is_premium")) { + return true; + } + if (!td_->auth_manager_->is_bot()) { + return false; + } + const User *u = get_user(get_my_id()); + return u == nullptr || u->usernames.get_active_usernames().size() > (u->usernames.has_editable_username() ? 1u : 0u); +} + UserId ContactsManager::get_my_id() const { LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my ID returned"; return my_id_; @@ -5960,14 +6175,14 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo User *u = get_user_force(my_id, "set_my_online_status"); if (u != nullptr) { int32 new_online; - int32 now = G()->unix_time(); + int32 unix_time = G()->unix_time(); if (is_online) { - new_online = now + 300; + new_online = unix_time + 300; } else { - new_online = now - 1; + new_online = unix_time - 1; } - auto old_was_online = get_user_was_online(u, my_id); + auto old_was_online = get_user_was_online(u, my_id, unix_time); if (is_local) { LOG(INFO) << "Update my local online from " << my_was_online_local_ << " to " << new_online; if (!is_online) { @@ -5984,7 +6199,7 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo u->need_save_to_database = true; } } - if (old_was_online != get_user_was_online(u, my_id)) { + if (old_was_online != get_user_was_online(u, my_id, unix_time)) { u->is_status_changed = true; u->is_online_status_changed = true; } @@ -6004,7 +6219,7 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo ContactsManager::MyOnlineStatusInfo ContactsManager::get_my_online_status() const { MyOnlineStatusInfo status_info; status_info.is_online_local = td_->is_online(); - status_info.is_online_remote = was_online_remote_ > G()->unix_time_cached(); + status_info.is_online_remote = was_online_remote_ > G()->unix_time(); status_info.was_online_local = was_online_local_; status_info.was_online_remote = was_online_remote_; @@ -6184,7 +6399,7 @@ bool ContactsManager::is_allowed_username(const string &username) { return true; } -int32 ContactsManager::get_user_was_online(const User *u, UserId user_id) const { +int32 ContactsManager::get_user_was_online(const User *u, UserId user_id, int32 unix_time) const { if (u == nullptr || u->is_deleted) { return 0; } @@ -6195,7 +6410,7 @@ int32 ContactsManager::get_user_was_online(const User *u, UserId user_id) const was_online = my_was_online_local_; } } else { - if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > G()->unix_time_cached()) { + if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > unix_time) { was_online = u->local_was_online; } } @@ -7461,6 +7676,15 @@ void ContactsManager::delete_profile_photo(int64 profile_photo_id, bool is_recur td_->create_handler(std::move(promise))->send(profile_photo_id); } +void ContactsManager::set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise) { + if (!accent_color_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); + } + + td_->create_handler(std::move(promise))->send(accent_color_id, background_custom_emoji_id); +} + void ContactsManager::set_name(const string &first_name, const string &last_name, Promise &&promise) { auto new_first_name = clean_name(first_name, MAX_NAME_LENGTH); auto new_last_name = clean_name(last_name, MAX_NAME_LENGTH); @@ -7508,6 +7732,18 @@ void ContactsManager::set_bio(const string &bio, Promise &&promise) { td_->create_handler(std::move(promise))->send(flags, "", "", new_bio); } +void ContactsManager::on_update_accent_color_success(AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id) { + auto user_id = get_my_id(); + User *u = get_user_force(user_id, "on_update_accent_color_success"); + if (u == nullptr) { + return; + } + on_update_user_accent_color_id(u, user_id, accent_color_id); + on_update_user_background_custom_emoji_id(u, user_id, background_custom_emoji_id); + update_user(u, user_id); +} + void ContactsManager::on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about) { CHECK(flags != 0); @@ -7772,6 +8008,27 @@ void ContactsManager::on_update_channel_active_usernames_order(ChannelId channel promise.set_value(Unit()); } +void ContactsManager::set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise) { + if (!accent_color_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); + } + + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (c->is_megagroup) { + return promise.set_error(Status::Error(400, "Accent color can be changed only in channel chats")); + } + if (!get_channel_status(c).can_change_info_and_settings()) { + return promise.set_error(Status::Error(400, "Not enough rights in the channel")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, accent_color_id, background_custom_emoji_id); +} + void ContactsManager::set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise) { auto c = get_channel(channel_id); @@ -8220,7 +8477,7 @@ bool ContactsManager::can_get_channel_message_statistics(DialogId dialog_id) con return channel_full->stats_dc_id.is_exact(); } - return c->status.is_administrator(); + return c->status.can_post_messages(); } void ContactsManager::report_channel_spam(ChannelId channel_id, const vector &message_ids, @@ -9309,8 +9566,7 @@ void ContactsManager::get_created_public_dialogs(PublicDialogType type, } return channel_id; }); - if (std::any_of(r_channel_ids.begin(), r_channel_ids.end(), - [](auto &r_channel_id) { return r_channel_id.is_error(); })) { + if (any_of(r_channel_ids, [](const auto &r_channel_id) { return r_channel_id.is_error(); })) { LOG(ERROR) << "Can't parse " << str; G()->td_db()->get_binlog_pmc()->erase(pmc_key); } else { @@ -9531,6 +9787,7 @@ void ContactsManager::remove_inactive_channel(ChannelId channel_id) { } void ContactsManager::register_message_users(MessageFullId message_full_id, vector user_ids) { + CHECK(message_full_id.get_dialog_id().is_valid()); for (auto user_id : user_ids) { CHECK(user_id.is_valid()); const User *u = get_user(user_id); @@ -10059,6 +10316,9 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, on_update_user_usernames(u, user_id, Usernames{std::move(user->username_), std::move(user->usernames_)}); } on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); + on_update_user_accent_color_id( + u, user_id, ((flags2 & telegram_api::user::COLOR_MASK) != 0 ? AccentColorId(user->color_) : AccentColorId())); + on_update_user_background_custom_emoji_id(u, user_id, CustomEmojiId(user->background_emoji_id_)); bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; @@ -10510,7 +10770,7 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id, const cha false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, 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(), nullptr, - vector>(), 0); + vector>(), 0, 0, 0); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); @@ -11731,14 +11991,31 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo }); u->is_photo_changed = false; } + if (u->is_accent_color_id_changed) { + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_accent_color_id_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_accent_color_id_updated(DialogId(secret_chat_id)); + }); + u->is_accent_color_id_changed = false; + } + if (u->is_background_custom_emoji_id_changed) { + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_background_custom_emoji_id_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_background_custom_emoji_id_updated(DialogId(secret_chat_id)); + }); + u->is_background_custom_emoji_id_changed = false; + } if (u->is_phone_number_changed) { if (!u->phone_number.empty() && !td_->auth_manager_->is_bot()) { resolved_phone_numbers_[u->phone_number] = user_id; } u->is_phone_number_changed = false; } + auto unix_time = G()->unix_time(); if (u->is_status_changed && user_id != get_my_id()) { - auto left_time = get_user_was_online(u, user_id) - G()->server_time_cached(); + auto left_time = get_user_was_online(u, user_id, unix_time) - G()->server_time(); if (left_time >= 0 && left_time < 30 * 86400) { left_time += 2.0; // to guarantee expiration LOG(DEBUG) << "Set online timeout for " << user_id << " in " << left_time << " seconds"; @@ -11761,7 +12038,6 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } } - auto unix_time = G()->unix_time(); auto effective_emoji_status = u->emoji_status.get_effective_emoji_status(u->is_premium, unix_time); if (effective_emoji_status != u->last_sent_emoji_status) { u->last_sent_emoji_status = effective_emoji_status; @@ -11811,8 +12087,9 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo u->is_status_saved = false; } CHECK(u->is_update_user_sent); - send_closure(G()->td(), &Td::send_update, - make_tl_object(user_id.get(), get_user_status_object(user_id, u))); + send_closure( + G()->td(), &Td::send_update, + make_tl_object(user_id.get(), get_user_status_object(user_id, u, unix_time))); u->is_status_changed = false; } if (u->is_online_status_changed) { @@ -11970,6 +12247,14 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from } } } + if (c->is_accent_color_id_changed) { + td_->messages_manager_->on_dialog_accent_color_id_updated(DialogId(channel_id)); + c->is_accent_color_id_changed = false; + } + if (c->is_background_custom_emoji_id_changed) { + td_->messages_manager_->on_dialog_background_custom_emoji_id_updated(DialogId(channel_id)); + c->is_background_custom_emoji_id_changed = false; + } if (c->is_title_changed) { td_->messages_manager_->on_dialog_title_updated(DialogId(channel_id)); c->is_title_changed = false; @@ -11979,7 +12264,7 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from auto until_date = c->status.get_until_date(); int32 left_time = 0; if (until_date > 0) { - left_time = until_date - G()->unix_time_cached() + 1; + left_time = until_date - G()->unix_time() + 1; CHECK(left_time > 0); } if (left_time > 0 && left_time < 366 * 86400) { @@ -12016,7 +12301,8 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from if (c->is_default_permissions_changed) { td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(channel_id)); if (c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false)) { + false, false, false, false, false, false, false, + ChannelType::Unknown)) { remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } c->is_default_permissions_changed = false; @@ -13080,9 +13366,10 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c SuggestedAction suggested_action(action_str, DialogId(channel_id)); if (!suggested_action.is_empty()) { if (suggested_action == SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)} && - (c->is_gigagroup || c->default_permissions != RestrictedRights(false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false))) { + (c->is_gigagroup || + c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, ChannelType::Unknown))) { LOG(INFO) << "Skip ConvertToGigagroup suggested action"; } else { suggested_actions.push_back(suggested_action); @@ -13327,6 +13614,26 @@ void ContactsManager::register_user_photo(User *u, UserId user_id, const Photo & } } +void ContactsManager::on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { + if (accent_color_id == AccentColorId(user_id) || !accent_color_id.is_valid()) { + accent_color_id = AccentColorId(); + } + if (u->accent_color_id != accent_color_id) { + u->accent_color_id = accent_color_id; + u->is_accent_color_id_changed = true; + u->is_changed = true; + } +} + +void ContactsManager::on_update_user_background_custom_emoji_id(User *u, UserId user_id, + CustomEmojiId background_custom_emoji_id) { + if (u->background_custom_emoji_id != background_custom_emoji_id) { + u->background_custom_emoji_id = background_custom_emoji_id; + u->is_background_custom_emoji_id_changed = true; + u->is_changed = true; + } +} + void ContactsManager::on_update_user_emoji_status(UserId user_id, tl_object_ptr &&emoji_status) { if (!user_id.is_valid()) { @@ -13412,7 +13719,7 @@ void ContactsManager::on_update_user_story_ids_impl(User *u, UserId user_id, Sto u->need_save_to_database = true; } if (has_unread_stories != get_user_has_unread_stories(u)) { - LOG(DEBUG) << "Change has_unread_stories of " << user_id; + LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; u->is_changed = true; } } @@ -13440,7 +13747,7 @@ void ContactsManager::on_update_user_max_read_story_id(User *u, UserId user_id, u->need_save_to_database = true; } if (has_unread_stories != get_user_has_unread_stories(u)) { - LOG(DEBUG) << "Change has_unread_stories of " << user_id; + LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; u->is_changed = true; } } @@ -13580,8 +13887,9 @@ void ContactsManager::on_update_user_online(User *u, UserId user_id, tl_object_p if (new_online != u->was_online) { LOG(DEBUG) << "Update " << user_id << " online from " << u->was_online << " to " << new_online; - bool old_is_online = u->was_online > G()->unix_time_cached(); - bool new_is_online = new_online > G()->unix_time_cached(); + auto unix_time = G()->unix_time(); + bool old_is_online = u->was_online > unix_time; + bool new_is_online = new_online > unix_time; u->was_online = new_online; u->is_status_changed = true; if (u->was_online > 0) { @@ -13622,20 +13930,21 @@ void ContactsManager::on_update_user_local_was_online(User *u, UserId user_id, i if (u->is_deleted || u->is_bot || u->is_support || user_id == get_my_id()) { return; } - if (u->was_online > G()->unix_time_cached()) { + int32 unix_time = G()->unix_time(); + if (u->was_online > unix_time) { // if user is currently online, ignore local online return; } // bring users online for 30 seconds local_was_online += 30; - if (local_was_online < G()->unix_time_cached() + 2 || local_was_online <= u->local_was_online || + if (local_was_online < unix_time + 2 || local_was_online <= u->local_was_online || local_was_online <= u->was_online) { return; } LOG(DEBUG) << "Update " << user_id << " local online from " << u->local_was_online << " to " << local_was_online; - bool old_is_online = u->local_was_online > G()->unix_time_cached(); + bool old_is_online = u->local_was_online > unix_time; u->local_was_online = local_was_online; u->is_status_changed = true; @@ -14096,7 +14405,7 @@ void ContactsManager::update_user_online_member_count(User *u) { return; } - auto now = G()->unix_time_cached(); + auto now = G()->unix_time(); vector expired_dialog_ids; for (const auto &it : u->online_member_dialogs) { auto dialog_id = it.first; @@ -14159,7 +14468,7 @@ void ContactsManager::update_dialog_online_member_count(const vectorunix_time(); + int32 unix_time = G()->unix_time(); for (const auto &participant : participants) { if (participant.dialog_id_.get_type() != DialogType::User) { continue; @@ -14167,11 +14476,11 @@ void ContactsManager::update_dialog_online_member_count(const vectoris_deleted && !u->is_bot) { - if (get_user_was_online(u, user_id) > time) { + if (get_user_was_online(u, user_id, unix_time) > unix_time) { online_member_count++; } if (is_from_server) { - u->online_member_dialogs[dialog_id] = time; + u->online_member_dialogs[dialog_id] = unix_time; } } } @@ -14284,17 +14593,17 @@ const DialogParticipant *ContactsManager::get_chat_full_participant(const ChatFu return nullptr; } -tl_object_ptr ContactsManager::get_chat_member_object( - const DialogParticipant &dialog_participant) const { +tl_object_ptr ContactsManager::get_chat_member_object(const DialogParticipant &dialog_participant, + const char *source) const { DialogId dialog_id = dialog_participant.dialog_id_; UserId participant_user_id; if (dialog_id.get_type() == DialogType::User) { participant_user_id = dialog_id.get_user_id(); } else { - td_->messages_manager_->force_create_dialog(dialog_id, "get_chat_member_object", true); + td_->messages_manager_->force_create_dialog(dialog_id, source, true); } return td_api::make_object( - get_message_sender_object_const(td_, dialog_id, "get_chat_member_object"), + get_message_sender_object_const(td_, dialog_id, source), get_user_id_object(dialog_participant.inviter_user_id_, "chatMember.inviter_user_id"), dialog_participant.joined_date_, dialog_participant.status_.get_chat_member_status_object()); } @@ -15242,6 +15551,7 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link, invite_link_info->dialog_id = DialogId(); invite_link_info->title = chat_invite->title_; invite_link_info->photo = get_photo(td_, std::move(chat_invite->photo_), DialogId()); + invite_link_info->accent_color_id = AccentColorId(chat_invite->color_); invite_link_info->description = std::move(chat_invite->about_); invite_link_info->participant_count = chat_invite->participants_count_; invite_link_info->participant_user_ids = std::move(participant_user_ids); @@ -15991,6 +16301,27 @@ void ContactsManager::on_update_channel_photo(Channel *c, ChannelId channel_id, } } +void ContactsManager::on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, + AccentColorId accent_color_id) { + if (accent_color_id == AccentColorId(channel_id) || !accent_color_id.is_valid()) { + accent_color_id = AccentColorId(); + } + if (c->accent_color_id != accent_color_id) { + c->accent_color_id = accent_color_id; + c->is_accent_color_id_changed = true; + c->need_save_to_database = true; + } +} + +void ContactsManager::on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, + CustomEmojiId background_custom_emoji_id) { + if (c->background_custom_emoji_id != background_custom_emoji_id) { + c->background_custom_emoji_id = background_custom_emoji_id; + c->is_background_custom_emoji_id_changed = true; + c->need_save_to_database = true; + } +} + void ContactsManager::on_update_channel_title(Channel *c, ChannelId channel_id, string &&title) { if (c->title != title) { c->title = std::move(title); @@ -16078,7 +16409,7 @@ void ContactsManager::on_channel_status_changed(Channel *c, ChannelId channel_id void ContactsManager::on_update_channel_default_permissions(Channel *c, ChannelId channel_id, RestrictedRights default_permissions) { - if (c->default_permissions != default_permissions) { + if (c->is_megagroup && c->default_permissions != default_permissions) { LOG(INFO) << "Update " << channel_id << " default permissions from " << c->default_permissions << " to " << default_permissions; c->default_permissions = default_permissions; @@ -16165,7 +16496,7 @@ void ContactsManager::on_update_channel_story_ids_impl(Channel *c, ChannelId cha c->need_save_to_database = true; } if (has_unread_stories != get_channel_has_unread_stories(c)) { - LOG(DEBUG) << "Change has_unread_stories of " << channel_id; + LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; c->is_changed = true; } } @@ -16193,7 +16524,7 @@ void ContactsManager::on_update_channel_max_read_story_id(Channel *c, ChannelId c->need_save_to_database = true; } if (has_unread_stories != get_channel_has_unread_stories(c)) { - LOG(DEBUG) << "Change has_unread_stories of " << channel_id; + LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; c->is_changed = true; } } @@ -16482,7 +16813,8 @@ void ContactsManager::send_update_chat_member(DialogId dialog_id, UserId agent_u td_->messages_manager_->get_chat_id_object(dialog_id, "updateChatMember"), get_user_id_object(agent_user_id, "send_update_chat_member"), date, invite_link.get_chat_invite_link_object(this), via_dialog_filter_invite_link, - get_chat_member_object(old_dialog_participant), get_chat_member_object(new_dialog_participant))); + get_chat_member_object(old_dialog_participant, "send_update_chat_member old"), + get_chat_member_object(new_dialog_participant, "send_update_chat_member new"))); } void ContactsManager::on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped) { @@ -16736,8 +17068,9 @@ Result ContactsManager::get_bot_data(UserId user_id) c } bool ContactsManager::is_user_online(UserId user_id, int32 tolerance) const { - int32 was_online = get_user_was_online(get_user(user_id), user_id); - return was_online > G()->unix_time() - tolerance; + auto unix_time = G()->unix_time(); + int32 was_online = get_user_was_online(get_user(user_id), user_id, unix_time); + return was_online > unix_time - tolerance; } bool ContactsManager::is_user_status_exact(UserId user_id) const { @@ -17464,10 +17797,6 @@ DialogParticipantStatus ContactsManager::get_channel_permissions(ChannelId chann DialogParticipantStatus ContactsManager::get_channel_permissions(const Channel *c) const { c->status.update_restrictions(); - if (!c->is_megagroup) { - // there is no restrictions in broadcast channels - return c->status; - } return c->status.apply_restrictions(c->default_permissions, td_->auth_manager_->is_bot()); } @@ -17857,6 +18186,7 @@ std::pair> ContactsManager::search_among_dialogs(const v const string &query, int32 limit) const { Hints hints; // TODO cache Hints + auto unix_time = G()->unix_time(); for (auto dialog_id : dialog_ids) { int64 rating = 0; if (dialog_id.get_type() == DialogType::User) { @@ -17870,7 +18200,7 @@ std::pair> ContactsManager::search_among_dialogs(const v } else { hints.add(dialog_id.get(), get_user_search_text(u)); } - rating = -get_user_was_online(u, user_id); + rating = -get_user_was_online(u, user_id, unix_time); } else { if (!td_->messages_manager_->have_dialog_info(dialog_id)) { continue; @@ -18040,7 +18370,7 @@ void ContactsManager::finish_get_dialog_participant(DialogParticipant &&dialog_p return promise.set_error(Status::Error(400, "Member not found")); } - promise.set_value(get_chat_member_object(dialog_participant)); + promise.set_value(get_chat_member_object(dialog_participant, "finish_get_dialog_participant")); } void ContactsManager::do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, @@ -18601,7 +18931,8 @@ void ContactsManager::on_get_chat(telegram_api::chat &chat, const char *source) c->need_save_to_database = true; } on_update_chat_status(c, chat_id, std::move(status)); - on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_), chat.version_); + on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_, ChannelType::Unknown), + chat.version_); on_update_chat_photo(c, chat_id, std::move(chat.photo_)); on_update_chat_active(c, chat_id, is_active); on_update_chat_noforwards(c, chat_id, chat.noforwards_); @@ -18730,7 +19061,8 @@ void ContactsManager::on_get_channel(telegram_api::channel &channel, const char return DialogParticipantStatus(false, std::move(channel.admin_rights_), string(), is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); } else if (channel.banned_rights_ != nullptr) { - return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_)); + return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_), + is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); } else if (has_left) { return DialogParticipantStatus::Left(); } else { @@ -18751,7 +19083,8 @@ void ContactsManager::on_get_channel(telegram_api::channel &channel, const char if (!c->status.is_banned()) { on_update_channel_photo(c, channel_id, std::move(channel.photo_)); } - on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_)); + on_update_channel_default_permissions(c, channel_id, + RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); on_update_channel_has_location(c, channel_id, channel.has_geo_); on_update_channel_noforwards(c, channel_id, channel.noforwards_); @@ -18794,6 +19127,9 @@ void ContactsManager::on_get_channel(telegram_api::channel &channel, const char if (td_->auth_manager_->is_bot()) { min_channel->photo_.minithumbnail.clear(); } + if ((channel.flags2_ & telegram_api::channel::COLOR_MASK) != 0) { + min_channel->accent_color_id_ = AccentColorId(channel.color_); + } min_channel->title_ = std::move(channel.title_); min_channel->is_megagroup_ = is_megagroup; @@ -18824,12 +19160,17 @@ void ContactsManager::on_get_channel(telegram_api::channel &channel, const char c->is_changed = true; } on_update_channel_photo(c, channel_id, std::move(channel.photo_)); + on_update_channel_accent_color_id( + c, channel_id, + ((channel.flags2_ & telegram_api::channel::COLOR_MASK) != 0 ? AccentColorId(channel.color_) : AccentColorId())); + on_update_channel_background_custom_emoji_id(c, channel_id, CustomEmojiId(channel.background_emoji_id_)); on_update_channel_status(c, channel_id, std::move(status)); on_update_channel_usernames( c, channel_id, Usernames(std::move(channel.username_), std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status - on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_)); + on_update_channel_default_permissions(c, channel_id, + RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); on_update_channel_has_location(c, channel_id, channel.has_geo_); on_update_channel_noforwards(c, channel_id, channel.noforwards_); if (!td_->auth_manager_->is_bot() && !channel.stories_hidden_min_) { @@ -18939,7 +19280,7 @@ void ContactsManager::on_get_channel_forbidden(telegram_api::channelForbidden &c on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(channel.until_date_)); // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it tl_object_ptr banned_rights; // == nullptr - on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights)); + on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights, ChannelType::Megagroup)); // on_update_channel_has_location(c, channel_id, false); on_update_channel_noforwards(c, channel_id, false); td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "on_get_channel_forbidden"); @@ -19084,12 +19425,13 @@ void ContactsManager::on_upload_profile_photo_error(FileId file_id, Status statu promise.set_error(std::move(status)); // TODO check that status has valid error code } -td_api::object_ptr ContactsManager::get_user_status_object(UserId user_id, const User *u) const { +td_api::object_ptr ContactsManager::get_user_status_object(UserId user_id, const User *u, + int32 unix_time) const { if (u->is_bot) { return make_tl_object(std::numeric_limits::max()); } - int32 was_online = get_user_was_online(u, user_id); + int32 was_online = get_user_was_online(u, user_id, unix_time); switch (was_online) { case -3: return make_tl_object(); @@ -19125,9 +19467,10 @@ td_api::object_ptr ContactsManager::get_update_user_object(U td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) const { auto have_access = user_id == get_my_id() || user_messages_.count(user_id) != 0; return td_api::make_object(td_api::make_object( - user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, nullptr, false, - false, false, false, false, false, "", false, false, false, false, have_access, - td_api::make_object(), "", false)); + user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, + td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)), 0, nullptr, false, false, false, false, + false, false, "", false, false, false, false, have_access, td_api::make_object(), "", + false)); } int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { @@ -19161,11 +19504,15 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con auto emoji_status = !u->last_sent_emoji_status.is_empty() ? u->last_sent_emoji_status.get_emoji_status_object() : nullptr; auto have_access = user_id == get_my_id() || have_input_peer_user(u, user_id, AccessRights::Know); + auto accent_color_id = u->accent_color_id.is_valid() ? u->accent_color_id : AccentColorId(user_id); return td_api::make_object( user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number, - get_user_status_object(user_id, u), get_profile_photo_object(td_->file_manager_.get(), u->photo), - std::move(emoji_status), u->is_contact, u->is_mutual_contact, u->is_close_friend, u->is_verified, u->is_premium, - u->is_support, get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, + get_user_status_object(user_id, u, G()->unix_time()), + get_profile_photo_object(td_->file_manager_.get(), u->photo), + td_->theme_manager_->get_accent_color_id_object(accent_color_id, AccentColorId(user_id)), + u->background_custom_emoji_id.get(), std::move(emoji_status), u->is_contact, u->is_mutual_contact, + u->is_close_friend, u->is_verified, u->is_premium, u->is_support, + get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->max_active_story_id.is_valid(), get_user_has_unread_stories(u), have_access, std::move(type), u->language_code, u->attach_menu_enabled); } @@ -19305,7 +19652,7 @@ tl_object_ptr ContactsManager::get_basic_group_full_ return commands.get_bot_commands_object(td); }); auto members = transform(chat_full->participants, [this](const DialogParticipant &dialog_participant) { - return get_chat_member_object(dialog_participant); + return get_chat_member_object(dialog_participant, "get_basic_group_full_info_object"); }); return make_tl_object( get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description, @@ -19476,6 +19823,7 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ string title; const DialogPhoto *photo = nullptr; DialogPhoto invite_link_photo; + int32 accent_color_id_object; string description; int32 participant_count = 0; vector member_user_ids; @@ -19501,6 +19849,7 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ } else { LOG(ERROR) << "Have no information about " << chat_id; } + accent_color_id_object = get_chat_accent_color_id_object(chat_id); break; } case DialogType::Channel: { @@ -19520,6 +19869,7 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ } else { LOG(ERROR) << "Have no information about " << channel_id; } + accent_color_id_object = get_channel_accent_color_id_object(channel_id); break; } default: @@ -19532,6 +19882,7 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ title = invite_link_info->title; invite_link_photo = as_fake_dialog_photo(invite_link_info->photo, dialog_id, false); photo = &invite_link_photo; + accent_color_id_object = td_->theme_manager_->get_accent_color_id_object(invite_link_info->accent_color_id); description = invite_link_info->description; participant_count = invite_link_info->participant_count; member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids, "get_chat_invite_link_info_object"); @@ -19564,8 +19915,8 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ return td_api::make_object( td_->messages_manager_->get_chat_id_object(dialog_id, "chatInviteLinkInfo"), accessible_for, std::move(chat_type), - title, get_chat_photo_info_object(td_->file_manager_.get(), photo), description, participant_count, - std::move(member_user_ids), creates_join_request, is_public, is_verified, is_scam, is_fake); + title, get_chat_photo_info_object(td_->file_manager_.get(), photo), accent_color_id_object, description, + participant_count, std::move(member_user_ids), creates_join_request, is_public, is_verified, is_scam, is_fake); } void ContactsManager::get_support_user(Promise> &&promise) { diff --git a/lib/tgchat/ext/td/td/telegram/ContactsManager.h b/lib/tgchat/ext/td/td/telegram/ContactsManager.h index 94a50f0e..6c1c5cb2 100644 --- a/lib/tgchat/ext/td/td/telegram/ContactsManager.h +++ b/lib/tgchat/ext/td/td/telegram/ContactsManager.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/AccentColorId.h" #include "td/telegram/AccessRights.h" #include "td/telegram/BotCommand.h" #include "td/telegram/BotMenuButton.h" @@ -13,6 +14,7 @@ #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" #include "td/telegram/Contact.h" +#include "td/telegram/CustomEmojiId.h" #include "td/telegram/DialogAdministrator.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogInviteLink.h" @@ -116,6 +118,18 @@ class ContactsManager final : public Actor { const DialogPhoto *get_channel_dialog_photo(ChannelId channel_id) const; const DialogPhoto *get_secret_chat_dialog_photo(SecretChatId secret_chat_id); + AccentColorId get_channel_accent_color_id(ChannelId channel_id) const; + + int32 get_user_accent_color_id_object(UserId user_id) const; + int32 get_chat_accent_color_id_object(ChatId chat_id) const; + int32 get_channel_accent_color_id_object(ChannelId channel_id) const; + int32 get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const; + + CustomEmojiId get_user_background_custom_emoji_id(UserId user_id) const; + CustomEmojiId get_chat_background_custom_emoji_id(ChatId chat_id) const; + CustomEmojiId get_channel_background_custom_emoji_id(ChannelId channel_id) const; + CustomEmojiId get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const; + string get_user_title(UserId user_id) const; string get_chat_title(ChatId chat_id) const; string get_channel_title(ChannelId channel_id) const; @@ -185,6 +199,7 @@ class ContactsManager final : public Actor { void on_get_channel_full_failed(ChannelId channel_id); void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about); + void on_update_accent_color_success(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id); void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames); void on_update_user_phone_number(UserId user_id, string &&phone_number); @@ -290,6 +305,8 @@ class ContactsManager final : public Actor { void unregister_message_channels(MessageFullId message_full_id, vector channel_ids); + bool can_use_premium_custom_emoji() const; + UserId get_my_id() const; void set_my_online_status(bool is_online, bool send_update, bool is_local); @@ -412,6 +429,9 @@ class ContactsManager final : public Actor { void delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise); + void set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise); + void set_name(const string &first_name, const string &last_name, Promise &&promise); void set_bio(const string &bio, Promise &&promise); @@ -439,6 +459,9 @@ class ContactsManager final : public Actor { void reorder_channel_usernames(ChannelId channel_id, vector &&usernames, Promise &&promise); + void set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise); + void set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise); void toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise); @@ -707,7 +730,8 @@ class ContactsManager final : public Actor { bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer, FolderId initial_folder_id); - tl_object_ptr get_chat_member_object(const DialogParticipant &dialog_participant) const; + tl_object_ptr get_chat_member_object(const DialogParticipant &dialog_participant, + const char *source) const; tl_object_ptr get_chat_invite_link_info_object(const string &invite_link); @@ -737,6 +761,9 @@ class ContactsManager final : public Actor { string inline_query_placeholder; int32 bot_info_version = -1; + AccentColorId accent_color_id; + CustomEmojiId background_custom_emoji_id; + int32 was_online = 0; int32 local_was_online = 0; @@ -784,6 +811,8 @@ class ContactsManager final : public Actor { bool is_name_changed = true; bool is_username_changed = true; bool is_photo_changed = true; + bool is_accent_color_id_changed = true; + bool is_background_custom_emoji_id_changed = true; bool is_phone_number_changed = true; bool is_emoji_status_changed = true; bool is_is_contact_changed = true; @@ -879,7 +908,7 @@ class ContactsManager final : public Actor { DialogParticipantStatus status = DialogParticipantStatus::Banned(0); RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false}; + false, false, false, false, false, false, false, false, ChannelType::Unknown}; static constexpr uint32 CACHE_VERSION = 4; uint32 cache_version = 0; @@ -949,11 +978,13 @@ class ContactsManager final : public Actor { int64 access_hash = 0; string title; DialogPhoto photo; + AccentColorId accent_color_id; + CustomEmojiId background_custom_emoji_id; Usernames usernames; vector restriction_reasons; DialogParticipantStatus status = DialogParticipantStatus::Banned(0); RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false}; + false, false, false, false, false, false, false, false, ChannelType::Unknown}; int32 date = 0; int32 participant_count = 0; @@ -986,6 +1017,8 @@ class ContactsManager final : public Actor { bool is_title_changed = true; bool is_username_changed = true; bool is_photo_changed = true; + bool is_accent_color_id_changed = true; + bool is_background_custom_emoji_id_changed = true; bool is_default_permissions_changed = true; bool is_status_changed = true; bool is_stories_hidden_changed = true; @@ -1119,9 +1152,10 @@ class ContactsManager final : public Actor { // unknown dialog string title; Photo photo; - string description; + AccentColorId accent_color_id; int32 participant_count = 0; vector participant_user_ids; + string description; bool creates_join_request = false; bool is_chat = false; bool is_channel = false; @@ -1405,6 +1439,8 @@ class ContactsManager final : public Actor { 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_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id); + void on_update_user_background_custom_emoji_id(User *u, UserId user_id, CustomEmojiId background_custom_emoji_id); void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status); void on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); void on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id); @@ -1474,6 +1510,9 @@ class ContactsManager final : public Actor { void on_update_channel_photo(Channel *c, ChannelId channel_id, tl_object_ptr &&chat_photo_ptr); void on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache); + void on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId accent_color_id); + void on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, + CustomEmojiId background_custom_emoji_id); static void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title); void on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames); void on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status); @@ -1614,7 +1653,7 @@ class ContactsManager final : public Actor { static bool is_user_bot(const User *u); - int32 get_user_was_online(const User *u, UserId user_id) const; + int32 get_user_was_online(const User *u, UserId user_id, int32 unix_time) const; int64 get_contacts_hash(); @@ -1758,7 +1797,7 @@ class ContactsManager final : public Actor { td_api::object_ptr get_update_unknown_user_object(UserId user_id) const; - td_api::object_ptr get_user_status_object(UserId user_id, const User *u) const; + td_api::object_ptr get_user_status_object(UserId user_id, const User *u, int32 unix_time) const; tl_object_ptr get_user_object(UserId user_id, const User *u) const; diff --git a/lib/tgchat/ext/td/td/telegram/DialogAction.cpp b/lib/tgchat/ext/td/td/telegram/DialogAction.cpp index a79a1982..7fca2169 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogAction.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogAction.cpp @@ -413,6 +413,9 @@ bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_con case MessageContentType::SetBackground: case MessageContentType::Story: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return false; default: UNREACHABLE(); diff --git a/lib/tgchat/ext/td/td/telegram/DialogDb.cpp b/lib/tgchat/ext/td/td/telegram/DialogDb.cpp index ceb9130b..4f2836d3 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogDb.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogDb.cpp @@ -10,7 +10,6 @@ #include "td/db/SqliteConnectionSafe.h" #include "td/db/SqliteDb.h" -#include "td/db/SqliteKeyValue.h" #include "td/db/SqliteStatement.h" #include "td/actor/actor.h" @@ -34,9 +33,7 @@ Status init_dialog_db(SqliteDb &db, int32 version, KeyValueSyncInterface &binlog TRY_RESULT(has_table, db.has_table("dialogs")); if (!has_table) { version = 0; - } - - if (version < static_cast(DbVersion::CreateDialogDb) || version > current_db_version()) { + } else if (version > current_db_version()) { TRY_STATUS(drop_dialog_db(db, version)); version = 0; } @@ -104,17 +101,8 @@ Status init_dialog_db(SqliteDb &db, int32 version, KeyValueSyncInterface &binlog // NB: must happen inside a transaction Status drop_dialog_db(SqliteDb &db, int version) { - if (version < static_cast(DbVersion::CreateDialogDb)) { - if (version != 0) { - LOG(WARNING) << "Drop old pmc dialog_db"; - } - SqliteKeyValue kv; - kv.init_with_connection(db.clone(), "common").ensure(); - kv.erase_by_prefix("di"); - } - if (version != 0) { - LOG(WARNING) << "Drop dialog_db " << tag("version", version) << tag("current_db_version", current_db_version()); + LOG(WARNING) << "Drop chat database " << tag("version", version) << tag("current_db_version", current_db_version()); } auto status = db.exec("DROP TABLE IF EXISTS dialogs"); TRY_STATUS(db.exec("DROP TABLE IF EXISTS notification_groups")); diff --git a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp index 4c048c34..f86f969f 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/DialogEventLog.h" +#include "td/telegram/AccentColorId.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatReactions.h" #include "td/telegram/ContactsManager.h" @@ -24,6 +25,7 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" @@ -145,8 +147,9 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionDefaultBannedRights::ID: { auto action = move_tl_object_as(action_ptr); - auto old_permissions = RestrictedRights(action->prev_banned_rights_); - auto new_permissions = RestrictedRights(action->new_banned_rights_); + auto channel_type = td->contacts_manager_->get_channel_type(channel_id); + auto old_permissions = RestrictedRights(action->prev_banned_rights_, channel_type); + auto new_permissions = RestrictedRights(action->new_banned_rights_, channel_type); return td_api::make_object(old_permissions.get_chat_permissions_object(), new_permissions.get_chat_permissions_object()); } @@ -426,6 +429,19 @@ static td_api::object_ptr get_chat_event_action_object( auto action = move_tl_object_as(action_ptr); return td_api::make_object(action->new_value_); } + case telegram_api::channelAdminLogEventActionChangeColor::ID: { + auto action = move_tl_object_as(action_ptr); + auto old_accent_color_id = AccentColorId(action->prev_value_); + auto new_accent_color_id = AccentColorId(action->new_value_); + return td_api::make_object( + td->theme_manager_->get_accent_color_id_object(old_accent_color_id, AccentColorId(channel_id)), + td->theme_manager_->get_accent_color_id_object(new_accent_color_id, AccentColorId(channel_id))); + } + case telegram_api::channelAdminLogEventActionChangeBackgroundEmoji::ID: { + auto action = move_tl_object_as(action_ptr); + return td_api::make_object(action->prev_value_, + action->new_value_); + } default: UNREACHABLE(); return nullptr; diff --git a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp index 75ecac42..e9871c87 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp @@ -1013,6 +1013,10 @@ void DialogFilterManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_fi promise.set_value(Unit()); } +void DialogFilterManager::load_input_dialog(const InputDialogId &input_dialog_id, Promise &&promise) { + td_->create_handler(std::move(promise))->send({input_dialog_id}); +} + void DialogFilterManager::delete_dialogs_from_filter(const DialogFilter *dialog_filter, vector &&dialog_ids, const char *source) { if (dialog_ids.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.h b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.h index 643b6d4f..ee065bd6 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.h @@ -120,6 +120,8 @@ class DialogFilterManager final : public Actor { void load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector &&input_dialog_ids, Promise &&promise); + void load_input_dialog(const InputDialogId &input_dialog_id, Promise &&promise); + void set_dialog_filter_has_my_invite_links(DialogFilterId dialog_filter_id, bool has_my_invite_links); void get_current_state(vector> &updates) const; diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp index dcd09489..22b4a159 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp @@ -214,8 +214,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRigh return string_builder; } -RestrictedRights::RestrictedRights(const tl_object_ptr &rights) { - if (rights == nullptr) { +RestrictedRights::RestrictedRights(const tl_object_ptr &rights, + ChannelType channel_type) { + if (rights == nullptr || channel_type == ChannelType::Broadcast) { flags_ = 0; return; } @@ -229,21 +230,23 @@ RestrictedRights::RestrictedRights(const tl_object_ptrsend_videos_, !rights->send_roundvideos_, !rights->send_voices_, !rights->send_stickers_, !rights->send_gifs_, !rights->send_games_, !rights->send_inline_, !rights->embed_links_, !rights->send_polls_, !rights->change_info_, !rights->invite_users_, - !rights->pin_messages_, !rights->manage_topics_); + !rights->pin_messages_, !rights->manage_topics_, channel_type); } -RestrictedRights::RestrictedRights(const td_api::object_ptr &rights) { - if (rights == nullptr) { +RestrictedRights::RestrictedRights(const td_api::object_ptr &rights, + ChannelType channel_type) { + if (rights == nullptr || channel_type == ChannelType::Broadcast) { flags_ = 0; return; } - *this = RestrictedRights( - rights->can_send_basic_messages_, rights->can_send_audios_, rights->can_send_documents_, rights->can_send_photos_, - rights->can_send_videos_, rights->can_send_video_notes_, rights->can_send_voice_notes_, - rights->can_send_other_messages_, rights->can_send_other_messages_, rights->can_send_other_messages_, - rights->can_send_other_messages_, rights->can_add_web_page_previews_, rights->can_send_polls_, - rights->can_change_info_, rights->can_invite_users_, rights->can_pin_messages_, rights->can_manage_topics_); + *this = RestrictedRights(rights->can_send_basic_messages_, rights->can_send_audios_, rights->can_send_documents_, + rights->can_send_photos_, rights->can_send_videos_, rights->can_send_video_notes_, + rights->can_send_voice_notes_, rights->can_send_other_messages_, + rights->can_send_other_messages_, rights->can_send_other_messages_, + rights->can_send_other_messages_, rights->can_add_web_page_previews_, + rights->can_send_polls_, rights->can_change_info_, rights->can_invite_users_, + rights->can_pin_messages_, rights->can_manage_topics_, channel_type); } RestrictedRights::RestrictedRights(bool can_send_messages, bool can_send_audios, bool can_send_documents, @@ -251,7 +254,11 @@ RestrictedRights::RestrictedRights(bool can_send_messages, bool can_send_audios, bool can_send_voice_notes, bool can_send_stickers, bool can_send_animations, bool can_send_games, bool can_use_inline_bots, bool can_add_web_page_previews, bool can_send_polls, bool can_change_info_and_settings, bool can_invite_users, - bool can_pin_messages, bool can_manage_topics) { + bool can_pin_messages, bool can_manage_topics, ChannelType channel_type) { + if (channel_type == ChannelType::Broadcast) { + flags_ = 0; + return; + } flags_ = (static_cast(can_send_messages) * CAN_SEND_MESSAGES) | (static_cast(can_send_audios) * CAN_SEND_AUDIOS) | (static_cast(can_send_documents) * CAN_SEND_DOCUMENTS) | @@ -443,9 +450,9 @@ DialogParticipantStatus DialogParticipantStatus::Member() { } DialogParticipantStatus DialogParticipantStatus::Restricted(RestrictedRights restricted_rights, bool is_member, - int32 restricted_until_date) { + int32 restricted_until_date, ChannelType channel_type) { uint64 flags = restricted_rights.flags_; - if (flags == RestrictedRights::ALL_RESTRICTED_RIGHTS) { + if (flags == RestrictedRights::ALL_RESTRICTED_RIGHTS || channel_type == ChannelType::Broadcast) { return is_member ? Member() : Left(); } flags |= (static_cast(is_member) * IS_MEMBER); @@ -478,25 +485,27 @@ DialogParticipantStatus::DialogParticipantStatus(bool can_be_edited, tl_object_ptr &&admin_rights, string rank, ChannelType channel_type) { CHECK(admin_rights != nullptr); - uint64 flags = AdministratorRights(admin_rights, channel_type).flags_ | AdministratorRights::CAN_MANAGE_DIALOG; - if (can_be_edited) { - flags |= CAN_BE_EDITED; - } - flags |= (RestrictedRights::ALL_RESTRICTED_RIGHTS & ~RestrictedRights::ALL_ADMIN_PERMISSION_RIGHTS) | IS_MEMBER; - *this = DialogParticipantStatus(Type::Administrator, flags, 0, std::move(rank)); + *this = Administrator(AdministratorRights(admin_rights, channel_type), std::move(rank), can_be_edited); } DialogParticipantStatus::DialogParticipantStatus(bool is_member, - tl_object_ptr &&banned_rights) { + tl_object_ptr &&banned_rights, + ChannelType channel_type) { CHECK(banned_rights != nullptr); if (banned_rights->view_messages_) { - *this = DialogParticipantStatus::Banned(banned_rights->until_date_); + *this = Banned(banned_rights->until_date_); + return; + } + if (channel_type == ChannelType::Broadcast) { + *this = is_member ? Member() : Left(); return; } auto until_date = fix_until_date(banned_rights->until_date_); banned_rights->until_date_ = std::numeric_limits::max(); - uint64 flags = RestrictedRights(banned_rights).flags_ | (static_cast(is_member) * IS_MEMBER); + + // manually create Restricted status, because the user can be restricted, but with yet unknown restrictions + uint64 flags = RestrictedRights(banned_rights, channel_type).flags_ | (static_cast(is_member) * IS_MEMBER); *this = DialogParticipantStatus(Type::Restricted, flags, until_date, string()); } @@ -505,7 +514,7 @@ RestrictedRights DialogParticipantStatus::get_effective_restricted_rights() cons can_send_videos(), can_send_video_notes(), can_send_voice_notes(), can_send_stickers(), can_send_animations(), can_send_games(), can_use_inline_bots(), can_add_web_page_previews(), can_send_polls(), can_change_info_and_settings(), can_invite_users(), can_pin_messages(), - can_create_topics()); + can_create_topics(), ChannelType::Unknown); } tl_object_ptr DialogParticipantStatus::get_chat_member_status_object() const { @@ -696,8 +705,8 @@ DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr(status.get()); - return DialogParticipantStatus::Restricted(RestrictedRights(st->permissions_), st->is_member_, - fix_until_date(st->restricted_until_date_)); + return DialogParticipantStatus::Restricted(RestrictedRights(st->permissions_, channel_type), st->is_member_, + fix_until_date(st->restricted_until_date_), channel_type); } case td_api::chatMemberStatusLeft::ID: return DialogParticipantStatus::Left(); @@ -789,7 +798,7 @@ DialogParticipant::DialogParticipant(tl_object_ptr(participant_ptr); *this = {DialogId(participant->peer_), UserId(participant->kicked_by_), participant->date_, - DialogParticipantStatus(!participant->left_, std::move(participant->banned_rights_))}; + DialogParticipantStatus(!participant->left_, std::move(participant->banned_rights_), channel_type)}; break; } default: @@ -814,11 +823,11 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant << ']'; } -td_api::object_ptr DialogParticipants::get_chat_members_object(Td *td) const { +td_api::object_ptr DialogParticipants::get_chat_members_object(Td *td, const char *source) const { vector> chat_members; chat_members.reserve(participants_.size()); for (auto &participant : participants_) { - chat_members.push_back(td->contacts_manager_->get_chat_member_object(participant)); + chat_members.push_back(td->contacts_manager_->get_chat_member_object(participant, source)); } return td_api::make_object(total_count_, std::move(chat_members)); diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipant.h b/lib/tgchat/ext/td/td/telegram/DialogParticipant.h index fa288615..0e48f916 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipant.h +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipant.h @@ -194,15 +194,15 @@ class RestrictedRights { } public: - explicit RestrictedRights(const tl_object_ptr &rights); + RestrictedRights(const tl_object_ptr &rights, ChannelType channel_type); - explicit RestrictedRights(const td_api::object_ptr &rights); + RestrictedRights(const td_api::object_ptr &rights, ChannelType channel_type); RestrictedRights(bool can_send_messages, bool can_send_audios, bool can_send_documents, bool can_send_photos, bool can_send_videos, bool can_send_video_notes, bool can_send_voice_notes, bool can_send_stickers, bool can_send_animations, bool can_send_games, bool can_use_inline_bots, bool can_add_web_page_previews, bool can_send_polls, bool can_change_info_and_settings, - bool can_invite_users, bool can_pin_messages, bool can_manage_topics); + bool can_invite_users, bool can_pin_messages, bool can_manage_topics, ChannelType channel_type); td_api::object_ptr get_chat_permissions_object() const; @@ -346,7 +346,7 @@ class DialogParticipantStatus { static DialogParticipantStatus Member(); static DialogParticipantStatus Restricted(RestrictedRights restricted_rights, bool is_member, - int32 restricted_until_date); + int32 restricted_until_date, ChannelType channel_type); static DialogParticipantStatus Left(); @@ -363,7 +363,8 @@ class DialogParticipantStatus { ChannelType channel_type); // forcely returns a restricted or banned - DialogParticipantStatus(bool is_member, tl_object_ptr &&banned_rights); + DialogParticipantStatus(bool is_member, tl_object_ptr &&banned_rights, + ChannelType channel_type); bool has_all_administrator_rights(AdministratorRights administrator_rights) const { auto flags = administrator_rights.flags_ & @@ -685,7 +686,7 @@ struct DialogParticipants { : total_count_(total_count), participants_(std::move(participants)) { } - td_api::object_ptr get_chat_members_object(Td *td) const; + td_api::object_ptr get_chat_members_object(Td *td, const char *source) const; }; DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr &status, diff --git a/lib/tgchat/ext/td/td/telegram/DownloadManager.cpp b/lib/tgchat/ext/td/td/telegram/DownloadManager.cpp index 7bd7e69f..0b78fd92 100644 --- a/lib/tgchat/ext/td/td/telegram/DownloadManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DownloadManager.cpp @@ -424,7 +424,7 @@ class DownloadManagerImpl final : public DownloadManager { sent_counters_ = Counters(); } } - } else { + } else if (!G()->td_db()->get_binlog_pmc()->get("dlds_counter").empty()) { G()->td_db()->get_binlog_pmc()->erase("dlds_counter"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("dlds#"); } diff --git a/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp b/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp index 313f7f38..fc086950 100644 --- a/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp +++ b/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp @@ -12,7 +12,6 @@ #include "td/telegram/MessageEntity.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" -#include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" @@ -40,27 +39,34 @@ class SaveDraftMessageQuery final : public Td::ResultHandler { } int32 flags = 0; - ServerMessageId reply_to_message_id; + telegram_api::object_ptr input_reply_to; vector> input_message_entities; + telegram_api::object_ptr media; if (draft_message != nullptr) { - if (draft_message->reply_to_message_id_.is_valid() && draft_message->reply_to_message_id_.is_server()) { - reply_to_message_id = draft_message->reply_to_message_id_.get_server_message_id(); - flags |= telegram_api::messages_saveDraft::REPLY_TO_MSG_ID_MASK; + input_reply_to = draft_message->message_input_reply_to_.get_input_reply_to(td_, MessageId() /*TODO*/); + if (input_reply_to != nullptr) { + flags |= telegram_api::messages_saveDraft::REPLY_TO_MASK; } if (draft_message->input_message_text_.disable_web_page_preview) { flags |= telegram_api::messages_saveDraft::NO_WEBPAGE_MASK; + } else if (draft_message->input_message_text_.show_above_text) { + flags |= telegram_api::messages_saveDraft::INVERT_MEDIA_MASK; } input_message_entities = get_input_message_entities( td_->contacts_manager_.get(), draft_message->input_message_text_.text.entities, "SaveDraftMessageQuery"); if (!input_message_entities.empty()) { flags |= telegram_api::messages_saveDraft::ENTITIES_MASK; } + media = draft_message->input_message_text_.get_input_media_web_page(); + if (media != nullptr) { + flags |= telegram_api::messages_saveDraft::MEDIA_MASK; + } } send_query(G()->net_query_creator().create( telegram_api::messages_saveDraft( - flags, false /*ignored*/, reply_to_message_id.get(), 0, std::move(input_peer), + flags, false /*ignored*/, false /*ignored*/, std::move(input_reply_to), std::move(input_peer), draft_message == nullptr ? string() : draft_message->input_message_text_.text.text, - std::move(input_message_entities)), + std::move(input_message_entities), std::move(media)), {{dialog_id}})); } @@ -146,7 +152,7 @@ class ClearAllDraftsQuery final : public Td::ResultHandler { }; bool DraftMessage::need_update_to(const DraftMessage &other, bool from_update) const { - if (reply_to_message_id_ == other.reply_to_message_id_ && input_message_text_ == other.input_message_text_) { + if (message_input_reply_to_ == other.message_input_reply_to_ && input_message_text_ == other.input_message_text_) { return date_ < other.date_; } else { return !from_update || date_ <= other.date_; @@ -154,28 +160,21 @@ bool DraftMessage::need_update_to(const DraftMessage &other, bool from_update) c } void DraftMessage::add_dependencies(Dependencies &dependencies) const { - add_formatted_text_dependencies(dependencies, &input_message_text_.text); + message_input_reply_to_.add_dependencies(dependencies); + input_message_text_.add_dependencies(dependencies); } -td_api::object_ptr DraftMessage::get_draft_message_object() const { - return td_api::make_object(reply_to_message_id_.get(), date_, - get_input_message_text_object(input_message_text_)); +td_api::object_ptr DraftMessage::get_draft_message_object(Td *td) const { + return td_api::make_object(message_input_reply_to_.get_input_message_reply_to_object(td), date_, + input_message_text_.get_input_message_text_object()); } -DraftMessage::DraftMessage(ContactsManager *contacts_manager, - telegram_api::object_ptr &&draft_message) { +DraftMessage::DraftMessage(Td *td, telegram_api::object_ptr &&draft_message) { CHECK(draft_message != nullptr); - auto flags = draft_message->flags_; date_ = draft_message->date_; - if ((flags & telegram_api::draftMessage::REPLY_TO_MSG_ID_MASK) != 0) { - reply_to_message_id_ = MessageId(ServerMessageId(draft_message->reply_to_msg_id_)); - if (!reply_to_message_id_.is_valid()) { - LOG(ERROR) << "Receive " << reply_to_message_id_ << " as reply_to_message_id in the draft message"; - reply_to_message_id_ = MessageId(); - } - } - - auto entities = get_message_entities(contacts_manager, std::move(draft_message->entities_), "draftMessage"); + message_input_reply_to_ = MessageInputReplyTo(td, std::move(draft_message->reply_to_)); + auto entities = + get_message_entities(td->contacts_manager_.get(), std::move(draft_message->entities_), "draftMessage"); auto status = fix_formatted_text(draft_message->message_, entities, true, true, true, true, true); if (status.is_error()) { LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft_message->message_; @@ -184,9 +183,25 @@ DraftMessage::DraftMessage(ContactsManager *contacts_manager, } entities = find_entities(draft_message->message_, false, true); } - input_message_text_.text = FormattedText{std::move(draft_message->message_), std::move(entities)}; - input_message_text_.disable_web_page_preview = draft_message->no_webpage_; - input_message_text_.clear_draft = false; + string web_page_url; + bool force_small_media = false; + bool force_large_media = false; + if (draft_message->media_ != nullptr) { + if (draft_message->media_->get_id() != telegram_api::inputMediaWebPage::ID) { + LOG(ERROR) << "Receive draft message with " << to_string(draft_message->media_); + } else { + auto media = telegram_api::move_object_as(draft_message->media_); + web_page_url = std::move(media->url_); + if (web_page_url.empty()) { + LOG(ERROR) << "Have no URL in a draft with manual link preview"; + } + force_small_media = media->force_small_media_; + force_large_media = media->force_large_media_; + } + } + input_message_text_ = InputMessageText(FormattedText{std::move(draft_message->message_), std::move(entities)}, + std::move(web_page_url), draft_message->no_webpage_, force_small_media, + force_large_media, draft_message->invert_media_, false); } Result> DraftMessage::get_draft_message( @@ -197,28 +212,20 @@ Result> DraftMessage::get_draft_message( } auto result = make_unique(); - result->reply_to_message_id_ = MessageId(draft_message->reply_to_message_id_); - if (result->reply_to_message_id_ != MessageId() && !result->reply_to_message_id_.is_valid()) { - return Status::Error(400, "Invalid reply_to_message_id specified"); - } - result->reply_to_message_id_ = - td->messages_manager_ - ->get_message_input_reply_to( - dialog_id, top_thread_message_id, - td_api::make_object(0, result->reply_to_message_id_.get()), true) - .message_id_; + result->message_input_reply_to_ = td->messages_manager_->get_message_input_reply_to( + dialog_id, top_thread_message_id, std::move(draft_message->reply_to_), true); auto input_message_content = std::move(draft_message->input_message_text_); if (input_message_content != nullptr) { if (input_message_content->get_id() != td_api::inputMessageText::ID) { return Status::Error(400, "Input message content type must be InputMessageText"); } - TRY_RESULT(message_content, + TRY_RESULT(input_message_text, process_input_message_text(td, dialog_id, std::move(input_message_content), false, true)); - result->input_message_text_ = std::move(message_content); + result->input_message_text_ = std::move(input_message_text); } - if (!result->reply_to_message_id_.is_valid() && result->input_message_text_.text.text.empty()) { + if (!result->message_input_reply_to_.is_valid() && result->input_message_text_.is_empty()) { return nullptr; } @@ -244,14 +251,15 @@ void add_draft_message_dependencies(Dependencies &dependencies, const unique_ptr draft_message->add_dependencies(dependencies); } -td_api::object_ptr get_draft_message_object(const unique_ptr &draft_message) { +td_api::object_ptr get_draft_message_object(Td *td, + const unique_ptr &draft_message) { if (draft_message == nullptr) { return nullptr; } - return draft_message->get_draft_message_object(); + return draft_message->get_draft_message_object(td); } -unique_ptr get_draft_message(ContactsManager *contacts_manager, +unique_ptr get_draft_message(Td *td, telegram_api::object_ptr &&draft_message_ptr) { if (draft_message_ptr == nullptr) { return nullptr; @@ -261,7 +269,7 @@ unique_ptr get_draft_message(ContactsManager *contacts_manager, case telegram_api::draftMessageEmpty::ID: return nullptr; case telegram_api::draftMessage::ID: - return td::make_unique(contacts_manager, + return td::make_unique(td, telegram_api::move_object_as(draft_message_ptr)); default: UNREACHABLE(); diff --git a/lib/tgchat/ext/td/td/telegram/DraftMessage.h b/lib/tgchat/ext/td/td/telegram/DraftMessage.h index 39cf8235..2728cc38 100644 --- a/lib/tgchat/ext/td/td/telegram/DraftMessage.h +++ b/lib/tgchat/ext/td/td/telegram/DraftMessage.h @@ -9,6 +9,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/InputMessageText.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -18,20 +19,19 @@ namespace td { -class ContactsManager; class Dependencies; class Td; class DraftMessage { int32 date_ = 0; - MessageId reply_to_message_id_; + MessageInputReplyTo message_input_reply_to_; InputMessageText input_message_text_; friend class SaveDraftMessageQuery; public: DraftMessage() = default; - DraftMessage(ContactsManager *contacts_manager, telegram_api::object_ptr &&draft_message); + DraftMessage(Td *td, telegram_api::object_ptr &&draft_message); int32 get_date() const { return date_; @@ -41,7 +41,7 @@ class DraftMessage { void add_dependencies(Dependencies &dependencies) const; - td_api::object_ptr get_draft_message_object() const; + td_api::object_ptr get_draft_message_object(Td *td) const; static Result> get_draft_message(Td *td, DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&draft_message); @@ -58,9 +58,10 @@ bool need_update_draft_message(const unique_ptr &old_draft_message void add_draft_message_dependencies(Dependencies &dependencies, const unique_ptr &draft_message); -td_api::object_ptr get_draft_message_object(const unique_ptr &draft_message); +td_api::object_ptr get_draft_message_object(Td *td, + const unique_ptr &draft_message); -unique_ptr get_draft_message(ContactsManager *contacts_manager, +unique_ptr get_draft_message(Td *td, telegram_api::object_ptr &&draft_message_ptr); void save_draft_message(Td *td, DialogId dialog_id, const unique_ptr &draft_message, diff --git a/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp b/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp index 0166828e..89429a37 100644 --- a/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp +++ b/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp @@ -9,6 +9,8 @@ #include "td/telegram/DraftMessage.h" #include "td/telegram/InputMessageText.hpp" +#include "td/telegram/MessageInputReplyTo.hpp" +#include "td/telegram/Version.h" #include "td/utils/tl_helpers.h" @@ -16,16 +18,49 @@ namespace td { template void DraftMessage::store(StorerT &storer) const { + bool has_message_input_reply_to = !message_input_reply_to_.is_empty(); + bool has_input_message_text = !input_message_text_.is_empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_input_message_text); + STORE_FLAG(has_message_input_reply_to); + END_STORE_FLAGS(); td::store(date_, storer); - td::store(reply_to_message_id_, storer); - td::store(input_message_text_, storer); + if (has_input_message_text) { + td::store(input_message_text_, storer); + } + if (has_message_input_reply_to) { + td::store(message_input_reply_to_, storer); + } } template void DraftMessage::parse(ParserT &parser) { + bool has_legacy_reply_to_message_id; + bool has_input_message_text; + bool has_message_input_reply_to; + if (parser.version() >= static_cast(Version::SupportRepliesInOtherChats)) { + has_legacy_reply_to_message_id = false; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_input_message_text); + PARSE_FLAG(has_message_input_reply_to); + END_PARSE_FLAGS(); + } else { + has_legacy_reply_to_message_id = true; + has_input_message_text = true; + has_message_input_reply_to = false; + } td::parse(date_, parser); - td::parse(reply_to_message_id_, parser); - td::parse(input_message_text_, parser); + if (has_legacy_reply_to_message_id) { + MessageId legacy_reply_to_message_id; + td::parse(legacy_reply_to_message_id, parser); + message_input_reply_to_ = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), FormattedText()}; + } + if (has_input_message_text) { + td::parse(input_message_text_, parser); + } + if (has_message_input_reply_to) { + td::parse(message_input_reply_to_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/EmojiStatus.cpp b/lib/tgchat/ext/td/td/telegram/EmojiStatus.cpp index db202c22..5d451f2b 100644 --- a/lib/tgchat/ext/td/td/telegram/EmojiStatus.cpp +++ b/lib/tgchat/ext/td/td/telegram/EmojiStatus.cpp @@ -312,12 +312,8 @@ void add_recent_emoji_status(Td *td, EmojiStatus emoji_status) { } 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); - } + add_to_top(statuses.emoji_statuses_, MAX_RECENT_EMOJI_STATUSES, emoji_status); save_emoji_statuses(get_recent_emoji_statuses_database_key(), statuses); } diff --git a/lib/tgchat/ext/td/td/telegram/ForumTopic.cpp b/lib/tgchat/ext/td/td/telegram/ForumTopic.cpp index a1dc0dc8..4a38b7e0 100644 --- a/lib/tgchat/ext/td/td/telegram/ForumTopic.cpp +++ b/lib/tgchat/ext/td/td/telegram/ForumTopic.cpp @@ -28,7 +28,7 @@ ForumTopic::ForumTopic(Td *td, tl_object_ptr &&forum_t is_pinned_ = forum_topic->pinned_; notification_settings_ = get_dialog_notification_settings(std::move(forum_topic->notify_settings_), current_notification_settings); - draft_message_ = get_draft_message(td->contacts_manager_.get(), std::move(forum_topic->draft_)); + draft_message_ = get_draft_message(td, std::move(forum_topic->draft_)); if (is_short_) { return; @@ -70,7 +70,7 @@ td_api::object_ptr ForumTopic::get_forum_topic_object(Td *td // TODO draft_message = can_send_message(dialog_id, info_.get_top_thread_message_id()).is_ok() ? ... : nullptr; auto last_message = td->messages_manager_->get_message_object({dialog_id, last_message_id_}, "get_forum_topic_object"); - auto draft_message = get_draft_message_object(draft_message_); + auto draft_message = get_draft_message_object(td, draft_message_); return td_api::make_object( info.get_forum_topic_info_object(td), std::move(last_message), is_pinned_, unread_count_, last_read_inbox_message_id_.get(), last_read_outbox_message_id_.get(), unread_mention_count_, diff --git a/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp new file mode 100644 index 00000000..a911aceb --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp @@ -0,0 +1,150 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/GiveawayParameters.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" + +#include "td/utils/Random.h" + +namespace td { + +Result GiveawayParameters::get_boosted_channel_id(Td *td, DialogId dialog_id) { + if (!td->messages_manager_->have_dialog_force(dialog_id, "get_boosted_channel_id")) { + return Status::Error(400, "Chat to boost not found"); + } + if (dialog_id.get_type() != DialogType::Channel) { + return Status::Error(400, "Can't boost the chat"); + } + auto channel_id = dialog_id.get_channel_id(); + if (!td->contacts_manager_->is_broadcast_channel(channel_id)) { + return Status::Error(400, "Can't boost the group"); + } + if (!td->contacts_manager_->get_channel_status(channel_id).can_post_messages()) { + return Status::Error(400, "Not enough rights in the chat"); + } + return channel_id; +} + +Result GiveawayParameters::get_giveaway_parameters( + Td *td, const td_api::premiumGiveawayParameters *parameters) { + if (parameters == nullptr) { + return Status::Error(400, "Giveaway parameters must be non-empty"); + } + TRY_RESULT(boosted_channel_id, get_boosted_channel_id(td, DialogId(parameters->boosted_chat_id_))); + vector additional_channel_ids; + for (auto additional_chat_id : parameters->additional_chat_ids_) { + TRY_RESULT(channel_id, get_boosted_channel_id(td, DialogId(additional_chat_id))); + additional_channel_ids.push_back(channel_id); + } + if (static_cast(additional_channel_ids.size()) > + td->option_manager_->get_option_integer("giveaway_additional_chat_count_max")) { + return Status::Error(400, "Too many additional chats specified"); + } + if (parameters->winners_selection_date_ < G()->unix_time()) { + return Status::Error(400, "Giveaway date is in the past"); + } + for (auto &country_code : parameters->country_codes_) { + if (country_code.size() != 2 || country_code[0] < 'A' || country_code[0] > 'Z') { + return Status::Error(400, "Invalid country code specified"); + } + } + if (static_cast(parameters->country_codes_.size()) > + td->option_manager_->get_option_integer("giveaway_country_count_max")) { + return Status::Error(400, "Too many countries specified"); + } + return GiveawayParameters(boosted_channel_id, std::move(additional_channel_ids), parameters->only_new_members_, + parameters->winners_selection_date_, vector(parameters->country_codes_)); +} + +vector GiveawayParameters::get_channel_ids() const { + auto result = additional_channel_ids_; + result.push_back(boosted_channel_id_); + return result; +} + +void GiveawayParameters::add_dependencies(Dependencies &dependencies) const { + dependencies.add_dialog_and_dependencies(DialogId(boosted_channel_id_)); + for (auto channel_id : additional_channel_ids_) { + dependencies.add_dialog_and_dependencies(DialogId(channel_id)); + } +} + +telegram_api::object_ptr +GiveawayParameters::get_input_store_payment_premium_giveaway(Td *td, const string ¤cy, int64 amount) const { + int64 random_id; + do { + random_id = Random::secure_int64(); + } while (random_id == 0); + + auto boost_input_peer = td->messages_manager_->get_input_peer(DialogId(boosted_channel_id_), AccessRights::Write); + CHECK(boost_input_peer != nullptr); + + vector> additional_input_peers; + for (auto additional_channel_id : additional_channel_ids_) { + auto input_peer = td->messages_manager_->get_input_peer(DialogId(additional_channel_id), AccessRights::Write); + CHECK(input_peer != nullptr); + additional_input_peers.push_back(std::move(input_peer)); + } + + int32 flags = 0; + if (only_new_subscribers_) { + flags |= telegram_api::inputStorePaymentPremiumGiveaway::ONLY_NEW_SUBSCRIBERS_MASK; + } + if (!additional_input_peers.empty()) { + flags |= telegram_api::inputStorePaymentPremiumGiveaway::ADDITIONAL_PEERS_MASK; + } + if (!country_codes_.empty()) { + flags |= telegram_api::inputStorePaymentPremiumGiveaway::COUNTRIES_ISO2_MASK; + } + return telegram_api::make_object( + flags, false /*ignored*/, std::move(boost_input_peer), std::move(additional_input_peers), + vector(country_codes_), random_id, date_, currency, amount); +} + +td_api::object_ptr GiveawayParameters::get_premium_giveaway_parameters_object( + Td *td) const { + CHECK(is_valid()); + vector chat_ids; + for (auto channel_id : additional_channel_ids_) { + DialogId dialog_id(channel_id); + td->messages_manager_->force_create_dialog(dialog_id, "premiumGiveawayParameters", true); + chat_ids.push_back(td->messages_manager_->get_chat_id_object(dialog_id, "premiumGiveawayParameters")); + } + DialogId dialog_id(boosted_channel_id_); + td->messages_manager_->force_create_dialog(dialog_id, "premiumGiveawayParameters", true); + return td_api::make_object( + td->messages_manager_->get_chat_id_object(dialog_id, "premiumGiveawayParameters"), std::move(chat_ids), date_, + only_new_subscribers_, vector(country_codes_)); +} + +bool operator==(const GiveawayParameters &lhs, const GiveawayParameters &rhs) { + return lhs.boosted_channel_id_ == rhs.boosted_channel_id_ && + lhs.additional_channel_ids_ == rhs.additional_channel_ids_ && + lhs.only_new_subscribers_ == rhs.only_new_subscribers_ && lhs.date_ == rhs.date_ && + lhs.country_codes_ == rhs.country_codes_; +} + +bool operator!=(const GiveawayParameters &lhs, const GiveawayParameters &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const GiveawayParameters &giveaway_parameters) { + return string_builder << "Giveaway[" << giveaway_parameters.boosted_channel_id_ << " + " + << giveaway_parameters.additional_channel_ids_ + << (giveaway_parameters.only_new_subscribers_ ? " only for new members" : "") + << " for countries " << giveaway_parameters.country_codes_ << " at " + << giveaway_parameters.date_ << ']'; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/GiveawayParameters.h b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.h new file mode 100644 index 00000000..07204623 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.h @@ -0,0 +1,87 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Dependencies; + +class Td; + +class GiveawayParameters { + ChannelId boosted_channel_id_; + vector additional_channel_ids_; + bool only_new_subscribers_ = false; + int32 date_ = 0; + vector country_codes_; + + static Result get_boosted_channel_id(Td *td, DialogId dialog_id); + + friend bool operator==(const GiveawayParameters &lhs, const GiveawayParameters &rhs); + friend bool operator!=(const GiveawayParameters &lhs, const GiveawayParameters &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const GiveawayParameters &giveaway_parameters); + + public: + GiveawayParameters() = default; + + GiveawayParameters(ChannelId boosted_channel_id, vector &&additional_channel_ids, + bool only_new_subscribers, int32 date, vector &&country_codes) + : boosted_channel_id_(boosted_channel_id) + , additional_channel_ids_(std::move(additional_channel_ids)) + , only_new_subscribers_(only_new_subscribers) + , date_(date) + , country_codes_(std::move(country_codes)) { + } + + static Result get_giveaway_parameters(Td *td, + const td_api::premiumGiveawayParameters *parameters); + + bool is_valid() const { + for (auto channel_id : additional_channel_ids_) { + if (!channel_id.is_valid()) { + return false; + } + } + return boosted_channel_id_.is_valid() && date_ > 0; + } + + DialogId get_boosted_dialog_id() const { + return DialogId(boosted_channel_id_); + } + + vector get_channel_ids() const; + + void add_dependencies(Dependencies &dependencies) const; + + telegram_api::object_ptr get_input_store_payment_premium_giveaway( + Td *td, const string ¤cy, int64 amount) const; + + td_api::object_ptr get_premium_giveaway_parameters_object(Td *td) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const GiveawayParameters &lhs, const GiveawayParameters &rhs); +bool operator!=(const GiveawayParameters &lhs, const GiveawayParameters &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const GiveawayParameters &giveaway_parameters); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/GiveawayParameters.hpp b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.hpp new file mode 100644 index 00000000..64a21a4d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.hpp @@ -0,0 +1,53 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/GiveawayParameters.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void GiveawayParameters::store(StorerT &storer) const { + bool has_additional_channel_ids = !additional_channel_ids_.empty(); + bool has_country_codes = !country_codes_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(only_new_subscribers_); + STORE_FLAG(has_additional_channel_ids); + STORE_FLAG(has_country_codes); + END_STORE_FLAGS(); + td::store(boosted_channel_id_, storer); + if (has_additional_channel_ids) { + td::store(additional_channel_ids_, storer); + } + td::store(date_, storer); + if (has_country_codes) { + td::store(country_codes_, storer); + } +} + +template +void GiveawayParameters::parse(ParserT &parser) { + bool has_additional_channel_ids; + bool has_country_codes; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(only_new_subscribers_); + PARSE_FLAG(has_additional_channel_ids); + PARSE_FLAG(has_country_codes); + END_PARSE_FLAGS(); + td::parse(boosted_channel_id_, parser); + if (has_additional_channel_ids) { + td::parse(additional_channel_ids_, parser); + } + td::parse(date_, parser); + if (has_country_codes) { + td::parse(country_codes_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Global.cpp b/lib/tgchat/ext/td/td/telegram/Global.cpp index 3163fcb7..23c2d287 100644 --- a/lib/tgchat/ext/td/td/telegram/Global.cpp +++ b/lib/tgchat/ext/td/td/telegram/Global.cpp @@ -38,13 +38,9 @@ void Global::log_out(Slice reason) { send_closure(auth_manager_, &AuthManager::on_authorization_lost, reason.str()); } -void Global::close_all(Promise<> on_finished) { - td_db_->close_all(std::move(on_finished)); - state_manager_.clear(); -} - -void Global::close_and_destroy_all(Promise<> on_finished) { - td_db_->close_and_destroy_all(std::move(on_finished)); +void Global::close_all(bool destroy_flag, Promise on_finished) { + td_db_->close(use_sqlite_pmc() ? get_database_scheduler_id() : get_slow_net_scheduler_id(), destroy_flag, + std::move(on_finished)); state_manager_.clear(); } diff --git a/lib/tgchat/ext/td/td/telegram/Global.h b/lib/tgchat/ext/td/td/telegram/Global.h index 6c3f2146..2cece6dc 100644 --- a/lib/tgchat/ext/td/td/telegram/Global.h +++ b/lib/tgchat/ext/td/td/telegram/Global.h @@ -36,6 +36,7 @@ class AttachMenuManager; class AuthManager; class AutosaveManager; class BackgroundManager; +class BoostManager; class CallManager; class ConfigManager; class ConnectionCreator; @@ -92,8 +93,7 @@ class Global final : public ActorContext { void log_out(Slice reason); - void close_all(Promise<> on_finished); - void close_and_destroy_all(Promise<> on_finished); + void close_all(bool destroy_flag, Promise<> on_finished); Status init(ActorId td, unique_ptr td_db_ptr) TD_WARN_UNUSED_RESULT; @@ -146,21 +146,12 @@ class Global final : public ActorContext { bool is_server_time_reliable() const { return server_time_difference_was_updated_.load(std::memory_order_relaxed); } - double to_server_time(double now) const { - return now + get_server_time_difference(); - } double server_time() const { - return to_server_time(Time::now()); - } - double server_time_cached() const { - return to_server_time(Time::now_cached()); + return Time::now() + get_server_time_difference(); } int32 unix_time() const { return to_unix_time(server_time()); } - int32 unix_time_cached() const { - return to_unix_time(server_time_cached()); - } void update_server_time_difference(double diff, bool force); @@ -224,6 +215,13 @@ class Global final : public ActorContext { background_manager_ = background_manager; } + ActorId boost_manager() const { + return boost_manager_; + } + void set_boost_manager(ActorId boost_manager) { + boost_manager_ = boost_manager; + } + ActorId call_manager() const { return call_manager_; } @@ -531,6 +529,7 @@ class Global final : public ActorContext { ActorId auth_manager_; ActorId autosave_manager_; ActorId background_manager_; + ActorId boost_manager_; ActorId call_manager_; ActorId config_manager_; ActorId contacts_manager_; diff --git a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp index 85987ed3..a0ebb12e 100644 --- a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp @@ -57,8 +57,6 @@ #include "td/utils/tl_helpers.h" #include "td/utils/tl_parsers.h" -#include - namespace td { class GetInlineBotResultsQuery final : public Td::ResultHandler { @@ -380,21 +378,48 @@ Result> InlineQueriesManager: if (constructor_id == td_api::inputMessageText::ID) { TRY_RESULT(input_message_text, process_input_message_text(td_, DialogId(td_->contacts_manager_->get_my_id()), std::move(input_message_content), true)); + auto entities = get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, + "get_inline_message"); + if (!input_message_text.web_page_url.empty()) { + int32 flags = 0; + if (input_reply_markup != nullptr) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::REPLY_MARKUP_MASK; + } + if (!entities.empty()) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::ENTITIES_MASK; + } + if (input_message_text.force_small_media) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::FORCE_SMALL_MEDIA_MASK; + } + if (input_message_text.force_large_media) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::FORCE_LARGE_MEDIA_MASK; + } + if (input_message_text.show_above_text) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::INVERT_MEDIA_MASK; + } + if (!input_message_text.text.text.empty()) { + flags |= telegram_api::inputBotInlineMessageMediaWebPage::OPTIONAL_MASK; + } + return make_tl_object( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_message_text.text.text), std::move(entities), input_message_text.web_page_url, + std::move(input_reply_markup)); + } int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK; } if (input_message_text.disable_web_page_preview) { flags |= telegram_api::inputBotInlineMessageText::NO_WEBPAGE_MASK; + } else if (input_message_text.show_above_text) { + flags |= telegram_api::inputBotInlineMessageText::INVERT_MEDIA_MASK; } - if (!input_message_text.text.entities.empty()) { + if (!entities.empty()) { flags |= telegram_api::inputBotInlineMessageText::ENTITIES_MASK; } - return make_tl_object( - flags, false /*ignored*/, std::move(input_message_text.text.text), - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, - "get_inline_message"), - std::move(input_reply_markup)); + return make_tl_object(flags, false /*ignored*/, false /*ignored*/, + std::move(input_message_text.text.text), + std::move(entities), std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageContact::ID) { TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content))); @@ -437,8 +462,8 @@ Result> InlineQueriesManager: if (!entities.empty()) { flags |= telegram_api::inputBotInlineMessageMediaAuto::ENTITIES_MASK; } - return make_tl_object(flags, caption.text, std::move(entities), - std::move(input_reply_markup)); + return make_tl_object( + flags, false /*ignored*/, caption.text, std::move(entities), std::move(input_reply_markup)); } return Status::Error(400, "Unallowed inline message content type"); } @@ -1097,8 +1122,8 @@ void InlineQueriesManager::loop() { pending_inline_query_ = nullptr; } else { if (!has_timeout()) { - LOG(INFO) << "Schedule send inline query " << pending_inline_query_->query_hash << " at " - << G()->to_server_time(next_inline_query_time_); + LOG(INFO) << "Schedule send inline query " << pending_inline_query_->query_hash << " in " + << next_inline_query_time_ - now; set_timeout_at(next_inline_query_time_); } } @@ -2183,17 +2208,8 @@ bool InlineQueriesManager::update_bot_usage(UserId bot_user_id) { return false; } - auto it = std::find(recently_used_bot_user_ids_.begin(), recently_used_bot_user_ids_.end(), bot_user_id); - if (it == recently_used_bot_user_ids_.end()) { - if (static_cast(recently_used_bot_user_ids_.size()) == MAX_RECENT_INLINE_BOTS) { - CHECK(!recently_used_bot_user_ids_.empty()); - recently_used_bot_user_ids_.back() = bot_user_id; - } else { - recently_used_bot_user_ids_.push_back(bot_user_id); - } - it = recently_used_bot_user_ids_.end() - 1; - } - std::rotate(recently_used_bot_user_ids_.begin(), it, it + 1); + add_to_top(recently_used_bot_user_ids_, MAX_RECENT_INLINE_BOTS, bot_user_id); + return true; } diff --git a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.h b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.h index cc8bfa3c..cb28b2be 100644 --- a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.h +++ b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.h @@ -87,8 +87,8 @@ class InlineQueriesManager final : public Actor { tl_object_ptr &&input_bot_inline_message_id); private: - static constexpr int32 MAX_RECENT_INLINE_BOTS = 20; // some reasonable value - static constexpr int32 INLINE_QUERY_DELAY_MS = 400; // server side limit + static constexpr size_t MAX_RECENT_INLINE_BOTS = 20; // some reasonable value + static constexpr int32 INLINE_QUERY_DELAY_MS = 400; // server side limit static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_PHOTO = 1 << 0; static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_DOCUMENT = 1 << 1; diff --git a/lib/tgchat/ext/td/td/telegram/InputDialogId.cpp b/lib/tgchat/ext/td/td/telegram/InputDialogId.cpp index 4298125b..94378a10 100644 --- a/lib/tgchat/ext/td/td/telegram/InputDialogId.cpp +++ b/lib/tgchat/ext/td/td/telegram/InputDialogId.cpp @@ -15,6 +15,25 @@ namespace td { +InputDialogId::InputDialogId(const telegram_api::object_ptr &input_user) { + CHECK(input_user != nullptr); + switch (input_user->get_id()) { + case telegram_api::inputUser::ID: { + auto user = static_cast(input_user.get()); + UserId user_id(user->user_id_); + if (user_id.is_valid()) { + dialog_id = DialogId(user_id); + access_hash = user->access_hash_; + return; + } + break; + } + default: + break; + } + LOG(ERROR) << "Receive " << to_string(input_user); +} + InputDialogId::InputDialogId(const tl_object_ptr &input_peer) { CHECK(input_peer != nullptr); switch (input_peer->get_id()) { diff --git a/lib/tgchat/ext/td/td/telegram/InputDialogId.h b/lib/tgchat/ext/td/td/telegram/InputDialogId.h index 11eca9d4..c6fcc154 100644 --- a/lib/tgchat/ext/td/td/telegram/InputDialogId.h +++ b/lib/tgchat/ext/td/td/telegram/InputDialogId.h @@ -25,6 +25,8 @@ class InputDialogId { explicit constexpr InputDialogId(DialogId dialog_id) : dialog_id(dialog_id) { } + explicit InputDialogId(const telegram_api::object_ptr &input_user); + explicit InputDialogId(const tl_object_ptr &input_peer); static vector get_input_dialog_ids(const vector> &input_peers, diff --git a/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp b/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp index 35e85de3..4601234c 100644 --- a/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp +++ b/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp @@ -6,7 +6,11 @@ // #include "td/telegram/InputMessageText.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" #include "td/telegram/MessageEntity.h" +#include "td/telegram/misc.h" +#include "td/telegram/Td.h" #include "td/utils/common.h" @@ -27,17 +31,74 @@ Result process_input_message_text(const Td *td, DialogId dialo CHECK(input_message_content != nullptr); CHECK(input_message_content->get_id() == td_api::inputMessageText::ID); auto input_message_text = static_cast(input_message_content.get()); - TRY_RESULT(text, get_formatted_text(td, dialog_id, std::move(input_message_text->text_), is_bot, for_draft, for_draft, - for_draft)); - return InputMessageText{std::move(text), input_message_text->disable_web_page_preview_, - input_message_text->clear_draft_}; + string web_page_url; + bool disable_web_page_preview = false; + bool force_small_media = false; + bool force_large_media = false; + bool show_above_text = false; + if (input_message_text->link_preview_options_ != nullptr) { + auto options = std::move(input_message_text->link_preview_options_); + web_page_url = std::move(options->url_); + disable_web_page_preview = options->is_disabled_; + force_small_media = options->force_small_media_; + force_large_media = options->force_large_media_; + show_above_text = options->show_above_text_; + + if (!clean_input_string(web_page_url)) { + return Status::Error(400, "Link preview URL must be encoded in UTF-8"); + } + + if (disable_web_page_preview || + (dialog_id.get_type() == DialogType::Channel && + !td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews())) { + web_page_url.clear(); + } + if (web_page_url.empty()) { + force_small_media = false; + force_large_media = false; + } + } + TRY_RESULT(text, get_formatted_text(td, dialog_id, std::move(input_message_text->text_), is_bot, + for_draft || !web_page_url.empty(), for_draft, for_draft)); + if (!disable_web_page_preview && web_page_url.empty() && dialog_id.get_type() == DialogType::SecretChat) { + web_page_url = get_first_url(text).str(); + } + return InputMessageText{ + std::move(text), std::move(web_page_url), disable_web_page_preview, force_small_media, + force_large_media, show_above_text, input_message_text->clear_draft_}; +} + +void InputMessageText::add_dependencies(Dependencies &dependencies) const { + add_formatted_text_dependencies(dependencies, &text); +} + +telegram_api::object_ptr InputMessageText::get_input_media_web_page() const { + if (web_page_url.empty()) { + return nullptr; + } + int32 flags = 0; + if (force_small_media) { + flags |= telegram_api::inputMediaWebPage::FORCE_SMALL_MEDIA_MASK; + } + if (force_large_media) { + flags |= telegram_api::inputMediaWebPage::FORCE_LARGE_MEDIA_MASK; + } + if (!text.text.empty()) { + flags |= telegram_api::inputMediaWebPage::OPTIONAL_MASK; + } + return telegram_api::make_object(flags, false /*ignored*/, false /*ignored*/, + false /*ignored*/, web_page_url); } // used only for draft -td_api::object_ptr get_input_message_text_object(const InputMessageText &input_message_text) { - return td_api::make_object(get_formatted_text_object(input_message_text.text, false, -1), - input_message_text.disable_web_page_preview, - input_message_text.clear_draft); +td_api::object_ptr InputMessageText::get_input_message_text_object() const { + td_api::object_ptr options; + if (!web_page_url.empty() || disable_web_page_preview || force_small_media || force_large_media || show_above_text) { + options = td_api::make_object(disable_web_page_preview, web_page_url, force_small_media, + force_large_media, show_above_text); + } + return td_api::make_object(get_formatted_text_object(text, false, -1), std::move(options), + clear_draft); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputMessageText.h b/lib/tgchat/ext/td/td/telegram/InputMessageText.h index 647bd4b2..c2da584a 100644 --- a/lib/tgchat/ext/td/td/telegram/InputMessageText.h +++ b/lib/tgchat/ext/td/td/telegram/InputMessageText.h @@ -9,22 +9,48 @@ #include "td/telegram/DialogId.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/utils/common.h" #include "td/utils/Status.h" namespace td { +class Dependencies; + class Td; class InputMessageText { public: FormattedText text; + string web_page_url; bool disable_web_page_preview = false; + bool force_small_media = false; + bool force_large_media = false; + bool show_above_text = false; bool clear_draft = false; + InputMessageText() = default; - InputMessageText(FormattedText text, bool disable_web_page_preview, bool clear_draft) - : text(std::move(text)), disable_web_page_preview(disable_web_page_preview), clear_draft(clear_draft) { + InputMessageText(FormattedText text, string &&web_page_url, bool disable_web_page_preview, bool force_small_media, + bool force_large_media, bool show_above_text, bool clear_draft) + : text(std::move(text)) + , web_page_url(std::move(web_page_url)) + , disable_web_page_preview(disable_web_page_preview) + , force_small_media(force_small_media) + , force_large_media(force_large_media) + , show_above_text(show_above_text) + , clear_draft(clear_draft) { + } + + bool is_empty() const { + return text.text.empty() && web_page_url.empty(); } + + void add_dependencies(Dependencies &dependencies) const; + + telegram_api::object_ptr get_input_media_web_page() const; + + td_api::object_ptr get_input_message_text_object() const; }; bool operator==(const InputMessageText &lhs, const InputMessageText &rhs); @@ -34,6 +60,4 @@ Result process_input_message_text(const Td *td, DialogId dialo tl_object_ptr &&input_message_content, bool is_bot, bool for_draft = false) TD_WARN_UNUSED_RESULT; -td_api::object_ptr get_input_message_text_object(const InputMessageText &input_message_text); - } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputMessageText.hpp b/lib/tgchat/ext/td/td/telegram/InputMessageText.hpp index 1bec66a2..225ff5a1 100644 --- a/lib/tgchat/ext/td/td/telegram/InputMessageText.hpp +++ b/lib/tgchat/ext/td/td/telegram/InputMessageText.hpp @@ -16,20 +16,42 @@ namespace td { template void store(const InputMessageText &input_message_text, StorerT &storer) { + bool has_web_page_url = !input_message_text.web_page_url.empty(); + bool has_empty_text = input_message_text.text.text.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(input_message_text.disable_web_page_preview); STORE_FLAG(input_message_text.clear_draft); + STORE_FLAG(input_message_text.force_small_media); + STORE_FLAG(input_message_text.force_large_media); + STORE_FLAG(has_web_page_url); + STORE_FLAG(has_empty_text); END_STORE_FLAGS(); - store(input_message_text.text, storer); + if (!has_empty_text) { + store(input_message_text.text, storer); + } + if (has_web_page_url) { + store(input_message_text.web_page_url, storer); + } } template void parse(InputMessageText &input_message_text, ParserT &parser) { + bool has_web_page_url; + bool has_empty_text; BEGIN_PARSE_FLAGS(); PARSE_FLAG(input_message_text.disable_web_page_preview); PARSE_FLAG(input_message_text.clear_draft); + PARSE_FLAG(input_message_text.force_small_media); + PARSE_FLAG(input_message_text.force_large_media); + PARSE_FLAG(has_web_page_url); + PARSE_FLAG(has_empty_text); END_PARSE_FLAGS(); - parse(input_message_text.text, parser); + if (!has_empty_text) { + parse(input_message_text.text, parser); + } + if (has_web_page_url) { + parse(input_message_text.web_page_url, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp index b23cb2e9..7defb22d 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp @@ -32,6 +32,7 @@ #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/buffer.h" +#include "td/utils/FlatHashSet.h" #include "td/utils/HttpUrl.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -560,6 +561,18 @@ class LinkManager::InternalLinkPremiumFeatures final : public InternalLink { } }; +class LinkManager::InternalLinkPremiumGiftCode final : public InternalLink { + string code_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(code_); + } + + public: + explicit InternalLinkPremiumGiftCode(string code) : code_(std::move(code)) { + } +}; + class LinkManager::InternalLinkPrivacyAndSecuritySettings final : public InternalLink { td_api::object_ptr get_internal_link_type_object() const final { return td_api::make_object(); @@ -1083,11 +1096,11 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { 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 != "addlist" && - subdomain != "addstickers" && subdomain != "addtheme" && subdomain != "auth" && subdomain != "confirmphone" && - subdomain != "invoice" && subdomain != "joinchat" && subdomain != "login" && subdomain != "proxy" && - subdomain != "setlanguage" && subdomain != "share" && subdomain != "socks" && subdomain != "web" && - subdomain != "k" && subdomain != "z") { + static const FlatHashSet disallowed_subdomains( + {"addemoji", "addlist", "addstickers", "addtheme", "auth", "boost", "confirmphone", + "contact", "giftcode", "invoice", "joinchat", "login", "proxy", "setlanguage", + "share", "socks", "web", "a", "k", "z"}); + if (is_valid_username(subdomain) && disallowed_subdomains.count(subdomain) == 0) { result.type_ = LinkType::TMe; result.query_ = PSTRING() << '/' << subdomain << http_url.query_; return result; @@ -1460,6 +1473,11 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que if (has_arg("slug")) { return td::make_unique(url_query.get_arg("slug").str()); } + } else if (path.size() == 1 && path[0] == "giftcode") { + // giftcode?slug= + if (has_arg("slug")) { + return td::make_unique(url_query.get_arg("slug").str()); + } } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) { // msg_url?url= // msg_url?url=&text= @@ -1610,6 +1628,11 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q // /invoice/ return td::make_unique(path[1]); } + } else if (path[0] == "giftcode") { + if (path.size() >= 2 && !path[1].empty()) { + // /giftcode/ + return td::make_unique(path[1]); + } } else if (path[0][0] == '$') { if (path[0].size() >= 2) { // /$ @@ -1643,6 +1666,18 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q << copy_arg("comment") << copy_arg("t")); } auto username = path[0]; + if (to_lower(username) == "boost") { + if (path.size() == 2 && is_valid_username(path[1])) { + // /boost/ + return td::make_unique(PSTRING() << "tg://boost?domain=" << url_encode(path[1])); + } + auto channel_id = url_query.get_arg("c"); + if (path.size() == 1 && to_integer(channel_id) > 0) { + // /boost?c= + return td::make_unique(PSTRING() + << "tg://boost?channel=" << to_integer(channel_id)); + } + } if (path.size() == 3 && path[1] == "s" && is_valid_story_id(path[2])) { // //s/ return td::make_unique(std::move(username), StoryId(to_integer(path[2]))); @@ -2167,6 +2202,14 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp } return PSTRING() << "tg://premium_offer?ref=" << url_encode(link->referrer_); } + case td_api::internalLinkTypePremiumGiftCode::ID: { + auto link = static_cast(type_ptr); + if (is_internal) { + return PSTRING() << "tg://giftcode?slug=" << url_encode(link->code_); + } else { + return PSTRING() << get_t_me_url() << "giftcode/" << url_encode(link->code_); + } + } case td_api::internalLinkTypePrivacyAndSecuritySettings::ID: if (!is_internal) { return Status::Error("HTTP link is unavailable for the link type"); diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.h b/lib/tgchat/ext/td/td/telegram/LinkManager.h index a8ea75a7..30323110 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.h +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.h @@ -141,6 +141,7 @@ class LinkManager final : public Actor { class InternalLinkMessageDraft; class InternalLinkPassportDataRequest; class InternalLinkPremiumFeatures; + class InternalLinkPremiumGiftCode; class InternalLinkPrivacyAndSecuritySettings; class InternalLinkProxy; class InternalLinkPublicDialog; diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp index 77b46910..a72878aa 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp @@ -38,6 +38,8 @@ #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Game.h" #include "td/telegram/Game.hpp" +#include "td/telegram/GiveawayParameters.h" +#include "td/telegram/GiveawayParameters.hpp" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/HashtagHints.h" @@ -63,6 +65,7 @@ #include "td/telegram/PollId.h" #include "td/telegram/PollId.hpp" #include "td/telegram/PollManager.h" +#include "td/telegram/RepliedMessageInfo.h" #include "td/telegram/secret_api.hpp" #include "td/telegram/SecureValue.h" #include "td/telegram/SecureValue.hpp" @@ -115,9 +118,26 @@ class MessageText final : public MessageContent { public: FormattedText text; WebPageId web_page_id; + bool force_small_media = false; + bool force_large_media = false; + bool skip_web_page_confirmation = false; + string web_page_url; MessageText() = default; - MessageText(FormattedText text, WebPageId web_page_id) : text(std::move(text)), web_page_id(web_page_id) { + MessageText(FormattedText text, WebPageId web_page_id, bool force_small_media, bool force_large_media, + bool skip_web_page_confirmation, string web_page_url) + : text(std::move(text)) + , web_page_id(web_page_id) + , force_small_media(force_small_media) + , force_large_media(force_large_media) + , skip_web_page_confirmation(skip_web_page_confirmation) + , web_page_url(std::move(web_page_url)) { + if (this->web_page_url.empty()) { + this->force_small_media = false; + this->force_large_media = false; + } else if (this->force_large_media) { + this->force_small_media = false; + } } MessageContentType get_type() const final { @@ -476,7 +496,7 @@ class MessageChatSetTtl final : public MessageContent { class MessageUnsupported final : public MessageContent { public: - static constexpr int32 CURRENT_VERSION = 19; + static constexpr int32 CURRENT_VERSION = 24; int32 version = CURRENT_VERSION; MessageUnsupported() = default; @@ -937,6 +957,53 @@ class MessageWriteAccessAllowedByRequest final : public MessageContent { } }; +class MessageGiftCode final : public MessageContent { + public: + DialogId creator_dialog_id; + int32 months = 0; + bool via_giveaway = false; + bool is_unclaimed = false; + string code; + + MessageGiftCode() = default; + MessageGiftCode(DialogId creator_dialog_id, int32 months, bool via_giveaway, bool is_unclaimed, string &&code) + : creator_dialog_id(creator_dialog_id) + , months(months) + , via_giveaway(via_giveaway || is_unclaimed) + , is_unclaimed(is_unclaimed) + , code(std::move(code)) { + } + + MessageContentType get_type() const final { + return MessageContentType::GiftCode; + } +}; + +class MessageGiveaway final : public MessageContent { + public: + GiveawayParameters giveaway_parameters; + int32 quantity = 0; + int32 months = 0; + + MessageGiveaway() = default; + MessageGiveaway(GiveawayParameters giveaway_parameters, int32 quantity, int32 months) + : giveaway_parameters(std::move(giveaway_parameters)), quantity(quantity), months(months) { + } + + MessageContentType get_type() const final { + return MessageContentType::Giveaway; + } +}; + +class MessageGiveawayLaunch final : public MessageContent { + public: + MessageGiveawayLaunch() = default; + + MessageContentType get_type() const final { + return MessageContentType::GiveawayLaunch; + } +}; + template static void store(const MessageContent *content, StorerT &storer) { CHECK(content != nullptr); @@ -1017,8 +1084,22 @@ static void store(const MessageContent *content, StorerT &storer) { } case MessageContentType::Text: { const auto *m = static_cast(content); + bool has_web_page_id = m->web_page_id.is_valid(); + bool has_web_page_url = !m->web_page_url.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_web_page_id); + STORE_FLAG(m->force_small_media); + STORE_FLAG(m->force_large_media); + STORE_FLAG(has_web_page_url); + STORE_FLAG(m->skip_web_page_confirmation); + END_STORE_FLAGS(); store(m->text, storer); - store(m->web_page_id, storer); + if (has_web_page_id) { + store(m->web_page_id, storer); + } + if (has_web_page_url) { + store(m->web_page_url, storer); + } break; } case MessageContentType::Unsupported: { @@ -1339,6 +1420,32 @@ static void store(const MessageContent *content, StorerT &storer) { } case MessageContentType::WriteAccessAllowedByRequest: break; + case MessageContentType::GiftCode: { + const auto *m = static_cast(content); + bool has_creator_dialog_id = m->creator_dialog_id.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(m->via_giveaway); + STORE_FLAG(has_creator_dialog_id); + STORE_FLAG(m->is_unclaimed); + END_STORE_FLAGS(); + if (has_creator_dialog_id) { + store(m->creator_dialog_id, storer); + } + store(m->months, storer); + store(m->code, storer); + break; + } + case MessageContentType::Giveaway: { + const auto *m = static_cast(content); + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + store(m->giveaway_parameters, storer); + store(m->quantity, storer); + store(m->months, storer); + break; + } + case MessageContentType::GiveawayLaunch: + break; default: UNREACHABLE(); } @@ -1467,8 +1574,24 @@ static void parse(unique_ptr &content, ParserT &parser) { } case MessageContentType::Text: { auto m = make_unique(); + bool has_web_page_id = true; + bool has_web_page_url = false; + if (parser.version() >= static_cast(Version::AddMessageTextFlags)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_web_page_id); + PARSE_FLAG(m->force_small_media); + PARSE_FLAG(m->force_large_media); + PARSE_FLAG(has_web_page_url); + PARSE_FLAG(m->skip_web_page_confirmation); + END_PARSE_FLAGS(); + } parse(m->text, parser); - parse(m->web_page_id, parser); + if (has_web_page_id) { + parse(m->web_page_id, parser); + } + if (has_web_page_url) { + parse(m->web_page_url, parser); + } content = std::move(m); break; } @@ -1885,6 +2008,39 @@ static void parse(unique_ptr &content, ParserT &parser) { case MessageContentType::WriteAccessAllowedByRequest: content = make_unique(); break; + case MessageContentType::GiftCode: { + auto m = make_unique(); + bool has_creator_dialog_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(m->via_giveaway); + PARSE_FLAG(has_creator_dialog_id); + PARSE_FLAG(m->is_unclaimed); + END_PARSE_FLAGS(); + if (has_creator_dialog_id) { + parse(m->creator_dialog_id, parser); + } + parse(m->months, parser); + parse(m->code, parser); + content = std::move(m); + break; + } + case MessageContentType::Giveaway: { + auto m = make_unique(); + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + parse(m->giveaway_parameters, parser); + parse(m->quantity, parser); + parse(m->months, parser); + if (!m->giveaway_parameters.is_valid()) { + is_bad = true; + } + content = std::move(m); + break; + } + case MessageContentType::GiveawayLaunch: + content = make_unique(); + break; + default: is_bad = true; } @@ -1919,6 +2075,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, InlineMessageContent result; tl_object_ptr reply_markup; result.disable_web_page_preview = false; + result.invert_media = false; switch (bot_inline_message->get_id()) { case telegram_api::botInlineMessageText::ID: { auto inline_message = move_tl_object_as(bot_inline_message); @@ -1931,13 +2088,41 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, } result.disable_web_page_preview = inline_message->no_webpage_; + result.invert_media = inline_message->invert_media_; FormattedText text{std::move(inline_message->message_), std::move(entities)}; WebPageId web_page_id; if (!result.disable_web_page_preview) { - web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(text)); + web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(text).str()); + } + result.message_content = + td::make_unique(std::move(text), web_page_id, false, false, false, string()); + reply_markup = std::move(inline_message->reply_markup_); + break; + } + case telegram_api::botInlineMessageMediaWebPage::ID: { + auto inline_message = move_tl_object_as(bot_inline_message); + string web_page_url; + if (inline_message->manual_) { + web_page_url = std::move(inline_message->url_); + } + auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_), + "botInlineMessageMediaWebPage"); + auto status = + fix_formatted_text(inline_message->message_, entities, !web_page_url.empty(), true, true, false, false); + if (status.is_error()) { + LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageMediaWebPage " + << inline_message->message_; + break; } - result.message_content = make_unique(std::move(text), web_page_id); + + FormattedText text{std::move(inline_message->message_), std::move(entities)}; + WebPageId web_page_id = + td->web_pages_manager_->get_web_page_by_url(web_page_url.empty() ? get_first_url(text).str() : web_page_url); + result.message_content = td::make_unique( + std::move(text), web_page_id, inline_message->force_small_media_, inline_message->force_large_media_, + inline_message->safe_, std::move(web_page_url)); reply_markup = std::move(inline_message->reply_markup_); + result.invert_media = inline_message->invert_media_; break; } case telegram_api::botInlineMessageMediaInvoice::ID: { @@ -2002,6 +2187,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, LOG(WARNING) << "Unallowed bot inline message " << to_string(inline_message); } + result.invert_media = inline_message->invert_media_; reply_markup = std::move(inline_message->reply_markup_); break; } @@ -2013,8 +2199,11 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, } unique_ptr create_text_message_content(string text, vector entities, - WebPageId web_page_id) { - return make_unique(FormattedText{std::move(text), std::move(entities)}, web_page_id); + WebPageId web_page_id, bool force_small_media, + bool force_large_media, bool skip_confirmation, + string &&web_page_url) { + return td::make_unique(FormattedText{std::move(text), std::move(entities)}, web_page_id, + force_small_media, force_large_media, skip_confirmation, std::move(web_page_url)); } unique_ptr create_contact_registered_message_content() { @@ -2047,6 +2236,7 @@ static Result create_input_message_content( } bool disable_web_page_preview = false; + bool invert_media = false; bool clear_draft = false; unique_ptr content; UserId via_bot_user_id; @@ -2058,7 +2248,9 @@ static Result create_input_message_content( case td_api::inputMessageText::ID: { TRY_RESULT(input_message_text, process_input_message_text(td, dialog_id, std::move(input_message_content), is_bot)); + auto web_page_url = std::move(input_message_text.web_page_url); disable_web_page_preview = input_message_text.disable_web_page_preview; + invert_media = input_message_text.show_above_text; clear_draft = input_message_text.clear_draft; if (is_bot && static_cast(utf8_length(input_message_text.text.text)) > @@ -2071,9 +2263,12 @@ static Result create_input_message_content( dialog_id.get_type() != DialogType::Channel || td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews(); if (!is_bot && !disable_web_page_preview && can_add_web_page_previews) { - web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(input_message_text.text)); + web_page_id = td->web_pages_manager_->get_web_page_by_url( + web_page_url.empty() ? get_first_url(input_message_text.text).str() : web_page_url); } - content = make_unique(std::move(input_message_text.text), web_page_id); + content = td::make_unique(std::move(input_message_text.text), web_page_id, + input_message_text.force_small_media, input_message_text.force_large_media, + false, std::move(web_page_url)); break; } case td_api::inputMessageAnimation::ID: { @@ -2274,12 +2469,8 @@ static Result create_input_message_content( if (correct_option_id < 0 || correct_option_id >= static_cast(input_poll->options_.size())) { return Status::Error(400, "Wrong correct option ID specified"); } - auto r_explanation = - get_formatted_text(td, dialog_id, std::move(type->explanation_), is_bot, true, true, false); - if (r_explanation.is_error()) { - return r_explanation.move_as_error(); - } - explanation = r_explanation.move_as_ok(); + TRY_RESULT_ASSIGN( + explanation, get_formatted_text(td, dialog_id, std::move(type->explanation_), is_bot, true, true, false)); break; } default: @@ -2342,7 +2533,7 @@ static Result create_input_message_content( } } - return InputMessageContent{std::move(content), disable_web_page_preview, clear_draft, ttl, + return InputMessageContent{std::move(content), disable_web_page_preview, invert_media, clear_draft, ttl, via_bot_user_id, std::move(emoji)}; } @@ -2460,9 +2651,10 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s case MessageContentType::Story: { auto story_full_id = static_cast(content)->story_full_id; auto dialog_id = story_full_id.get_dialog_id(); - CHECK(dialog_id.get_type() == DialogType::User); - return td->contacts_manager_->get_input_user(dialog_id.get_user_id()).is_ok(); + return td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read) != nullptr; } + case MessageContentType::Giveaway: + return is_server; case MessageContentType::Unsupported: case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: @@ -2503,6 +2695,8 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::GiveawayLaunch: return false; case MessageContentType::Animation: case MessageContentType::Audio: @@ -2566,7 +2760,10 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, CHECK(input_file == nullptr); CHECK(thumbnail.empty()); const auto *m = static_cast(content); - return td->web_pages_manager_->get_secret_input_media(m->web_page_id); + if (m->web_page_url.empty()) { + return SecretInputMedia{}; + } + return SecretInputMedia{nullptr, make_tl_object(m->web_page_url)}; } case MessageContentType::Venue: { const auto *m = static_cast(content); @@ -2633,6 +2830,9 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: break; default: UNREACHABLE(); @@ -2766,6 +2966,9 @@ static tl_object_ptr get_input_media_impl( case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: break; default: UNREACHABLE(); @@ -2855,6 +3058,30 @@ tl_object_ptr get_fake_input_media(Td *td, tl_object_p } } +tl_object_ptr get_message_content_input_media_web_page(const Td *td, + const MessageContent *content) { + CHECK(content != nullptr); + if (content->get_type() != MessageContentType::Text) { + return nullptr; + } + auto *text = static_cast(content); + if (text->web_page_url.empty()) { + return nullptr; + } + int32 flags = 0; + if (text->force_small_media) { + flags |= telegram_api::inputMediaWebPage::FORCE_SMALL_MEDIA_MASK; + } + if (text->force_large_media) { + flags |= telegram_api::inputMediaWebPage::FORCE_LARGE_MEDIA_MASK; + } + if (!text->text.text.empty()) { + flags |= telegram_api::inputMediaWebPage::OPTIONAL_MASK; + } + return telegram_api::make_object(flags, false /*ignored*/, false /*ignored*/, + false /*ignored*/, text->web_page_url); +} + void delete_message_content_thumbnail(MessageContent *content, Td *td) { switch (content->get_type()) { case MessageContentType::Animation: { @@ -2939,6 +3166,9 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) { case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: break; default: UNREACHABLE(); @@ -3007,6 +3237,14 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten return Status::Error(400, "Not enough rights to send games to the chat"); } break; + case MessageContentType::Giveaway: + if (!permissions.can_send_messages()) { + return Status::Error(400, "Not enough rights to send giveaways to the chat"); + } + if (dialog_type == DialogType::SecretChat) { + return Status::Error(400, "Giveaways can't be sent to secret chats"); + } + break; case MessageContentType::Invoice: if (!permissions.can_send_messages()) { return Status::Error(400, "Not enough rights to send invoice messages to the chat"); @@ -3059,6 +3297,9 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten if (!permissions.can_send_photos() || !permissions.can_send_videos()) { return Status::Error(400, "Not enough rights to send stories to the chat"); } + if (dialog_type == DialogType::SecretChat) { + return Status::Error(400, "Story messages can't be sent to secret chats"); + } break; case MessageContentType::Text: if (!permissions.can_send_messages()) { @@ -3134,6 +3375,8 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::GiveawayLaunch: UNREACHABLE(); } return Status::OK(); @@ -3143,7 +3386,8 @@ 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 must be non-empty in the new message + // text must be non-empty if there is no link preview + return !is_empty_string(text->text.text) || text->web_page_id.is_valid() || !text->web_page_url.empty(); } if (content_type == MessageContentType::Poll) { auto *poll = static_cast(content); @@ -3272,6 +3516,9 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return 0; default: UNREACHABLE(); @@ -3351,6 +3598,7 @@ std::pair get_message_content_group_call_info(const Mess } vector get_message_content_min_user_ids(const Td *td, const MessageContent *message_content) { + CHECK(message_content != nullptr); switch (message_content->get_type()) { case MessageContentType::Text: { const auto *content = static_cast(message_content); @@ -3459,6 +3707,7 @@ vector get_message_content_min_user_ids(const Td *td, const MessageConte case MessageContentType::Dice: break; case MessageContentType::ProximityAlertTriggered: + // not supported server-side break; case MessageContentType::GroupCall: break; @@ -3498,10 +3747,49 @@ vector get_message_content_min_user_ids(const Td *td, const MessageConte } case MessageContentType::WriteAccessAllowedByRequest: break; + case MessageContentType::GiftCode: + break; + case MessageContentType::Giveaway: + break; + case MessageContentType::GiveawayLaunch: + break; default: UNREACHABLE(); break; } + // not supported server-side + // return get_user_ids(get_message_content_text(message_content)); + return {}; +} + +vector get_message_content_min_channel_ids(const Td *td, const MessageContent *message_content) { + CHECK(message_content != nullptr); + switch (message_content->get_type()) { + case MessageContentType::Text: { + const auto *content = static_cast(message_content); + if (content->web_page_id.is_valid()) { + return td->web_pages_manager_->get_web_page_channel_ids(content->web_page_id); + } + break; + } + case MessageContentType::ProximityAlertTriggered: + // not supported server-side + break; + case MessageContentType::Story: { + const auto *content = static_cast(message_content); + auto dialog_id = content->story_full_id.get_dialog_id(); + if (dialog_id.get_type() == DialogType::Channel) { + return {dialog_id.get_channel_id()}; + } + break; + } + case MessageContentType::Giveaway: { + const auto *content = static_cast(message_content); + return content->giveaway_parameters.get_channel_ids(); + } + default: + break; + } return {}; } @@ -3564,7 +3852,12 @@ bool has_message_content_web_page(const MessageContent *content) { void remove_message_content_web_page(MessageContent *content) { CHECK(content->get_type() == MessageContentType::Text); - static_cast(content)->web_page_id = WebPageId(); + auto message_text = static_cast(content); + message_text->web_page_id = WebPageId(); + message_text->force_small_media = false; + message_text->force_large_media = false; + message_text->skip_web_page_confirmation = false; + message_text->web_page_url = string(); } bool can_message_content_have_media_timestamp(const MessageContent *content) { @@ -3678,15 +3971,14 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); auto get_content_object = [td, dialog_id](const MessageContent *content) { - return to_string( - get_message_content_object(content, td, dialog_id, -1, false, false, std::numeric_limits::max())); + return to_string(get_message_content_object(content, td, dialog_id, -1, false, false, + std::numeric_limits::max(), false, false)); }; if (old_->text.text != new_->text.text) { if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) { LOG(ERROR) << "Message text has changed in " << get_content_object(old_content) << ". New content is " << get_content_object(new_content); } - need_update = true; } if (old_->text.entities != new_->text.entities) { const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-side limit @@ -3697,71 +3989,30 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo LOG(WARNING) << "Entities have changed in " << get_content_object(old_content) << ". New content is " << get_content_object(new_content); } - need_update = true; - } - if (old_->web_page_id != new_->web_page_id) { - LOG(INFO) << "Old: " << old_->web_page_id << ", new: " << new_->web_page_id; - is_content_changed = true; - need_update |= td->web_pages_manager_->have_web_page(old_->web_page_id) || - td->web_pages_manager_->have_web_page(new_->web_page_id); } break; } case MessageContentType::Animation: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->animations_manager_->merge_animations(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->caption != new_->caption || old_->has_spoiler != new_->has_spoiler) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->animations_manager_->merge_animations(new_->file_id, old_->file_id); } break; } case MessageContentType::Audio: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->audios_manager_->merge_audios(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->caption != new_->caption) { - need_update = true; - } - break; - } - case MessageContentType::Contact: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->contact != new_->contact) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->audios_manager_->merge_audios(new_->file_id, old_->file_id); } break; } case MessageContentType::Document: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->documents_manager_->merge_documents(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->caption != new_->caption) { - need_update = true; - } - break; - } - case MessageContentType::Game: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->game != new_->game) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->documents_manager_->merge_documents(new_->file_id, old_->file_id); } break; } @@ -3769,25 +4020,12 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo const auto *old_ = static_cast(old_content); auto *new_ = static_cast(new_content); new_->input_invoice.update_from(old_->input_invoice); - if (old_->input_invoice != new_->input_invoice) { - need_update = true; - } else if (old_->input_invoice.is_equal_but_different(new_->input_invoice)) { - is_content_changed = true; - } break; } case MessageContentType::LiveLocation: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->location != new_->location) { - need_update = true; - } - if (old_->period != new_->period || old_->heading != new_->heading || - old_->proximity_alert_radius != new_->proximity_alert_radius) { - need_update = true; - } if (old_->location.get_access_hash() != new_->location.get_access_hash()) { - is_content_changed = true; merge_location_access_hash(old_->location, new_->location); } break; @@ -3795,11 +4033,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Location: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->location != new_->location) { - need_update = true; - } if (old_->location.get_access_hash() != new_->location.get_access_hash()) { - is_content_changed = true; merge_location_access_hash(old_->location, new_->location); } break; @@ -3808,33 +4042,20 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo const auto *old_ = static_cast(old_content); auto *new_ = static_cast(new_content); merge_photos(td, &old_->photo, &new_->photo, dialog_id, need_merge_files, is_content_changed, need_update); - if (old_->caption != new_->caption || old_->has_spoiler != new_->has_spoiler) { - need_update = true; - } break; } case MessageContentType::Sticker: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->stickers_manager_->merge_stickers(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->is_premium != new_->is_premium) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->stickers_manager_->merge_stickers(new_->file_id, old_->file_id); } break; } case MessageContentType::Venue: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->venue != new_->venue) { - need_update = true; - } if (old_->venue.location().get_access_hash() != new_->venue.location().get_access_hash()) { - is_content_changed = true; merge_location_access_hash(old_->venue.location(), new_->venue.location()); } break; @@ -3842,65 +4063,402 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::Video: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->videos_manager_->merge_videos(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->caption != new_->caption || old_->has_spoiler != new_->has_spoiler) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->videos_manager_->merge_videos(new_->file_id, old_->file_id); } break; } case MessageContentType::VideoNote: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->video_notes_manager_->merge_video_notes(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->is_viewed != new_->is_viewed) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->video_notes_manager_->merge_video_notes(new_->file_id, old_->file_id); } break; } case MessageContentType::VoiceNote: { const auto *old_ = static_cast(old_content); const auto *new_ = static_cast(new_content); - if (old_->file_id != new_->file_id) { - if (need_merge_files) { - td->voice_notes_manager_->merge_voice_notes(new_->file_id, old_->file_id); - } - need_update = true; - } - if (old_->caption != new_->caption || old_->is_listened != new_->is_listened) { - need_update = true; + if (old_->file_id != new_->file_id && need_merge_files) { + td->voice_notes_manager_->merge_voice_notes(new_->file_id, old_->file_id); } break; } - case MessageContentType::ChatCreate: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->title != new_->title || old_->participant_user_ids != new_->participant_user_ids) { - need_update = true; + case MessageContentType::Contact: + case MessageContentType::Game: + case MessageContentType::ChatCreate: + case MessageContentType::ChatChangeTitle: + case MessageContentType::ChatChangePhoto: + case MessageContentType::ChatDeletePhoto: + case MessageContentType::ChatDeleteHistory: + case MessageContentType::ChatAddUsers: + case MessageContentType::ChatJoinedByLink: + case MessageContentType::ChatDeleteUser: + case MessageContentType::ChatMigrateTo: + case MessageContentType::ChannelCreate: + case MessageContentType::ChannelMigrateFrom: + case MessageContentType::PinMessage: + case MessageContentType::GameScore: + case MessageContentType::ScreenshotTaken: + case MessageContentType::ChatSetTtl: + case MessageContentType::Call: + case MessageContentType::PaymentSuccessful: + case MessageContentType::ContactRegistered: + case MessageContentType::ExpiredPhoto: + case MessageContentType::ExpiredVideo: + case MessageContentType::CustomServiceAction: + case MessageContentType::WebsiteConnected: + case MessageContentType::PassportDataSent: + case MessageContentType::PassportDataReceived: + case MessageContentType::Poll: + case MessageContentType::Dice: + case MessageContentType::ProximityAlertTriggered: + case MessageContentType::GroupCall: + case MessageContentType::InviteToGroupCall: + case MessageContentType::ChatSetTheme: + case MessageContentType::WebViewDataSent: + case MessageContentType::WebViewDataReceived: + case MessageContentType::GiftPremium: + case MessageContentType::TopicCreate: + case MessageContentType::TopicEdit: + case MessageContentType::Unsupported: + case MessageContentType::SuggestProfilePhoto: + case MessageContentType::WriteAccessAllowed: + case MessageContentType::RequestedDialog: + case MessageContentType::WebViewWriteAccessAllowed: + case MessageContentType::SetBackground: + case MessageContentType::Story: + case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: + break; + default: + UNREACHABLE(); + break; + } +} + +bool merge_message_content_file_id(Td *td, MessageContent *message_content, FileId new_file_id) { + if (!new_file_id.is_valid()) { + return false; + } + + // secret chats only + LOG(INFO) << "Merge message content of a message with file " << new_file_id; + MessageContentType content_type = message_content->get_type(); + switch (content_type) { + case MessageContentType::Animation: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->animations_manager_->merge_animations(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; } break; } - case MessageContentType::ChatChangeTitle: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->title != new_->title) { - need_update = true; + case MessageContentType::Audio: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->audios_manager_->merge_audios(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::Document: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->documents_manager_->merge_documents(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::Photo: { + auto content = static_cast(message_content); + Photo *photo = &content->photo; + if (!photo->photos.empty() && photo->photos.back().type == 'i') { + FileId &old_file_id = photo->photos.back().file_id; + if (old_file_id != new_file_id) { + LOG_STATUS(td->file_manager_->merge(new_file_id, old_file_id)); + old_file_id = new_file_id; + return true; + } + } + break; + } + case MessageContentType::Sticker: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->stickers_manager_->merge_stickers(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::Video: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->videos_manager_->merge_videos(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::VideoNote: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->video_notes_manager_->merge_video_notes(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::VoiceNote: { + auto content = static_cast(message_content); + if (new_file_id != content->file_id) { + td->voice_notes_manager_->merge_voice_notes(new_file_id, content->file_id); + content->file_id = new_file_id; + return true; + } + break; + } + case MessageContentType::Contact: + case MessageContentType::Game: + case MessageContentType::Invoice: + case MessageContentType::LiveLocation: + case MessageContentType::Location: + case MessageContentType::Story: + case MessageContentType::Text: + case MessageContentType::Venue: + case MessageContentType::ChatCreate: + case MessageContentType::ChatChangeTitle: + case MessageContentType::ChatChangePhoto: + case MessageContentType::ChatDeletePhoto: + case MessageContentType::ChatDeleteHistory: + case MessageContentType::ChatAddUsers: + case MessageContentType::ChatJoinedByLink: + case MessageContentType::ChatDeleteUser: + case MessageContentType::ChatMigrateTo: + case MessageContentType::ChannelCreate: + case MessageContentType::ChannelMigrateFrom: + case MessageContentType::PinMessage: + case MessageContentType::GameScore: + case MessageContentType::ScreenshotTaken: + case MessageContentType::ChatSetTtl: + case MessageContentType::Unsupported: + case MessageContentType::Call: + case MessageContentType::PaymentSuccessful: + case MessageContentType::ContactRegistered: + case MessageContentType::ExpiredPhoto: + case MessageContentType::ExpiredVideo: + case MessageContentType::CustomServiceAction: + case MessageContentType::WebsiteConnected: + case MessageContentType::PassportDataSent: + case MessageContentType::PassportDataReceived: + case MessageContentType::Poll: + case MessageContentType::Dice: + case MessageContentType::ProximityAlertTriggered: + case MessageContentType::GroupCall: + case MessageContentType::InviteToGroupCall: + case MessageContentType::ChatSetTheme: + case MessageContentType::WebViewDataSent: + case MessageContentType::WebViewDataReceived: + case MessageContentType::GiftPremium: + case MessageContentType::TopicCreate: + case MessageContentType::TopicEdit: + case MessageContentType::SuggestProfilePhoto: + case MessageContentType::WriteAccessAllowed: + case MessageContentType::RequestedDialog: + case MessageContentType::WebViewWriteAccessAllowed: + case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: + LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type; + break; + default: + UNREACHABLE(); + break; + } + return false; +} + +void compare_message_contents(Td *td, const MessageContent *old_content, const MessageContent *new_content, + bool &is_content_changed, bool &need_update) { + if (old_content == nullptr) { + if (new_content != nullptr) { + need_update = true; + } + return; + } + MessageContentType content_type = old_content->get_type(); + if (new_content == nullptr || new_content->get_type() != content_type) { + need_update = true; + return; + } + + switch (content_type) { + case MessageContentType::Text: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->text.text != rhs->text.text || lhs->text.entities != rhs->text.entities || + lhs->web_page_url != rhs->web_page_url) { + need_update = true; + } else if (lhs->web_page_id != rhs->web_page_id || lhs->force_small_media != rhs->force_small_media || + lhs->force_large_media != rhs->force_large_media || + lhs->skip_web_page_confirmation != rhs->skip_web_page_confirmation) { + is_content_changed = true; + if (td == nullptr || td->web_pages_manager_->have_web_page(lhs->web_page_id) || + td->web_pages_manager_->have_web_page(rhs->web_page_id)) { + need_update = true; + } + } + break; + } + case MessageContentType::Animation: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->caption != rhs->caption || lhs->has_spoiler != rhs->has_spoiler) { + need_update = true; + } + break; + } + case MessageContentType::Audio: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->caption != rhs->caption) { + need_update = true; + } + break; + } + case MessageContentType::Contact: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->contact != rhs->contact) { + need_update = true; + } + break; + } + case MessageContentType::Document: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->caption != rhs->caption) { + need_update = true; + } + break; + } + case MessageContentType::Game: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->game != rhs->game) { + need_update = true; + } + break; + } + case MessageContentType::Invoice: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->input_invoice != rhs->input_invoice) { + need_update = true; + } else if (lhs->input_invoice.is_equal_but_different(rhs->input_invoice)) { + is_content_changed = true; + } + break; + } + case MessageContentType::LiveLocation: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->location != rhs->location || lhs->period != rhs->period || lhs->heading != rhs->heading || + lhs->proximity_alert_radius != rhs->proximity_alert_radius) { + need_update = true; + } else if (lhs->location.get_access_hash() != rhs->location.get_access_hash()) { + is_content_changed = true; + } + break; + } + case MessageContentType::Location: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->location != rhs->location) { + need_update = true; + } else if (lhs->location.get_access_hash() != rhs->location.get_access_hash()) { + is_content_changed = true; + } + break; + } + case MessageContentType::Photo: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->caption != rhs->caption || lhs->has_spoiler != rhs->has_spoiler) { + need_update = true; + } + break; + } + case MessageContentType::Sticker: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->is_premium != rhs->is_premium) { + need_update = true; + } + break; + } + case MessageContentType::Venue: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->venue != rhs->venue) { + need_update = true; + } else if (lhs->venue.location().get_access_hash() != rhs->venue.location().get_access_hash()) { + is_content_changed = true; + } + break; + } + case MessageContentType::Video: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->caption != rhs->caption || lhs->has_spoiler != rhs->has_spoiler) { + need_update = true; + } + break; + } + case MessageContentType::VideoNote: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->is_viewed != rhs->is_viewed) { + need_update = true; + } + break; + } + case MessageContentType::VoiceNote: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->file_id != rhs->file_id || lhs->caption != rhs->caption || lhs->is_listened != rhs->is_listened) { + need_update = true; + } + break; + } + case MessageContentType::ChatCreate: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->title != rhs->title || lhs->participant_user_ids != rhs->participant_user_ids) { + need_update = true; + } + break; + } + case MessageContentType::ChatChangeTitle: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->title != rhs->title) { + need_update = true; } break; } case MessageContentType::ChatChangePhoto: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->photo != new_->photo) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->photo != rhs->photo) { need_update = true; } break; @@ -3910,66 +4468,65 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::ChatDeleteHistory: break; case MessageContentType::ChatAddUsers: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->user_ids != new_->user_ids) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->user_ids != rhs->user_ids) { need_update = true; } break; } case MessageContentType::ChatJoinedByLink: { - auto old_ = static_cast(old_content); - auto new_ = static_cast(new_content); - if (old_->is_approved != new_->is_approved) { + auto lhs = static_cast(old_content); + auto rhs = static_cast(new_content); + if (lhs->is_approved != rhs->is_approved) { need_update = true; } break; } case MessageContentType::ChatDeleteUser: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->user_id != new_->user_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->user_id != rhs->user_id) { need_update = true; } break; } case MessageContentType::ChatMigrateTo: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->migrated_to_channel_id != new_->migrated_to_channel_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->migrated_to_channel_id != rhs->migrated_to_channel_id) { need_update = true; } break; } case MessageContentType::ChannelCreate: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->title != new_->title) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->title != rhs->title) { need_update = true; } break; } case MessageContentType::ChannelMigrateFrom: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->title != new_->title || old_->migrated_from_chat_id != new_->migrated_from_chat_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->title != rhs->title || lhs->migrated_from_chat_id != rhs->migrated_from_chat_id) { need_update = true; } break; } case MessageContentType::PinMessage: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->message_id != new_->message_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->message_id != rhs->message_id) { need_update = true; } break; } case MessageContentType::GameScore: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->game_message_id != new_->game_message_id || old_->game_id != new_->game_id || - old_->score != new_->score) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->game_message_id != rhs->game_message_id || lhs->game_id != rhs->game_id || lhs->score != rhs->score) { need_update = true; } break; @@ -3977,40 +4534,35 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::ScreenshotTaken: break; case MessageContentType::ChatSetTtl: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->ttl != new_->ttl) { - LOG(ERROR) << "Message auto-delete time has changed from " << old_->ttl << " to " << new_->ttl; - need_update = true; - } - if (old_->from_user_id != new_->from_user_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->ttl != rhs->ttl || lhs->from_user_id != rhs->from_user_id) { need_update = true; } break; } case MessageContentType::Call: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->call_id != new_->call_id) { - is_content_changed = true; - } - if (old_->duration != new_->duration || old_->discard_reason != new_->discard_reason || - old_->is_video != new_->is_video) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->duration != rhs->duration || lhs->discard_reason != rhs->discard_reason || + lhs->is_video != rhs->is_video) { need_update = true; + } else if (lhs->call_id != rhs->call_id) { + is_content_changed = true; } break; } case MessageContentType::PaymentSuccessful: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->invoice_dialog_id != new_->invoice_dialog_id || old_->invoice_message_id != new_->invoice_message_id || - old_->currency != new_->currency || old_->total_amount != new_->total_amount || - old_->invoice_payload != new_->invoice_payload || old_->shipping_option_id != new_->shipping_option_id || - old_->telegram_payment_charge_id != new_->telegram_payment_charge_id || - old_->provider_payment_charge_id != new_->provider_payment_charge_id || - ((old_->order_info != nullptr || new_->order_info != nullptr) && - (old_->order_info == nullptr || new_->order_info == nullptr || *old_->order_info != *new_->order_info)) || - old_->is_recurring != new_->is_recurring || old_->is_first_recurring != new_->is_first_recurring) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->invoice_dialog_id != rhs->invoice_dialog_id || lhs->invoice_message_id != rhs->invoice_message_id || + lhs->currency != rhs->currency || lhs->total_amount != rhs->total_amount || + lhs->invoice_payload != rhs->invoice_payload || lhs->shipping_option_id != rhs->shipping_option_id || + lhs->telegram_payment_charge_id != rhs->telegram_payment_charge_id || + lhs->provider_payment_charge_id != rhs->provider_payment_charge_id || + ((lhs->order_info != nullptr || rhs->order_info != nullptr) && + (lhs->order_info == nullptr || rhs->order_info == nullptr || *lhs->order_info != *rhs->order_info)) || + lhs->is_recurring != rhs->is_recurring || lhs->is_first_recurring != rhs->is_first_recurring) { need_update = true; } break; @@ -4022,147 +4574,145 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::ExpiredVideo: break; case MessageContentType::CustomServiceAction: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->message != new_->message) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->message != rhs->message) { need_update = true; } break; } case MessageContentType::WebsiteConnected: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->domain_name != new_->domain_name) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->domain_name != rhs->domain_name) { need_update = true; } break; } case MessageContentType::PassportDataSent: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->types != new_->types) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->types != rhs->types) { need_update = true; } break; } case MessageContentType::PassportDataReceived: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->values != new_->values || old_->credentials != new_->credentials) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->values != rhs->values || lhs->credentials != rhs->credentials) { need_update = true; } break; } case MessageContentType::Poll: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->poll_id != new_->poll_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->poll_id != rhs->poll_id) { need_update = true; } break; } case MessageContentType::Dice: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->emoji != new_->emoji || old_->dice_value != new_->dice_value) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->emoji != rhs->emoji || lhs->dice_value != rhs->dice_value) { need_update = true; } break; } case MessageContentType::ProximityAlertTriggered: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->traveler_dialog_id != new_->traveler_dialog_id || old_->watcher_dialog_id != new_->watcher_dialog_id || - old_->distance != new_->distance) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->traveler_dialog_id != rhs->traveler_dialog_id || lhs->watcher_dialog_id != rhs->watcher_dialog_id || + lhs->distance != rhs->distance) { need_update = true; } break; } case MessageContentType::GroupCall: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->input_group_call_id != new_->input_group_call_id || old_->duration != new_->duration || - old_->schedule_date != new_->schedule_date) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->input_group_call_id != rhs->input_group_call_id || lhs->duration != rhs->duration || + lhs->schedule_date != rhs->schedule_date) { need_update = true; - } - if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) { + } else if (!lhs->input_group_call_id.is_identical(rhs->input_group_call_id)) { is_content_changed = true; } break; } case MessageContentType::InviteToGroupCall: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->input_group_call_id != new_->input_group_call_id || old_->user_ids != new_->user_ids) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->input_group_call_id != rhs->input_group_call_id || lhs->user_ids != rhs->user_ids) { need_update = true; - } - if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) { + } else if (!lhs->input_group_call_id.is_identical(rhs->input_group_call_id)) { is_content_changed = true; } break; } case MessageContentType::ChatSetTheme: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->emoji != new_->emoji) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->emoji != rhs->emoji) { need_update = true; } break; } case MessageContentType::WebViewDataSent: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->button_text != new_->button_text) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->button_text != rhs->button_text) { need_update = true; } break; } case MessageContentType::WebViewDataReceived: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->button_text != new_->button_text || old_->data != new_->data) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->button_text != rhs->button_text || lhs->data != rhs->data) { need_update = true; } break; } case MessageContentType::GiftPremium: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->currency != new_->currency || old_->amount != new_->amount || - old_->crypto_currency != new_->crypto_currency || old_->crypto_amount != new_->crypto_amount || - old_->months != new_->months) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->currency != rhs->currency || lhs->amount != rhs->amount || + lhs->crypto_currency != rhs->crypto_currency || lhs->crypto_amount != rhs->crypto_amount || + lhs->months != rhs->months) { need_update = true; } break; } case MessageContentType::TopicCreate: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->title != new_->title || old_->icon != new_->icon) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->title != rhs->title || lhs->icon != rhs->icon) { need_update = true; } break; } case MessageContentType::TopicEdit: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->edited_data != new_->edited_data) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->edited_data != rhs->edited_data) { need_update = true; } break; } case MessageContentType::Unsupported: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->version != new_->version) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->version != rhs->version) { is_content_changed = true; } break; } case MessageContentType::SuggestProfilePhoto: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->photo != new_->photo) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->photo != rhs->photo) { need_update = true; } break; @@ -4170,187 +4720,63 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::WriteAccessAllowed: break; case MessageContentType::RequestedDialog: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->dialog_id != new_->dialog_id || old_->button_id != new_->button_id) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->dialog_id != rhs->dialog_id || lhs->button_id != rhs->button_id) { need_update = true; } break; } case MessageContentType::WebViewWriteAccessAllowed: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->web_app != new_->web_app) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->web_app != rhs->web_app) { need_update = true; } break; } case MessageContentType::SetBackground: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->old_message_id != new_->old_message_id || old_->background_info != new_->background_info) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->old_message_id != rhs->old_message_id || lhs->background_info != rhs->background_info) { need_update = true; } break; } case MessageContentType::Story: { - const auto *old_ = static_cast(old_content); - const auto *new_ = static_cast(new_content); - if (old_->story_full_id != new_->story_full_id || old_->via_mention != new_->via_mention) { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->story_full_id != rhs->story_full_id || lhs->via_mention != rhs->via_mention) { need_update = true; } break; } case MessageContentType::WriteAccessAllowedByRequest: break; - default: - UNREACHABLE(); - break; - } -} - -bool merge_message_content_file_id(Td *td, MessageContent *message_content, FileId new_file_id) { - if (!new_file_id.is_valid()) { - return false; - } - - // secret chats only - LOG(INFO) << "Merge message content of a message with file " << new_file_id; - MessageContentType content_type = message_content->get_type(); - switch (content_type) { - case MessageContentType::Animation: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->animations_manager_->merge_animations(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; - } - break; - } - case MessageContentType::Audio: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->audios_manager_->merge_audios(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; - } - break; - } - case MessageContentType::Document: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->documents_manager_->merge_documents(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; - } - break; - } - case MessageContentType::Photo: { - auto content = static_cast(message_content); - Photo *photo = &content->photo; - if (!photo->photos.empty() && photo->photos.back().type == 'i') { - FileId &old_file_id = photo->photos.back().file_id; - if (old_file_id != new_file_id) { - LOG_STATUS(td->file_manager_->merge(new_file_id, old_file_id)); - old_file_id = new_file_id; - return true; - } - } - break; - } - case MessageContentType::Sticker: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->stickers_manager_->merge_stickers(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; - } - break; - } - case MessageContentType::Video: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->videos_manager_->merge_videos(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; - } - break; - } - case MessageContentType::VideoNote: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->video_notes_manager_->merge_video_notes(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; + case MessageContentType::GiftCode: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->creator_dialog_id != rhs->creator_dialog_id || lhs->months != rhs->months || + lhs->via_giveaway != rhs->via_giveaway || lhs->is_unclaimed != rhs->is_unclaimed || lhs->code != rhs->code) { + need_update = true; } break; } - case MessageContentType::VoiceNote: { - auto content = static_cast(message_content); - if (new_file_id != content->file_id) { - td->voice_notes_manager_->merge_voice_notes(new_file_id, content->file_id); - content->file_id = new_file_id; - return true; + case MessageContentType::Giveaway: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->giveaway_parameters != rhs->giveaway_parameters || lhs->quantity != rhs->quantity || + lhs->months != rhs->months) { + need_update = true; } break; } - case MessageContentType::Contact: - case MessageContentType::Game: - case MessageContentType::Invoice: - case MessageContentType::LiveLocation: - case MessageContentType::Location: - case MessageContentType::Story: - case MessageContentType::Text: - case MessageContentType::Venue: - case MessageContentType::ChatCreate: - case MessageContentType::ChatChangeTitle: - case MessageContentType::ChatChangePhoto: - case MessageContentType::ChatDeletePhoto: - case MessageContentType::ChatDeleteHistory: - case MessageContentType::ChatAddUsers: - case MessageContentType::ChatJoinedByLink: - case MessageContentType::ChatDeleteUser: - case MessageContentType::ChatMigrateTo: - case MessageContentType::ChannelCreate: - case MessageContentType::ChannelMigrateFrom: - case MessageContentType::PinMessage: - case MessageContentType::GameScore: - case MessageContentType::ScreenshotTaken: - case MessageContentType::ChatSetTtl: - case MessageContentType::Unsupported: - case MessageContentType::Call: - case MessageContentType::PaymentSuccessful: - case MessageContentType::ContactRegistered: - case MessageContentType::ExpiredPhoto: - case MessageContentType::ExpiredVideo: - case MessageContentType::CustomServiceAction: - case MessageContentType::WebsiteConnected: - case MessageContentType::PassportDataSent: - case MessageContentType::PassportDataReceived: - case MessageContentType::Poll: - case MessageContentType::Dice: - case MessageContentType::ProximityAlertTriggered: - case MessageContentType::GroupCall: - case MessageContentType::InviteToGroupCall: - case MessageContentType::ChatSetTheme: - case MessageContentType::WebViewDataSent: - case MessageContentType::WebViewDataReceived: - case MessageContentType::GiftPremium: - case MessageContentType::TopicCreate: - case MessageContentType::TopicEdit: - case MessageContentType::SuggestProfilePhoto: - case MessageContentType::WriteAccessAllowed: - case MessageContentType::RequestedDialog: - case MessageContentType::WebViewWriteAccessAllowed: - case MessageContentType::SetBackground: - case MessageContentType::WriteAccessAllowedByRequest: - LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type; + case MessageContentType::GiveawayLaunch: break; default: UNREACHABLE(); break; } - return false; } static bool can_be_animated_emoji(const FormattedText &text) { @@ -4401,6 +4827,12 @@ void register_message_content(Td *td, const MessageContent *content, MessageFull case MessageContentType::GiftPremium: return td->stickers_manager_->register_premium_gift(static_cast(content)->months, message_full_id, source); + case MessageContentType::GiftCode: + return td->stickers_manager_->register_premium_gift(static_cast(content)->months, + message_full_id, source); + case MessageContentType::Giveaway: + return td->stickers_manager_->register_premium_gift(static_cast(content)->months, + message_full_id, source); case MessageContentType::SuggestProfilePhoto: return td->contacts_manager_->register_suggested_profile_photo( static_cast(content)->photo); @@ -4460,6 +4892,18 @@ void reregister_message_content(Td *td, const MessageContent *old_content, const return; } break; + case MessageContentType::GiftCode: + if (static_cast(old_content)->months == + static_cast(new_content)->months) { + return; + } + break; + case MessageContentType::Giveaway: + if (static_cast(old_content)->months == + static_cast(new_content)->months) { + return; + } + break; case MessageContentType::Story: if (static_cast(old_content)->story_full_id == static_cast(new_content)->story_full_id) { @@ -4503,6 +4947,12 @@ void unregister_message_content(Td *td, const MessageContent *content, MessageFu case MessageContentType::GiftPremium: return td->stickers_manager_->unregister_premium_gift(static_cast(content)->months, message_full_id, source); + case MessageContentType::GiftCode: + return td->stickers_manager_->unregister_premium_gift(static_cast(content)->months, + message_full_id, source); + case MessageContentType::Giveaway: + return td->stickers_manager_->unregister_premium_gift(static_cast(content)->months, + message_full_id, source); case MessageContentType::Story: return td->story_manager_->unregister_story(static_cast(content)->story_full_id, message_full_id, source); @@ -4511,6 +4961,24 @@ void unregister_message_content(Td *td, const MessageContent *content, MessageFu } } +void register_reply_message_content(Td *td, const MessageContent *content) { + switch (content->get_type()) { + case MessageContentType::Poll: + return td->poll_manager_->register_reply_poll(static_cast(content)->poll_id); + default: + return; + } +} + +void unregister_reply_message_content(Td *td, const MessageContent *content) { + switch (content->get_type()) { + case MessageContentType::Poll: + return td->poll_manager_->unregister_reply_poll(static_cast(content)->poll_id); + default: + return; + } +} + template static tl_object_ptr secret_to_telegram(FromT &from); @@ -4854,7 +5322,8 @@ unique_ptr get_secret_message_content( } auto url = r_http_url.ok().get_url(); - auto result = make_unique(FormattedText{std::move(message_text), std::move(entities)}, WebPageId()); + auto result = td::make_unique(FormattedText{std::move(message_text), std::move(entities)}, + WebPageId(), false, false, false, url); td->web_pages_manager_->get_web_page_by_url( url, PromiseCreator::lambda([&web_page_id = result->web_page_id, promise = load_data_multipromise.get_promise()]( @@ -4880,7 +5349,8 @@ unique_ptr get_secret_message_content( is_media_empty = true; } if (is_media_empty) { - return create_text_message_content(std::move(message_text), std::move(entities), WebPageId()); + return create_text_message_content(std::move(message_text), std::move(entities), WebPageId(), false, false, false, + string()); } switch (constructor_id) { case secret_api::decryptedMessageMediaPhoto::ID: { @@ -4940,7 +5410,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, if (disable_web_page_preview != nullptr) { *disable_web_page_preview = true; } - return make_unique(std::move(message), WebPageId()); + return td::make_unique(std::move(message), WebPageId(), false, false, false, string()); case telegram_api::messageMediaPhoto::ID: { auto media = move_tl_object_as(media_ptr); if (media->photo_ == nullptr) { @@ -5063,8 +5533,16 @@ unique_ptr get_message_content(Td *td, FormattedText message, if (disable_web_page_preview != nullptr) { *disable_web_page_preview = (media->webpage_ == nullptr); } + string web_page_url; + if (media->manual_) { + web_page_url = WebPagesManager::get_web_page_url(media->webpage_); + if (web_page_url.empty()) { + LOG(ERROR) << "Have no URL in manual link preview"; + } + } auto web_page_id = td->web_pages_manager_->on_get_web_page(std::move(media->webpage_), owner_dialog_id); - return make_unique(std::move(message), web_page_id); + return td::make_unique(std::move(message), web_page_id, media->force_small_media_, + media->force_large_media_, media->safe_, std::move(web_page_url)); } case telegram_api::messageMediaPoll::ID: { auto media = move_tl_object_as(media_ptr); @@ -5093,6 +5571,27 @@ unique_ptr get_message_content(Td *td, FormattedText message, td->messages_manager_->force_create_dialog(dialog_id, "messageMediaStory"); return make_unique(story_full_id, media->via_mention_); } + case telegram_api::messageMediaGiveaway::ID: { + auto media = move_tl_object_as(media_ptr); + vector channel_ids; + for (auto channel : media->channels_) { + ChannelId channel_id(channel); + if (channel_id.is_valid()) { + channel_ids.push_back(channel_id); + td->messages_manager_->force_create_dialog(DialogId(channel_id), "messageMediaGiveaway", true); + } + } + if (channel_ids.empty() || media->quantity_ <= 0 || media->months_ <= 0 || media->until_date_ < 0) { + LOG(ERROR) << "Receive " << to_string(media); + break; + } + auto boosted_channel_id = channel_ids[0]; + channel_ids.erase(channel_ids.begin()); + return td::make_unique( + GiveawayParameters{boosted_channel_id, std::move(channel_ids), media->only_new_subscribers_, + media->until_date_, std::move(media->countries_iso2_)}, + media->quantity_, media->months_); + } case telegram_api::messageMediaUnsupported::ID: return make_unique(); default: @@ -5103,7 +5602,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, if (disable_web_page_preview != nullptr) { *disable_web_page_preview = true; } - return make_unique(std::move(message), WebPageId()); + return td::make_unique(std::move(message), WebPageId(), false, false, false, string()); } unique_ptr dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content, @@ -5180,6 +5679,11 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } case MessageContentType::Game: return make_unique(*static_cast(content)); + case MessageContentType::Giveaway: + if (type != MessageContentDupType::Forward) { + return nullptr; + } + return make_unique(*static_cast(content)); case MessageContentType::Invoice: if (type == MessageContentDupType::Copy) { return nullptr; @@ -5284,7 +5788,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const case MessageContentType::Story: return make_unique(static_cast(content)->story_full_id, false); case MessageContentType::Text: { - auto result = make_unique(*static_cast(content)); + auto result = td::make_unique(*static_cast(content)); if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) { remove_unallowed_entities(td, result->text, dialog_id); } @@ -5367,6 +5871,8 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::GiveawayLaunch: return nullptr; default: UNREACHABLE(); @@ -5376,8 +5882,8 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } unique_ptr get_action_message_content(Td *td, tl_object_ptr &&action_ptr, - DialogId owner_dialog_id, DialogId reply_in_dialog_id, - MessageId reply_to_message_id) { + DialogId owner_dialog_id, + const RepliedMessageInfo &replied_message_info) { CHECK(action_ptr != nullptr); switch (action_ptr->get_id()) { @@ -5472,12 +5978,7 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(std::move(action->title_), chat_id); } case telegram_api::messageActionPinMessage::ID: { - if (reply_in_dialog_id.is_valid() && reply_in_dialog_id != owner_dialog_id) { - LOG(ERROR) << "Receive pinned message with " << reply_to_message_id << " in " << owner_dialog_id - << " in another " << reply_in_dialog_id; - reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); - } + auto reply_to_message_id = replied_message_info.get_same_chat_reply_to_message_id(); if (!reply_to_message_id.is_valid()) { // possible in basic groups LOG(INFO) << "Receive pinned message with " << reply_to_message_id << " in " << owner_dialog_id; @@ -5486,12 +5987,7 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(reply_to_message_id); } case telegram_api::messageActionGameScore::ID: { - if (reply_in_dialog_id.is_valid() && reply_in_dialog_id != owner_dialog_id) { - LOG(ERROR) << "Receive game score with " << reply_to_message_id << " in " << owner_dialog_id << " in another " - << reply_in_dialog_id; - reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); - } + auto reply_to_message_id = replied_message_info.get_same_chat_reply_to_message_id(); if (!reply_to_message_id.is_valid()) { // possible in basic groups LOG(INFO) << "Receive game score with " << reply_to_message_id << " in " << owner_dialog_id; @@ -5516,20 +6012,20 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(action_ptr); - if (!reply_to_message_id.is_valid()) { - if (reply_to_message_id != MessageId()) { - LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id << " in " << owner_dialog_id; + auto message_full_id = replied_message_info.get_reply_message_full_id(DialogId()); + if (!message_full_id.get_message_id().is_valid()) { + if (message_full_id.get_message_id() != MessageId()) { + LOG(ERROR) << "Receive successful payment message with " << message_full_id << " in " << owner_dialog_id; } - reply_in_dialog_id = DialogId(); - reply_to_message_id = MessageId(); + message_full_id = {}; } if (action->total_amount_ <= 0 || !check_currency_amount(action->total_amount_)) { LOG(ERROR) << "Receive invalid total amount " << action->total_amount_; action->total_amount_ = 0; } return td::make_unique( - reply_in_dialog_id, reply_to_message_id, std::move(action->currency_), action->total_amount_, - std::move(action->invoice_slug_), action->recurring_used_, action->recurring_init_); + message_full_id.get_dialog_id(), message_full_id.get_message_id(), std::move(action->currency_), + action->total_amount_, std::move(action->invoice_slug_), action->recurring_used_, action->recurring_init_); } case telegram_api::messageActionPaymentSentMe::ID: { if (!td->auth_manager_->is_bot()) { @@ -5729,30 +6225,47 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(MessageId(), std::move(background_info)); } case telegram_api::messageActionSetSameChatWallPaper::ID: { - if (reply_in_dialog_id.is_valid() && reply_in_dialog_id != owner_dialog_id) { - LOG(ERROR) << "Receive old background message with " << reply_to_message_id << " in " << owner_dialog_id - << " in another " << reply_in_dialog_id; - reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); - } auto action = move_tl_object_as(action_ptr); BackgroundInfo background_info(td, std::move(action->wallpaper_)); if (!background_info.is_valid()) { break; } + auto reply_to_message_id = replied_message_info.get_same_chat_reply_to_message_id(); + if (!reply_to_message_id.is_valid()) { + reply_to_message_id = MessageId(); + } return make_unique(reply_to_message_id, std::move(background_info)); } + case telegram_api::messageActionGiveawayLaunch::ID: + return make_unique(); + case telegram_api::messageActionGiftCode::ID: { + auto action = move_tl_object_as(action_ptr); + DialogId dialog_id; + if (action->boost_peer_ != nullptr) { + dialog_id = DialogId(action->boost_peer_); + if (!dialog_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << oneline(to_string(action)); + break; + } + } + if (dialog_id.get_type() != DialogType::User) { + td->messages_manager_->force_create_dialog(dialog_id, "messageActionGiftCode", true); + } + return td::make_unique(dialog_id, action->months_, action->via_giveaway_, action->unclaimed_, + std::move(action->slug_)); + } default: UNREACHABLE(); } // explicit empty or wrong action - return make_unique(FormattedText(), WebPageId()); + return td::make_unique(FormattedText(), WebPageId(), false, false, false, string()); } tl_object_ptr get_message_content_object(const MessageContent *content, Td *td, DialogId dialog_id, int32 message_date, bool is_content_secret, bool skip_bot_commands, - int32 max_media_timestamp) { + int32 max_media_timestamp, bool invert_media, + bool disable_web_page_preview) { CHECK(content != nullptr); switch (content->get_type()) { case MessageContentType::Animation: { @@ -5788,7 +6301,7 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::LiveLocation: { const auto *m = static_cast(content); - auto passed = max(G()->unix_time_cached() - message_date, 0); + auto passed = max(G()->unix_time() - message_date, 0); auto expires_in = max(0, m->period - passed); auto heading = expires_in == 0 ? 0 : m->heading; auto proximity_alert_radius = expires_in == 0 ? 0 : m->proximity_alert_radius; @@ -5828,9 +6341,26 @@ tl_object_ptr get_message_content_object(const MessageCo return td_api::make_object(std::move(animated_emoji), m->text.text); } } + auto web_page = td->web_pages_manager_->get_web_page_object( + m->web_page_id, m->force_small_media, m->force_large_media, m->skip_web_page_confirmation, invert_media); + if (web_page != nullptr && !web_page->skip_confirmation_ && is_visible_url(m->text, web_page->url_)) { + web_page->skip_confirmation_ = true; + } + if (web_page == nullptr && get_first_url(m->text).empty()) { + disable_web_page_preview = false; + } else if (disable_web_page_preview && web_page != nullptr) { + LOG(ERROR) << "Have " << m->web_page_id << " in a message with link preview disabled"; + web_page = nullptr; + } + td_api::object_ptr link_preview_options; + if (disable_web_page_preview || !m->web_page_url.empty() || m->force_small_media || m->force_large_media || + invert_media) { + link_preview_options = td_api::make_object( + disable_web_page_preview, m->web_page_url, m->force_small_media, m->force_large_media, invert_media); + } return make_tl_object( - get_formatted_text_object(m->text, skip_bot_commands, max_media_timestamp), - td->web_pages_manager_->get_web_page_object(m->web_page_id)); + get_formatted_text_object(m->text, skip_bot_commands, max_media_timestamp), std::move(web_page), + std::move(link_preview_options)); } case MessageContentType::Unsupported: return make_tl_object(); @@ -5957,7 +6487,8 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::WebsiteConnected: { const auto *m = static_cast(content); - return make_tl_object(m->domain_name); + return td_api::make_object( + td_api::make_object(m->domain_name)); } case MessageContentType::PassportDataSent: { const auto *m = static_cast(content); @@ -6052,7 +6583,8 @@ tl_object_ptr get_message_content_object(const MessageCo return make_tl_object(std::move(photo)); } case MessageContentType::WriteAccessAllowed: - return make_tl_object(nullptr, false); + return td_api::make_object( + td_api::make_object()); case MessageContentType::RequestedDialog: { const auto *m = static_cast(content); if (m->dialog_id.get_type() == DialogType::User) { @@ -6074,7 +6606,8 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::WebViewWriteAccessAllowed: { const auto *m = static_cast(content); - return td_api::make_object(m->web_app.get_web_app_object(td), false); + return td_api::make_object( + td_api::make_object(m->web_app.get_web_app_object(td))); } case MessageContentType::SetBackground: { const auto *m = static_cast(content); @@ -6088,7 +6621,22 @@ tl_object_ptr get_message_content_object(const MessageCo m->story_full_id.get_story_id().get(), m->via_mention); } case MessageContentType::WriteAccessAllowedByRequest: - return make_tl_object(nullptr, true); + return td_api::make_object( + td_api::make_object()); + case MessageContentType::GiftCode: { + const auto *m = static_cast(content); + return td_api::make_object( + get_message_sender_object(td, m->creator_dialog_id, "messagePremiumGiftCode"), m->via_giveaway, + m->is_unclaimed, m->months, td->stickers_manager_->get_premium_gift_sticker_object(m->months), m->code); + } + case MessageContentType::Giveaway: { + const auto *m = static_cast(content); + return td_api::make_object( + m->giveaway_parameters.get_premium_giveaway_parameters_object(td), m->quantity, m->months, + td->stickers_manager_->get_premium_gift_sticker_object(m->months)); + } + case MessageContentType::GiveawayLaunch: + return td_api::make_object(); default: UNREACHABLE(); return nullptr; @@ -6334,6 +6882,7 @@ FileId get_message_content_thumbnail_file_id(const MessageContent *content, cons } vector get_message_content_file_ids(const MessageContent *content, const Td *td) { + CHECK(content != nullptr); switch (content->get_type()) { case MessageContentType::Photo: return photo_get_file_ids(static_cast(content)->photo); @@ -6515,6 +7064,9 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return string(); default: UNREACHABLE(); @@ -6649,6 +7201,7 @@ void update_failed_to_send_message_content(Td *td, unique_ptr &c } void add_message_content_dependencies(Dependencies &dependencies, const MessageContent *message_content, bool is_bot) { + CHECK(message_content != nullptr); switch (message_content->get_type()) { case MessageContentType::Text: { const auto *content = static_cast(message_content); @@ -6822,6 +7375,18 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC } case MessageContentType::WriteAccessAllowedByRequest: break; + case MessageContentType::GiftCode: { + const auto *content = static_cast(message_content); + dependencies.add_message_sender_dependencies(content->creator_dialog_id); + break; + } + case MessageContentType::Giveaway: { + const auto *content = static_cast(message_content); + content->giveaway_parameters.add_dependencies(dependencies); + break; + } + case MessageContentType::GiveawayLaunch: + break; default: UNREACHABLE(); break; diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.h b/lib/tgchat/ext/td/td/telegram/MessageContent.h index a3812e9e..d04248e3 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.h +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.h @@ -7,6 +7,7 @@ #pragma once #include "td/telegram/BackgroundInfo.h" +#include "td/telegram/ChannelId.h" #include "td/telegram/DialogId.h" #include "td/telegram/EncryptedFile.h" #include "td/telegram/files/FileId.h" @@ -43,10 +44,11 @@ class DialogAction; class Game; class MultiPromiseActor; struct Photo; +class RepliedMessageInfo; class Td; class Venue; -// Do not forget to update merge_message_contents when one of the inheritors of this class changes +// Do not forget to update merge_message_contents and compare_message_contents when one of the inheritors of this class changes class MessageContent { public: MessageContent() = default; @@ -62,15 +64,17 @@ class MessageContent { struct InputMessageContent { unique_ptr content; bool disable_web_page_preview = false; + bool invert_media = false; bool clear_draft = false; int32 ttl = 0; UserId via_bot_user_id; string emoji; - InputMessageContent(unique_ptr &&content, bool disable_web_page_preview, bool clear_draft, int32 ttl, - UserId via_bot_user_id, string emoji) + InputMessageContent(unique_ptr &&content, bool disable_web_page_preview, bool invert_media, + bool clear_draft, int32 ttl, UserId via_bot_user_id, string emoji) : content(std::move(content)) , disable_web_page_preview(disable_web_page_preview) + , invert_media(invert_media) , clear_draft(clear_draft) , ttl(ttl) , via_bot_user_id(via_bot_user_id) @@ -82,6 +86,7 @@ struct InlineMessageContent { unique_ptr message_content; unique_ptr message_reply_markup; bool disable_web_page_preview; + bool invert_media; }; void store_message_content(const MessageContent *content, LogEventStorerCalcLength &storer); @@ -95,7 +100,9 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, int32 allowed_media_content_id, Photo *photo, Game *game); unique_ptr create_text_message_content(string text, vector entities, - WebPageId web_page_id); + WebPageId web_page_id, bool force_small_media, + bool force_large_media, bool skip_confitmation, + string &&web_page_url); unique_ptr create_contact_registered_message_content(); @@ -124,6 +131,9 @@ tl_object_ptr get_input_media(const MessageContent *co tl_object_ptr get_fake_input_media(Td *td, tl_object_ptr input_file, FileId file_id); +tl_object_ptr get_message_content_input_media_web_page(const Td *td, + const MessageContent *content); + void delete_message_content_thumbnail(MessageContent *content, Td *td); Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward, const Td *td); @@ -148,6 +158,8 @@ std::pair get_message_content_group_call_info(const Mess vector get_message_content_min_user_ids(const Td *td, const MessageContent *message_content); +vector get_message_content_min_channel_ids(const Td *td, const MessageContent *message_content); + vector get_message_content_added_user_ids(const MessageContent *content); UserId get_message_content_deleted_user_id(const MessageContent *content); @@ -182,6 +194,9 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo bool merge_message_content_file_id(Td *td, MessageContent *message_content, FileId new_file_id); +void compare_message_contents(Td *td, const MessageContent *lhs_content, const MessageContent *rhs_content, + bool &is_content_changed, bool &need_update); + void register_message_content(Td *td, const MessageContent *content, MessageFullId message_full_id, const char *source); void reregister_message_content(Td *td, const MessageContent *old_content, const MessageContent *new_content, @@ -190,6 +205,10 @@ void reregister_message_content(Td *td, const MessageContent *old_content, const void unregister_message_content(Td *td, const MessageContent *content, MessageFullId message_full_id, const char *source); +void register_reply_message_content(Td *td, const MessageContent *content); + +void unregister_reply_message_content(Td *td, const MessageContent *content); + unique_ptr get_secret_message_content( Td *td, string message_text, unique_ptr file, tl_object_ptr &&media_ptr, @@ -201,19 +220,26 @@ unique_ptr get_message_content(Td *td, FormattedText message_tex DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id, int32 *ttl, bool *disable_web_page_preview, const char *source); -enum class MessageContentDupType : int32 { Send, SendViaBot, Forward, Copy, ServerCopy }; +enum class MessageContentDupType : int32 { + Send, // normal message sending + SendViaBot, // message sending via bot + Forward, // server-side message forward + Copy, // local message copy + ServerCopy // server-side message copy +}; unique_ptr dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content, MessageContentDupType type, MessageCopyOptions &©_options); unique_ptr get_action_message_content(Td *td, tl_object_ptr &&action_ptr, - DialogId owner_dialog_id, DialogId reply_in_dialog_id, - MessageId reply_to_message_id); + DialogId owner_dialog_id, + const RepliedMessageInfo &replied_message_info); tl_object_ptr get_message_content_object(const MessageContent *content, Td *td, DialogId dialog_id, int32 message_date, bool is_content_secret, bool skip_bot_commands, - int32 max_media_timestamp); + int32 max_media_timestamp, bool invert_media, + bool disable_web_page_preview); FormattedText *get_message_content_text_mutable(MessageContent *content); diff --git a/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp b/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp index 025ea06b..cc057f64 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp @@ -128,6 +128,12 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont return string_builder << "Story"; case MessageContentType::WriteAccessAllowedByRequest: return string_builder << "WriteAccessAllowedByRequest"; + case MessageContentType::GiftCode: + return string_builder << "GiftCode"; + case MessageContentType::Giveaway: + return string_builder << "Giveaway"; + case MessageContentType::GiveawayLaunch: + return string_builder << "GiveawayLaunch"; default: return string_builder << "Invalid type " << static_cast(content_type); } @@ -194,6 +200,9 @@ bool is_allowed_media_group_content(MessageContentType content_type) { case MessageContentType::SetBackground: case MessageContentType::Story: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return false; default: UNREACHABLE(); @@ -269,6 +278,9 @@ bool is_secret_message_content(int32 ttl, MessageContentType content_type) { case MessageContentType::SetBackground: case MessageContentType::Story: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return false; default: UNREACHABLE(); @@ -281,13 +293,17 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::Animation: case MessageContentType::Audio: case MessageContentType::Contact: + case MessageContentType::Dice: case MessageContentType::Document: case MessageContentType::Game: + case MessageContentType::Giveaway: case MessageContentType::Invoice: case MessageContentType::LiveLocation: case MessageContentType::Location: case MessageContentType::Photo: + case MessageContentType::Poll: case MessageContentType::Sticker: + case MessageContentType::Story: case MessageContentType::Text: case MessageContentType::Unsupported: case MessageContentType::Venue: @@ -296,9 +312,6 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::VoiceNote: case MessageContentType::ExpiredPhoto: case MessageContentType::ExpiredVideo: - case MessageContentType::Poll: - case MessageContentType::Dice: - case MessageContentType::Story: return false; case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: @@ -337,6 +350,8 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::GiveawayLaunch: return true; default: UNREACHABLE(); @@ -405,6 +420,9 @@ bool can_have_message_content_caption(MessageContentType content_type) { case MessageContentType::SetBackground: case MessageContentType::Story: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::Giveaway: + case MessageContentType::GiveawayLaunch: return false; default: UNREACHABLE(); diff --git a/lib/tgchat/ext/td/td/telegram/MessageContentType.h b/lib/tgchat/ext/td/td/telegram/MessageContentType.h index ff962e4d..680a6c4f 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContentType.h +++ b/lib/tgchat/ext/td/td/telegram/MessageContentType.h @@ -71,7 +71,10 @@ enum class MessageContentType : int32 { WebViewWriteAccessAllowed, SetBackground, Story, - WriteAccessAllowedByRequest + WriteAccessAllowedByRequest, + GiftCode, + Giveaway, + GiveawayLaunch }; // increase MessageUnsupported::CURRENT_VERSION each time a new message content type is added diff --git a/lib/tgchat/ext/td/td/telegram/MessageDb.cpp b/lib/tgchat/ext/td/td/telegram/MessageDb.cpp index 13c88b93..643d3f26 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageDb.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageDb.cpp @@ -49,7 +49,7 @@ Status init_message_db(SqliteDb &db, int32 version) { TRY_RESULT(has_table, db.has_table("messages")); if (!has_table) { version = 0; - } else if (version < static_cast(DbVersion::CreateDialogDb) || version > current_db_version()) { + } else if (version > current_db_version()) { TRY_STATUS(drop_message_db(db, version)); version = 0; } diff --git a/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp b/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp index 542d3f2e..b1a9090e 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp @@ -151,7 +151,7 @@ tl_object_ptr MessageEntity::get_text_entity_type_object case MessageEntity::Type::Strikethrough: return make_tl_object(); case MessageEntity::Type::BlockQuote: - return nullptr; + return make_tl_object(); case MessageEntity::Type::Code: return make_tl_object(); case MessageEntity::Type::Pre: @@ -1015,133 +1015,125 @@ bool is_email_address(Slice str) { static bool is_common_tld(Slice str) { static const FlatHashSet tlds( - {"aaa", "aarp", "abarth", "abb", "abbott", "abbvie", "abc", "able", "abogado", "abudhabi", "ac", "academy", - "accenture", "accountant", "accountants", "aco", "active", "actor", "ad", "adac", "ads", "adult", "ae", "aeg", - "aero", "aetna", "af", "afamilycompany", "afl", "africa", "ag", "agakhan", "agency", "ai", "aig", "aigo", - "airbus", "airforce", "airtel", "akdn", "al", "alfaromeo", "alibaba", "alipay", "allfinanz", "allstate", "ally", - "alsace", "alstom", "am", "americanexpress", "americanfamily", "amex", "amfam", "amica", "amsterdam", - "analytics", "android", "anquan", "anz", "ao", "aol", "apartments", "app", "apple", "aq", "aquarelle", "ar", - "arab", "aramco", "archi", "army", "arpa", "art", "arte", "as", "asda", "asia", "associates", "at", "athleta", - "attorney", "au", "auction", "audi", "audible", "audio", "auspost", "author", "auto", "autos", "avianca", "aw", - "aws", "ax", "axa", "az", "azure", "ba", "baby", "baidu", "banamex", "bananarepublic", "band", "bank", "bar", - "barcelona", "barclaycard", "barclays", "barefoot", "bargains", "baseball", "basketball", "bauhaus", "bayern", - "bb", "bbc", "bbt", "bbva", "bcg", "bcn", "bd", "be", "beats", "beauty", "beer", "bentley", "berlin", "best", - "bestbuy", "bet", "bf", "bg", "bh", "bharti", "bi", "bible", "bid", "bike", "bing", "bingo", "bio", "biz", "bj", - "black", "blackfriday", "blanco", "blockbuster", "blog", "bloomberg", "blue", "bm", "bms", "bmw", "bn", "bnl", - "bnpparibas", "bo", "boats", "boehringer", "bofa", "bom", "bond", "boo", "book", "booking", "boots", "bosch", - "bostik", "boston", "bot", "boutique", "box", "br", "bradesco", "bridgestone", "broadway", "broker", "brother", - "brussels", "bs", "bt", "budapest", "bugatti", "build", "builders", "business", "buy", "buzz", "bv", "bw", "by", - "bz", "bzh", "ca", "cab", "cafe", "cal", "call", "calvinklein", "cam", "camera", "camp", "cancerresearch", - "canon", "capetown", "capital", "capitalone", "car", "caravan", "cards", "care", "career", "careers", "cars", - "cartier", "casa", "case", "caseih", "cash", "casino", "cat", "catering", "catholic", "cba", "cbn", "cbre", - "cbs", "cc", "cd", "ceb", "center", "ceo", "cern", "cf", "cfa", "cfd", "cg", "ch", "chanel", "channel", "chase", - "chat", "cheap", "chintai", "christmas", "chrome", "chrysler", "church", "ci", "cipriani", "circle", "cisco", - "citadel", "citi", "citic", "city", "cityeats", "ck", "cl", "claims", "cleaning", "click", "clinic", "clinique", - "clothing", "cloud", "club", "clubmed", "cm", "cn", "co", "coach", "codes", "coffee", "college", "cologne", - "com", "comcast", "commbank", "community", "company", "compare", "computer", "comsec", "condos", "construction", - "consulting", "contact", "contractors", "cooking", "cookingchannel", "cool", "coop", "corsica", "country", - "coupon", "coupons", "courses", "cr", "credit", "creditcard", "creditunion", "cricket", "crown", "crs", "cruise", - "cruises", "csc", "cu", "cuisinella", "cv", "cw", "cx", "cy", "cymru", "cyou", "cz", "dabur", "dad", "dance", - "data", "date", "dating", "datsun", "day", "dclk", "dds", "de", "deal", "dealer", "deals", "degree", "delivery", - "dell", "deloitte", "delta", "democrat", "dental", "dentist", "desi", "design", "dev", "dhl", "diamonds", "diet", - "digital", "direct", "directory", "discount", "discover", "dish", "diy", "dj", "dk", "dm", "dnp", "do", "docs", - "doctor", "dodge", "dog", "doha", "domains", "dot", "download", "drive", "dtv", "dubai", "duck", "dunlop", - "duns", "dupont", "durban", "dvag", "dvr", "dz", "earth", "eat", "ec", "eco", "edeka", "edu", "education", "ee", - "eg", "email", "emerck", "energy", "engineer", "engineering", "enterprises", "epost", "epson", "equipment", "er", - "ericsson", "erni", "es", "esq", "estate", "esurance", "et", "etisalat", "eu", "eurovision", "eus", "events", - "everbank", "exchange", "expert", "exposed", "express", "extraspace", "fage", "fail", "fairwinds", "faith", - "family", "fan", "fans", "farm", "farmers", "fashion", "fast", "fedex", "feedback", "ferrari", "ferrero", "fi", - "fiat", "fidelity", "fido", "film", "final", "finance", "financial", "fire", "firestone", "firmdale", "fish", - "fishing", "fit", "fitness", "fj", "fk", "flickr", "flights", "flir", "florist", "flowers", "fly", "fm", "fo", - "foo", "food", "foodnetwork", "football", "ford", "forex", "forsale", "forum", "foundation", "fox", "fr", "free", - "fresenius", "frl", "frogans", "frontdoor", "frontier", "ftr", "fujitsu", "fujixerox", "fun", "fund", - "furniture", "futbol", "fyi", "ga", "gal", "gallery", "gallo", "gallup", "game", "games", "gap", "garden", "gb", + {"aaa", "aarp", "abb", "abbott", "abbvie", "abc", "able", "abogado", "abudhabi", "ac", "academy", "accenture", + "accountant", "accountants", "aco", "actor", "ad", "ads", "adult", "ae", "aeg", "aero", "aetna", "af", "afl", + "africa", "ag", "agakhan", "agency", "ai", "aig", "airbus", "airforce", "airtel", "akdn", "al", "alibaba", + "alipay", "allfinanz", "allstate", "ally", "alsace", "alstom", "am", "amazon", "americanexpress", + "americanfamily", "amex", "amfam", "amica", "amsterdam", "analytics", "android", "anquan", "anz", "ao", "aol", + "apartments", "app", "apple", "aq", "aquarelle", "ar", "arab", "aramco", "archi", "army", "arpa", "art", "arte", + "as", "asda", "asia", "associates", "at", "athleta", "attorney", "au", "auction", "audi", "audible", "audio", + "auspost", "author", "auto", "autos", "avianca", "aw", "aws", "ax", "axa", "az", "azure", "ba", "baby", "baidu", + "banamex", "bananarepublic", "band", "bank", "bar", "barcelona", "barclaycard", "barclays", "barefoot", + "bargains", "baseball", "basketball", "bauhaus", "bayern", "bb", "bbc", "bbt", "bbva", "bcg", "bcn", "bd", "be", + "beats", "beauty", "beer", "bentley", "berlin", "best", "bestbuy", "bet", "bf", "bg", "bh", "bharti", "bi", + "bible", "bid", "bike", "bing", "bingo", "bio", "biz", "bj", "black", "blackfriday", "blockbuster", "blog", + "bloomberg", "blue", "bm", "bms", "bmw", "bn", "bnpparibas", "bo", "boats", "boehringer", "bofa", "bom", "bond", + "boo", "book", "booking", "bosch", "bostik", "boston", "bot", "boutique", "box", "br", "bradesco", "bridgestone", + "broadway", "broker", "brother", "brussels", "bs", "bt", "build", "builders", "business", "buy", "buzz", "bv", + "bw", "by", "bz", "bzh", "ca", "cab", "cafe", "cal", "call", "calvinklein", "cam", "camera", "camp", "canon", + "capetown", "capital", "capitalone", "car", "caravan", "cards", "care", "career", "careers", "cars", "casa", + "case", "cash", "casino", "cat", "catering", "catholic", "cba", "cbn", "cbre", "cbs", "cc", "cd", "center", + "ceo", "cern", "cf", "cfa", "cfd", "cg", "ch", "chanel", "channel", "charity", "chase", "chat", "cheap", + "chintai", "christmas", "chrome", "church", "ci", "cipriani", "circle", "cisco", "citadel", "citi", "citic", + "city", "cityeats", "ck", "cl", "claims", "cleaning", "click", "clinic", "clinique", "clothing", "cloud", "club", + "clubmed", "cm", "cn", "co", "coach", "codes", "coffee", "college", "cologne", "com", "comcast", "commbank", + "community", "company", "compare", "computer", "comsec", "condos", "construction", "consulting", "contact", + "contractors", "cooking", "cool", "coop", "corsica", "country", "coupon", "coupons", "courses", "cpa", "cr", + "credit", "creditcard", "creditunion", "cricket", "crown", "crs", "cruise", "cruises", "cu", "cuisinella", "cv", + "cw", "cx", "cy", "cymru", "cyou", "cz", "dabur", "dad", "dance", "data", "date", "dating", "datsun", "day", + "dclk", "dds", "de", "deal", "dealer", "deals", "degree", "delivery", "dell", "deloitte", "delta", "democrat", + "dental", "dentist", "desi", "design", "dev", "dhl", "diamonds", "diet", "digital", "direct", "directory", + "discount", "discover", "dish", "diy", "dj", "dk", "dm", "dnp", "do", "docs", "doctor", "dog", "domains", "dot", + "download", "drive", "dtv", "dubai", "dunlop", "dupont", "durban", "dvag", "dvr", "dz", "earth", "eat", "ec", + "eco", "edeka", "edu", "education", "ee", "eg", "email", "emerck", "energy", "engineer", "engineering", + "enterprises", "epson", "equipment", "er", "ericsson", "erni", "es", "esq", "estate", "et", "etisalat", "eu", + "eurovision", "eus", "events", "exchange", "expert", "exposed", "express", "extraspace", "fage", "fail", + "fairwinds", "faith", "family", "fan", "fans", "farm", "farmers", "fashion", "fast", "fedex", "feedback", + "ferrari", "ferrero", "fi", "fidelity", "fido", "film", "final", "finance", "financial", "fire", "firestone", + "firmdale", "fish", "fishing", "fit", "fitness", "fj", "fk", "flickr", "flights", "flir", "florist", "flowers", + "fly", "fm", "fo", "foo", "food", "football", "ford", "forex", "forsale", "forum", "foundation", "fox", "fr", + "free", "fresenius", "frl", "frogans", "frontdoor", "frontier", "ftr", "fujitsu", "fun", "fund", "furniture", + "futbol", "fyi", "ga", "gal", "gallery", "gallo", "gallup", "game", "games", "gap", "garden", "gay", "gb", "gbiz", "gd", "gdn", "ge", "gea", "gent", "genting", "george", "gf", "gg", "ggee", "gh", "gi", "gift", "gifts", - "gives", "giving", "gl", "glade", "glass", "gle", "global", "globo", "gm", "gmail", "gmbh", "gmo", "gmx", "gn", - "godaddy", "gold", "goldpoint", "golf", "goo", "goodhands", "goodyear", "goog", "google", "gop", "got", "gov", - "gp", "gq", "gr", "grainger", "graphics", "gratis", "green", "gripe", "grocery", "group", "gs", "gt", "gu", - "guardian", "gucci", "guge", "guide", "guitars", "guru", "gw", "gy", "hair", "hamburg", "hangout", "haus", "hbo", - "hdfc", "hdfcbank", "health", "healthcare", "help", "helsinki", "here", "hermes", "hgtv", "hiphop", "hisamitsu", - "hitachi", "hiv", "hk", "hkt", "hm", "hn", "hockey", "holdings", "holiday", "homedepot", "homegoods", "homes", - "homesense", "honda", "honeywell", "horse", "hospital", "host", "hosting", "hot", "hoteles", "hotels", "hotmail", - "house", "how", "hr", "hsbc", "ht", "hu", "hughes", "hyatt", "hyundai", "ibm", "icbc", "ice", "icu", "id", "ie", - "ieee", "ifm", "ikano", "il", "im", "imamat", "imdb", "immo", "immobilien", "in", "industries", "infiniti", - "info", "ing", "ink", "institute", "insurance", "insure", "int", "intel", "international", "intuit", - "investments", "io", "ipiranga", "iq", "ir", "irish", "is", "iselect", "ismaili", "ist", "istanbul", "it", - "itau", "itv", "iveco", "iwc", "jaguar", "java", "jcb", "jcp", "je", "jeep", "jetzt", "jewelry", "jio", "jlc", - "jll", "jm", "jmp", "jnj", "jo", "jobs", "joburg", "jot", "joy", "jp", "jpmorgan", "jprs", "juegos", "juniper", - "kaufen", "kddi", "ke", "kerryhotels", "kerrylogistics", "kerryproperties", "kfh", "kg", "kh", "ki", "kia", - "kim", "kinder", "kindle", "kitchen", "kiwi", "km", "kn", "koeln", "komatsu", "kosher", "kp", "kpmg", "kpn", - "kr", "krd", "kred", "kuokgroup", "kw", "ky", "kyoto", "kz", "la", "lacaixa", "ladbrokes", "lamborghini", - "lamer", "lancaster", "lancia", "lancome", "land", "landrover", "lanxess", "lasalle", "lat", "latino", "latrobe", - "law", "lawyer", "lb", "lc", "lds", "lease", "leclerc", "lefrak", "legal", "lego", "lexus", "lgbt", "li", - "liaison", "lidl", "life", "lifeinsurance", "lifestyle", "lighting", "like", "lilly", "limited", "limo", - "lincoln", "linde", "link", "lipsy", "live", "living", "lixil", "lk", "loan", "loans", "locker", "locus", "loft", - "lol", "london", "lotte", "lotto", "love", "lpl", "lplfinancial", "lr", "ls", "lt", "ltd", "ltda", "lu", - "lundbeck", "lupin", "luxe", "luxury", "lv", "ly", "ma", "macys", "madrid", "maif", "maison", "makeup", "man", - "management", "mango", "map", "market", "marketing", "markets", "marriott", "marshalls", "maserati", "mattel", - "mba", "mc", "mckinsey", "md", "me", "med", "media", "meet", "melbourne", "meme", "memorial", "men", "menu", - "meo", "merckmsd", "metlife", "mg", "mh", "miami", "microsoft", "mil", "mini", "mint", "mit", "mitsubishi", "mk", - "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile", "mobily", "moda", "moe", "moi", "mom", "monash", - "money", "monster", "mopar", "mormon", "mortgage", "moscow", "moto", "motorcycles", "mov", "movie", "movistar", - "mp", "mq", "mr", "ms", "msd", "mt", "mtn", "mtr", "mu", "museum", "mutual", "mv", "mw", "mx", "my", "mz", "na", - "nab", "nadex", "nagoya", "name", "nationwide", "natura", "navy", "nba", "nc", "ne", "nec", "net", "netbank", - "netflix", "network", "neustar", "new", "newholland", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", - "ngo", "nhk", "ni", "nico", "nike", "nikon", "ninja", "nissan", "nissay", "nl", "no", "nokia", - "northwesternmutual", "norton", "now", "nowruz", "nowtv", "np", "nr", "nra", "nrw", "ntt", "nu", "nyc", "nz", - "obi", "observer", "off", "office", "okinawa", "olayan", "olayangroup", "oldnavy", "ollo", "om", "omega", "one", - "ong", "onion", "onl", "online", "onyourside", "ooo", "open", "oracle", "orange", "org", "organic", "origins", - "osaka", "otsuka", "ott", "ovh", "pa", "page", "panasonic", "panerai", "paris", "pars", "partners", "parts", - "party", "passagens", "pay", "pccw", "pe", "pet", "pf", "pfizer", "pg", "ph", "pharmacy", "phd", "philips", - "phone", "photo", "photography", "photos", "physio", "piaget", "pics", "pictet", "pictures", "pid", "pin", + "gives", "giving", "gl", "glass", "gle", "global", "globo", "gm", "gmail", "gmbh", "gmo", "gmx", "gn", "godaddy", + "gold", "goldpoint", "golf", "goo", "goodyear", "goog", "google", "gop", "got", "gov", "gp", "gq", "gr", + "grainger", "graphics", "gratis", "green", "gripe", "grocery", "group", "gs", "gt", "gu", "guardian", "gucci", + "guge", "guide", "guitars", "guru", "gw", "gy", "hair", "hamburg", "hangout", "haus", "hbo", "hdfc", "hdfcbank", + "health", "healthcare", "help", "helsinki", "here", "hermes", "hiphop", "hisamitsu", "hitachi", "hiv", "hk", + "hkt", "hm", "hn", "hockey", "holdings", "holiday", "homedepot", "homegoods", "homes", "homesense", "honda", + "horse", "hospital", "host", "hosting", "hot", "hotels", "hotmail", "house", "how", "hr", "hsbc", "ht", "hu", + "hughes", "hyatt", "hyundai", "ibm", "icbc", "ice", "icu", "id", "ie", "ieee", "ifm", "ikano", "il", "im", + "imamat", "imdb", "immo", "immobilien", "in", "inc", "industries", "infiniti", "info", "ing", "ink", "institute", + "insurance", "insure", "int", "international", "intuit", "investments", "io", "ipiranga", "iq", "ir", "irish", + "is", "ismaili", "ist", "istanbul", "it", "itau", "itv", "jaguar", "java", "jcb", "je", "jeep", "jetzt", + "jewelry", "jio", "jll", "jm", "jmp", "jnj", "jo", "jobs", "joburg", "jot", "joy", "jp", "jpmorgan", "jprs", + "juegos", "juniper", "kaufen", "kddi", "ke", "kerryhotels", "kerrylogistics", "kerryproperties", "kfh", "kg", + "kh", "ki", "kia", "kids", "kim", "kinder", "kindle", "kitchen", "kiwi", "km", "kn", "koeln", "komatsu", + "kosher", "kp", "kpmg", "kpn", "kr", "krd", "kred", "kuokgroup", "kw", "ky", "kyoto", "kz", "la", "lacaixa", + "lamborghini", "lamer", "lancaster", "land", "landrover", "lanxess", "lasalle", "lat", "latino", "latrobe", + "law", "lawyer", "lb", "lc", "lds", "lease", "leclerc", "lefrak", "legal", "lego", "lexus", "lgbt", "li", "lidl", + "life", "lifeinsurance", "lifestyle", "lighting", "like", "lilly", "limited", "limo", "lincoln", "link", "lipsy", + "live", "living", "lk", "llc", "llp", "loan", "loans", "locker", "locus", "lol", "london", "lotte", "lotto", + "love", "lpl", "lplfinancial", "lr", "ls", "lt", "ltd", "ltda", "lu", "lundbeck", "luxe", "luxury", "lv", "ly", + "ma", "madrid", "maif", "maison", "makeup", "man", "management", "mango", "map", "market", "marketing", + "markets", "marriott", "marshalls", "mattel", "mba", "mc", "mckinsey", "md", "me", "med", "media", "meet", + "melbourne", "meme", "memorial", "men", "menu", "merckmsd", "mg", "mh", "miami", "microsoft", "mil", "mini", + "mint", "mit", "mitsubishi", "mk", "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile", "moda", "moe", + "moi", "mom", "monash", "money", "monster", "mormon", "mortgage", "moscow", "moto", "motorcycles", "mov", + "movie", "mp", "mq", "mr", "ms", "msd", "mt", "mtn", "mtr", "mu", "museum", "music", "mv", "mw", "mx", "my", + "mz", "na", "nab", "nagoya", "name", "natura", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix", + "network", "neustar", "new", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", "ngo", "nhk", "ni", + "nico", "nike", "nikon", "ninja", "nissan", "nissay", "nl", "no", "nokia", "norton", "now", "nowruz", "nowtv", + "np", "nr", "nra", "nrw", "ntt", "nu", "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", + "olayangroup", "oldnavy", "ollo", "om", "omega", "one", "ong", "onl", "online", "ooo", "open", "oracle", + "orange", "org", "organic", "origins", "osaka", "otsuka", "ott", "ovh", "pa", "page", "panasonic", "paris", + "pars", "partners", "parts", "party", "pay", "pccw", "pe", "pet", "pf", "pfizer", "pg", "ph", "pharmacy", "phd", + "philips", "phone", "photo", "photography", "photos", "physio", "pics", "pictet", "pictures", "pid", "pin", "ping", "pink", "pioneer", "pizza", "pk", "pl", "place", "play", "playstation", "plumbing", "plus", "pm", "pn", "pnc", "pohl", "poker", "politie", "porn", "post", "pr", "pramerica", "praxi", "press", "prime", "pro", "prod", "productions", "prof", "progressive", "promo", "properties", "property", "protection", "pru", "prudential", "ps", - "pt", "pub", "pw", "pwc", "py", "qa", "qpon", "quebec", "quest", "qvc", "racing", "radio", "raid", "re", "read", - "realestate", "realtor", "realty", "recipes", "red", "redstone", "redumbrella", "rehab", "reise", "reisen", - "reit", "reliance", "ren", "rent", "rentals", "repair", "report", "republican", "rest", "restaurant", "review", - "reviews", "rexroth", "rich", "richardli", "ricoh", "rightathome", "ril", "rio", "rip", "rmit", "ro", "rocher", - "rocks", "rodeo", "rogers", "room", "rs", "rsvp", "ru", "rugby", "ruhr", "run", "rw", "rwe", "ryukyu", "sa", - "saarland", "safe", "safety", "sakura", "sale", "salon", "samsclub", "samsung", "sandvik", "sandvikcoromant", - "sanofi", "sap", "sapo", "sarl", "sas", "save", "saxo", "sb", "sbi", "sbs", "sc", "sca", "scb", "schaeffler", - "schmidt", "scholarships", "school", "schule", "schwarz", "science", "scjohnson", "scor", "scot", "sd", "se", - "search", "seat", "secure", "security", "seek", "select", "sener", "services", "ses", "seven", "sew", "sex", - "sexy", "sfr", "sg", "sh", "shangrila", "sharp", "shaw", "shell", "shia", "shiksha", "shoes", "shop", "shopping", - "shouji", "show", "showtime", "shriram", "si", "silk", "sina", "singles", "site", "sj", "sk", "ski", "skin", - "sky", "skype", "sl", "sling", "sm", "smart", "smile", "sn", "sncf", "so", "soccer", "social", "softbank", - "software", "sohu", "solar", "solutions", "song", "sony", "soy", "space", "spiegel", "sport", "spot", - "spreadbetting", "sr", "srl", "srt", "st", "stada", "staples", "star", "starhub", "statebank", "statefarm", - "statoil", "stc", "stcgroup", "stockholm", "storage", "store", "stream", "studio", "study", "style", "su", - "sucks", "supplies", "supply", "support", "surf", "surgery", "suzuki", "sv", "swatch", "swiftcover", "swiss", - "sx", "sy", "sydney", "symantec", "systems", "sz", "tab", "taipei", "talk", "taobao", "target", "tatamotors", - "tatar", "tattoo", "tax", "taxi", "tc", "tci", "td", "tdk", "team", "tech", "technology", "tel", "telecity", - "telefonica", "temasek", "tennis", "teva", "tf", "tg", "th", "thd", "theater", "theatre", "tiaa", "tickets", - "tienda", "tiffany", "tips", "tires", "tirol", "tj", "tjmaxx", "tjx", "tk", "tkmaxx", "tl", "tm", "tmall", "tn", - "to", "today", "tokyo", "tools", "top", "toray", "toshiba", "total", "tours", "town", "toyota", "toys", "tr", - "trade", "trading", "training", "travel", "travelchannel", "travelers", "travelersinsurance", "trust", "trv", - "tt", "tube", "tui", "tunes", "tushu", "tv", "tvs", "tw", "tz", "ua", "ubank", "ubs", "uconnect", "ug", "uk", - "unicom", "university", "uno", "uol", "ups", "us", "uy", "uz", "va", "vacations", "vana", "vanguard", "vc", "ve", - "vegas", "ventures", "verisign", "versicherung", "vet", "vg", "vi", "viajes", "video", "vig", "viking", "villas", - "vin", "vip", "virgin", "visa", "vision", "vista", "vistaprint", "viva", "vivo", "vlaanderen", "vn", "vodka", - "volkswagen", "volvo", "vote", "voting", "voto", "voyage", "vu", "vuelos", "wales", "walmart", "walter", "wang", - "wanggou", "warman", "watch", "watches", "weather", "weatherchannel", "webcam", "weber", "website", "wed", - "wedding", "weibo", "weir", "wf", "whoswho", "wien", "wiki", "williamhill", "win", "windows", "wine", "winners", - "wme", "wolterskluwer", "woodside", "work", "works", "world", "wow", "ws", "wtc", "wtf", "xbox", "xerox", - "xfinity", "xihuan", "xin", "कॉम", "セール", "佛山", "ಭಾರತ", "慈善", "集团", "在线", "한국", "ଭାରତ", "大众汽车", - "点看", "คอม", "ভাৰত", "ভারত", "八卦", "موقع", "বাংলা", "公益", "公司", "香格里拉", "网站", "移动", "我爱你", - "москва", "қаз", "католик", "онлайн", "сайт", "联通", "срб", "бг", "бел", "קום", "时尚", "微博", "淡马锡", - "ファッション", "орг", "नेट", "ストア", "삼성", "சிங்கப்பூர்", "商标", "商店", "商城", "дети", "мкд", "ею", - "ポイント", "新闻", "工行", "家電", "كوم", "中文网", "中信", "中国", "中國", "娱乐", "谷歌", "భారత్", "ලංකා", - "電訊盈科", "购物", "クラウド", "ભારત", "通販", "भारतम्", "भारत", "भारोत", "网店", "संगठन", "餐厅", "网络", "ком", - "укр", "香港", "诺基亚", "食品", "飞利浦", "台湾", "台灣", "手表", "手机", "мон", "الجزائر", "عمان", "ارامكو", - "ایران", "العليان", "اتصالات", "امارات", "بازار", "پاکستان", "الاردن", "موبايلي", "بارت", "بھارت", "المغرب", - "ابوظبي", "السعودية", "ڀارت", "كاثوليك", "سودان", "همراه", "عراق", "مليسيا", "澳門", "닷컴", "政府", "شبكة", - "بيتك", "عرب", "გე", "机构", "组织机构", "健康", "ไทย", "سورية", "招聘", "рус", "рф", "珠宝", "تونس", "大拿", - "みんな", "グーグル", "ελ", "世界", "書籍", "ഭാരതം", "ਭਾਰਤ", "网址", "닷넷", "コム", "天主教", "游戏", - "vermögensberater", "vermögensberatung", "企业", "信息", "嘉里大酒店", "嘉里", "مصر", "قطر", "广东", "இலங்கை", - "இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xperia", "xxx", "xyz", "yachts", "yahoo", "yamaxun", "yandex", - "ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", "yun", "za", "zappos", "zara", "zero", "zip", - "zippo", "zm", "zone", "zuerich", + "pt", "pub", "pw", "pwc", "py", "qa", "qpon", "quebec", "quest", "racing", "radio", "re", "read", "realestate", + "realtor", "realty", "recipes", "red", "redstone", "redumbrella", "rehab", "reise", "reisen", "reit", "reliance", + "ren", "rent", "rentals", "repair", "report", "republican", "rest", "restaurant", "review", "reviews", "rexroth", + "rich", "richardli", "ricoh", "ril", "rio", "rip", "ro", "rocher", "rocks", "rodeo", "rogers", "room", "rs", + "rsvp", "ru", "rugby", "ruhr", "run", "rw", "rwe", "ryukyu", "sa", "saarland", "safe", "safety", "sakura", + "sale", "salon", "samsclub", "samsung", "sandvik", "sandvikcoromant", "sanofi", "sap", "sarl", "sas", "save", + "saxo", "sb", "sbi", "sbs", "sc", "sca", "scb", "schaeffler", "schmidt", "scholarships", "school", "schule", + "schwarz", "science", "scot", "sd", "se", "search", "seat", "secure", "security", "seek", "select", "sener", + "services", "seven", "sew", "sex", "sexy", "sfr", "sg", "sh", "shangrila", "sharp", "shaw", "shell", "shia", + "shiksha", "shoes", "shop", "shopping", "shouji", "show", "showtime", "si", "silk", "sina", "singles", "site", + "sj", "sk", "ski", "skin", "sky", "skype", "sl", "sling", "sm", "smart", "smile", "sn", "sncf", "so", "soccer", + "social", "softbank", "software", "sohu", "solar", "solutions", "song", "sony", "soy", "spa", "space", "sport", + "spot", "sr", "srl", "ss", "st", "stada", "staples", "star", "statebank", "statefarm", "stc", "stcgroup", + "stockholm", "storage", "store", "stream", "studio", "study", "style", "su", "sucks", "supplies", "supply", + "support", "surf", "surgery", "suzuki", "sv", "swatch", "swiss", "sx", "sy", "sydney", "systems", "sz", "tab", + "taipei", "talk", "taobao", "target", "tatamotors", "tatar", "tattoo", "tax", "taxi", "tc", "tci", "td", "tdk", + "team", "tech", "technology", "tel", "temasek", "tennis", "teva", "tf", "tg", "th", "thd", "theater", "theatre", + "tiaa", "tickets", "tienda", "tips", "tires", "tirol", "tj", "tjmaxx", "tjx", "tk", "tkmaxx", "tl", "tm", + "tmall", "tn", "to", "today", "tokyo", "tools", "top", "toray", "toshiba", "total", "tours", "town", "toyota", + "toys", "tr", "trade", "trading", "training", "travel", "travelers", "travelersinsurance", "trust", "trv", "tt", + "tube", "tui", "tunes", "tushu", "tv", "tvs", "tw", "tz", "ua", "ubank", "ubs", "ug", "uk", "unicom", + "university", "uno", "uol", "ups", "us", "uy", "uz", "va", "vacations", "vana", "vanguard", "vc", "ve", "vegas", + "ventures", "verisign", "versicherung", "vet", "vg", "vi", "viajes", "video", "vig", "viking", "villas", "vin", + "vip", "virgin", "visa", "vision", "viva", "vivo", "vlaanderen", "vn", "vodka", "volkswagen", "volvo", "vote", + "voting", "voto", "voyage", "vu", "wales", "walmart", "walter", "wang", "wanggou", "watch", "watches", "weather", + "weatherchannel", "webcam", "weber", "website", "wed", "wedding", "weibo", "weir", "wf", "whoswho", "wien", + "wiki", "williamhill", "win", "windows", "wine", "winners", "wme", "wolterskluwer", "woodside", "work", "works", + "world", "wow", "ws", "wtc", "wtf", "xbox", "xerox", "xfinity", "xihuan", "xin", "कॉम", "セール", "佛山", "ಭಾರತ", + "慈善", "集团", "在线", "한국", "ଭାରତ", "点看", "คอม", "ভাৰত", "ভারত", "八卦", "ישראל", "موقع", "বাংলা", "公益", + "公司", "香格里拉", "网站", "移动", "我爱你", "москва", "қаз", "католик", "онлайн", "сайт", "联通", "срб", "бг", + "бел", "קום", "时尚", "微博", "淡马锡", "ファッション", "орг", "नेट", "ストア", "アマゾン", "삼성", "சிங்கப்பூர்", + "商标", "商店", "商城", "дети", "мкд", "ею", "ポイント", "新闻", "家電", "كوم", "中文网", "中信", "中国", "中國", + "娱乐", "谷歌", "భారత్", "ලංකා", "電訊盈科", "购物", "クラウド", "ભારત", "通販", "भारतम्", "भारत", "भारोत", "网店", + "संगठन", "餐厅", "网络", "ком", "укр", "香港", "亚马逊", "食品", "飞利浦", "台湾", "台灣", "手机", "мон", + "الجزائر", "عمان", "ارامكو", "ایران", "العليان", "اتصالات", "امارات", "بازار", "موريتانيا", "پاکستان", "الاردن", + "بارت", "بھارت", "المغرب", "ابوظبي", "البحرين", "السعودية", "ڀارت", "كاثوليك", "سودان", "همراه", "عراق", + "مليسيا", "澳門", "닷컴", "政府", "شبكة", "بيتك", "عرب", "გე", "机构", "组织机构", "健康", "ไทย", "سورية", + "招聘", "рус", "рф", "تونس", "大拿", "ລາວ", "みんな", "グーグル", "ευ", "ελ", "世界", "書籍", "ഭാരതം", "ਭਾਰਤ", + "网址", "닷넷", "コム", "天主教", "游戏", "vermögensberater", "vermögensberatung", "企业", "信息", "嘉里大酒店", + "嘉里", "مصر", "قطر", "广东", "இலங்கை", "இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xxx", "xyz", "yachts", + "yahoo", "yamaxun", "yandex", "ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", "yun", "za", + "zappos", "zara", "zero", "zip", "zm", "zone", "zuerich", // comment for clang-format to prevent it from placing all strings on separate lines "zw"}); bool is_lower = true; @@ -1423,6 +1415,24 @@ void remove_empty_entities(vector &entities) { }); } +static bool is_allowed_quote_entity(const MessageEntity &entity) { + switch (entity.type) { + case MessageEntity::Type::Bold: + case MessageEntity::Type::Italic: + case MessageEntity::Type::Underline: + case MessageEntity::Type::Strikethrough: + case MessageEntity::Type::Spoiler: + case MessageEntity::Type::CustomEmoji: + return true; + default: + return false; + } +} + +void remove_unallowed_quote_entities(FormattedText &text) { + td::remove_if(text.entities, [](const auto &entity) { return !is_allowed_quote_entity(entity); }); +} + static int32 text_length(Slice text) { return narrow_cast(utf8_utf16_length(text)); } @@ -1775,7 +1785,7 @@ static bool is_plain_domain(Slice url) { return url.find('/') >= url.size() && url.find('?') >= url.size() && url.find('#') >= url.size(); } -string get_first_url(const FormattedText &text) { +Slice get_first_url(const FormattedText &text) { for (auto &entity : text.entities) { switch (entity.type) { case MessageEntity::Type::Mention: @@ -1793,7 +1803,7 @@ string get_first_url(const FormattedText &text) { if (scheme == "ton:" || begins_with(scheme, "tg:") || scheme == "ftp:" || is_plain_domain(url)) { continue; } - return url.str(); + return url; } case MessageEntity::Type::EmailAddress: break; @@ -1815,10 +1825,11 @@ string get_first_url(const FormattedText &text) { break; case MessageEntity::Type::TextUrl: { Slice url = entity.argument; - if (begins_with(url, "ton:") || begins_with(url, "tg:") || begins_with(url, "ftp:")) { + string scheme = to_lower(url.substr(0, 4)); + if (scheme == "ton:" || begins_with(scheme, "tg:") || scheme == "ftp:") { continue; } - return url.str(); + return url; } case MessageEntity::Type::MentionName: break; @@ -1839,7 +1850,29 @@ string get_first_url(const FormattedText &text) { } } - return string(); + return Slice(); +} + +bool is_visible_url(const FormattedText &text, const string &url) { + if (url.empty()) { + return false; + } + + auto url_size = static_cast(utf8_utf16_length(url)); + auto cur_offset = 0; + Slice left_text = text.text; + for (auto &entity : text.entities) { + if (entity.type == MessageEntity::Type::Url && url_size == entity.length) { + CHECK(entity.offset >= cur_offset); + left_text = utf8_utf16_substr(left_text, entity.offset - cur_offset); + cur_offset = entity.offset; + if (begins_with(left_text, url)) { + return true; + } + } + } + + return false; } Result> parse_markdown(string &text) { @@ -1982,8 +2015,9 @@ Result> parse_markdown_v2(string &text) { , entity_begin_pos(entity_begin_pos) { } }; - std::vector nested_entities; + vector nested_entities; + bool have_blockquote = false; for (size_t i = 0; i < text.size(); i++) { auto c = static_cast(text[i]); if (c == '\\' && text[i + 1] > 0 && text[i + 1] <= 126) { @@ -1993,7 +2027,7 @@ Result> parse_markdown_v2(string &text) { continue; } - Slice reserved_characters("_*[]()~`>#+-=|{}.!"); + Slice reserved_characters("_*[]()~`>#+-=|{}.!\n"); if (!nested_entities.empty()) { switch (nested_entities.back().type) { case MessageEntity::Type::Code: @@ -2017,6 +2051,9 @@ Result> parse_markdown_v2(string &text) { bool is_end_of_an_entity = false; if (!nested_entities.empty()) { is_end_of_an_entity = [&] { + if (have_blockquote && c == '\n' && (i + 1 == text.size() || text[i + 1] != '>')) { + return true; + } switch (nested_entities.back().type) { case MessageEntity::Type::Bold: return c == '*'; @@ -2037,6 +2074,8 @@ Result> parse_markdown_v2(string &text) { return c == '|' && text[i + 1] == '|'; case MessageEntity::Type::CustomEmoji: return c == ']'; + case MessageEntity::Type::BlockQuote: + return false; default: UNREACHABLE(); return false; @@ -2112,14 +2151,42 @@ Result> parse_markdown_v2(string &text) { << "' is reserved and must be escaped with the preceding '\\'"); } break; + case '\n': + utf16_offset += 1; + text[result_size++] = '\n'; + type = MessageEntity::Type::Size; + if (i + 1 < text.size() && text[i + 1] == '>') { + i++; + if (!have_blockquote) { + type = MessageEntity::Type::BlockQuote; + have_blockquote = true; + } + } + break; + case '>': + if (i == 0) { + type = MessageEntity::Type::BlockQuote; + have_blockquote = true; + } else { + return Status::Error(400, PSLICE() << "Character '" << text[i] + << "' is reserved and must be escaped with the preceding '\\'"); + } + break; default: return Status::Error( 400, PSLICE() << "Character '" << text[i] << "' is reserved and must be escaped with the preceding '\\'"); } + if (type == MessageEntity::Type::Size) { + continue; + } nested_entities.emplace_back(type, std::move(argument), utf16_offset, entity_byte_offset, result_size); } else { // end of an entity auto type = nested_entities.back().type; + if (c == '\n' && type != MessageEntity::Type::BlockQuote) { + return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type + << " entity at byte offset " << nested_entities.back().entity_byte_offset); + } auto argument = std::move(nested_entities.back().argument); UserId user_id; CustomEmojiId custom_emoji_id; @@ -2192,6 +2259,12 @@ Result> parse_markdown_v2(string &text) { TRY_RESULT_ASSIGN(custom_emoji_id, LinkManager::get_link_custom_emoji_id(url)); break; } + case MessageEntity::Type::BlockQuote: + CHECK(have_blockquote); + have_blockquote = false; + text[result_size++] = text[i]; + utf16_offset += 1; + break; default: UNREACHABLE(); return false; @@ -2211,6 +2284,18 @@ Result> parse_markdown_v2(string &text) { nested_entities.pop_back(); } } + if (have_blockquote) { + CHECK(!nested_entities.empty()); + if (nested_entities.back().type == MessageEntity::Type::BlockQuote) { + have_blockquote = false; + auto entity_offset = nested_entities.back().entity_offset; + auto entity_length = utf16_offset - entity_offset; + if (entity_length != 0) { + entities.emplace_back(MessageEntity::Type::BlockQuote, entity_offset, entity_length); + } + nested_entities.pop_back(); + } + } if (!nested_entities.empty()) { return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type << " entity at byte offset " << nested_entities.back().entity_byte_offset); @@ -2626,9 +2711,29 @@ static FormattedText parse_pre_entities_v3(Slice text) { if (end_tag_end - end_tag_begin == j - i) { // end tag found CHECK(entity_length > 0); - entities.emplace_back(j - i == 3 ? MessageEntity::Type::Pre : MessageEntity::Type::Code, utf16_offset, - entity_length); - result.append(text.begin() + j, end_tag_begin - j); + auto entity_begin = j; + string language_code; + if (j - i == 3) { + size_t language_code_end = j; + while (language_code_end < end_tag_begin - 1 && 33 <= text[language_code_end] && + text[language_code_end] <= 126) { + language_code_end++; + } + if (language_code_end < end_tag_begin - 1 && text[language_code_end] == '\n') { + language_code = text.substr(entity_begin, language_code_end - entity_begin).str(); + entity_begin = language_code_end + 1; + entity_length -= static_cast(entity_begin - j); + CHECK(entity_length > 0); + } + } + if (!language_code.empty()) { + entities.emplace_back(MessageEntity::Type::PreCode, utf16_offset, entity_length, + std::move(language_code)); + } else { + entities.emplace_back(j - i == 3 ? MessageEntity::Type::Pre : MessageEntity::Type::Code, utf16_offset, + entity_length); + } + result.append(text.begin() + entity_begin, end_tag_begin - entity_begin); utf16_offset += entity_length; i = end_tag_end - 1; is_found = true; @@ -2689,10 +2794,7 @@ static FormattedText parse_pre_entities_v3(Slice text, vector ent result_text_utf16_length += part_end - max_end; } else { FormattedText parsed_text = parse_pre_entities_v3(parsed_part_text); - int32 new_skipped_length = 0; - for (auto &entity : parsed_text.entities) { - new_skipped_length += (entity.type == MessageEntity::Type::Pre ? 6 : 2); - } + auto new_skipped_length = static_cast(parsed_part_text.size() - parsed_text.text.size()); CHECK(new_skipped_length < part_end - max_end); result.text += parsed_text.text; for (auto &entity : parsed_text.entities) { @@ -2822,6 +2924,14 @@ FormattedText get_markdown_v3(FormattedText text) { int32 utf16_offset = 0; int32 utf16_added = 0; + auto is_valid_language_code = [](Slice code) { + for (auto c : code) { + if (c < 33 || c > 126) { + return false; + } + } + return true; + }; for (size_t pos = 0; pos <= text.text.size(); pos++) { auto c = static_cast(text.text[pos]); if (is_utf8_character_first_code_unit(c)) { @@ -2834,6 +2944,7 @@ FormattedText get_markdown_v3(FormattedText text) { CHECK(utf16_offset == entity_end); + bool need_entity = false; switch (entity->type) { case MessageEntity::Type::Italic: result.text += "__"; @@ -2865,12 +2976,23 @@ FormattedText get_markdown_v3(FormattedText text) { result.text += "```"; utf16_added += 3; break; + case MessageEntity::Type::PreCode: + if (is_valid_language_code(entity->argument)) { + result.text += "```"; + utf16_added += 3; + } else { + need_entity = true; + } + break; default: - result.entities.push_back(*entity); - result.entities.back().offset += nested_entities_stack.back().utf16_added_before; - result.entities.back().length += utf16_added - nested_entities_stack.back().utf16_added_before; + need_entity = true; break; } + if (need_entity) { + result.entities.push_back(*entity); + result.entities.back().offset += nested_entities_stack.back().utf16_added_before; + result.entities.back().length += utf16_added - nested_entities_stack.back().utf16_added_before; + } nested_entities_stack.pop_back(); } @@ -2904,6 +3026,24 @@ FormattedText get_markdown_v3(FormattedText text) { case MessageEntity::Type::Pre: result.text += "```"; utf16_added += 3; + if (c != '\n') { + result.text += "\n"; + utf16_added++; + } + break; + case MessageEntity::Type::PreCode: + if (is_valid_language_code(text.entities[current_entity].argument)) { + result.text += "```"; + utf16_added += 3; + if (!text.entities[current_entity].argument.empty()) { + result.text += text.entities[current_entity].argument; + utf16_added += static_cast(text.entities[current_entity].argument.size()); + } + if (c != '\n') { + result.text += "\n"; + utf16_added++; + } + } break; default: // keep as is @@ -3039,7 +3179,7 @@ Result> parse_html(string &str) { if (tag_name != "a" && tag_name != "b" && tag_name != "strong" && tag_name != "i" && tag_name != "em" && tag_name != "s" && tag_name != "strike" && tag_name != "del" && tag_name != "u" && tag_name != "ins" && tag_name != "tg-spoiler" && tag_name != "tg-emoji" && tag_name != "span" && tag_name != "pre" && - tag_name != "code") { + tag_name != "code" && tag_name != "blockquote") { return Status::Error(400, PSLICE() << "Unsupported start tag \"" << tag_name << "\" at byte offset " << begin_pos); } @@ -3207,6 +3347,8 @@ Result> parse_html(string &str) { entities.emplace_back(MessageEntity::Type::Code, entity_offset, entity_length, nested_entities.back().argument); } + } else if (tag_name == "blockquote") { + entities.emplace_back(MessageEntity::Type::BlockQuote, entity_offset, entity_length); } else { UNREACHABLE(); } @@ -3279,7 +3421,7 @@ vector> get_input_secret_message_entiti break; case MessageEntity::Type::BlockQuote: if (layer >= static_cast(SecretChatLayer::NewEntities)) { - result.push_back(make_tl_object(entity.offset, entity.length)); + // result.push_back(make_tl_object(entity.offset, entity.length)); } break; case MessageEntity::Type::Code: @@ -3367,6 +3509,9 @@ Result> get_message_entities(const ContactsManager *contac case td_api::textEntityTypeStrikethrough::ID: entities.emplace_back(MessageEntity::Type::Strikethrough, offset, length); break; + case td_api::textEntityTypeBlockQuote::ID: + entities.emplace_back(MessageEntity::Type::BlockQuote, offset, length); + break; case td_api::textEntityTypeCode::ID: entities.emplace_back(MessageEntity::Type::Code, offset, length); break; @@ -4288,6 +4433,25 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m return FormattedText{std::move(message_text), std::move(entities)}; } +void truncate_formatted_text(FormattedText &text, size_t length) { + auto result_size = utf8_truncate(Slice(text.text), length).size(); + if (result_size == text.text.size()) { + return; + } + text.text.resize(result_size); + auto utf16_length = narrow_cast(utf8_utf16_length(text.text)); + for (auto &entity : text.entities) { + if (entity.offset + entity.length > utf16_length) { + if (entity.offset >= utf16_length || is_continuous_entity(entity.type)) { + entity.length = 0; + continue; + } + entity.length = utf16_length - entity.offset; // truncate the entity + } + } + remove_empty_entities(text.entities); +} + td_api::object_ptr extract_input_caption( tl_object_ptr &input_message_content) { switch (input_message_content->get_id()) { @@ -4536,10 +4700,69 @@ void remove_unallowed_entities(const Td *td, FormattedText &text, DialogId dialo remove_intersecting_entities(text.entities); } } - if (!td->option_manager_->get_option_boolean("is_premium") && - dialog_id != DialogId(td->contacts_manager_->get_my_id())) { + if (dialog_id != DialogId(td->contacts_manager_->get_my_id()) && + !td->contacts_manager_->can_use_premium_custom_emoji()) { remove_premium_custom_emoji_entities(td, text.entities, false); } } +int32 search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position) { + auto process_quote_entities = [](FormattedText &text, int32 length) { + remove_unallowed_quote_entities(text); + td::remove_if(text.entities, [length](const MessageEntity &entity) { + if (entity.offset < 0 || entity.offset >= length) { + return true; + } + if (entity.length <= 0 || entity.length > length - entity.offset) { + return true; + } + return false; + }); + remove_empty_entities(text.entities); + fix_entities(text.entities); + remove_invalid_entities(text.text, text.entities); + }; + int32 length = text_length(text.text); + int32 quote_length = text_length(quote.text); + if (quote_length == 0 || quote_length > length) { + return -1; + } + process_quote_entities(text, length); + process_quote_entities(quote, quote_length); + + quote_position = clamp(quote_position, 0, length - 1); + vector byte_positions; + byte_positions.reserve(length); + for (size_t i = 0; i < text.text.size(); i++) { + auto c = static_cast(text.text[i]); + if (is_utf8_character_first_code_unit(c)) { + byte_positions.push_back(i); + if (c >= 0xf0) { // >= 4 bytes in symbol => surrogate pair + byte_positions.push_back(string::npos); + } + } + } + CHECK(byte_positions.size() == static_cast(length)); + auto check_position = [&text, "e, &byte_positions, length, quote_length](int32 position) { + if (position < 0 || position > length - quote_length) { + return false; + } + auto byte_position = byte_positions[position]; + if (byte_position == string::npos || text.text[byte_position] != quote.text[0] || + Slice(text.text).substr(byte_position, quote.text.size()) != quote.text) { + return false; + } + return true; + }; + for (int32 i = 0; quote_position - i >= 0 || quote_position + i + 1 <= length - quote_length; i++) { + if (check_position(quote_position - i)) { + return quote_position - i; + } + if (check_position(quote_position + i + 1)) { + return quote_position + i + 1; + } + } + return -1; +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageEntity.h b/lib/tgchat/ext/td/td/telegram/MessageEntity.h index ea20d5e8..f8e0012e 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageEntity.h +++ b/lib/tgchat/ext/td/td/telegram/MessageEntity.h @@ -183,7 +183,13 @@ vector> find_media_timestamps(Slice str); // slice + me void remove_empty_entities(vector &entities); -string get_first_url(const FormattedText &text); +void remove_unallowed_quote_entities(FormattedText &text); + +int32 search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position); + +Slice get_first_url(const FormattedText &text); + +bool is_visible_url(const FormattedText &text, const string &url); Result> parse_markdown(string &text); @@ -230,6 +236,8 @@ FormattedText get_message_text(const ContactsManager *contacts_manager, string m bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album, const char *source); +void truncate_formatted_text(FormattedText &text, size_t length); + td_api::object_ptr extract_input_caption( tl_object_ptr &input_message_content); diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp index ad4e22c2..3c98cf91 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp @@ -6,35 +6,87 @@ // #include "td/telegram/MessageInputReplyTo.h" +#include "td/telegram/AccessRights.h" #include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" +#include "td/telegram/InputDialogId.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/misc.h" +#include "td/telegram/ServerMessageId.h" #include "td/telegram/StoryId.h" #include "td/telegram/Td.h" +#include "td/telegram/UserId.h" #include "td/utils/logging.h" namespace td { -/* -MessageInputReplyTo::MessageInputReplyTo(const td_api::object_ptr &reply_to_ptr) { - if (reply_to_ptr == nullptr) { + +MessageInputReplyTo::~MessageInputReplyTo() = default; + +MessageInputReplyTo::MessageInputReplyTo(Td *td, + telegram_api::object_ptr &&input_reply_to) { + if (input_reply_to == nullptr) { return; } - switch (reply_to_ptr->get_id()) { - case td_api::messageReplyToMessage::ID: { - auto reply_to = static_cast(reply_to_ptr.get()); - message_id_ = MessageId(reply_to->message_id_); + switch (input_reply_to->get_id()) { + case telegram_api::inputReplyToStory::ID: { + auto reply_to = telegram_api::move_object_as(input_reply_to); + if (reply_to->user_id_->get_id() != telegram_api::inputUser::ID) { + return; + } + auto user_id = UserId(static_cast(reply_to->user_id_.get())->user_id_); + auto story_id = StoryId(reply_to->story_id_); + if (user_id.is_valid() && story_id.is_valid()) { + DialogId dialog_id(user_id); + td->messages_manager_->force_create_dialog(dialog_id, "MessageInputReplyTo", true); + story_full_id_ = {dialog_id, story_id}; + } break; } - case td_api::messageReplyToStory::ID: { - auto reply_to = static_cast(reply_to_ptr.get()); - story_full_id_ = {DialogId(reply_to->story_sender_chat_id_), StoryId(reply_to->story_id_)}; + case telegram_api::inputReplyToMessage::ID: { + auto reply_to = telegram_api::move_object_as(input_reply_to); + MessageId message_id(ServerMessageId(reply_to->reply_to_msg_id_)); + if (!message_id.is_valid() && !message_id_.is_valid_scheduled()) { + return; + } + DialogId dialog_id; + if (reply_to->reply_to_peer_id_ != nullptr) { + dialog_id = InputDialogId(reply_to->reply_to_peer_id_).get_dialog_id(); + if (!dialog_id.is_valid() || !td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return; + } + td->messages_manager_->force_create_dialog(dialog_id, "inputReplyToMessage"); + } + message_id_ = message_id; + dialog_id_ = dialog_id; + + if (!reply_to->quote_text_.empty()) { + auto entities = get_message_entities(td->contacts_manager_.get(), std::move(reply_to->quote_entities_), + "inputReplyToMessage"); + auto status = fix_formatted_text(reply_to->quote_text_, entities, true, true, true, true, false); + if (status.is_error()) { + if (!clean_input_string(reply_to->quote_text_)) { + reply_to->quote_text_.clear(); + } + entities.clear(); + } + quote_ = FormattedText{std::move(reply_to->quote_text_), std::move(entities)}; + remove_unallowed_quote_entities(quote_); + } break; } default: UNREACHABLE(); } } -*/ + +void MessageInputReplyTo::add_dependencies(Dependencies &dependencies) const { + dependencies.add_dialog_and_dependencies(dialog_id_); + add_formatted_text_dependencies(dependencies, "e_); // just in case + dependencies.add_dialog_and_dependencies(story_full_id_.get_dialog_id()); // just in case +} + telegram_api::object_ptr MessageInputReplyTo::get_input_reply_to( Td *td, MessageId top_thread_message_id) const { if (story_full_id_.is_valid()) { @@ -61,13 +113,77 @@ telegram_api::object_ptr MessageInputReplyTo::get_in CHECK(top_thread_message_id.is_server()); flags |= telegram_api::inputReplyToMessage::TOP_MSG_ID_MASK; } + telegram_api::object_ptr input_peer; + if (dialog_id_ != DialogId()) { + input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); + if (input_peer == nullptr) { + LOG(INFO) << "Failed to get input peer for " << dialog_id_; + return nullptr; + } + flags |= telegram_api::inputReplyToMessage::REPLY_TO_PEER_ID_MASK; + } + if (!quote_.text.empty()) { + flags |= telegram_api::inputReplyToMessage::QUOTE_TEXT_MASK; + } + auto quote_entities = get_input_message_entities(td->contacts_manager_.get(), quote_.entities, "get_input_reply_to"); + if (!quote_entities.empty()) { + flags |= telegram_api::inputReplyToMessage::QUOTE_ENTITIES_MASK; + } return telegram_api::make_object( - flags, reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get()); + flags, reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(), + std::move(input_peer), quote_.text, std::move(quote_entities)); +} + +// only for draft messages +td_api::object_ptr MessageInputReplyTo::get_input_message_reply_to_object(Td *td) const { + if (story_full_id_.is_valid()) { + return td_api::make_object( + td->messages_manager_->get_chat_id_object(story_full_id_.get_dialog_id(), "inputMessageReplyToStory"), + story_full_id_.get_story_id().get()); + } + if (!message_id_.is_valid() && !message_id_.is_valid_scheduled()) { + return nullptr; + } + td_api::object_ptr quote; + if (!quote_.text.empty()) { + quote = get_formatted_text_object(quote_, true, -1); + } + return td_api::make_object( + td->messages_manager_->get_chat_id_object(dialog_id_, "inputMessageReplyToMessage"), message_id_.get(), + std::move(quote)); +} + +MessageId MessageInputReplyTo::get_same_chat_reply_to_message_id() const { + return dialog_id_ == DialogId() && (message_id_.is_valid() || message_id_.is_valid_scheduled()) ? message_id_ + : MessageId(); +} + +MessageFullId MessageInputReplyTo::get_reply_message_full_id(DialogId owner_dialog_id) const { + if (!message_id_.is_valid() && !message_id_.is_valid_scheduled()) { + return {}; + } + return {dialog_id_ != DialogId() ? dialog_id_ : owner_dialog_id, message_id_}; +} + +bool operator==(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs) { + return lhs.message_id_ == rhs.message_id_ && lhs.dialog_id_ == rhs.dialog_id_ && + lhs.story_full_id_ == rhs.story_full_id_ && lhs.quote_ == rhs.quote_; +} + +bool operator!=(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs) { + return !(lhs == rhs); } StringBuilder &operator<<(StringBuilder &string_builder, const MessageInputReplyTo &input_reply_to) { - if (input_reply_to.message_id_.is_valid()) { - return string_builder << input_reply_to.message_id_; + if (input_reply_to.message_id_.is_valid() || input_reply_to.message_id_.is_valid_scheduled()) { + string_builder << input_reply_to.message_id_; + if (input_reply_to.dialog_id_ != DialogId()) { + string_builder << " in " << input_reply_to.dialog_id_; + } + if (!input_reply_to.quote_.text.empty()) { + string_builder << " with " << input_reply_to.quote_.text.size() << " quoted bytes"; + } + return string_builder; } if (input_reply_to.story_full_id_.is_valid()) { return string_builder << input_reply_to.story_full_id_; diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h index 0a7e2b77..186a691b 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h @@ -6,6 +6,9 @@ // #pragma once +#include "td/telegram/DialogId.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" @@ -16,30 +19,88 @@ namespace td { +class Dependencies; + class Td; -struct MessageInputReplyTo { +class MessageInputReplyTo { MessageId message_id_; + DialogId dialog_id_; + FormattedText quote_; // or StoryFullId story_full_id_; + friend bool operator==(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageInputReplyTo &input_reply_to); + + friend class RepliedMessageInfo; + + public: + MessageInputReplyTo() = default; + MessageInputReplyTo(const MessageInputReplyTo &) = delete; + MessageInputReplyTo &operator=(const MessageInputReplyTo &) = delete; + MessageInputReplyTo(MessageInputReplyTo &&) = default; + MessageInputReplyTo &operator=(MessageInputReplyTo &&) = default; + ~MessageInputReplyTo(); + + MessageInputReplyTo(MessageId message_id, DialogId dialog_id, FormattedText &"e) + : message_id_(message_id), dialog_id_(dialog_id), quote_(std::move(quote)) { + remove_unallowed_quote_entities(quote_); + } + + explicit MessageInputReplyTo(StoryFullId story_full_id) : story_full_id_(story_full_id) { + } + + MessageInputReplyTo(Td *td, telegram_api::object_ptr &&input_reply_to); + + bool is_empty() const { + return !message_id_.is_valid() && !message_id_.is_valid_scheduled() && !story_full_id_.is_valid(); + } + bool is_valid() const { - return message_id_.is_valid() || story_full_id_.is_valid(); + return !is_empty(); } - MessageInputReplyTo() = default; + bool has_quote() const { + return !quote_.text.empty(); + } + + void set_quote(FormattedText &"e) { + quote_ = std::move(quote); + } - MessageInputReplyTo(MessageId message_id, StoryFullId story_full_id) - : message_id_(message_id), story_full_id_(story_full_id) { - CHECK(!story_full_id_.is_valid() || !message_id_.is_valid()); + StoryFullId get_story_full_id() const { + return story_full_id_; } - //explicit MessageInputReplyTo(const td_api::object_ptr &reply_to_ptr); + void add_dependencies(Dependencies &dependencies) const; telegram_api::object_ptr get_input_reply_to(Td *td, MessageId top_thread_message_id) const; + + td_api::object_ptr get_input_message_reply_to_object(Td *td) const; + + void set_message_id(MessageId new_message_id) { + CHECK(message_id_.is_valid() || message_id_.is_valid_scheduled()); + message_id_ = new_message_id; + } + + MessageId get_same_chat_reply_to_message_id() const; + + MessageFullId get_reply_message_full_id(DialogId owner_dialog_id) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); }; +bool operator==(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs); + +bool operator!=(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs); + StringBuilder &operator<<(StringBuilder &string_builder, const MessageInputReplyTo &input_reply_to); } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp new file mode 100644 index 00000000..d25c0c7f --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp @@ -0,0 +1,70 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/MessageInputReplyTo.h" + +#include "td/telegram/MessageOrigin.hpp" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MessageInputReplyTo::store(StorerT &storer) const { + bool has_message_id = message_id_.is_valid(); + bool has_story_full_id = story_full_id_.is_valid(); + bool has_quote = !quote_.text.empty(); + bool has_dialog_id = dialog_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_message_id); + STORE_FLAG(has_story_full_id); + STORE_FLAG(has_quote); + STORE_FLAG(has_dialog_id); + END_STORE_FLAGS(); + if (has_message_id) { + td::store(message_id_, storer); + } + if (has_story_full_id) { + td::store(story_full_id_, storer); + } + if (has_quote) { + td::store(quote_, storer); + } + if (has_dialog_id) { + td::store(dialog_id_, storer); + } +} + +template +void MessageInputReplyTo::parse(ParserT &parser) { + bool has_message_id; + bool has_story_full_id; + bool has_quote; + bool has_dialog_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_message_id); + PARSE_FLAG(has_story_full_id); + PARSE_FLAG(has_quote); + PARSE_FLAG(has_dialog_id); + END_PARSE_FLAGS(); + if (has_message_id) { + td::parse(message_id_, parser); + } + if (has_story_full_id) { + td::parse(story_full_id_, parser); + } + if (has_quote) { + td::parse(quote_, parser); + remove_unallowed_quote_entities(quote_); + } + if (has_dialog_id) { + td::parse(dialog_id_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp b/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp new file mode 100644 index 00000000..36c467a5 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp @@ -0,0 +1,171 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/MessageOrigin.h" + +#include "td/telegram/ChannelId.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/Td.h" + +#include "td/utils/logging.h" +#include "td/utils/misc.h" + +namespace td { + +Result MessageOrigin::get_message_origin( + Td *td, telegram_api::object_ptr &&forward_header) { + CHECK(forward_header != nullptr); + DialogId sender_dialog_id; + if (forward_header->from_id_ != nullptr) { + sender_dialog_id = DialogId(forward_header->from_id_); + if (!sender_dialog_id.is_valid()) { + LOG(ERROR) << "Receive invalid sender identifier in message forward header: " + << oneline(to_string(forward_header)); + sender_dialog_id = DialogId(); + } + } + + MessageId message_id; + if (forward_header->channel_post_ != 0) { + message_id = MessageId(ServerMessageId(forward_header->channel_post_)); + if (!message_id.is_valid()) { + LOG(ERROR) << "Receive " << message_id << " in message forward header: " << oneline(to_string(forward_header)); + message_id = MessageId(); + } + } + + string author_signature = std::move(forward_header->post_author_); + string sender_name = std::move(forward_header->from_name_); + + UserId sender_user_id; + if (sender_dialog_id.get_type() == DialogType::User) { + sender_user_id = sender_dialog_id.get_user_id(); + sender_dialog_id = DialogId(); + } + if (!sender_dialog_id.is_valid()) { + if (sender_user_id.is_valid()) { + if (message_id.is_valid()) { + LOG(ERROR) << "Receive non-empty message identifier in message forward header: " + << oneline(to_string(forward_header)); + message_id = MessageId(); + } + } else if (sender_name.empty()) { + LOG(ERROR) << "Receive wrong message forward header: " << oneline(to_string(forward_header)); + return Status::Error("Receive empty forward header"); + } + } else if (sender_dialog_id.get_type() != DialogType::Channel) { + LOG(ERROR) << "Receive wrong message forward header with non-channel sender: " + << oneline(to_string(forward_header)); + return Status::Error("Forward from a non-channel"); + } else { + auto channel_id = sender_dialog_id.get_channel_id(); + if (!td->contacts_manager_->have_channel(channel_id)) { + LOG(ERROR) << "Receive forward from " << (td->contacts_manager_->have_min_channel(channel_id) ? "min" : "unknown") + << ' ' << channel_id; + } + td->messages_manager_->force_create_dialog(sender_dialog_id, "get_message_origin", true); + CHECK(!sender_user_id.is_valid()); + } + + return MessageOrigin{sender_user_id, sender_dialog_id, message_id, std::move(author_signature), + std::move(sender_name)}; +} + +td_api::object_ptr MessageOrigin::get_message_origin_object(const Td *td) const { + if (is_sender_hidden()) { + return td_api::make_object(sender_name_.empty() ? author_signature_ + : sender_name_); + } + if (message_id_.is_valid()) { + return td_api::make_object( + td->messages_manager_->get_chat_id_object(sender_dialog_id_, "messageOriginChannel"), message_id_.get(), + author_signature_); + } + if (sender_dialog_id_.is_valid()) { + return td_api::make_object( + td->messages_manager_->get_chat_id_object(sender_dialog_id_, "messageOriginChat"), + sender_name_.empty() ? author_signature_ : sender_name_); + } + return td_api::make_object( + td->contacts_manager_->get_user_id_object(sender_user_id_, "messageOriginUser")); +} + +bool MessageOrigin::is_sender_hidden() const { + if (!sender_name_.empty()) { + return true; + } + DialogId hidden_sender_dialog_id(ChannelId(static_cast(G()->is_test_dc() ? 10460537 : 1228946795))); + return sender_dialog_id_ == hidden_sender_dialog_id && !author_signature_.empty() && !message_id_.is_valid(); +} + +MessageFullId MessageOrigin::get_message_full_id() const { + if (!message_id_.is_valid() || !sender_dialog_id_.is_valid() || is_sender_hidden()) { + return MessageFullId(); + } + return {sender_dialog_id_, message_id_}; +} + +DialogId MessageOrigin::get_sender() const { + if (is_sender_hidden()) { + return DialogId(); + } + return message_id_.is_valid() || sender_dialog_id_.is_valid() ? sender_dialog_id_ : DialogId(sender_user_id_); +} + +void MessageOrigin::hide_sender_if_needed(Td *td) { + if (!is_sender_hidden() && !message_id_.is_valid() && !sender_dialog_id_.is_valid()) { + auto private_forward_name = td->contacts_manager_->get_user_private_forward_name(sender_user_id_); + if (!private_forward_name.empty()) { + sender_user_id_ = UserId(); + sender_name_ = std::move(private_forward_name); + } + } +} + +void MessageOrigin::add_dependencies(Dependencies &dependencies) const { + dependencies.add(sender_user_id_); + dependencies.add_dialog_and_dependencies(sender_dialog_id_); +} + +void MessageOrigin::add_user_ids(vector &user_ids) const { + if (sender_user_id_.is_valid()) { + user_ids.push_back(sender_user_id_); + } +} + +void MessageOrigin::add_channel_ids(vector &channel_ids) const { + if (sender_dialog_id_.get_type() == DialogType::Channel) { + channel_ids.push_back(sender_dialog_id_.get_channel_id()); + } +} + +bool operator==(const MessageOrigin &lhs, const MessageOrigin &rhs) { + return lhs.sender_user_id_ == rhs.sender_user_id_ && lhs.sender_dialog_id_ == rhs.sender_dialog_id_ && + lhs.message_id_ == rhs.message_id_ && lhs.author_signature_ == rhs.author_signature_ && + lhs.sender_name_ == rhs.sender_name_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageOrigin &origin) { + string_builder << "sender " << origin.sender_user_id_; + if (!origin.author_signature_.empty() || !origin.sender_name_.empty()) { + string_builder << '(' << origin.author_signature_ << '/' << origin.sender_name_ << ')'; + } + if (origin.sender_dialog_id_.is_valid()) { + string_builder << ", source "; + if (origin.message_id_.is_valid()) { + string_builder << MessageFullId(origin.sender_dialog_id_, origin.message_id_); + } else { + string_builder << origin.sender_dialog_id_; + } + } + return string_builder; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageOrigin.h b/lib/tgchat/ext/td/td/telegram/MessageOrigin.h new file mode 100644 index 00000000..b3972da9 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageOrigin.h @@ -0,0 +1,101 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/MessageFullId.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Dependencies; + +class Td; + +class MessageOrigin { + UserId sender_user_id_; + DialogId sender_dialog_id_; + MessageId message_id_; + string author_signature_; + string sender_name_; + + public: + MessageOrigin() = default; + + MessageOrigin(UserId sender_user_id, DialogId sender_dialog_id, MessageId message_id, string &&author_signature, + string &&sender_name) + : sender_user_id_(sender_user_id) + , sender_dialog_id_(sender_dialog_id) + , message_id_(message_id) + , author_signature_(std::move(author_signature)) + , sender_name_(std::move(sender_name)) { + } + + static Result get_message_origin( + Td *td, telegram_api::object_ptr &&forward_header); + + bool is_empty() const { + return !sender_user_id_.is_valid() && !sender_dialog_id_.is_valid() && !message_id_.is_valid() && + author_signature_.empty() && sender_name_.empty(); + } + + td_api::object_ptr get_message_origin_object(const Td *td) const; + + bool is_sender_hidden() const; + + MessageFullId get_message_full_id() const; + + const string &get_sender_name() const { + return sender_name_; + } + + bool is_channel_post() const { + return message_id_.is_valid(); + } + + bool has_sender_signature() const { + return !author_signature_.empty() || !sender_name_.empty(); + } + + DialogId get_sender() const; + + void hide_sender_if_needed(Td *td); + + void add_dependencies(Dependencies &dependencies) const; + + void add_user_ids(vector &user_ids) const; + + void add_channel_ids(vector &channel_ids) const; + + friend bool operator==(const MessageOrigin &lhs, const MessageOrigin &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageOrigin &origin); + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const MessageOrigin &lhs, const MessageOrigin &rhs); + +inline bool operator!=(const MessageOrigin &lhs, const MessageOrigin &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageOrigin &origin); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageOrigin.hpp b/lib/tgchat/ext/td/td/telegram/MessageOrigin.hpp new file mode 100644 index 00000000..5e94cbed --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageOrigin.hpp @@ -0,0 +1,79 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/MessageOrigin.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MessageOrigin::store(StorerT &storer) const { + bool has_sender_user_id = sender_user_id_.is_valid(); + bool has_sender_dialog_id = sender_dialog_id_.is_valid(); + bool has_message_id = message_id_.is_valid(); + bool has_author_signature = !author_signature_.empty(); + bool has_sender_name = !sender_name_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_sender_user_id); + STORE_FLAG(has_sender_dialog_id); + STORE_FLAG(has_message_id); + STORE_FLAG(has_author_signature); + STORE_FLAG(has_sender_name); + END_STORE_FLAGS(); + if (has_sender_user_id) { + td::store(sender_user_id_, storer); + } + if (has_sender_dialog_id) { + td::store(sender_dialog_id_, storer); + } + if (has_message_id) { + td::store(message_id_, storer); + } + if (has_author_signature) { + td::store(author_signature_, storer); + } + if (has_sender_name) { + td::store(sender_name_, storer); + } +} + +template +void MessageOrigin::parse(ParserT &parser) { + bool has_sender_user_id; + bool has_sender_dialog_id; + bool has_message_id; + bool has_author_signature; + bool has_sender_name; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_sender_user_id); + PARSE_FLAG(has_sender_dialog_id); + PARSE_FLAG(has_message_id); + PARSE_FLAG(has_author_signature); + PARSE_FLAG(has_sender_name); + END_PARSE_FLAGS(); + if (has_sender_user_id) { + td::parse(sender_user_id_, parser); + } + if (has_sender_dialog_id) { + td::parse(sender_dialog_id_, parser); + CHECK(sender_dialog_id_.get_type() == DialogType::Channel); + } + if (has_message_id) { + td::parse(message_id_, parser); + } + if (has_author_signature) { + td::parse(author_signature_, parser); + } + if (has_sender_name) { + td::parse(sender_name_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp index f31487b0..91649f8d 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp @@ -225,8 +225,8 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { auto message_sender = get_min_message_sender_object(td_, dialog_id, "GetMessageReactionsListQuery"); if (message_sender != nullptr) { - reactions.push_back(td_api::make_object(reaction_type.get_reaction_type_object(), - std::move(message_sender), reaction->date_)); + reactions.push_back(td_api::make_object( + reaction_type.get_reaction_type_object(), std::move(message_sender), reaction->my_, reaction->date_)); } } @@ -297,17 +297,13 @@ MessageReaction::MessageReaction(ReactionType reaction_type, int32 choose_count, } } -void MessageReaction::add_recent_chooser_dialog_id(DialogId dialog_id) { +void MessageReaction::add_my_recent_chooser_dialog_id(DialogId dialog_id) { CHECK(!my_recent_chooser_dialog_id_.is_valid()); my_recent_chooser_dialog_id_ = dialog_id; - recent_chooser_dialog_ids_.insert(recent_chooser_dialog_ids_.begin(), dialog_id); - if (recent_chooser_dialog_ids_.size() > MAX_RECENT_CHOOSERS + 1) { - LOG(ERROR) << "Have " << recent_chooser_dialog_ids_.size() << " recent reaction choosers"; - recent_chooser_dialog_ids_.resize(MAX_RECENT_CHOOSERS + 1); - } + add_to_top(recent_chooser_dialog_ids_, MAX_RECENT_CHOOSERS + 1, dialog_id); } -bool MessageReaction::remove_recent_chooser_dialog_id() { +bool MessageReaction::remove_my_recent_chooser_dialog_id() { if (my_recent_chooser_dialog_id_.is_valid()) { bool is_removed = td::remove(recent_chooser_dialog_ids_, my_recent_chooser_dialog_id_); CHECK(is_removed); @@ -350,8 +346,8 @@ void MessageReaction::set_as_chosen(DialogId my_dialog_id, bool have_recent_choo is_chosen_ = true; choose_count_++; if (have_recent_choosers) { - remove_recent_chooser_dialog_id(); - add_recent_chooser_dialog_id(my_dialog_id); + remove_my_recent_chooser_dialog_id(); + add_my_recent_chooser_dialog_id(my_dialog_id); } } @@ -360,7 +356,7 @@ void MessageReaction::unset_as_chosen() { is_chosen_ = false; choose_count_--; - remove_recent_chooser_dialog_id(); + remove_my_recent_chooser_dialog_id(); } void MessageReaction::set_my_recent_chooser_dialog_id(DialogId my_dialog_id) { @@ -381,12 +377,14 @@ td_api::object_ptr MessageReaction::get_message_reactio UserId peer_user_id) const { CHECK(!is_empty()); + td_api::object_ptr used_sender; vector> recent_choosers; 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) { + used_sender = get_min_message_sender_object(td, DialogId(my_user_id), "get_message_reaction_object"); recent_choosers.push_back(std::move(recent_chooser)); } } @@ -400,6 +398,9 @@ td_api::object_ptr MessageReaction::get_message_reactio 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) { + if (is_chosen() && dialog_id == my_recent_chooser_dialog_id_) { + used_sender = get_min_message_sender_object(td, dialog_id, "get_message_reaction_object"); + } recent_choosers.push_back(std::move(recent_chooser)); if (recent_choosers.size() == MAX_RECENT_CHOOSERS) { break; @@ -408,7 +409,7 @@ td_api::object_ptr MessageReaction::get_message_reactio } } return td_api::make_object(reaction_type_.get_reaction_type_object(), choose_count_, - is_chosen_, std::move(recent_choosers)); + is_chosen_, std::move(used_sender), std::move(recent_choosers)); } bool operator==(const MessageReaction &lhs, const MessageReaction &rhs) { @@ -563,7 +564,7 @@ MessageReaction *MessageReactions::get_reaction(const ReactionType &reaction_typ } const MessageReaction *MessageReactions::get_reaction(const ReactionType &reaction_type) const { - for (auto &added_reaction : reactions_) { + for (const auto &added_reaction : reactions_) { if (added_reaction.get_reaction_type() == reaction_type) { return &added_reaction; } @@ -602,8 +603,8 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { } } -bool MessageReactions::add_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, - bool have_recent_choosers) { +bool MessageReactions::add_my_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, + bool have_recent_choosers) { vector new_chosen_reaction_order = get_chosen_reaction_types(); auto added_reaction = get_reaction(reaction_type); @@ -628,7 +629,7 @@ bool MessageReactions::add_reaction(const ReactionType &reaction_type, bool is_b while (new_chosen_reaction_order.size() > max_reaction_count) { auto index = new_chosen_reaction_order[0] == reaction_type ? 1 : 0; CHECK(static_cast(index) < new_chosen_reaction_order.size()); - bool is_removed = do_remove_reaction(new_chosen_reaction_order[index]); + bool is_removed = do_remove_my_reaction(new_chosen_reaction_order[index]); CHECK(is_removed); new_chosen_reaction_order.erase(new_chosen_reaction_order.begin() + index); } @@ -645,8 +646,8 @@ bool MessageReactions::add_reaction(const ReactionType &reaction_type, bool is_b return true; } -bool MessageReactions::remove_reaction(const ReactionType &reaction_type, DialogId my_dialog_id) { - if (do_remove_reaction(reaction_type)) { +bool MessageReactions::remove_my_reaction(const ReactionType &reaction_type, DialogId my_dialog_id) { + if (do_remove_my_reaction(reaction_type)) { if (!chosen_reaction_order_.empty()) { bool is_removed = td::remove(chosen_reaction_order_, reaction_type); CHECK(is_removed); @@ -654,7 +655,7 @@ bool MessageReactions::remove_reaction(const ReactionType &reaction_type, Dialog // if the user isn't a Premium user, then max_reaction_count could be reduced from 3 to 1 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]); + is_removed = do_remove_my_reaction(chosen_reaction_order_[0]); CHECK(is_removed); chosen_reaction_order_.erase(chosen_reaction_order_.begin()); } @@ -673,7 +674,7 @@ bool MessageReactions::remove_reaction(const ReactionType &reaction_type, Dialog return false; } -bool MessageReactions::do_remove_reaction(const ReactionType &reaction_type) { +bool MessageReactions::do_remove_my_reaction(const ReactionType &reaction_type) { for (auto it = reactions_.begin(); it != reactions_.end(); ++it) { auto &message_reaction = *it; if (message_reaction.get_reaction_type() == reaction_type) { @@ -714,7 +715,7 @@ void MessageReactions::fix_chosen_reaction() { if (!reaction.is_chosen() && reaction.get_my_recent_chooser_dialog_id().is_valid()) { my_dialog_id = reaction.get_my_recent_chooser_dialog_id(); LOG(WARNING) << "Fix recent chosen reaction in " << *this; - reaction.remove_recent_chooser_dialog_id(); + reaction.remove_my_recent_chooser_dialog_id(); } } if (!my_dialog_id.is_valid()) { @@ -722,7 +723,7 @@ void MessageReactions::fix_chosen_reaction() { } for (auto &reaction : reactions_) { if (reaction.is_chosen() && !reaction.get_my_recent_chooser_dialog_id().is_valid()) { - reaction.add_recent_chooser_dialog_id(my_dialog_id); + reaction.add_my_recent_chooser_dialog_id(my_dialog_id); } } } @@ -742,7 +743,7 @@ vector MessageReactions::get_chosen_reaction_types() const { } vector reaction_order; - for (auto &reaction : reactions_) { + for (const auto &reaction : reactions_) { if (reaction.is_chosen()) { reaction_order.push_back(reaction.get_reaction_type()); } diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.h b/lib/tgchat/ext/td/td/telegram/MessageReaction.h index c453da04..67e0c5a2 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.h +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.h @@ -63,9 +63,9 @@ class MessageReaction { void unset_as_chosen(); - void add_recent_chooser_dialog_id(DialogId dialog_id); + void add_my_recent_chooser_dialog_id(DialogId dialog_id); - bool remove_recent_chooser_dialog_id(); + bool remove_my_recent_chooser_dialog_id(); void update_from(const MessageReaction &old_reaction); @@ -167,9 +167,10 @@ struct MessageReactions { void update_from(const MessageReactions &old_reactions); - bool add_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, bool have_recent_choosers); + bool add_my_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, + bool have_recent_choosers); - bool remove_reaction(const ReactionType &reaction_type, DialogId my_dialog_id); + bool remove_my_reaction(const ReactionType &reaction_type, DialogId my_dialog_id); void sort_reactions(const FlatHashMap &active_reaction_pos); @@ -203,7 +204,7 @@ struct MessageReactions { void parse(ParserT &parser); private: - bool do_remove_reaction(const ReactionType &reaction_type); + bool do_remove_my_reaction(const ReactionType &reaction_type); }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions); diff --git a/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.cpp b/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.cpp index fc368d02..3ae14b1f 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.cpp @@ -6,8 +6,6 @@ // #include "td/telegram/MessageReplyHeader.h" -#include "td/telegram/MessageFullId.h" -#include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StoryId.h" #include "td/telegram/UserId.h" @@ -16,7 +14,7 @@ namespace td { -MessageReplyHeader::MessageReplyHeader(tl_object_ptr &&reply_header_ptr, +MessageReplyHeader::MessageReplyHeader(Td *td, tl_object_ptr &&reply_header_ptr, DialogId dialog_id, MessageId message_id, int32 date, bool can_have_thread) { if (reply_header_ptr == nullptr) { return; @@ -34,43 +32,33 @@ MessageReplyHeader::MessageReplyHeader(tl_object_ptrget_id() == telegram_api::messageReplyHeader::ID); auto reply_header = telegram_api::move_object_as(reply_header_ptr); - if (reply_header->reply_to_scheduled_) { - reply_to_message_id_ = MessageId(ScheduledServerMessageId(reply_header->reply_to_msg_id_), date); - if (message_id.is_scheduled()) { - auto reply_to_peer_id = std::move(reply_header->reply_to_peer_id_); - if (reply_to_peer_id != nullptr) { - reply_in_dialog_id_ = DialogId(reply_to_peer_id); - LOG(ERROR) << "Receive reply to " << MessageFullId{reply_in_dialog_id_, reply_to_message_id_} << " in " - << MessageFullId{dialog_id, message_id}; - reply_to_message_id_ = MessageId(); - reply_in_dialog_id_ = DialogId(); - } - } else { - LOG(ERROR) << "Receive reply to " << reply_to_message_id_ << " in " << MessageFullId{dialog_id, message_id}; - reply_to_message_id_ = MessageId(); - } - } else { - reply_to_message_id_ = MessageId(ServerMessageId(reply_header->reply_to_msg_id_)); - auto reply_to_peer_id = std::move(reply_header->reply_to_peer_id_); - if (reply_to_peer_id != nullptr) { - reply_in_dialog_id_ = DialogId(reply_to_peer_id); - if (!reply_in_dialog_id_.is_valid()) { - LOG(ERROR) << "Receive reply in invalid " << to_string(reply_to_peer_id); - reply_to_message_id_ = MessageId(); - reply_in_dialog_id_ = DialogId(); - } - if (reply_in_dialog_id_ == dialog_id) { - reply_in_dialog_id_ = DialogId(); // just in case + + if (!message_id.is_scheduled() && can_have_thread) { + if ((reply_header->flags_ & telegram_api::messageReplyHeader::REPLY_TO_TOP_ID_MASK) != 0) { + top_thread_message_id_ = MessageId(ServerMessageId(reply_header->reply_to_top_id_)); + if (!top_thread_message_id_.is_valid()) { + LOG(ERROR) << "Receive " << to_string(reply_header); + top_thread_message_id_ = MessageId(); } } - if (reply_to_message_id_.is_valid() && !message_id.is_scheduled() && !reply_in_dialog_id_.is_valid() && - can_have_thread) { - if ((reply_header->flags_ & telegram_api::messageReplyHeader::REPLY_TO_TOP_ID_MASK) != 0) { - top_thread_message_id_ = MessageId(ServerMessageId(reply_header->reply_to_top_id_)); + is_topic_message_ = reply_header->forum_topic_; + } + + replied_message_info_ = RepliedMessageInfo(td, std::move(reply_header), dialog_id, message_id, date); + + if (!message_id.is_scheduled() && can_have_thread) { + if (!top_thread_message_id_.is_valid()) { + auto same_chat_reply_to_message_id = replied_message_info_.get_same_chat_reply_to_message_id(); + if (same_chat_reply_to_message_id.is_valid()) { + CHECK(same_chat_reply_to_message_id.is_server()); + top_thread_message_id_ = same_chat_reply_to_message_id; } else { - top_thread_message_id_ = reply_to_message_id_; + is_topic_message_ = false; } - is_topic_message_ = reply_header->forum_topic_; + } + if (top_thread_message_id_ >= message_id) { + LOG(ERROR) << "Receive top thread " << top_thread_message_id_ << " in " << message_id << " in " << dialog_id; + top_thread_message_id_ = MessageId(); } } } diff --git a/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.h b/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.h index 5f596768..00e79c19 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.h +++ b/lib/tgchat/ext/td/td/telegram/MessageReplyHeader.h @@ -8,6 +8,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/RepliedMessageInfo.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/telegram_api.h" @@ -15,9 +16,11 @@ namespace td { +class Td; + struct MessageReplyHeader { - MessageId reply_to_message_id_; - DialogId reply_in_dialog_id_; + RepliedMessageInfo replied_message_info_; + MessageId top_thread_message_id_; bool is_topic_message_ = false; @@ -27,7 +30,7 @@ struct MessageReplyHeader { MessageReplyHeader() = default; - MessageReplyHeader(tl_object_ptr &&reply_header_ptr, DialogId dialog_id, + MessageReplyHeader(Td *td, tl_object_ptr &&reply_header_ptr, DialogId dialog_id, MessageId message_id, int32 date, bool can_have_thread); }; diff --git a/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp b/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp index 865b58ee..b38e6942 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp @@ -166,13 +166,10 @@ bool MessageReplyInfo::add_reply(DialogId replier_dialog_id, MessageId reply_mes } } - td::remove(recent_replier_dialog_ids_, replier_dialog_id); if (diff > 0) { - recent_replier_dialog_ids_.insert(recent_replier_dialog_ids_.begin(), replier_dialog_id); - if (recent_replier_dialog_ids_.size() > MAX_RECENT_REPLIERS) { - recent_replier_dialog_ids_.pop_back(); - } + add_to_top(recent_replier_dialog_ids_, MAX_RECENT_REPLIERS, replier_dialog_id); } else { + td::remove(recent_replier_dialog_ids_, replier_dialog_id); auto max_repliers = static_cast(reply_count_); if (recent_replier_dialog_ids_.size() > max_repliers) { recent_replier_dialog_ids_.resize(max_repliers); diff --git a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp index 12434b05..c946f342 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp @@ -42,6 +42,7 @@ #include "td/telegram/MessageDb.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageEntity.hpp" +#include "td/telegram/MessageOrigin.hpp" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessageReaction.hpp" #include "td/telegram/MessageReplyInfo.hpp" @@ -60,6 +61,7 @@ #include "td/telegram/PollId.h" #include "td/telegram/PublicDialogType.h" #include "td/telegram/ReactionManager.h" +#include "td/telegram/RepliedMessageInfo.hpp" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReplyMarkup.hpp" #include "td/telegram/ReportReason.h" @@ -2663,60 +2665,6 @@ class GetRecentLocationsQuery final : public Td::ResultHandler { } }; -class GetMessagePublicForwardsQuery final : public Td::ResultHandler { - Promise> promise_; - DialogId dialog_id_; - int32 limit_; - - public: - explicit GetMessagePublicForwardsQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(DcId dc_id, MessageFullId message_full_id, int32 offset_date, DialogId offset_dialog_id, - ServerMessageId offset_message_id, int32 limit) { - dialog_id_ = message_full_id.get_dialog_id(); - limit_ = limit; - - auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id); - CHECK(input_peer != nullptr); - - auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()); - CHECK(input_channel != nullptr); - - send_query(G()->net_query_creator().create( - telegram_api::stats_getMessagePublicForwards( - std::move(input_channel), message_full_id.get_message_id().get_server_message_id().get(), offset_date, - std::move(input_peer), offset_message_id.get(), limit), - {}, dc_id)); - } - - 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 info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetMessagePublicForwardsQuery"); - td_->messages_manager_->get_channel_differences_if_needed( - std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), - promise = std::move(promise_)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - auto info = result.move_as_ok(); - send_closure(actor_id, &MessagesManager::on_get_message_public_forwards, info.total_count, - std::move(info.messages), info.next_rate, std::move(promise)); - } - })); - } - - void on_error(Status status) final { - td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePublicForwardsQuery"); - promise_.set_error(std::move(status)); - } -}; - class HidePromoDataQuery final : public Td::ResultHandler { DialogId dialog_id_; @@ -3154,7 +3102,7 @@ class SendMessageQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageInputReplyTo input_reply_to, MessageId top_thread_message_id, int32 schedule_date, + const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, bool is_copy, int64 random_id, NetQueryRef *send_query_ref) { @@ -3181,8 +3129,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*/, - false /*ignored*/, std::move(input_peer), std::move(reply_to), text, random_id, std::move(reply_markup), - std::move(entities), schedule_date, std::move(as_input_peer)), + false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), 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")) { @@ -3296,7 +3244,7 @@ class SendInlineBotResultQuery final : public Td::ResultHandler { public: NetQueryRef send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageInputReplyTo input_reply_to, MessageId top_thread_message_id, int32 schedule_date, + const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, int64 random_id, int64 query_id, const string &result_id) { random_id_ = random_id; dialog_id_ = dialog_id; @@ -3354,7 +3302,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageInputReplyTo input_reply_to, MessageId top_thread_message_id, int32 schedule_date, + const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, vector &&file_ids, vector> &&input_single_media, bool is_copy) { for (auto &single_media : input_single_media) { @@ -3383,9 +3331,9 @@ class SendMultiMediaQuery final : public Td::ResultHandler { // no quick ack, because file reference errors are very likely to happen send_query(G()->net_query_creator().create( telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, std::move(input_peer), - std::move(reply_to), std::move(input_single_media), schedule_date, - std::move(as_input_peer)), + false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_peer), std::move(reply_to), std::move(input_single_media), + schedule_date, std::move(as_input_peer)), {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo}, {dialog_id, MessageContentType::Photo}})); } @@ -3472,7 +3420,7 @@ class SendMediaQuery final : public Td::ResultHandler { public: void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, - tl_object_ptr as_input_peer, MessageInputReplyTo input_reply_to, + tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, @@ -3506,7 +3454,7 @@ class SendMediaQuery final : public Td::ResultHandler { auto query = G()->net_query_creator().create( telegram_api::messages_sendMedia( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - std::move(input_peer), std::move(reply_to), std::move(input_media), text, random_id, + false /*ignored*/, std::move(input_peer), std::move(reply_to), 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_) { @@ -3718,7 +3666,7 @@ class EditMessageQuery final : public Td::ResultHandler { void send(int32 flags, DialogId dialog_id, MessageId message_id, const string &text, vector> &&entities, - tl_object_ptr &&input_media, + tl_object_ptr &&input_media, bool invert_media, tl_object_ptr &&reply_markup, int32 schedule_date) { dialog_id_ = dialog_id; message_id_ = message_id; @@ -3744,6 +3692,9 @@ class EditMessageQuery final : public Td::ResultHandler { if (input_media != nullptr) { flags |= telegram_api::messages_editMessage::MEDIA_MASK; } + if (invert_media) { + flags |= telegram_api::messages_editMessage::INVERT_MEDIA_MASK; + } if (schedule_date != 0) { flags |= telegram_api::messages_editMessage::SCHEDULE_DATE_MASK; } @@ -3751,9 +3702,9 @@ class EditMessageQuery final : public Td::ResultHandler { int32 server_message_id = schedule_date != 0 ? message_id.get_scheduled_server_message_id().get() : message_id.get_server_message_id().get(); send_query(G()->net_query_creator().create( - telegram_api::messages_editMessage(flags, false /*ignored*/, std::move(input_peer), server_message_id, text, - std::move(input_media), std::move(reply_markup), std::move(entities), - schedule_date), + telegram_api::messages_editMessage(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + server_message_id, text, std::move(input_media), std::move(reply_markup), + std::move(entities), schedule_date), {{dialog_id}})); } @@ -3790,7 +3741,7 @@ class EditInlineMessageQuery final : public Td::ResultHandler { void send(int32 flags, tl_object_ptr input_bot_inline_message_id, const string &text, vector> &&entities, - tl_object_ptr &&input_media, + tl_object_ptr &&input_media, bool invert_media, tl_object_ptr &&reply_markup) { CHECK(input_bot_inline_message_id != nullptr); @@ -3810,12 +3761,15 @@ class EditInlineMessageQuery final : public Td::ResultHandler { if (input_media != nullptr) { flags |= telegram_api::messages_editInlineBotMessage::MEDIA_MASK; } + if (invert_media) { + flags |= telegram_api::messages_editInlineBotMessage::INVERT_MEDIA_MASK; + } auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id)); send_query(G()->net_query_creator().create( - telegram_api::messages_editInlineBotMessage(flags, false /*ignored*/, std::move(input_bot_inline_message_id), - text, std::move(input_media), std::move(reply_markup), - std::move(entities)), + telegram_api::messages_editInlineBotMessage( + flags, false /*ignored*/, false /*ignored*/, std::move(input_bot_inline_message_id), text, + std::move(input_media), std::move(reply_markup), std::move(entities)), {}, dc_id)); } @@ -3973,7 +3927,9 @@ class SendScreenshotNotificationQuery final : public Td::ResultHandler { send_query(G()->net_query_creator().create( telegram_api::messages_sendScreenshotNotification( - std::move(input_peer), telegram_api::make_object(0, 0, 0), random_id), + std::move(input_peer), + telegram_api::make_object(0, 0, 0, nullptr, string(), Auto()), + random_id), {{dialog_id, MessageContentType::Text}})); } @@ -4680,21 +4636,18 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_edit_date = edit_date > 0; bool has_random_id = random_id != 0; bool is_forwarded = forward_info != nullptr; - bool is_reply = reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled(); bool is_reply_to_random_id = reply_to_random_id != 0; bool is_via_bot = via_bot_user_id.is_valid(); bool has_view_count = view_count > 0; bool has_reply_markup = reply_markup != nullptr; bool has_ttl = ttl != 0; bool has_author_signature = !author_signature.empty(); - bool has_forward_author_signature = is_forwarded && !forward_info->author_signature.empty(); bool has_media_album_id = media_album_id != 0; bool has_forward_from = is_forwarded && (forward_info->from_dialog_id.is_valid() || forward_info->from_message_id.is_valid()); bool has_send_date = message_id.is_yet_unsent() && send_date != 0; bool has_flags2 = true; bool has_notification_id = notification_id.is_valid(); - bool has_forward_sender_name = is_forwarded && !forward_info->sender_name.empty(); bool has_send_error_code = send_error_code != 0; bool has_real_forward_from = real_forward_from_dialog_id.is_valid() && real_forward_from_message_id.is_valid(); bool has_legacy_layer = legacy_layer != 0; @@ -4703,7 +4656,6 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_forward_count = forward_count > 0; bool has_reply_info = !reply_info.is_empty(); bool has_sender_dialog_id = sender_dialog_id.is_valid(); - bool has_reply_in_dialog_id = is_reply && reply_in_dialog_id.is_valid(); bool has_top_thread_message_id = top_thread_message_id.is_valid(); bool has_thread_draft_message = thread_draft_message != nullptr; bool has_local_thread_message_ids = !local_thread_message_ids.empty(); @@ -4719,6 +4671,9 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_available_reactions_generation = available_reactions_generation != 0; bool has_history_generation = history_generation != 0; bool is_reply_to_story = reply_to_story_full_id != StoryFullId(); + bool has_forward_origin = is_forwarded; + bool has_input_reply_to = !message_id.is_any_server() && input_reply_to.is_valid(); + bool has_replied_message_info = !replied_message_info.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); @@ -4734,14 +4689,14 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(has_edit_date); STORE_FLAG(has_random_id); STORE_FLAG(is_forwarded); - STORE_FLAG(is_reply); + STORE_FLAG(false); STORE_FLAG(is_reply_to_random_id); STORE_FLAG(is_via_bot); STORE_FLAG(has_view_count); STORE_FLAG(has_reply_markup); STORE_FLAG(has_ttl); STORE_FLAG(has_author_signature); - STORE_FLAG(has_forward_author_signature); + STORE_FLAG(false); STORE_FLAG(had_reply_markup); STORE_FLAG(contains_unread_mention); STORE_FLAG(has_media_album_id); @@ -4756,7 +4711,7 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(has_notification_id); STORE_FLAG(is_mention_notification_disabled); STORE_FLAG(had_forward_info); - STORE_FLAG(has_forward_sender_name); + STORE_FLAG(false); STORE_FLAG(has_send_error_code); STORE_FLAG(hide_via_bot); STORE_FLAG(is_bot_start_message); @@ -4770,7 +4725,7 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(has_forward_count); STORE_FLAG(has_reply_info); STORE_FLAG(has_sender_dialog_id); - STORE_FLAG(has_reply_in_dialog_id); + STORE_FLAG(false); STORE_FLAG(has_top_thread_message_id); STORE_FLAG(has_thread_draft_message); STORE_FLAG(has_local_thread_message_ids); @@ -4795,6 +4750,10 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(is_topic_message); STORE_FLAG(has_history_generation); STORE_FLAG(is_reply_to_story); + STORE_FLAG(has_forward_origin); + STORE_FLAG(invert_media); + STORE_FLAG(has_input_reply_to); + STORE_FLAG(has_replied_message_info); END_STORE_FLAGS(); } @@ -4813,16 +4772,8 @@ void MessagesManager::Message::store(StorerT &storer) const { store(random_id, storer); } if (is_forwarded) { - store(forward_info->sender_user_id, storer); + store(forward_info->origin, storer); store(forward_info->date, storer); - store(forward_info->sender_dialog_id, storer); - store(forward_info->message_id, storer); - if (has_forward_author_signature) { - store(forward_info->author_signature, storer); - } - if (has_forward_sender_name) { - store(forward_info->sender_name, storer); - } if (has_forward_from) { store(forward_info->from_dialog_id, storer); store(forward_info->from_message_id, storer); @@ -4835,9 +4786,6 @@ void MessagesManager::Message::store(StorerT &storer) const { store(real_forward_from_dialog_id, storer); store(real_forward_from_message_id, storer); } - if (is_reply) { - store(reply_to_message_id, storer); - } if (is_reply_to_random_id) { store(reply_to_random_id, storer); } @@ -4882,9 +4830,6 @@ void MessagesManager::Message::store(StorerT &storer) const { if (has_sender_dialog_id) { store(sender_dialog_id, storer); } - if (has_reply_in_dialog_id) { - store(reply_in_dialog_id, storer); - } if (has_top_thread_message_id) { store(top_thread_message_id, storer); } @@ -4925,6 +4870,12 @@ void MessagesManager::Message::store(StorerT &storer) const { if (is_reply_to_story) { store(reply_to_story_full_id, storer); } + if (has_input_reply_to) { + store(input_reply_to, storer); + } + if (has_replied_message_info) { + store(replied_message_info, storer); + } } // do not forget to resolve message dependencies @@ -4937,20 +4888,20 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_edit_date; bool has_random_id; bool is_forwarded; - bool is_reply; + bool legacy_is_reply; bool is_reply_to_random_id; bool is_via_bot; bool has_view_count; bool has_reply_markup; bool has_ttl; bool has_author_signature; - bool has_forward_author_signature; + bool legacy_has_forward_author_signature; bool has_media_album_id; bool has_forward_from; bool has_send_date; bool has_flags2; bool has_notification_id = false; - bool has_forward_sender_name = false; + bool legacy_has_forward_sender_name = false; bool has_send_error_code = false; bool has_real_forward_from = false; bool has_legacy_layer = false; @@ -4959,7 +4910,7 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_forward_count = false; bool has_reply_info = false; bool has_sender_dialog_id = false; - bool has_reply_in_dialog_id = false; + bool legacy_has_reply_in_dialog_id = false; bool has_top_thread_message_id = false; bool has_thread_draft_message = false; bool has_local_thread_message_ids = false; @@ -4974,6 +4925,9 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_available_reactions_generation = false; bool has_history_generation = false; bool is_reply_to_story = false; + bool has_forward_origin = false; + bool has_input_reply_to = false; + bool has_replied_message_info = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); @@ -4989,14 +4943,14 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(has_edit_date); PARSE_FLAG(has_random_id); PARSE_FLAG(is_forwarded); - PARSE_FLAG(is_reply); + PARSE_FLAG(legacy_is_reply); PARSE_FLAG(is_reply_to_random_id); PARSE_FLAG(is_via_bot); PARSE_FLAG(has_view_count); PARSE_FLAG(has_reply_markup); PARSE_FLAG(has_ttl); PARSE_FLAG(has_author_signature); - PARSE_FLAG(has_forward_author_signature); + PARSE_FLAG(legacy_has_forward_author_signature); PARSE_FLAG(had_reply_markup); PARSE_FLAG(contains_unread_mention); PARSE_FLAG(has_media_album_id); @@ -5011,7 +4965,7 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(has_notification_id); PARSE_FLAG(is_mention_notification_disabled); PARSE_FLAG(had_forward_info); - PARSE_FLAG(has_forward_sender_name); + PARSE_FLAG(legacy_has_forward_sender_name); PARSE_FLAG(has_send_error_code); PARSE_FLAG(hide_via_bot); PARSE_FLAG(is_bot_start_message); @@ -5025,7 +4979,7 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(has_forward_count); PARSE_FLAG(has_reply_info); PARSE_FLAG(has_sender_dialog_id); - PARSE_FLAG(has_reply_in_dialog_id); + PARSE_FLAG(legacy_has_reply_in_dialog_id); PARSE_FLAG(has_top_thread_message_id); PARSE_FLAG(has_thread_draft_message); PARSE_FLAG(has_local_thread_message_ids); @@ -5050,6 +5004,10 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(is_topic_message); PARSE_FLAG(has_history_generation); PARSE_FLAG(is_reply_to_story); + PARSE_FLAG(has_forward_origin); + PARSE_FLAG(invert_media); + PARSE_FLAG(has_input_reply_to); + PARSE_FLAG(has_replied_message_info); END_PARSE_FLAGS(); } @@ -5077,15 +5035,27 @@ void MessagesManager::Message::parse(ParserT &parser) { } if (is_forwarded) { forward_info = make_unique(); - parse(forward_info->sender_user_id, parser); - parse(forward_info->date, parser); - parse(forward_info->sender_dialog_id, parser); - parse(forward_info->message_id, parser); - if (has_forward_author_signature) { - parse(forward_info->author_signature, parser); - } - if (has_forward_sender_name) { - parse(forward_info->sender_name, parser); + if (has_forward_origin) { + parse(forward_info->origin, parser); + parse(forward_info->date, parser); + } else { + UserId forward_sender_user_id; + DialogId forward_sender_dialog_id; + MessageId forward_message_id; + string forward_author_signature; + string forward_sender_name; + parse(forward_sender_user_id, parser); + parse(forward_info->date, parser); + parse(forward_sender_dialog_id, parser); + parse(forward_message_id, parser); + if (legacy_has_forward_author_signature) { + parse(forward_author_signature, parser); + } + if (legacy_has_forward_sender_name) { + parse(forward_sender_name, parser); + } + forward_info->origin = MessageOrigin(forward_sender_user_id, forward_sender_dialog_id, forward_message_id, + std::move(forward_author_signature), std::move(forward_sender_name)); } if (has_forward_from) { parse(forward_info->from_dialog_id, parser); @@ -5100,8 +5070,9 @@ void MessagesManager::Message::parse(ParserT &parser) { parse(real_forward_from_dialog_id, parser); parse(real_forward_from_message_id, parser); } - if (is_reply) { - parse(reply_to_message_id, parser); + MessageId legacy_reply_to_message_id; + if (legacy_is_reply) { + parse(legacy_reply_to_message_id, parser); } if (is_reply_to_random_id) { parse(reply_to_random_id, parser); @@ -5147,8 +5118,9 @@ void MessagesManager::Message::parse(ParserT &parser) { if (has_sender_dialog_id) { parse(sender_dialog_id, parser); } - if (has_reply_in_dialog_id) { - parse(reply_in_dialog_id, parser); + DialogId legacy_reply_in_dialog_id; + if (legacy_has_reply_in_dialog_id) { + parse(legacy_reply_in_dialog_id, parser); } if (has_top_thread_message_id) { parse(top_thread_message_id, parser); @@ -5190,6 +5162,20 @@ void MessagesManager::Message::parse(ParserT &parser) { if (is_reply_to_story) { parse(reply_to_story_full_id, parser); } + if (has_input_reply_to) { + parse(input_reply_to, parser); + } else if (!message_id.is_any_server()) { + if (reply_to_story_full_id.is_valid()) { + input_reply_to = MessageInputReplyTo(reply_to_story_full_id); + } else if (legacy_reply_to_message_id.is_valid()) { + input_reply_to = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), FormattedText()}; + } + } + if (has_replied_message_info) { + parse(replied_message_info, parser); + } else { + replied_message_info = RepliedMessageInfo::legacy(legacy_reply_to_message_id, legacy_reply_in_dialog_id); + } CHECK(content != nullptr); is_content_secret |= @@ -5922,6 +5908,13 @@ MessagesManager::~MessagesManager() { previous_repaired_read_inbox_max_message_id_, failed_to_load_dialogs_); } +MessagesManager::AddDialogData::AddDialogData(int32 dependent_dialog_count, unique_ptr &&last_message, + unique_ptr &&draft_message) + : dependent_dialog_count_(dependent_dialog_count) + , last_message_(std::move(last_message)) + , draft_message_(std::move(draft_message)) { +} + void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { if (G()->close_flag()) { return; @@ -6604,10 +6597,11 @@ void MessagesManager::on_update_service_notification(tl_object_ptrget_type()); if (update->popup_) { - send_closure(G()->td(), &Td::send_update, - td_api::make_object( - update->type_, get_message_content_object(content.get(), td_, owner_dialog_id, date, - is_content_secret, true, -1))); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + update->type_, get_message_content_object(content.get(), td_, owner_dialog_id, date, is_content_secret, + true, -1, update->invert_media_, disable_web_page_preview))); } if (has_date && is_user) { Dialog *d = get_service_notifications_dialog(); @@ -6622,6 +6616,7 @@ void MessagesManager::on_update_service_notification(tl_object_ptrttl = ttl; new_message->disable_web_page_preview = disable_web_page_preview; new_message->is_content_secret = is_content_secret; + new_message->invert_media = update->invert_media_; new_message->content = std::move(content); bool need_update = true; @@ -7151,6 +7146,7 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int } reactions->sort_reactions(active_reaction_pos_); reactions->fix_chosen_reaction(); + reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); } bool need_update_reactions = has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get()); @@ -7686,7 +7682,7 @@ void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_ active_dialog_action_timeout_.cancel_timeout(dialog_id.get()); } } else { - if (date < G()->unix_time_cached() - DIALOG_ACTION_TIMEOUT - 60) { + if (date < G()->unix_time() - DIALOG_ACTION_TIMEOUT - 60) { LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date; return; } @@ -8114,10 +8110,9 @@ bool MessagesManager::update_dialog_notification_settings(DialogId dialog_id, return need_update.need_update_server; } -void MessagesManager::schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until) { - auto now = G()->unix_time_cached(); - if (!use_default && mute_until >= now && mute_until < now + 366 * 86400) { - dialog_unmute_timeout_.set_timeout_in(dialog_id.get(), mute_until - now + 1); +void MessagesManager::schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until, int32 unix_time) { + if (!use_default && mute_until >= unix_time && mute_until < unix_time + 366 * 86400) { + dialog_unmute_timeout_.set_timeout_in(dialog_id.get(), mute_until - unix_time + 1); } else { dialog_unmute_timeout_.cancel_timeout(dialog_id.get()); } @@ -8136,7 +8131,7 @@ void MessagesManager::update_dialog_unmute_timeout(Dialog *d, bool &old_use_defa CHECK(d != nullptr); CHECK(old_mute_until >= 0); - schedule_dialog_unmute(d->dialog_id, new_use_default, new_mute_until); + schedule_dialog_unmute(d->dialog_id, new_use_default, new_mute_until, G()->unix_time()); auto scope = get_dialog_notification_setting_scope(d->dialog_id); auto scope_mute_until = td_->notification_settings_manager_->get_scope_mute_until(scope); @@ -8262,11 +8257,11 @@ void MessagesManager::on_dialog_unmute(DialogId dialog_id) { return; } - auto now = G()->unix_time(); - if (d->notification_settings.mute_until > now) { - LOG(ERROR) << "Failed to unmute " << dialog_id << " in " << now << ", will be unmuted in " - << d->notification_settings.mute_until; - schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until); + auto unix_time = G()->unix_time(); + if (d->notification_settings.mute_until > unix_time) { + LOG(INFO) << "Failed to unmute " << dialog_id << " in " << unix_time << ", will be unmuted in " + << d->notification_settings.mute_until; + schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until, unix_time); return; } @@ -8483,13 +8478,15 @@ ChatReactions MessagesManager::get_dialog_active_reactions(const Dialog *d) cons } } +// affects all users 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 ChatReactions(); } - if (is_discussion_message(d->dialog_id, m)) { + auto dialog_id = d->dialog_id; + if (is_discussion_message(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 @@ -10574,54 +10571,6 @@ void MessagesManager::on_get_recent_locations(DialogId dialog_id, int32 limit, i promise.set_value(get_messages_object(total_count, dialog_id, result, true, "on_get_recent_locations")); } -void MessagesManager::on_get_message_public_forwards(int32 total_count, - vector> &&messages, - int32 next_rate, - Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - LOG(INFO) << "Receive " << messages.size() << " forwarded messages"; - vector> result; - int32 last_message_date = 0; - MessageId last_message_id; - DialogId last_dialog_id; - for (auto &message : messages) { - auto message_date = get_message_date(message); - auto message_id = MessageId::get_message_id(message, false); - auto dialog_id = DialogId::get_message_dialog_id(message); - if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) { - last_message_date = message_date; - last_message_id = message_id; - last_dialog_id = dialog_id; - } - - auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, - false, "get message public forwards"); - if (new_message_full_id != MessageFullId()) { - CHECK(dialog_id == new_message_full_id.get_dialog_id()); - result.push_back(get_message_object(new_message_full_id, "on_get_message_public_forwards")); - CHECK(result.back() != nullptr); - } else { - total_count--; - } - } - if (total_count < static_cast(result.size())) { - LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() - << " messages"; - total_count = static_cast(result.size()); - } - string next_offset; - if (!result.empty()) { - if (next_rate > 0) { - last_message_date = next_rate; - } - next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ',' - << last_message_id.get_server_message_id().get(); - } - - promise.set_value(td_api::make_object(total_count, std::move(result), next_offset)); -} - void MessagesManager::delete_messages_from_updates(const vector &message_ids, bool is_permanent) { FlatHashMap, DialogIdHash> deleted_message_ids; FlatHashMap need_update_dialog_pos; @@ -10819,7 +10768,7 @@ bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Messa return false; } if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 || - m->had_forward_info || (m->forward_info != nullptr && m->forward_info->message_id.is_valid())) { + m->had_forward_info || (m->forward_info != nullptr && m->forward_info->origin.is_channel_post())) { return false; } return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id); @@ -10839,7 +10788,7 @@ bool MessagesManager::can_delete_channel_message(const DialogParticipantStatus & return true; } - if (is_bot && G()->unix_time_cached() >= m->date + 2 * 86400) { + if (is_bot && G()->unix_time() >= m->date + 2 * 86400) { // bots can't delete messages older than 2 days return false; } @@ -10920,12 +10869,12 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c int64 revoke_time_limit = td_->option_manager_->get_option_integer("revoke_pm_time_limit", DEFAULT_REVOKE_TIME_LIMIT); - if (G()->unix_time_cached() - m->date < 86400 && content_type == MessageContentType::Dice) { + if (G()->unix_time() - m->date < 86400 && content_type == MessageContentType::Dice) { return false; } return ((m->is_outgoing && !is_service_message_content(content_type)) || (can_revoke_incoming && content_type != MessageContentType::ScreenshotTaken)) && - G()->unix_time_cached() - m->date <= revoke_time_limit; + G()->unix_time() - m->date <= revoke_time_limit; } case DialogType::Chat: { bool is_appointed_administrator = @@ -10934,7 +10883,7 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c td_->option_manager_->get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT); return ((m->is_outgoing && !is_service_message_content(content_type)) || is_appointed_administrator) && - G()->unix_time_cached() - m->date <= revoke_time_limit; + G()->unix_time() - m->date <= revoke_time_limit; } case DialogType::Channel: return true; // any server message that can be deleted will be deleted for all participants @@ -11836,8 +11785,7 @@ void MessagesManager::unload_dialog(DialogId dialog_id, int32 delay) { } bool has_left_to_unload_messages = false; - auto to_unload_message_ids = - find_unloadable_messages(d, G()->unix_time_cached() - delay, has_left_to_unload_messages); + auto to_unload_message_ids = find_unloadable_messages(d, G()->unix_time() - delay, has_left_to_unload_messages); vector unloaded_message_ids; vector> unloaded_messages; @@ -13129,6 +13077,7 @@ void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { vector reaction_message_ids; vector views_message_ids; vector extended_media_message_ids; + int32 newest_message_date = 0; for (auto &message_it : info->message_id_to_view_id) { Message *m = get_message_force(d, message_it.first, "on_update_viewed_messages_timeout"); CHECK(m != nullptr); @@ -13145,6 +13094,9 @@ void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { m->has_get_extended_media_query = true; extended_media_message_ids.push_back(m->message_id); } + if (m->date > newest_message_date) { + newest_message_date = m->date; + } } if (!reaction_message_ids.empty()) { @@ -13157,7 +13109,8 @@ void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { td_->create_handler()->send(dialog_id, std::move(extended_media_message_ids)); } - update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD); + int divisor = 5 - min(max(G()->unix_time() - newest_message_date, 0) / UPDATE_VIEWED_MESSAGES_PERIOD, 4); + update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD / divisor); } void MessagesManager::on_send_update_chat_read_inbox_timeout(DialogId dialog_id) { @@ -13196,26 +13149,32 @@ vector MessagesManager::get_message_user_ids(const Message *m) const { if (m->via_bot_user_id.is_valid()) { user_ids.push_back(m->via_bot_user_id); } - if (m->forward_info != nullptr && m->forward_info->sender_user_id.is_valid()) { - user_ids.push_back(m->forward_info->sender_user_id); + if (m->forward_info != nullptr) { + m->forward_info->origin.add_user_ids(user_ids); } append(user_ids, get_message_content_min_user_ids(td_, m->content.get())); + if (!m->replied_message_info.is_empty()) { + append(user_ids, m->replied_message_info.get_min_user_ids(td_)); + } return user_ids; } -vector MessagesManager::get_message_channel_ids(const Message *m) { +vector MessagesManager::get_message_channel_ids(const Message *m) const { vector channel_ids; if (m->sender_dialog_id.is_valid() && m->sender_dialog_id.get_type() == DialogType::Channel) { channel_ids.push_back(m->sender_dialog_id.get_channel_id()); } - if (m->forward_info != nullptr && m->forward_info->sender_dialog_id.is_valid() && - m->forward_info->sender_dialog_id.get_type() == DialogType::Channel) { - channel_ids.push_back(m->forward_info->sender_dialog_id.get_channel_id()); + if (m->forward_info != nullptr) { + m->forward_info->origin.add_channel_ids(channel_ids); } if (m->forward_info != nullptr && m->forward_info->from_dialog_id.is_valid() && m->forward_info->from_dialog_id.get_type() == DialogType::Channel) { channel_ids.push_back(m->forward_info->from_dialog_id.get_channel_id()); } + append(channel_ids, get_message_content_min_channel_ids(td_, m->content.get())); + if (!m->replied_message_info.is_empty()) { + append(channel_ids, m->replied_message_info.get_min_channel_ids(td_)); + } return channel_ids; } @@ -13361,6 +13320,7 @@ void MessagesManager::ttl_loop(double now) { TtlNode *ttl_node = TtlNode::from_heap_node(ttl_heap_.pop()); auto message_full_id = ttl_node->message_full_id_; auto dialog_id = message_full_id.get_dialog_id(); + CHECK(dialog_id.is_valid()); if (dialog_id.get_type() == DialogType::SecretChat || ttl_node->by_ttl_period_) { to_delete[dialog_id].push_back(message_full_id.get_message_id()); } else { @@ -13426,11 +13386,12 @@ void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *m, bool is remove_message_notification_id(d, m, true, true); update_message_contains_unread_mention(d, m, false, "on_message_ttl_expired_impl"); remove_message_unread_reactions(d, m, "on_message_ttl_expired_impl"); - set_message_reply(d, m, MessageId(), is_message_in_dialog); + set_message_reply(d, m, MessageInputReplyTo(), is_message_in_dialog); m->noforwards = false; m->contains_mention = false; m->linked_top_thread_message_id = MessageId(); m->is_content_secret = false; + m->invert_media = false; } void MessagesManager::loop() { @@ -13637,8 +13598,7 @@ void MessagesManager::init() { } return dialog_id; }); - if (std::any_of(r_dialog_ids.begin(), r_dialog_ids.end(), - [](auto &r_dialog_id) { return r_dialog_id.is_error(); })) { + if (any_of(r_dialog_ids, [](const auto &r_dialog_id) { return r_dialog_id.is_error(); })) { LOG(ERROR) << "Can't parse " << it.second; reload_pinned_dialogs(DialogListId(folder_id), Auto()); } else { @@ -13701,8 +13661,7 @@ void MessagesManager::init() { } auto counts = transform(full_split(Slice(it.second)), [](Slice str) { return to_integer_safe(str); }); - if ((counts.size() != 4 && counts.size() != 6) || - std::any_of(counts.begin(), counts.end(), [](auto &c) { return c.is_error(); })) { + if ((counts.size() != 4 && counts.size() != 6) || any_of(counts, [](const auto &c) { return c.is_error(); })) { LOG(ERROR) << "Can't parse " << it.second; } else { DialogListId dialog_list_id(r_dialog_list_id.ok()); @@ -13729,7 +13688,7 @@ void MessagesManager::init() { } } } - } else { + } else if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->erase_by_prefix("pinned_dialog_ids"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("last_server_dialog_date"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_message_count"); @@ -14082,6 +14041,9 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId message_info.date = date; message_info.random_id = message->random_id_; message_info.ttl = min(message->ttl_, 0x7FFFFFFE); + message_info.has_unread_content = true; + message_info.is_silent = message->silent_; + message_info.media_album_id = message->grouped_id_; Dialog *d = get_dialog_force(message_info.dialog_id, "on_get_secret_message"); if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_get_secret_message")) { @@ -14099,22 +14061,18 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId pending_secret_message->load_data_multipromise.add_promise(Auto()); auto lock_promise = pending_secret_message->load_data_multipromise.get_promise(); - int32 flags = MESSAGE_FLAG_HAS_UNREAD_CONTENT | MESSAGE_FLAG_HAS_FROM_ID; if ((message->flags_ & secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK) != 0) { - message_info.reply_header.reply_to_message_id_ = - get_message_id_by_random_id(d, message->reply_to_random_id_, "on_get_secret_message"); - if (!message_info.reply_header.reply_to_message_id_.is_valid()) { + auto reply_to_message_id = get_message_id_by_random_id(d, message->reply_to_random_id_, "on_get_secret_message"); + if (!reply_to_message_id.is_valid()) { auto dialog_it = pending_secret_message_ids_.find(message_info.dialog_id); if (dialog_it != pending_secret_message_ids_.end()) { auto message_it = dialog_it->second.find(message->reply_to_random_id_); if (message_it != dialog_it->second.end()) { - message_info.reply_header.reply_to_message_id_ = message_it->second; + reply_to_message_id = message_it->second; } } } - } - if ((message->flags_ & secret_api::decryptedMessage::SILENT_MASK) != 0) { - flags |= MESSAGE_FLAG_IS_SILENT; + message_info.reply_header.replied_message_info_ = RepliedMessageInfo::legacy(reply_to_message_id); } if (!clean_input_string(message->via_bot_name_)) { @@ -14130,11 +14088,7 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId }); search_public_dialog(message->via_bot_name_, false, std::move(request_promise)); } - if ((message->flags_ & secret_api::decryptedMessage::GROUPED_ID_MASK) != 0 && message->grouped_id_ != 0) { - message_info.media_album_id = message->grouped_id_; - } - message_info.flags = flags; message_info.content = get_secret_message_content( td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_), message_info.dialog_id, pending_secret_message->load_data_multipromise, @@ -14152,7 +14106,6 @@ void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const stri auto user_id = dialog_id.get_user_id(); auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id); if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) { - message_info_ptr->flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT; message_info_ptr->via_bot_user_id = user_id; } } @@ -14176,7 +14129,6 @@ void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_i message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = random_id; - message_info.flags = MESSAGE_FLAG_HAS_FROM_ID; message_info.content = create_screenshot_taken_message_content(); Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_screenshot_taken"); @@ -14214,7 +14166,6 @@ void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, Us message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = random_id; - message_info.flags = MESSAGE_FLAG_HAS_FROM_ID; message_info.content = create_chat_set_ttl_message_content(ttl, UserId()); Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_ttl_changed"); @@ -14333,37 +14284,36 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.forward_header = std::move(message->fwd_from_); bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(message_info.dialog_id); - message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id, + message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); - if (message->flags_ & MESSAGE_FLAG_IS_SENT_VIA_BOT) { + if (message->flags_ & telegram_api::message::VIA_BOT_ID_MASK) { message_info.via_bot_user_id = UserId(message->via_bot_id_); if (!message_info.via_bot_user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << message_info.via_bot_user_id << " from " << source; message_info.via_bot_user_id = UserId(); } } - if (message->flags_ & MESSAGE_FLAG_HAS_INTERACTION_INFO) { - message_info.view_count = message->views_; - message_info.forward_count = message->forwards_; - } - if (message->flags_ & MESSAGE_FLAG_HAS_REPLY_INFO) { - message_info.reply_info = std::move(message->replies_); - } - if (message->flags_ & MESSAGE_FLAG_HAS_REACTIONS) { - message_info.reactions = std::move(message->reactions_); - } - if (message->flags_ & MESSAGE_FLAG_HAS_EDIT_DATE) { - message_info.edit_date = message->edit_date_; - } - if (message->flags_ & MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID) { - message_info.media_album_id = message->grouped_id_; - } - if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) { - message_info.ttl_period = message->ttl_period_; - } - message_info.flags = message->flags_; - bool is_content_read = (message->flags_ & MESSAGE_FLAG_HAS_UNREAD_CONTENT) == 0; - if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0)) { + message_info.view_count = message->views_; + message_info.forward_count = message->forwards_; + message_info.reply_info = std::move(message->replies_); + message_info.reactions = std::move(message->reactions_); + message_info.edit_date = message->edit_date_; + message_info.media_album_id = message->grouped_id_; + message_info.ttl_period = message->ttl_period_; + message_info.is_outgoing = message->out_; + message_info.is_silent = message->silent_; + message_info.is_channel_post = message->post_; + message_info.is_legacy = message->legacy_; + message_info.hide_edit_date = message->edit_hide_; + message_info.is_from_scheduled = message->from_scheduled_; + message_info.is_pinned = message->pinned_; + message_info.noforwards = message->noforwards_; + message_info.has_mention = message->mentioned_; + message_info.has_unread_content = message->media_unread_; + message_info.invert_media = message->invert_media_; + + bool is_content_read = !message_info.has_unread_content; + if (is_message_auto_read(message_info.dialog_id, message_info.is_outgoing)) { is_content_read = true; } if (is_scheduled) { @@ -14394,19 +14344,20 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.sender_dialog_id = message_info.dialog_id; } message_info.date = message->date_; - if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) { - message_info.ttl_period = message->ttl_period_; - } - message_info.flags = message->flags_; + message_info.ttl_period = message->ttl_period_; + message_info.is_outgoing = message->out_; + message_info.is_silent = message->silent_; + message_info.is_channel_post = message->post_; + message_info.is_legacy = message->legacy_; + message_info.has_mention = message->mentioned_; + message_info.has_unread_content = message->media_unread_; bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(message_info.dialog_id); - message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id, + message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); message_info.content = get_action_message_content(td_, std::move(message->action_), message_info.dialog_id, - message_info.reply_header.reply_in_dialog_id_, - message_info.reply_header.reply_to_message_id_); - message_info.reply_header.reply_in_dialog_id_ = DialogId(); - message_info.reply_header.reply_to_message_id_ = MessageId(); + message_info.reply_header.replied_message_info_); + message_info.reply_header.replied_message_info_ = RepliedMessageInfo(); message_info.reply_header.story_full_id_ = StoryFullId(); break; } @@ -14468,18 +14419,10 @@ std::pair> MessagesManager::creat is_channel_message = (dialog_type == DialogType::Channel); } - int32 flags = message_info.flags; - bool is_outgoing = (flags & MESSAGE_FLAG_IS_OUT) != 0; - bool is_silent = (flags & MESSAGE_FLAG_IS_SILENT) != 0; - bool is_channel_post = (flags & MESSAGE_FLAG_IS_POST) != 0; - bool is_legacy = (flags & MESSAGE_FLAG_IS_LEGACY) != 0; - bool hide_edit_date = (flags & MESSAGE_FLAG_HIDE_EDIT_DATE) != 0; - bool is_from_scheduled = (flags & MESSAGE_FLAG_IS_FROM_SCHEDULED) != 0; - bool is_pinned = (flags & MESSAGE_FLAG_IS_PINNED) != 0; - bool noforwards = (flags & MESSAGE_FLAG_NOFORWARDS) != 0; - LOG_IF(ERROR, is_channel_message != (dialog_type == DialogType::Channel)) << "Receive wrong is_channel_message for " << message_id << " in " << dialog_id; + + bool is_channel_post = message_info.is_channel_post; if (is_channel_post && !is_broadcast_channel(dialog_id)) { LOG(ERROR) << "Receive is_channel_post for " << message_id << " in " << dialog_id; is_channel_post = false; @@ -14494,10 +14437,11 @@ std::pair> MessagesManager::creat sender_dialog_id = DialogId(); } + bool is_outgoing = message_info.is_outgoing; bool supposed_to_be_outgoing = sender_user_id == my_id && !(dialog_id == my_dialog_id && !message_id.is_scheduled()); if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing) { - LOG(ERROR) << "Receive wrong message out flag: me is " << my_id << ", message is from " << sender_user_id - << ", flags = " << flags << " for " << message_id << " in " << dialog_id; + LOG(ERROR) << "Receive wrong out flag for " << message_id << " in " << dialog_id << ": me is " << my_id + << ", but message is from " << sender_user_id; is_outgoing = supposed_to_be_outgoing; /* @@ -14520,13 +14464,8 @@ std::pair> MessagesManager::creat date = 1; } - MessageId reply_to_message_id = message_info.reply_header.reply_to_message_id_; - DialogId reply_in_dialog_id = message_info.reply_header.reply_in_dialog_id_; MessageId top_thread_message_id = message_info.reply_header.top_thread_message_id_; bool is_topic_message = message_info.reply_header.is_topic_message_; - fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, reply_to_message_id); - fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, top_thread_message_id); - auto reply_to_story_full_id = message_info.reply_header.story_full_id_; if (reply_to_story_full_id != StoryFullId() && (dialog_type != DialogType::User || (reply_to_story_full_id.get_dialog_id() != my_dialog_id && @@ -14550,10 +14489,13 @@ std::pair> MessagesManager::creat if (content_type == MessageContentType::Sticker && get_message_content_sticker_type(td_, message_info.content.get()) == StickerType::CustomEmoji) { LOG(INFO) << "Replace emoji sticker with an empty message"; - message_info.content = create_text_message_content("Invalid sticker", {}, WebPageId()); + message_info.content = + create_text_message_content("Invalid sticker", {}, WebPageId(), false, false, false, string()); + message_info.invert_media = false; content_type = message_info.content->get_type(); } + bool hide_edit_date = message_info.hide_edit_date; if (hide_edit_date && td_->auth_manager_->is_bot()) { hide_edit_date = false; } @@ -14617,6 +14559,7 @@ std::pair> MessagesManager::creat if (reactions != nullptr) { reactions->sort_reactions(active_reaction_pos_); reactions->fix_chosen_reaction(); + reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); } bool has_forward_info = message_info.forward_header != nullptr; @@ -14627,17 +14570,19 @@ std::pair> MessagesManager::creat force_create_dialog(sender_dialog_id, "create_message", true); } + bool noforwards = message_info.noforwards; bool is_expired = content_type == MessageContentType::ExpiredPhoto || content_type == MessageContentType::ExpiredVideo; if (is_expired) { CHECK(ttl == 0); // self-destruct time is ignored/set to 0 if the message has already been expired - reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); + message_info.reply_header.replied_message_info_ = {}; reply_to_story_full_id = StoryFullId(); noforwards = false; is_content_secret = false; + message_info.invert_media = false; } + bool is_pinned = message_info.is_pinned; if (is_pinned && message_id.is_scheduled()) { LOG(ERROR) << "Receive pinned " << message_id << " in " << dialog_id; is_pinned = false; @@ -14655,9 +14600,8 @@ std::pair> MessagesManager::creat message->disable_web_page_preview = message_info.disable_web_page_preview; message->edit_date = edit_date; message->random_id = message_info.random_id; - message->forward_info = get_message_forward_info(std::move(message_info.forward_header), {dialog_id, message_id}); - message->reply_to_message_id = reply_to_message_id; - message->reply_in_dialog_id = reply_in_dialog_id; + message->forward_info = get_message_forward_info(std::move(message_info.forward_header)); + message->replied_message_info = std::move(message_info.reply_header.replied_message_info_); message->top_thread_message_id = top_thread_message_id; message->is_topic_message = is_topic_message; message->via_bot_user_id = via_bot_user_id; @@ -14666,18 +14610,17 @@ std::pair> MessagesManager::creat message->author_signature = std::move(message_info.author_signature); message->is_outgoing = is_outgoing; message->is_channel_post = is_channel_post; - message->contains_mention = - !is_outgoing && dialog_type != DialogType::User && !is_expired && - ((flags & MESSAGE_FLAG_HAS_MENTION) != 0 || content_type == MessageContentType::PinMessage) && - !td_->auth_manager_->is_bot(); + message->contains_mention = !is_outgoing && dialog_type != DialogType::User && !is_expired && + (message_info.has_mention || content_type == MessageContentType::PinMessage) && + !td_->auth_manager_->is_bot(); message->contains_unread_mention = !message_id.is_scheduled() && message_id.is_server() && message->contains_mention && - (flags & MESSAGE_FLAG_HAS_UNREAD_CONTENT) != 0 && + message_info.has_unread_content && (dialog_type == DialogType::Chat || (dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id))); - message->disable_notification = is_silent; + message->disable_notification = message_info.is_silent; message->is_content_secret = is_content_secret; message->hide_edit_date = hide_edit_date; - message->is_from_scheduled = is_from_scheduled; + message->is_from_scheduled = message_info.is_from_scheduled; message->is_pinned = is_pinned; message->noforwards = noforwards; message->interaction_info_update_date = G()->unix_time(); @@ -14685,7 +14628,8 @@ std::pair> MessagesManager::creat message->forward_count = forward_count; message->reply_info = std::move(reply_info); message->reactions = std::move(reactions); - message->legacy_layer = (is_legacy ? MTPROTO_LAYER : 0); + message->legacy_layer = (message_info.is_legacy ? MTPROTO_LAYER : 0); + message->invert_media = message_info.invert_media; message->content = std::move(message_info.content); message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false, message->contains_mention || dialog_type == DialogType::User); @@ -14702,8 +14646,7 @@ std::pair> MessagesManager::creat if (content_type != MessageContentType::Unsupported) { LOG(ERROR) << "Receive media group identifier " << message_info.media_album_id << " in " << message_id << " from " << dialog_id << " with content " - << oneline(to_string(get_message_content_object(message->content.get(), td_, dialog_id, - message->date, is_content_secret, false, -1))); + << oneline(to_string(get_message_message_content_object(dialog_id, message.get()))); } } else { message->media_album_id = message_info.media_album_id; @@ -14837,8 +14780,10 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const update_message(d, old_message.get(), std::move(new_message), false); new_message = std::move(old_message); - if (new_message->reply_to_message_id != MessageId() && new_message->reply_to_message_id.is_yet_unsent()) { - set_message_reply(d, new_message.get(), MessageId(), false); + auto reply_message_full_id = new_message->replied_message_info.get_reply_message_full_id(dialog_id); + auto reply_message_id = reply_message_full_id.get_message_id(); + if (reply_message_id.is_valid() && reply_message_id.is_yet_unsent()) { + set_message_reply(d, new_message.get(), MessageInputReplyTo(), false); } new_message->message_id = message_id; @@ -14958,10 +14903,7 @@ void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_messa d->is_last_message_deleted_locally = false; on_dialog_updated(d->dialog_id, "update_delete_last_message_date"); } - if (d->pending_last_message_date != 0) { - d->pending_last_message_date = 0; - d->pending_last_message_id = MessageId(); - } + d->pending_order = DEFAULT_ORDER; } void MessagesManager::set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id, @@ -15165,10 +15107,7 @@ void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) { on_dialog_updated(d->dialog_id, "set_dialog_is_empty"); } - if (d->pending_last_message_date != 0) { - d->pending_last_message_date = 0; - d->pending_last_message_id = MessageId(); - } + d->pending_order = DEFAULT_ORDER; if (d->last_database_message_id.is_valid()) { set_dialog_first_database_message_id(d, MessageId(), "set_dialog_is_empty"); set_dialog_last_database_message_id(d, MessageId(), "set_dialog_is_empty"); @@ -15232,35 +15171,31 @@ bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog * if (!list->are_pinned_dialogs_inited_) { return false; } - bool was_pinned = false; - for (size_t pos = 0; pos < list->pinned_dialogs_.size(); pos++) { - auto &pinned_dialog = list->pinned_dialogs_[pos]; - if (pinned_dialog.get_dialog_id() == d->dialog_id) { - // the dialog was already pinned - if (is_pinned) { - if (pos == 0) { - return false; - } - auto order = get_next_pinned_dialog_order(); - pinned_dialog = DialogDate(order, d->dialog_id); - std::rotate(list->pinned_dialogs_.begin(), list->pinned_dialogs_.begin() + pos, - list->pinned_dialogs_.begin() + pos + 1); - list->pinned_dialog_id_orders_[d->dialog_id] = order; - } else { - list->pinned_dialogs_.erase(list->pinned_dialogs_.begin() + pos); - list->pinned_dialog_id_orders_.erase(d->dialog_id); - } - was_pinned = true; - break; - } - } - if (!was_pinned) { - if (!is_pinned) { + auto dialog_id = d->dialog_id; + auto is_changed_dialog = [dialog_id](const DialogDate &dialog_date) { + return dialog_date.get_dialog_id() == dialog_id; + }; + if (is_pinned) { + if (!list->pinned_dialogs_.empty() && is_changed_dialog(list->pinned_dialogs_[0])) { return false; } auto order = get_next_pinned_dialog_order(); - list->pinned_dialogs_.insert(list->pinned_dialogs_.begin(), {order, d->dialog_id}); - list->pinned_dialog_id_orders_.emplace(d->dialog_id, order); + DialogDate dialog_date(order, dialog_id); + add_to_top_if(list->pinned_dialogs_, list->pinned_dialogs_.size() + 1, dialog_date, is_changed_dialog); + auto it = list->pinned_dialog_id_orders_.find(dialog_id); + if (it != list->pinned_dialog_id_orders_.end()) { + CHECK(list->pinned_dialogs_[0] != dialog_date); + list->pinned_dialogs_[0] = dialog_date; + it->second = order; + } else { + CHECK(list->pinned_dialogs_[0] == dialog_date); + list->pinned_dialog_id_orders_.emplace(dialog_id, order); + } + } else { + if (!td::remove_if(list->pinned_dialogs_, is_changed_dialog)) { + return false; + } + list->pinned_dialog_id_orders_.erase(dialog_id); } LOG(INFO) << "Set " << d->dialog_id << " is pinned in " << dialog_list_id << " to " << is_pinned; @@ -15415,9 +15350,11 @@ void MessagesManager::remove_dialog_mention_notifications(Dialog *d) { CHECK(!message_id.is_scheduled()); if (message_id != d->notification_info->pinned_message_notification_message_id_) { auto m = get_message_force(d, message_id, "remove_dialog_mention_notifications"); - if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) { + if (m != nullptr && m->notification_id.is_valid()) { CHECK(is_from_mention_notification_group(m)); - removed_notification_ids_set.insert(m->notification_id); + if (is_message_notification_active(d, m)) { + removed_notification_ids_set.insert(m->notification_id); + } } } } @@ -15496,6 +15433,7 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, bool is_content_changed = false; merge_message_contents(td_, m->content.get(), new_content.get(), need_message_changed_warning(m), dialog_id, false, is_content_changed, need_update); + compare_message_contents(td_, m->content.get(), new_content.get(), is_content_changed, need_update); if (is_content_changed || need_update) { reregister_message_content(td_, m->content.get(), new_content.get(), message_full_id, @@ -15760,8 +15698,8 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorcontacts_manager_.get(), std::move(dialog->draft_)), true, false); + need_update_dialog_pos |= + update_dialog_draft_message(d, get_draft_message(td_, std::move(dialog->draft_)), true, false); if (is_new) { bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0; if (last_message_id.is_valid() && !td_->auth_manager_->is_bot()) { @@ -17614,14 +17552,14 @@ void MessagesManager::block_message_sender_from_replies(MessageId message_id, bo return promise.set_error(Status::Error(400, "Wrong message specified")); } - UserId sender_user_id; + DialogId sender_dialog_id; if (m->forward_info != nullptr) { - sender_user_id = m->forward_info->sender_user_id; + sender_dialog_id = m->forward_info->origin.get_sender(); } vector message_ids; - if (need_delete_all_messages && sender_user_id.is_valid()) { - message_ids = find_dialog_messages(d, [sender_user_id](const Message *m) { - return !m->is_outgoing && m->forward_info != nullptr && m->forward_info->sender_user_id == sender_user_id; + if (need_delete_all_messages && sender_dialog_id.is_valid()) { + message_ids = find_dialog_messages(d, [sender_dialog_id](const Message *m) { + return !m->is_outgoing && m->forward_info != nullptr && m->forward_info->origin.get_sender() == sender_dialog_id; }); CHECK(td::contains(message_ids, message_id)); } else if (need_delete_message) { @@ -17778,16 +17716,17 @@ MessageFullId MessagesManager::get_replied_message_id(DialogId dialog_id, const } auto message_full_id = get_message_content_replied_message_id(dialog_id, m->content.get()); if (message_full_id.get_message_id().is_valid()) { - CHECK(m->reply_to_message_id == MessageId()); + CHECK(m->replied_message_info.is_empty()); return message_full_id; } - if (m->reply_to_message_id == MessageId()) { - if (m->top_thread_message_id.is_valid() && is_service_message_content(m->content->get_type())) { - return {dialog_id, m->top_thread_message_id}; - } - return {}; + auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id); + if (reply_message_full_id.get_message_id() != MessageId()) { + return reply_message_full_id; } - return {m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, m->reply_to_message_id}; + if (m->top_thread_message_id.is_valid() && is_service_message_content(m->content->get_type())) { + return {dialog_id, m->top_thread_message_id}; + } + return {}; } void MessagesManager::get_message_force_from_server(Dialog *d, MessageId message_id, Promise &&promise, @@ -18119,7 +18058,7 @@ td_api::object_ptr MessagesManager::get_message_threa if (can_send_message(d->dialog_id).is_ok()) { const Message *m = get_message_force(d, top_thread_message_id, "get_message_thread_info_object 2"); if (m != nullptr && !m->reply_info.is_comment_ && is_active_message_reply_info(d->dialog_id, m->reply_info)) { - draft_message = get_draft_message_object(m->thread_draft_message); + draft_message = get_draft_message_object(td_, m->thread_draft_message); } } } @@ -18571,10 +18510,12 @@ Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const M if (dialog_id.get_type() != DialogType::Channel) { auto forward_info = m->forward_info.get(); if (!can_message_content_have_media_timestamp(m->content.get()) || forward_info == nullptr || - forward_info->is_imported || is_forward_info_sender_hidden(forward_info) || - !forward_info->message_id.is_valid() || !m->forward_info->message_id.is_server() || - !forward_info->sender_dialog_id.is_valid() || - forward_info->sender_dialog_id.get_type() != DialogType::Channel) { + forward_info->is_imported) { + return Status::Error(400, "Message links are available only for messages in supergroups and channel chats"); + } + auto origin_message_full_id = forward_info->origin.get_message_full_id(); + auto origin_message_id = origin_message_full_id.get_message_id(); + if (!origin_message_id.is_valid() || !origin_message_id.is_server()) { return Status::Error(400, "Message links are available only for messages in supergroups and channel chats"); } return Status::OK(); @@ -18636,13 +18577,14 @@ Result> MessagesManager::get_message_link(MessageFullId auto message_id = m->message_id; if (dialog_id.get_type() != DialogType::Channel) { CHECK(m->forward_info != nullptr); - CHECK(m->forward_info->sender_dialog_id.get_type() == DialogType::Channel); - dialog_id = m->forward_info->sender_dialog_id; - message_id = m->forward_info->message_id; + auto origin_message_full_id = m->forward_info->origin.get_message_full_id(); + dialog_id = origin_message_full_id.get_dialog_id(); + message_id = origin_message_full_id.get_message_id(); + CHECK(dialog_id.get_type() == DialogType::Channel); for_group = false; in_message_thread = false; - auto channel_message = get_message({dialog_id, message_id}); + auto channel_message = get_message(origin_message_full_id); if (channel_message != nullptr && channel_message->media_album_id == 0) { for_group = true; // default is true } @@ -19974,6 +19916,8 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess if (is_dialog_history) { td_->sponsored_message_manager_->view_sponsored_message(dialog_id, message_id); continue; + } else if (source == MessageSource::HistoryPreview || source == MessageSource::Other) { + continue; } else { return Status::Error(400, "Can't view the message from the specified source"); } @@ -20193,7 +20137,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess auto &info = dialog_viewed_messages_[dialog_id]; CHECK(info != nullptr); CHECK(info->message_id_to_view_id.size() == info->recently_viewed_messages.size()); - constexpr size_t MAX_RECENTLY_VIEWED_MESSAGES = 50; + constexpr size_t MAX_RECENTLY_VIEWED_MESSAGES = 25; while (info->recently_viewed_messages.size() > MAX_RECENTLY_VIEWED_MESSAGES) { auto it = info->recently_viewed_messages.begin(); info->message_id_to_view_id.erase(it->second); @@ -20796,13 +20740,14 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * auto can_delete = can_delete_dialog(d); // TODO hide/show draft message when need_hide_dialog_draft_message changes auto draft_message = - !need_hide_dialog_draft_message(d->dialog_id) ? get_draft_message_object(d->draft_message) : nullptr; + !need_hide_dialog_draft_message(d->dialog_id) ? get_draft_message_object(td_, d->draft_message) : nullptr; auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); auto is_translatable = d->is_translatable && is_premium; auto block_list_id = BlockListId(d->is_blocked, d->is_blocked_for_stories); 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)), + get_dialog_accent_color_id_object(d->dialog_id), get_dialog_background_custom_emoji_id(d->dialog_id).get(), get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(), get_message_object(d->dialog_id, get_message(d, d->last_message_id), "get_chat_object"), get_chat_positions_object(d), get_default_message_sender_object(d), block_list_id.get_block_list_object(), @@ -22144,6 +22089,7 @@ void MessagesManager::add_active_live_location(MessageFullId message_full_id) { if (td_->auth_manager_->is_bot()) { return; } + CHECK(message_full_id.get_message_id().is_valid()); if (!active_live_location_message_full_ids_.insert(message_full_id).second) { return; } @@ -23830,6 +23776,11 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, if (td_->contacts_manager_->is_megagroup_channel(channel_id) && !td_->contacts_manager_->get_channel_status(channel_id).is_member() && can_send_message(d->dialog_id).is_error()) { + // can't use reactions if can't send messages to the group without joining + can_use_reactions = false; + } else if (is_anonymous_administrator(d->dialog_id, nullptr) && !is_broadcast_channel(d->dialog_id) && + !td_->contacts_manager_->get_channel_status(channel_id).is_creator()) { + // only creator can react as the chat can_use_reactions = false; } } @@ -23862,6 +23813,18 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, return active_reactions; } +DialogId MessagesManager::get_my_reaction_dialog_id(const Dialog *d) const { + auto my_dialog_id = get_my_dialog_id(); + auto reaction_dialog_id = + d->default_send_message_as_dialog_id.is_valid() ? d->default_send_message_as_dialog_id : my_dialog_id; + if (reaction_dialog_id == my_dialog_id && is_anonymous_administrator(d->dialog_id, nullptr) && + !is_broadcast_channel(d->dialog_id)) { + // react as the supergroup + return d->dialog_id; + } + return reaction_dialog_id; +} + void MessagesManager::add_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, bool is_big, bool add_to_recent, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); @@ -23886,9 +23849,8 @@ void MessagesManager::add_message_reaction(MessageFullId message_full_id, Reacti m->available_reactions_generation = d->available_reactions_generation; } - auto my_dialog_id = - d->default_send_message_as_dialog_id.is_valid() ? d->default_send_message_as_dialog_id : get_my_dialog_id(); - if (!m->reactions->add_reaction(reaction_type, is_big, my_dialog_id, have_recent_choosers)) { + LOG(INFO) << "Have message with " << *m->reactions; + if (!m->reactions->add_my_reaction(reaction_type, is_big, get_my_reaction_dialog_id(d), have_recent_choosers)) { return promise.set_value(Unit()); } @@ -23916,9 +23878,12 @@ void MessagesManager::remove_message_reaction(MessageFullId message_full_id, Rea return promise.set_error(Status::Error(400, "Invalid reaction specified")); } - auto my_dialog_id = - d->default_send_message_as_dialog_id.is_valid() ? d->default_send_message_as_dialog_id : get_my_dialog_id(); - if (m->reactions == nullptr || !m->reactions->remove_reaction(reaction_type, my_dialog_id)) { + if (m->reactions == nullptr) { + return promise.set_value(Unit()); + } + + LOG(INFO) << "Have message with " << *m->reactions; + if (!m->reactions->remove_my_reaction(reaction_type, get_my_reaction_dialog_id(d))) { return promise.set_value(Unit()); } @@ -23991,71 +23956,6 @@ void MessagesManager::on_read_message_reactions(DialogId dialog_id, vector> &&promise) { - auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), message_full_id, offset = std::move(offset), - limit, promise = std::move(promise)](Result r_dc_id) mutable { - if (r_dc_id.is_error()) { - return promise.set_error(r_dc_id.move_as_error()); - } - send_closure(actor_id, &MessagesManager::send_get_message_public_forwards_query, r_dc_id.move_as_ok(), - message_full_id, std::move(offset), limit, std::move(promise)); - }); - td_->contacts_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, - std::move(dc_id_promise)); -} - -void MessagesManager::send_get_message_public_forwards_query( - DcId dc_id, MessageFullId message_full_id, string offset, int32 limit, - Promise> &&promise) { - auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "send_get_message_public_forwards_query"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - const Message *m = get_message_force(d, message_full_id.get_message_id(), "send_get_message_public_forwards_query"); - if (m == nullptr) { - return promise.set_error(Status::Error(400, "Message not found")); - } - - if (m->view_count == 0 || m->forward_info != nullptr || m->had_forward_info || m->message_id.is_scheduled() || - !m->message_id.is_server()) { - return promise.set_error(Status::Error(400, "Message forwards are inaccessible")); - } - - if (limit <= 0) { - return promise.set_error(Status::Error(400, "Parameter limit must be positive")); - } - if (limit > MAX_SEARCH_MESSAGES) { - limit = MAX_SEARCH_MESSAGES; - } - - int32 offset_date = std::numeric_limits::max(); - DialogId offset_dialog_id; - ServerMessageId offset_server_message_id; - - if (!offset.empty()) { - auto parts = full_split(offset, ','); - if (parts.size() != 3) { - return promise.set_error(Status::Error(400, "Invalid offset specified")); - } - auto r_offset_date = to_integer_safe(parts[0]); - auto r_offset_dialog_id = to_integer_safe(parts[1]); - auto r_offset_server_message_id = to_integer_safe(parts[2]); - if (r_offset_date.is_error() || r_offset_dialog_id.is_error() || r_offset_server_message_id.is_error()) { - return promise.set_error(Status::Error(400, "Invalid offset specified")); - } - - offset_date = r_offset_date.ok(); - offset_dialog_id = DialogId(r_offset_dialog_id.ok()); - offset_server_message_id = ServerMessageId(r_offset_server_message_id.ok()); - } - - td_->create_handler(std::move(promise)) - ->send(dc_id, message_full_id, offset_date, offset_dialog_id, offset_server_message_id, limit); -} - Result MessagesManager::get_message_schedule_date( td_api::object_ptr &&scheduling_state) { if (scheduling_state == nullptr) { @@ -24097,9 +23997,13 @@ tl_object_ptr MessagesManager::get_message_sending_ auto error_code = m->send_error_code > 0 ? m->send_error_code : 400; auto need_another_sender = can_retry && error_code == 400 && m->send_error_message == CSlice("SEND_AS_PEER_INVALID"); + auto need_another_reply_quote = + can_retry && error_code == 400 && m->send_error_message == CSlice("QUOTE_TEXT_INVALID"); + auto need_drop_reply = + can_retry && error_code == 400 && m->send_error_message == CSlice("REPLY_MESSAGE_ID_INVALID"); return td_api::make_object( td_api::make_object(error_code, m->send_error_message), can_retry, need_another_sender, - max(m->try_resend_at - Time::now(), 0.0)); + need_another_reply_quote, need_drop_reply, max(m->try_resend_at - Time::now(), 0.0)); } return nullptr; } @@ -24111,6 +24015,14 @@ tl_object_ptr MessagesManager::get_message_sched return td_api::make_object(send_date); } +td_api::object_ptr MessagesManager::get_message_message_content_object(DialogId dialog_id, + const Message *m) const { + auto live_location_date = m->is_failed_to_send ? 0 : m->date; + return get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret, + need_skip_bot_commands(dialog_id, m), get_message_max_media_timestamp(m), + m->invert_media, m->disable_web_page_preview); +} + td_api::object_ptr MessagesManager::get_dialog_event_log_message_object( DialogId dialog_id, tl_object_ptr &&message, DialogId &sender_dialog_id) { auto dialog_message = create_message(parse_telegram_api_message(std::move(message), false, "dialog_event_log"), @@ -24125,19 +24037,22 @@ td_api::object_ptr MessagesManager::get_dialog_event_log_messag auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, "get_dialog_event_log_message_object"); auto forward_info = get_message_forward_info_object(m->forward_info); + auto import_info = get_message_import_info_object(m->forward_info); auto interaction_info = get_message_interaction_info_object(dialog_id, m); auto can_be_saved = can_save_message(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 edit_date = m->hide_edit_date ? 0 : m->edit_date; auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); - auto content = get_message_content_object(m->content.get(), td_, dialog_id, 0, false, true, - get_message_own_max_media_timestamp(m)); + auto content = + get_message_content_object(m->content.get(), td_, dialog_id, 0, false, true, + get_message_own_max_media_timestamp(m), m->invert_media, m->disable_web_page_preview); return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_dialog_event_log_message_object"), - nullptr, nullptr, m->is_outgoing, false, false, false, can_be_saved, false, false, false, false, false, false, - false, false, true, m->is_channel_post, m->is_topic_message, false, m->date, edit_date, std::move(forward_info), - std::move(interaction_info), Auto(), nullptr, 0, nullptr, 0.0, 0.0, via_bot_user_id, m->author_signature, 0, - get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); + nullptr, nullptr, m->is_outgoing, false, false, false, false, can_be_saved, false, false, false, false, false, + false, false, false, true, m->is_channel_post, m->is_topic_message, false, m->date, edit_date, + std::move(forward_info), std::move(import_info), std::move(interaction_info), Auto(), nullptr, 0, nullptr, 0.0, + 0.0, via_bot_user_id, m->author_signature, 0, get_restriction_reason_description(m->restriction_reasons), + std::move(content), std::move(reply_markup)); } tl_object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, const char *source) { @@ -24188,7 +24103,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial // i.e. a message is incoming only if it's a forwarded message with known from_dialog_id or with a hidden sender auto forward_info = m->forward_info.get(); is_outgoing = is_scheduled || forward_info == nullptr || - (!forward_info->from_dialog_id.is_valid() && !is_forward_info_sender_hidden(forward_info)); + (!forward_info->from_dialog_id.is_valid() && !forward_info->origin.is_sender_hidden()); } double ttl_expires_in = m->ttl_expires_at != 0 ? clamp(m->ttl_expires_at - Time::now(), 1e-3, m->ttl - 1e-3) : 0.0; @@ -24197,11 +24112,13 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, source); auto scheduling_state = is_scheduled ? get_message_scheduling_state_object(m->date) : nullptr; auto forward_info = get_message_forward_info_object(m->forward_info); + auto import_info = get_message_import_info_object(m->forward_info); auto interaction_info = get_message_interaction_info_object(dialog_id, m); auto unread_reactions = get_unread_reactions_object(dialog_id, m); auto can_be_saved = can_save_message(dialog_id, m); auto can_be_edited = can_edit_message(dialog_id, m, false, td_->auth_manager_->is_bot()); auto can_be_forwarded = can_be_saved && can_forward_message(dialog_id, m); + auto can_be_replied_in_another_chat = can_be_forwarded && m->message_id.is_server(); auto can_get_added_reactions = m->reactions != nullptr && m->reactions->can_get_added_reactions_; auto can_get_statistics = can_get_message_statistics(dialog_id, m); auto can_get_message_thread = get_top_thread_message_full_id(dialog_id, m, false).is_ok(); @@ -24210,15 +24127,13 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto can_report_reactions = 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 reply_to = [&]() -> td_api::object_ptr { - if (m->reply_to_message_id != MessageId()) { - if (m->is_topic_message && m->reply_in_dialog_id == DialogId() && - m->reply_to_message_id == m->top_thread_message_id && !td_->auth_manager_->is_bot()) { + if (!m->replied_message_info.is_empty()) { + if (m->is_topic_message && + m->replied_message_info.get_same_chat_reply_to_message_id() == m->top_thread_message_id && + !td_->auth_manager_->is_bot()) { return nullptr; } - return td_api::make_object( - get_chat_id_object(m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - "messageReplyToMessage"), - m->reply_to_message_id.get()); + return m->replied_message_info.get_message_reply_to_message_object(td_, dialog_id); } if (m->reply_to_story_full_id.is_valid()) { return td_api::make_object( @@ -24232,11 +24147,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto edit_date = m->hide_edit_date ? 0 : m->edit_date; auto has_timestamped_media = reply_to == nullptr || m->max_own_media_timestamp >= 0; auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); - auto live_location_date = m->is_failed_to_send ? 0 : m->date; - auto skip_bot_commands = need_skip_bot_commands(dialog_id, m); - auto max_media_timestamp = get_message_max_media_timestamp(m); - auto content = get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret, - skip_bot_commands, max_media_timestamp); + auto content = get_message_message_content_object(dialog_id, m); auto self_destruct_type = [&]() -> td_api::object_ptr { if (m->ttl == 0x7FFFFFFF) { return td_api::make_object(); @@ -24250,13 +24161,14 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_message_object"), std::move(sending_state), std::move(scheduling_state), is_outgoing, m->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, can_report_reactions, - has_timestamped_media, m->is_channel_post, m->is_topic_message, m->contains_unread_mention, date, edit_date, - std::move(forward_info), std::move(interaction_info), std::move(unread_reactions), std::move(reply_to), - top_thread_message_id, std::move(self_destruct_type), ttl_expires_in, auto_delete_in, via_bot_user_id, - m->author_signature, m->media_album_id, get_restriction_reason_description(m->restriction_reasons), - std::move(content), std::move(reply_markup)); + can_be_replied_in_another_chat, 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, can_report_reactions, has_timestamped_media, m->is_channel_post, + m->is_topic_message, m->contains_unread_mention, date, edit_date, std::move(forward_info), std::move(import_info), + std::move(interaction_info), std::move(unread_reactions), std::move(reply_to), top_thread_message_id, + std::move(self_destruct_type), ttl_expires_in, auto_delete_in, via_bot_user_id, m->author_signature, + m->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, @@ -24338,9 +24250,9 @@ DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId return d->default_send_message_as_dialog_id; } -MessageInputReplyTo MessagesManager::get_message_input_reply_to(DialogId dialog_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, - bool for_draft) { +MessageInputReplyTo MessagesManager::get_message_input_reply_to( + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + bool for_draft) { return get_message_input_reply_to(get_dialog(dialog_id), top_thread_message_id, std::move(reply_to), for_draft); } @@ -24354,9 +24266,9 @@ int64 MessagesManager::generate_new_random_id(const Dialog *d) { } unique_ptr MessagesManager::create_message_to_send( - Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo input_reply_to, const MessageSendOptions &options, - unique_ptr &&content, bool suppress_reply_info, unique_ptr forward_info, - bool is_copy, DialogId send_as_dialog_id) const { + Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, + unique_ptr &&content, bool invert_media, bool suppress_reply_info, + unique_ptr forward_info, bool is_copy, DialogId send_as_dialog_id) const { CHECK(d != nullptr); CHECK(content != nullptr); @@ -24368,28 +24280,28 @@ unique_ptr MessagesManager::create_message_to_send( int64 reply_to_random_id = 0; bool is_topic_message = false; - if (input_reply_to.message_id_.is_valid()) { + auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); + if (same_chat_reply_to_message_id.is_valid() || same_chat_reply_to_message_id.is_valid_scheduled()) { // the message was forcely preloaded in get_message_input_reply_to // it can be missing, only if it is unknown message from a push notification, or an unknown top thread message - const Message *reply_m = get_message(d, input_reply_to.message_id_); - if (reply_m != nullptr) { + const Message *reply_m = get_message(d, same_chat_reply_to_message_id); + if (reply_m != nullptr && !same_chat_reply_to_message_id.is_scheduled()) { if (reply_m->top_thread_message_id.is_valid()) { top_thread_message_id = reply_m->top_thread_message_id; } is_topic_message = reply_m->is_topic_message; } - if (dialog_type == DialogType::SecretChat || input_reply_to.message_id_.is_yet_unsent()) { + if (dialog_type == DialogType::SecretChat || same_chat_reply_to_message_id.is_yet_unsent()) { if (reply_m != nullptr) { reply_to_random_id = reply_m->random_id; } else { CHECK(dialog_type == DialogType::SecretChat); CHECK(top_thread_message_id == MessageId()); - input_reply_to.message_id_ = MessageId(); + input_reply_to = MessageInputReplyTo(); } } - } else if (top_thread_message_id.is_valid()) { - LOG(ERROR) << "Creating a message in thread of " << top_thread_message_id << " in " << d->dialog_id - << " without reply"; + } + if (top_thread_message_id.is_valid()) { const Message *top_m = get_message(d, top_thread_message_id); if (top_m != nullptr) { is_topic_message = top_m->is_topic_message; @@ -24429,9 +24341,10 @@ unique_ptr MessagesManager::create_message_to_send( } m->send_date = G()->unix_time(); m->date = is_scheduled ? options.schedule_date : m->send_date; - m->reply_to_message_id = input_reply_to.message_id_; + m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); + m->reply_to_story_full_id = input_reply_to.get_story_full_id(); + m->input_reply_to = std::move(input_reply_to); m->reply_to_random_id = reply_to_random_id; - m->reply_to_story_full_id = input_reply_to.story_full_id_; m->top_thread_message_id = top_thread_message_id; m->is_topic_message = is_topic_message; m->is_channel_post = is_channel_post; @@ -24457,7 +24370,7 @@ unique_ptr MessagesManager::create_message_to_send( if (is_channel_post) { return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id()); } - return !input_reply_to.is_valid(); + return !m->input_reply_to.is_valid(); }()) { m->reply_info.reply_count_ = 0; if (is_channel_post) { @@ -24470,6 +24383,7 @@ unique_ptr MessagesManager::create_message_to_send( } } m->content = std::move(content); + m->invert_media = invert_media; m->forward_info = std::move(forward_info); m->is_copy = is_copy || m->forward_info != nullptr; m->sending_id = options.sending_id; @@ -24494,13 +24408,14 @@ unique_ptr MessagesManager::create_message_to_send( } MessagesManager::Message *MessagesManager::get_message_to_send( - Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo input_reply_to, const MessageSendOptions &options, - unique_ptr &&content, bool *need_update_dialog_pos, bool suppress_reply_info, + Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, + unique_ptr &&content, bool invert_media, bool *need_update_dialog_pos, bool suppress_reply_info, unique_ptr forward_info, bool is_copy, DialogId send_as_dialog_id) { d->was_opened = true; - auto message = create_message_to_send(d, top_thread_message_id, input_reply_to, options, std::move(content), - suppress_reply_info, std::move(forward_info), is_copy, send_as_dialog_id); + auto message = + create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), options, std::move(content), + invert_media, suppress_reply_info, std::move(forward_info), is_copy, send_as_dialog_id); MessageId message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date) : get_next_yet_unsent_message_id(d); @@ -24573,95 +24488,127 @@ MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId return message_id; } -MessageInputReplyTo MessagesManager::get_message_input_reply_to(Dialog *d, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, - bool for_draft) { +MessageInputReplyTo MessagesManager::get_message_input_reply_to( + Dialog *d, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + bool for_draft) { CHECK(d != nullptr); if (top_thread_message_id.is_valid() && !have_message_force(d, top_thread_message_id, "get_message_input_reply_to 1")) { LOG(INFO) << "Have reply in the thread of unknown " << top_thread_message_id; } - if (reply_to != nullptr && reply_to->get_id() == td_api::messageReplyToStory::ID) { - CHECK(!for_draft); - auto reply_to_story = td_api::move_object_as(reply_to); - auto story_id = StoryId(reply_to_story->story_id_); - auto sender_dialog_id = DialogId(reply_to_story->story_sender_chat_id_); - if (d->dialog_id != sender_dialog_id || sender_dialog_id.get_type() != DialogType::User) { - LOG(INFO) << "Ignore reply to story from " << sender_dialog_id << " in a wrong " << d->dialog_id; - return {}; - } - if (!story_id.is_server()) { - LOG(INFO) << "Ignore reply to invalid " << story_id; - return {}; - } - return {MessageId(), StoryFullId(sender_dialog_id, story_id)}; - } - MessageId message_id; - if (reply_to != nullptr && reply_to->get_id() == td_api::messageReplyToMessage::ID) { - auto reply_to_message = td_api::move_object_as(reply_to); - message_id = MessageId(reply_to_message->message_id_); - } - if (!message_id.is_valid()) { - if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() && - top_thread_message_id.is_server()) { - return {top_thread_message_id, StoryFullId()}; - } - return {}; - } - message_id = get_persistent_message_id(d, message_id); - if (message_id == MessageId(ServerMessageId(1)) && d->dialog_id.get_type() == DialogType::Channel) { - return {}; - } - const Message *m = get_message_force(d, message_id, "get_message_input_reply_to 2"); - if (m == nullptr || m->message_id.is_yet_unsent() || - (m->message_id.is_local() && d->dialog_id.get_type() != DialogType::SecretChat)) { - if (message_id.is_server() && d->dialog_id.get_type() != DialogType::SecretChat && - message_id > d->last_new_message_id && - (d->notification_info != nullptr && message_id <= d->notification_info->max_push_notification_message_id_)) { - // allow to reply yet unreceived server message - return {message_id, StoryFullId()}; - } + if (reply_to == nullptr) { if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { - return {top_thread_message_id, StoryFullId()}; + return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText()}; } - - // TODO local replies to local messages can be allowed - // TODO replies to yet unsent messages can be allowed with special handling of them on application restart return {}; } - return {m->message_id, StoryFullId()}; -} - -void MessagesManager::fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id, - DialogId reply_in_dialog_id, - MessageId &reply_to_message_id) const { - if (!reply_to_message_id.is_valid()) { - if (reply_to_message_id.is_valid_scheduled()) { - CHECK(message_id.is_scheduled()); - CHECK(reply_in_dialog_id == DialogId()); - if (message_id == reply_to_message_id) { - LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id; - reply_to_message_id = MessageId(); + switch (reply_to->get_id()) { + case td_api::inputMessageReplyToStory::ID: { + if (for_draft) { + return {}; } - return; + auto reply_to_story = td_api::move_object_as(reply_to); + auto story_id = StoryId(reply_to_story->story_id_); + auto sender_dialog_id = DialogId(reply_to_story->story_sender_chat_id_); + if (d->dialog_id != sender_dialog_id || sender_dialog_id.get_type() != DialogType::User) { + LOG(INFO) << "Ignore reply to story from " << sender_dialog_id << " in a wrong " << d->dialog_id; + return {}; + } + if (!story_id.is_server()) { + LOG(INFO) << "Ignore reply to invalid " << story_id; + return {}; + } + return MessageInputReplyTo{StoryFullId(sender_dialog_id, story_id)}; } - if (reply_to_message_id != MessageId()) { - LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id; - reply_to_message_id = MessageId(); + case td_api::inputMessageReplyToMessage::ID: { + auto reply_to_message = td_api::move_object_as(reply_to); + auto message_id = MessageId(reply_to_message->message_id_); + if (!message_id.is_valid()) { + if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() && + top_thread_message_id.is_server()) { + return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText()}; + } + return {}; + } + auto *reply_d = d; + auto reply_dialog_id = DialogId(reply_to_message->chat_id_); + if (reply_dialog_id != DialogId()) { + reply_d = get_dialog_force(reply_dialog_id, "get_message_input_reply_to"); + if (reply_d == nullptr) { + return {}; + } + if (d->dialog_id.get_type() == DialogType::SecretChat) { + return {}; + } + } + message_id = get_persistent_message_id(reply_d, message_id); + if (message_id == MessageId(ServerMessageId(1)) && reply_d->dialog_id.get_type() == DialogType::Channel) { + return {}; + } + FormattedText quote; + auto r_quote = get_formatted_text(td_, get_my_dialog_id(), std::move(reply_to_message->quote_), + td_->auth_manager_->is_bot(), true, true, true); + if (r_quote.is_ok() && d->dialog_id.get_type() != DialogType::SecretChat) { + quote = r_quote.move_as_ok(); + } + const Message *m = get_message_force(reply_d, message_id, "get_message_input_reply_to 2"); + if (m == nullptr || m->message_id.is_yet_unsent() || + (m->message_id.is_local() && reply_d->dialog_id.get_type() != DialogType::SecretChat)) { + if (message_id.is_server() && reply_d->dialog_id.get_type() != DialogType::SecretChat && + reply_dialog_id == DialogId() && message_id > reply_d->last_new_message_id && + (reply_d->notification_info != nullptr && + message_id <= reply_d->notification_info->max_push_notification_message_id_)) { + // allow to reply yet unreceived server message in the same chat + return MessageInputReplyTo{message_id, reply_dialog_id, std::move(quote)}; + } + if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { + return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText()}; + } + LOG(INFO) << "Can't find " << message_id << " in " << reply_d->dialog_id; + + // TODO local replies to local messages can be allowed + // TODO replies to yet unsent messages can be allowed with special handling of them on application restart + return {}; + } + if (reply_dialog_id != DialogId() && (!can_forward_message(reply_dialog_id, m) || !m->message_id.is_server())) { + LOG(INFO) << "Can't reply in another chat " << m->message_id << " in " << reply_d->dialog_id; + return {}; + } + return MessageInputReplyTo{m->message_id, reply_dialog_id, std::move(quote)}; } - return; + default: + UNREACHABLE(); + return {}; } +} - if (!message_id.is_scheduled() && !reply_in_dialog_id.is_valid() && - ((reply_to_message_id > message_id && !has_qts_messages(dialog_id)) || reply_to_message_id == message_id)) { - LOG(ERROR) << "Receive reply to wrong " << reply_to_message_id << " in " << message_id << " in " << dialog_id; - reply_to_message_id = MessageId(); +MessagesManager::ForwardedMessageInfo MessagesManager::get_forwarded_message_info(MessageFullId message_full_id) { + ForwardedMessageInfo result; + auto *m = get_message_force(message_full_id, "get_forwarded_message_info"); + if (m == nullptr || m->message_id.is_scheduled()) { + return result; } + auto dialog_id = message_full_id.get_dialog_id(); + result.origin_date_ = m->forward_info != nullptr ? m->forward_info->date : m->date; + result.origin_ = get_forwarded_message_origin(dialog_id, m); + result.content_ = dup_message_content(td_, get_my_dialog_id(), m->content.get(), MessageContentDupType::Forward, + MessageCopyOptions()); + return result; +} + +const MessageInputReplyTo *MessagesManager::get_message_input_reply_to(const Message *m) { + CHECK(m != nullptr); + CHECK(!m->message_id.is_any_server()); + return &m->input_reply_to; } vector MessagesManager::get_message_file_ids(const Message *m) const { CHECK(m != nullptr); - return get_message_content_file_ids(m->content.get(), td_); + auto file_ids = get_message_content_file_ids(m->content.get(), td_); + if (!m->replied_message_info.is_empty()) { + append(file_ids, m->replied_message_info.get_file_ids(td_)); + } + return file_ids; } void MessagesManager::cancel_upload_message_content_files(const MessageContent *content) { @@ -24706,36 +24653,45 @@ void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) m->send_message_log_event_id = 0; } - if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) { - CHECK(m->reply_in_dialog_id == DialogId()); - auto it = replied_by_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); - CHECK(it != replied_by_yet_unsent_messages_.end()); - it->second--; - CHECK(it->second >= 0); - if (it->second == 0) { - replied_by_yet_unsent_messages_.erase(it); - } - } - if ((m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_valid_scheduled()) && - m->reply_to_message_id.is_yet_unsent()) { - CHECK(m->reply_in_dialog_id == DialogId()); - auto it = replied_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id}); - CHECK(it != replied_yet_unsent_messages_.end()); - size_t erased_count = it->second.erase(m->message_id); - CHECK(erased_count > 0); - if (it->second.empty()) { - replied_yet_unsent_messages_.erase(it); + { + const auto *input_reply_to = get_message_input_reply_to(m); + if (input_reply_to != nullptr && !input_reply_to->is_empty()) { + auto replied_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); + auto replied_message_id = replied_message_full_id.get_message_id(); + if (replied_message_id.is_valid() || replied_message_id.is_valid_scheduled()) { + if (!replied_message_id.is_yet_unsent()) { + auto it = replied_by_yet_unsent_messages_.find(replied_message_full_id); + CHECK(it != replied_by_yet_unsent_messages_.end()); + it->second--; + CHECK(it->second >= 0); + if (it->second == 0) { + replied_by_yet_unsent_messages_.erase(it); + } + } else { + auto it = replied_yet_unsent_messages_.find(replied_message_full_id); + CHECK(it != replied_yet_unsent_messages_.end()); + size_t erased_count = it->second.erase({dialog_id, m->message_id}); + CHECK(erased_count > 0); + if (it->second.empty()) { + replied_yet_unsent_messages_.erase(it); + } + } + } } } { auto it = replied_yet_unsent_messages_.find({dialog_id, m->message_id}); if (it != replied_yet_unsent_messages_.end()) { - Dialog *d = get_dialog(dialog_id); - for (auto message_id : it->second) { - auto replied_m = get_message(d, message_id); + for (auto message_full_id : it->second) { + auto reply_d = get_dialog(message_full_id.get_dialog_id()); + CHECK(reply_d != nullptr); + auto replied_m = get_message(reply_d, message_full_id.get_message_id()); CHECK(replied_m != nullptr); - CHECK(replied_m->reply_to_message_id == m->message_id); - set_message_reply(d, replied_m, replied_m->top_thread_message_id, true); + const auto *input_reply_to = get_message_input_reply_to(replied_m); + CHECK(input_reply_to != nullptr); + CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, m->message_id)); + set_message_reply(reply_d, replied_m, + MessageInputReplyTo{replied_m->top_thread_message_id, DialogId(), FormattedText()}, true); } replied_yet_unsent_messages_.erase(it); } @@ -24804,15 +24760,15 @@ bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing) } void MessagesManager::add_message_dependencies(Dependencies &dependencies, const Message *m) { + auto is_bot = td_->auth_manager_->is_bot(); dependencies.add(m->sender_user_id); dependencies.add_dialog_and_dependencies(m->sender_dialog_id); - dependencies.add_dialog_and_dependencies(m->reply_in_dialog_id); + m->replied_message_info.add_dependencies(dependencies, is_bot); dependencies.add_dialog_and_dependencies(m->reply_to_story_full_id.get_dialog_id()); dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id); dependencies.add(m->via_bot_user_id); if (m->forward_info != nullptr) { - dependencies.add(m->forward_info->sender_user_id); - dependencies.add_dialog_and_dependencies(m->forward_info->sender_dialog_id); + m->forward_info->origin.add_dependencies(dependencies); dependencies.add_dialog_and_dependencies(m->forward_info->from_dialog_id); } for (const auto &replier_min_channel : m->reply_info.replier_min_channels_) { @@ -24826,7 +24782,7 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, const m->reactions->add_min_channels(td_); m->reactions->add_dependencies(dependencies); } - add_message_content_dependencies(dependencies, m->content.get(), td_->auth_manager_->is_bot()); + add_message_content_dependencies(dependencies, m->content.get(), is_bot); add_reply_markup_dependencies(dependencies, m->reply_markup.get()); add_draft_message_dependencies(dependencies, m->thread_draft_message); } @@ -24987,7 +24943,7 @@ class MessagesManager::SendMessageLogEvent { }; Result> MessagesManager::send_message( - DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content) { if (input_message_content == nullptr) { @@ -25020,11 +24976,25 @@ Result> MessagesManager::send_message( // there must be no errors after get_message_to_send call + auto content = dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send, + MessageCopyOptions()); bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, top_thread_message_id, input_reply_to, message_send_options, - dup_message_content(td_, dialog_id, message_content.content.get(), - MessageContentDupType::Send, MessageCopyOptions()), - &need_update_dialog_pos, false, nullptr, message_content.via_bot_user_id.is_valid()); + unique_ptr message; + Message *m; + if (message_send_options.only_preview) { + message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(content), message_content.invert_media, false, nullptr, + message_content.via_bot_user_id.is_valid(), DialogId()); + MessageId new_message_id = message_send_options.schedule_date != 0 + ? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date) + : get_next_yet_unsent_message_id(d); + message->message_id = new_message_id; + m = message.get(); + } else { + m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(content), message_content.invert_media, &need_update_dialog_pos, false, nullptr, + message_content.via_bot_user_id.is_valid()); + } m->reply_markup = std::move(message_reply_markup); m->via_bot_user_id = message_content.via_bot_user_id; m->disable_web_page_preview = message_content.disable_web_page_preview; @@ -25035,6 +25005,10 @@ Result> MessagesManager::send_message( } m->send_emoji = std::move(message_content.emoji); + if (message_send_options.only_preview) { + return get_message_object(dialog_id, m, "send_message"); + } + if (m->clear_draft) { if (top_thread_message_id.is_valid()) { set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore(); @@ -25096,8 +25070,8 @@ Result MessagesManager::process_input_message_content( return Status::Error(400, "Can't copy message content"); } - return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), false, 0, - UserId(), copied_message->send_emoji); + return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), + copied_message->invert_media, false, 0, UserId(), copied_message->send_emoji); } bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); @@ -25136,6 +25110,7 @@ Result MessagesManager::process_message_sen result.update_stickersets_order = options->update_order_of_installed_sticker_sets_; } result.protect_content = options->protect_content_; + result.only_preview = options->only_preview_; TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_))); result.sending_id = options->sending_id_; } @@ -25196,21 +25171,22 @@ Status MessagesManager::can_use_top_thread_message_id(Dialog *d, MessageId top_t if (d->dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(d->dialog_id)) { return Status::Error(400, "Chat doesn't have threads"); } - if (input_reply_to.story_full_id_.is_valid()) { + if (input_reply_to.get_story_full_id().is_valid()) { return Status::Error(400, "Can't send story replies to the thread"); } - if (input_reply_to.message_id_.is_valid()) { - const Message *reply_m = get_message_force(d, input_reply_to.message_id_, "can_use_top_thread_message_id 1"); + auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); + if (same_chat_reply_to_message_id.is_valid()) { + const Message *reply_m = get_message_force(d, same_chat_reply_to_message_id, "can_use_top_thread_message_id 1"); if (reply_m != nullptr && top_thread_message_id != reply_m->top_thread_message_id) { if (reply_m->top_thread_message_id.is_valid() || reply_m->media_album_id == 0) { - return Status::Error(400, "The message to reply is not in the specified message thread"); + return Status::Error(400, "The message to be replied is not in the specified message thread"); } // if the message is in an album and not in the thread, it can be in the album of top_thread_message_id const Message *top_m = get_message_force(d, top_thread_message_id, "can_use_top_thread_message_id 2"); if (top_m != nullptr && (top_m->media_album_id != reply_m->media_album_id || top_m->top_thread_message_id != top_m->message_id)) { - return Status::Error(400, "The message to reply is not in the specified message thread root album"); + return Status::Error(400, "The message to be replied is not in the specified message thread root album"); } } } @@ -25227,9 +25203,9 @@ int64 MessagesManager::generate_new_media_album_id() { } Result> MessagesManager::send_message_group( - DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, - vector> &&input_message_contents, bool only_preview) { + vector> &&input_message_contents) { if (input_message_contents.size() > MAX_GROUPED_MESSAGES) { return Status::Error(400, "Too many messages to send as an album"); } @@ -25245,7 +25221,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), true)); - vector, int32>> message_contents; + vector message_contents; std::unordered_set message_content_types; for (auto &input_message_content : input_message_contents) { TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); @@ -25256,7 +25232,7 @@ Result> MessagesManager::send_message_group } message_content_types.insert(message_content_type); - message_contents.emplace_back(std::move(message_content.content), message_content.ttl); + message_contents.push_back(std::move(message_content)); } if (message_content_types.size() > 1) { for (auto message_content_type : message_content_types) { @@ -25282,22 +25258,23 @@ Result> MessagesManager::send_message_group auto &message_content = message_contents[i]; unique_ptr message; Message *m; - if (only_preview) { - message = create_message_to_send(d, top_thread_message_id, input_reply_to, message_send_options, - std::move(message_content.first), i != 0, nullptr, false, DialogId()); + if (message_send_options.only_preview) { + message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(message_content.content), message_content.invert_media, i != 0, + nullptr, false, DialogId()); MessageId new_message_id = message_send_options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date) : get_next_yet_unsent_message_id(d); message->message_id = new_message_id; m = message.get(); } else { - m = get_message_to_send(d, top_thread_message_id, input_reply_to, message_send_options, - dup_message_content(td_, dialog_id, message_content.first.get(), + m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send, MessageCopyOptions()), - &need_update_dialog_pos, i != 0); + message_content.invert_media, &need_update_dialog_pos, i != 0); } - auto ttl = message_content.second; + auto ttl = message_content.ttl; if (ttl > 0) { m->ttl = ttl; m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type()); @@ -25306,7 +25283,7 @@ Result> MessagesManager::send_message_group result.push_back(get_message_object(dialog_id, m, "send_message_group")); - if (!only_preview) { + if (!message_send_options.only_preview) { save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); @@ -25317,7 +25294,7 @@ Result> MessagesManager::send_message_group } if (need_update_dialog_pos) { - CHECK(!only_preview); + CHECK(!message_send_options.only_preview); send_update_chat_last_message(d, "send_message_group"); } @@ -25439,7 +25416,7 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Messag td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, message_id, caption == nullptr ? "" : caption->text, get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_message_media"), - std::move(input_media), std::move(input_reply_markup), schedule_date); + std::move(input_media), m->edited_invert_media, std::move(input_reply_markup), schedule_date); return; } @@ -25461,8 +25438,8 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Messag int64 random_id = begin_send_message(dialog_id, m); td_->create_handler()->send( file_id, thumbnail_file_id, get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), - {m->reply_to_message_id, m->reply_to_story_full_id}, m->top_thread_message_id, - get_message_schedule_date(m), get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), + *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), + get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), get_input_message_entities(td_->contacts_manager_.get(), caption, "on_message_media_uploaded"), caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), m->is_copy, random_id, &m->send_query_ref); @@ -25762,7 +25739,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { vector random_ids; vector> input_single_media; tl_object_ptr as_input_peer; - MessageInputReplyTo input_reply_to; + const MessageInputReplyTo *input_reply_to = nullptr; MessageId top_thread_message_id; int32 flags = 0; int32 schedule_date = 0; @@ -25775,7 +25752,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { continue; } - input_reply_to = {m->reply_to_message_id, m->reply_to_story_full_id}; + input_reply_to = get_message_input_reply_to(m); top_thread_message_id = m->top_thread_message_id; flags = get_message_flags(m); schedule_date = get_message_schedule_date(m); @@ -25805,9 +25782,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { << request.is_finished << " " << request.results << " " << m->ttl << " " << has_remote << " " << file_view.has_alive_remote_location() << " " << file_view.has_active_upload_remote_location() << " " << file_view.has_active_download_remote_location() << " " << file_view.is_encrypted() << " " << is_web - << " " << file_view.has_url() << " " - << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date, - m->is_content_secret, false, -1)); + << " " << file_view.has_url() << " " << to_string(get_message_message_content_object(dialog_id, m)); } auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group"); int32 input_single_media_flags = 0; @@ -25842,7 +25817,8 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { if (input_single_media.empty()) { LOG(INFO) << "Media group " << media_album_id << " from " << dialog_id << " is empty"; } - td_->create_handler()->send(flags, dialog_id, std::move(as_input_peer), input_reply_to, + CHECK(input_reply_to != nullptr); + td_->create_handler()->send(flags, dialog_id, std::move(as_input_peer), *input_reply_to, top_thread_message_id, schedule_date, std::move(file_ids), std::move(input_single_media), is_copy); } @@ -25871,14 +25847,23 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI } else { const FormattedText *message_text = get_message_content_text(content); CHECK(message_text != nullptr); - int64 random_id = begin_send_message(dialog_id, m); - td_->create_handler()->send( - get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), - {m->reply_to_message_id, m->reply_to_story_full_id}, m->top_thread_message_id, get_message_schedule_date(m), - get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), - get_input_message_entities(td_->contacts_manager_.get(), message_text->entities, "do_send_message"), - message_text->text, m->is_copy, random_id, &m->send_query_ref); + auto input_media = get_message_content_input_media_web_page(td_, content); + if (input_media == nullptr) { + td_->create_handler()->send( + get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), + m->top_thread_message_id, get_message_schedule_date(m), + get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), + get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, + m->is_copy, random_id, &m->send_query_ref); + } else { + td_->create_handler()->send( + FileId(), FileId(), get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), + *get_message_input_reply_to(m), m->top_thread_message_id, get_message_schedule_date(m), + get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), + get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, + std::move(input_media), MessageContentType::Text, m->is_copy, random_id, &m->send_query_ref); + } } } @@ -26025,9 +26010,10 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di vector text_entities; text_entities.emplace_back(MessageEntity::Type::BotCommand, 0, narrow_cast(text.size())); bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), - create_text_message_content(text, std::move(text_entities), WebPageId()), - &need_update_dialog_pos); + Message *m = get_message_to_send( + d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), + create_text_message_content(text, std::move(text_entities), WebPageId(), false, false, false, string()), false, + &need_update_dialog_pos); m->is_bot_start_message = true; send_update_new_message(d, m); @@ -26118,8 +26104,8 @@ void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dia std::move(input_peer), parameter, random_id); } -Result MessagesManager::send_inline_query_result_message( - DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, +Result> MessagesManager::send_inline_query_result_message( + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, int64 query_id, const string &result_id, bool hide_via_bot) { Dialog *d = get_dialog_force(dialog_id, "send_inline_query_result_message"); if (d == nullptr) { @@ -26160,11 +26146,26 @@ Result MessagesManager::send_inline_query_result_message( TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, td_)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); + auto message_content = dup_message_content(td_, dialog_id, content->message_content.get(), + MessageContentDupType::SendViaBot, MessageCopyOptions()); bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, top_thread_message_id, input_reply_to, message_send_options, - dup_message_content(td_, dialog_id, content->message_content.get(), - MessageContentDupType::SendViaBot, MessageCopyOptions()), - &need_update_dialog_pos, false, nullptr, true); + unique_ptr message; + Message *m; + if (message_send_options.only_preview) { + message = + create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(message_content), content->invert_media, false, nullptr, true, DialogId()); + MessageId new_message_id = message_send_options.schedule_date != 0 + ? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date) + : get_next_yet_unsent_message_id(d); + message->message_id = new_message_id; + m = message.get(); + } else { + m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(message_content), content->invert_media, &need_update_dialog_pos, false, nullptr, + true); + } + m->hide_via_bot = hide_via_bot; if (!hide_via_bot) { m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id); @@ -26175,6 +26176,10 @@ Result MessagesManager::send_inline_query_result_message( m->disable_web_page_preview = content->disable_web_page_preview; m->clear_draft = !hide_via_bot; + if (message_send_options.only_preview) { + return get_message_object(dialog_id, m, "send_inline_query_result_message"); + } + if (m->clear_draft) { if (top_thread_message_id.is_valid()) { set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore(); @@ -26191,13 +26196,12 @@ Result MessagesManager::send_inline_query_result_message( if (to_secret) { save_send_message_log_event(dialog_id, m); do_send_message(dialog_id, m); - return m->message_id; + } else { + save_send_inline_query_result_message_log_event(dialog_id, m, query_id, result_id); + send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, m->message_id, + query_id, result_id); } - - save_send_inline_query_result_message_log_event(dialog_id, m, query_id, result_id); - send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, m->message_id, - query_id, result_id); - return m->message_id; + return get_message_object(dialog_id, m, "send_inline_query_result_message"); } class MessagesManager::SendInlineQueryResultMessageLogEvent { @@ -26262,23 +26266,8 @@ void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, Me flags |= telegram_api::messages_sendInlineBotResult::HIDE_VIA_MASK; } m->send_query_ref = td_->create_handler()->send( - flags, dialog_id, get_send_message_as_input_peer(m), {m->reply_to_message_id, m->reply_to_story_full_id}, - m->top_thread_message_id, get_message_schedule_date(m), random_id, query_id, result_id); -} - -bool MessagesManager::has_qts_messages(DialogId dialog_id) const { - switch (dialog_id.get_type()) { - case DialogType::User: - case DialogType::Chat: - return td_->option_manager_->get_option_integer("session_count") > 1; - case DialogType::Channel: - case DialogType::SecretChat: - return false; - case DialogType::None: - default: - UNREACHABLE(); - return false; - } + flags, dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), m->top_thread_message_id, + get_message_schedule_date(m), random_id, query_id, result_id); } bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, @@ -26370,7 +26359,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo if (has_edit_time_limit) { const int32 DEFAULT_EDIT_TIME_LIMIT = 2 * 86400; int64 edit_time_limit = td_->option_manager_->get_option_integer("edit_time_limit", DEFAULT_EDIT_TIME_LIMIT); - if (G()->unix_time_cached() - m->date - (is_editing ? 300 : 0) >= edit_time_limit) { + if (G()->unix_time() - m->date - (is_editing ? 300 : 0) >= edit_time_limit) { return false; } } @@ -26390,7 +26379,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo // there is no caption to edit, but bot can edit inline reply_markup return true; } - return G()->unix_time_cached() - m->date < get_message_content_live_location_period(m->content.get()); + return G()->unix_time() - m->date < get_message_content_live_location_period(m->content.get()); } case MessageContentType::Poll: { if (is_bot && only_reply_markup) { @@ -26405,6 +26394,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo case MessageContentType::Story: case MessageContentType::Contact: case MessageContentType::Dice: + case MessageContentType::Giveaway: case MessageContentType::Location: case MessageContentType::Sticker: case MessageContentType::Venue: @@ -26452,6 +26442,8 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: case MessageContentType::WriteAccessAllowedByRequest: + case MessageContentType::GiftCode: + case MessageContentType::GiveawayLaunch: return false; default: UNREACHABLE(); @@ -26462,7 +26454,8 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo bool MessagesManager::can_resend_message(const Message *m) const { if (m->send_error_code != 429 && m->send_error_message != "Message is too old to be re-sent automatically" && - m->send_error_message != "SCHEDULE_TOO_MUCH" && m->send_error_message != "SEND_AS_PEER_INVALID") { + m->send_error_message != "SCHEDULE_TOO_MUCH" && m->send_error_message != "SEND_AS_PEER_INVALID" && + m->send_error_message != "QUOTE_TEXT_INVALID" && m->send_error_message != "REPLY_MESSAGE_ID_INVALID") { return false; } if (m->is_bot_start_message) { @@ -26553,13 +26546,10 @@ DialogId MessagesManager::get_message_original_sender(const Message *m) { CHECK(m != nullptr); if (m->forward_info != nullptr) { auto forward_info = m->forward_info.get(); - if (forward_info->is_imported || is_forward_info_sender_hidden(forward_info)) { + if (forward_info->is_imported) { return DialogId(); } - if (forward_info->message_id.is_valid() || forward_info->sender_dialog_id.is_valid()) { - return forward_info->sender_dialog_id; - } - return DialogId(forward_info->sender_user_id); + return forward_info->origin.get_sender(); } return get_message_sender(m); } @@ -26610,7 +26600,7 @@ void MessagesManager::edit_message_text(MessageFullId message_full_id, if (r_input_message_text.is_error()) { return promise.set_error(r_input_message_text.move_as_error()); } - InputMessageText input_message_text = r_input_message_text.move_as_ok(); + const InputMessageText input_message_text = r_input_message_text.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, has_message_sender_user_id(dialog_id, m)); @@ -26627,7 +26617,8 @@ void MessagesManager::edit_message_text(MessageFullId message_full_id, ->send(flags, dialog_id, m->message_id, input_message_text.text.text, get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, "edit_message_text"), - nullptr, std::move(input_reply_markup), get_message_schedule_date(m)); + input_message_text.get_input_media_web_page(), input_message_text.show_above_text, + std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_live_location(MessageFullId message_full_id, @@ -26686,7 +26677,7 @@ void MessagesManager::edit_message_live_location(MessageFullId message_full_id, flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), - std::move(input_media), std::move(input_reply_markup), get_message_schedule_date(m)); + std::move(input_media), false, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message) { @@ -26697,6 +26688,7 @@ void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, cancel_upload_message_content_files(m->edited_content.get()); m->edited_content = nullptr; + m->edited_invert_media = false; m->edited_reply_markup = nullptr; m->edit_generation = 0; m->edit_promise.set_error(Status::Error(400, error_message)); @@ -26781,6 +26773,7 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess m->edited_schedule_date = 0; } m->edited_content = nullptr; + m->edited_invert_media = false; m->edited_reply_markup = nullptr; m->edit_generation = 0; if (result.is_ok()) { @@ -26870,6 +26863,7 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, m->edited_content = dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions()); CHECK(m->edited_content != nullptr); + m->edited_invert_media = content.invert_media; m->edited_reply_markup = r_new_reply_markup.move_as_ok(); m->edit_generation = ++current_message_edit_generation_; m->edit_promise = std::move(promise); @@ -26921,7 +26915,7 @@ void MessagesManager::edit_message_caption(MessageFullId message_full_id, td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, m->message_id, caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_message_caption"), - nullptr, std::move(input_reply_markup), get_message_schedule_date(m)); + nullptr, m->invert_media, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_reply_markup(MessageFullId message_full_id, @@ -26956,7 +26950,7 @@ void MessagesManager::edit_message_reply_markup(MessageFullId message_full_id, auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, - std::move(input_reply_markup), get_message_schedule_date(m)); + false, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_inline_message_text(const string &inline_message_id, @@ -26978,7 +26972,7 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id, if (r_input_message_text.is_error()) { return promise.set_error(r_input_message_text.move_as_error()); } - InputMessageText input_message_text = r_input_message_text.move_as_ok(); + const InputMessageText input_message_text = r_input_message_text.move_as_ok(); auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); if (r_new_reply_markup.is_error()) { @@ -26998,7 +26992,8 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id, ->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text, get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, "edit_inline_message_text"), - nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + input_message_text.get_input_media_web_page(), input_message_text.show_above_text, + get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_live_location(const string &inline_message_id, @@ -27034,7 +27029,8 @@ void MessagesManager::edit_inline_message_live_location(const string &inline_mes flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), "", vector>(), - std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + std::move(input_media), false, + get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_media(const string &inline_message_id, @@ -27083,7 +27079,8 @@ void MessagesManager::edit_inline_message_media(const string &inline_message_id, td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption == nullptr ? "" : caption->text, get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_inline_message_media"), - std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + std::move(input_media), content.invert_media, + get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_caption(const string &inline_message_id, @@ -27112,7 +27109,7 @@ void MessagesManager::edit_inline_message_caption(const string &inline_message_i td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_inline_message_caption"), - nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_inline_message_reply_markup(const string &inline_message_id, @@ -27132,7 +27129,7 @@ void MessagesManager::edit_inline_message_reply_markup(const string &inline_mess td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), string(), vector>(), - nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); } void MessagesManager::edit_message_scheduling_state( @@ -27174,7 +27171,7 @@ void MessagesManager::edit_message_scheduling_state( if (schedule_date > 0) { td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, - nullptr, schedule_date); + false, nullptr, schedule_date); } else { td_->create_handler(std::move(promise))->send(dialog_id, m->message_id); } @@ -27230,18 +27227,21 @@ void MessagesManager::update_message_max_reply_media_timestamp(const Dialog *d, } auto new_max_reply_media_timestamp = -1; - if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) { - const auto *reply_d = m->reply_in_dialog_id != DialogId() ? get_dialog(m->reply_in_dialog_id) : d; + auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(d->dialog_id); + auto reply_message_id = reply_message_full_id.get_message_id(); + if (reply_message_id.is_valid() && !reply_message_id.is_yet_unsent()) { + const auto *reply_d = + reply_message_full_id.get_dialog_id() != d->dialog_id ? get_dialog(reply_message_full_id.get_dialog_id()) : d; if (reply_d == nullptr) { // replied message isn't loaded yet return; } - auto replied_m = get_message(reply_d, m->reply_to_message_id); + auto replied_m = get_message(reply_d, reply_message_id); if (replied_m != nullptr) { new_max_reply_media_timestamp = get_message_own_max_media_timestamp(replied_m); - } else if (!is_deleted_message(reply_d, m->reply_to_message_id) && - m->reply_to_message_id > reply_d->last_clear_history_message_id && - m->reply_to_message_id > reply_d->max_unavailable_message_id) { + } else if (!is_deleted_message(reply_d, reply_message_id) && + reply_message_id > reply_d->last_clear_history_message_id && + reply_message_id > reply_d->max_unavailable_message_id) { // replied message isn't deleted and isn't loaded yet return; } @@ -27317,8 +27317,7 @@ void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messag Dialog *d = get_dialog(replied_dialog_id); auto m = get_message(d, replied_message_full_id.get_message_id()); CHECK(m != nullptr); - CHECK((m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : replied_dialog_id) == dialog_id); - CHECK(m->reply_to_message_id == reply_to_message_id); + CHECK(m->replied_message_info.get_reply_message_full_id(replied_dialog_id) == message_full_id); update_message_max_reply_media_timestamp(d, m, true); } } @@ -27345,7 +27344,9 @@ bool MessagesManager::can_register_message_reply(const Message *m) const { if (td_->auth_manager_->is_bot()) { return false; } - if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) { + auto reply_message_full_id = m->replied_message_info.get_reply_message_full_id(DialogId()); + auto reply_message_id = reply_message_full_id.get_message_id(); + if (reply_message_id.is_valid() && !reply_message_id.is_yet_unsent()) { return true; } if (m->reply_to_story_full_id.is_valid()) { @@ -27355,6 +27356,8 @@ bool MessagesManager::can_register_message_reply(const Message *m) const { } void MessagesManager::register_message_reply(DialogId dialog_id, const Message *m) { + m->replied_message_info.register_content(td_); + if (!can_register_message_reply(m)) { return; } @@ -27367,8 +27370,7 @@ void MessagesManager::register_message_reply(DialogId dialog_id, const Message * .second; CHECK(is_inserted); } else { - MessageFullId message_full_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; + auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id); LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << message_full_id; bool is_inserted = message_to_replied_media_timestamp_messages_[message_full_id].insert({dialog_id, m->message_id}).second; @@ -27378,6 +27380,7 @@ void MessagesManager::register_message_reply(DialogId dialog_id, const Message * } void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message *m) { + // reply itself wan't changed, so there is nothing to reregister if (!can_register_message_reply(m)) { return; } @@ -27388,8 +27391,7 @@ void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message was_registered = it != story_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; } else { - MessageFullId message_full_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; + auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id); auto it = message_to_replied_media_timestamp_messages_.find(message_full_id); was_registered = it != message_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; @@ -27407,6 +27409,8 @@ void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message } void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message *m) { + m->replied_message_info.unregister_content(td_); + if (!can_register_message_reply(m)) { return; } @@ -27426,8 +27430,7 @@ void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message } } } else { - MessageFullId message_full_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; + auto message_full_id = m->replied_message_info.get_reply_message_full_id(dialog_id); auto it = message_to_replied_media_timestamp_messages_.find(message_full_id); if (it == message_to_replied_media_timestamp_messages_.end()) { return; @@ -27479,6 +27482,9 @@ int32 MessagesManager::get_message_flags(const Message *m) { if (m->update_stickersets_order) { flags |= SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER; } + if (m->invert_media) { + flags |= SEND_MESSAGE_FLAG_INVERT_MEDIA; + } return flags; } @@ -27561,53 +27567,19 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c return true; } -bool MessagesManager::is_forward_info_sender_hidden(const MessageForwardInfo *forward_info) { - CHECK(forward_info != nullptr); - if (forward_info->is_imported) { - return false; - } - if (!forward_info->sender_name.empty()) { - return true; - } - DialogId hidden_sender_dialog_id(ChannelId(static_cast(G()->is_test_dc() ? 10460537 : 1228946795))); - return forward_info->sender_dialog_id == hidden_sender_dialog_id && !forward_info->author_signature.empty() && - !forward_info->message_id.is_valid(); -} - unique_ptr MessagesManager::get_message_forward_info( - tl_object_ptr &&forward_header, MessageFullId message_full_id) { + tl_object_ptr &&forward_header) { if (forward_header == nullptr) { return nullptr; } - - if (forward_header->date_ <= 0) { + auto date = forward_header->date_; + if (date <= 0) { LOG(ERROR) << "Wrong date in message forward header: " << oneline(to_string(forward_header)); return nullptr; } - auto flags = forward_header->flags_; - DialogId sender_dialog_id; - MessageId message_id; - string author_signature = std::move(forward_header->post_author_); DialogId from_dialog_id; MessageId from_message_id; - string sender_name = std::move(forward_header->from_name_); - bool is_imported = forward_header->imported_; - if (forward_header->from_id_ != nullptr) { - sender_dialog_id = DialogId(forward_header->from_id_); - if (!sender_dialog_id.is_valid()) { - LOG(ERROR) << "Receive invalid sender identifier in message forward header: " - << oneline(to_string(forward_header)); - sender_dialog_id = DialogId(); - } - } - if ((flags & telegram_api::messageFwdHeader::CHANNEL_POST_MASK) != 0) { - message_id = MessageId(ServerMessageId(forward_header->channel_post_)); - if (!message_id.is_valid()) { - LOG(ERROR) << "Receive " << message_id << " in message forward header: " << oneline(to_string(forward_header)); - message_id = MessageId(); - } - } if (forward_header->saved_from_peer_ != nullptr) { from_dialog_id = DialogId(forward_header->saved_from_peer_); from_message_id = MessageId(ServerMessageId(forward_header->saved_from_msg_id_)); @@ -27616,81 +27588,40 @@ unique_ptr MessagesManager::get_message_for << " in message forward header: " << oneline(to_string(forward_header)); from_dialog_id = DialogId(); from_message_id = MessageId(); + } else { + force_create_dialog(from_dialog_id, "get_message_forward_info", true); } } - - UserId sender_user_id; - if (sender_dialog_id.get_type() == DialogType::User) { - sender_user_id = sender_dialog_id.get_user_id(); - sender_dialog_id = DialogId(); - } - if (!sender_dialog_id.is_valid()) { - if (sender_user_id.is_valid()) { - if (message_id.is_valid()) { - LOG(ERROR) << "Receive non-empty message identifier in message forward header: " - << oneline(to_string(forward_header)); - message_id = MessageId(); - } - } else if (sender_name.empty()) { - LOG(ERROR) << "Receive wrong message forward header: " << oneline(to_string(forward_header)); - return nullptr; - } - } else if (sender_dialog_id.get_type() != DialogType::Channel) { - LOG(ERROR) << "Receive wrong message forward header with non-channel sender: " - << oneline(to_string(forward_header)); + bool is_imported = forward_header->imported_; + auto psa_type = std::move(forward_header->psa_type_); + auto r_origin = MessageOrigin::get_message_origin(td_, std::move(forward_header)); + if (r_origin.is_error()) { return nullptr; - } else { - auto channel_id = sender_dialog_id.get_channel_id(); - if (!td_->contacts_manager_->have_channel(channel_id)) { - LOG(ERROR) << "Receive forward from " - << (td_->contacts_manager_->have_min_channel(channel_id) ? "min" : "unknown") << ' ' << channel_id - << " in " << message_full_id; - } - force_create_dialog(sender_dialog_id, "message forward info", true); - CHECK(!sender_user_id.is_valid()); - } - if (from_dialog_id.is_valid()) { - force_create_dialog(from_dialog_id, "message forward from info", true); } - return td::make_unique(sender_user_id, forward_header->date_, sender_dialog_id, message_id, - std::move(author_signature), std::move(sender_name), from_dialog_id, - from_message_id, std::move(forward_header->psa_type_), is_imported); + return td::make_unique(r_origin.move_as_ok(), date, from_dialog_id, from_message_id, + std::move(psa_type), is_imported); } td_api::object_ptr MessagesManager::get_message_forward_info_object( const unique_ptr &forward_info) const { - if (forward_info == nullptr) { + if (forward_info == nullptr || forward_info->is_imported) { return nullptr; } - auto origin = [&]() -> td_api::object_ptr { - if (forward_info->is_imported) { - return td_api::make_object(forward_info->sender_name); - } - if (is_forward_info_sender_hidden(forward_info.get())) { - return td_api::make_object( - forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name); - } - if (forward_info->message_id.is_valid()) { - return td_api::make_object( - get_chat_id_object(forward_info->sender_dialog_id, "messageForwardOriginChannel"), - forward_info->message_id.get(), forward_info->author_signature); - } - if (forward_info->sender_dialog_id.is_valid()) { - return td_api::make_object( - get_chat_id_object(forward_info->sender_dialog_id, "messageForwardOriginChat"), - forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name); - } - return td_api::make_object( - td_->contacts_manager_->get_user_id_object(forward_info->sender_user_id, "messageForwardOriginUser")); - }(); - return td_api::make_object( - std::move(origin), forward_info->date, forward_info->psa_type, + forward_info->origin.get_message_origin_object(td_), forward_info->date, forward_info->psa_type, get_chat_id_object(forward_info->from_dialog_id, "messageForwardInfo"), forward_info->from_message_id.get()); } +td_api::object_ptr MessagesManager::get_message_import_info_object( + const unique_ptr &forward_info) const { + if (forward_info == nullptr || !forward_info->is_imported) { + return nullptr; + } + return td_api::make_object(forward_info->origin.get_sender_name(), forward_info->date); +} + Result> MessagesManager::get_dialog_reply_markup( DialogId dialog_id, tl_object_ptr &&reply_markup_ptr) const { if (reply_markup_ptr == nullptr) { @@ -27850,7 +27781,7 @@ Result> MessagesManager::forward_message( vector all_copy_options; all_copy_options.push_back(std::move(copy_options)); TRY_RESULT(result, forward_messages(to_dialog_id, top_thread_message_id, from_dialog_id, {message_id}, - std::move(options), in_game_share, std::move(all_copy_options), false)); + std::move(options), in_game_share, std::move(all_copy_options))); CHECK(result->messages_.size() == 1); if (result->messages_[0] == nullptr) { return Status::Error(400, @@ -27859,49 +27790,62 @@ Result> MessagesManager::forward_message( return std::move(result->messages_[0]); } -unique_ptr MessagesManager::create_message_forward_info( - DialogId from_dialog_id, DialogId to_dialog_id, const Message *forwarded_message) const { - auto content_type = forwarded_message->content->get_type(); +MessageOrigin MessagesManager::get_forwarded_message_origin(DialogId dialog_id, const Message *m) const { + CHECK(m != nullptr); + MessageOrigin origin; + if (m->forward_info != nullptr) { + origin = m->forward_info->origin; + } else if (m->is_channel_post) { + if (is_broadcast_channel(dialog_id)) { + auto author_signature = m->sender_user_id.is_valid() ? td_->contacts_manager_->get_user_title(m->sender_user_id) + : m->author_signature; + origin = MessageOrigin{UserId(), dialog_id, m->message_id, std::move(author_signature), string()}; + } else { + LOG(ERROR) << "Don't know how to forward a channel post not from a channel"; + } + } else if (m->sender_user_id.is_valid() || m->sender_dialog_id.is_valid()) { + auto author_signature = m->author_signature; + origin = MessageOrigin{m->sender_user_id, m->sender_dialog_id, MessageId(), string(), std::move(author_signature)}; + } else { + LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender"; + } + origin.hide_sender_if_needed(td_); + return origin; +} + +unique_ptr MessagesManager::create_message_forward_info(DialogId from_dialog_id, + DialogId to_dialog_id, + const Message *m) const { + auto content_type = m->content->get_type(); if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio || content_type == MessageContentType::Story) { return nullptr; } - auto my_id = td_->contacts_manager_->get_my_id(); - auto message_id = forwarded_message->message_id; + auto my_dialog_id = get_my_dialog_id(); DialogId saved_from_dialog_id; MessageId saved_from_message_id; - if (to_dialog_id == DialogId(my_id)) { + if (to_dialog_id == my_dialog_id) { saved_from_dialog_id = from_dialog_id; - saved_from_message_id = message_id; + saved_from_message_id = m->message_id; } - if (forwarded_message->forward_info != nullptr) { - auto forward_info = make_unique(*forwarded_message->forward_info); + if (m->forward_info != nullptr) { + auto forward_info = make_unique(*m->forward_info); forward_info->from_dialog_id = saved_from_dialog_id; forward_info->from_message_id = saved_from_message_id; + if (!forward_info->is_imported) { + forward_info->origin.hide_sender_if_needed(td_); + } return forward_info; } - if (from_dialog_id != DialogId(my_id) || content_type == MessageContentType::Dice) { - if (forwarded_message->is_channel_post) { - if (is_broadcast_channel(from_dialog_id)) { - auto author_signature = forwarded_message->sender_user_id.is_valid() - ? td_->contacts_manager_->get_user_title(forwarded_message->sender_user_id) - : forwarded_message->author_signature; - return td::make_unique(UserId(), forwarded_message->date, from_dialog_id, - forwarded_message->message_id, std::move(author_signature), "", - saved_from_dialog_id, saved_from_message_id, "", false); - } else { - LOG(ERROR) << "Don't know how to forward a channel post not from a channel"; - } - } else if (forwarded_message->sender_user_id.is_valid() || forwarded_message->sender_dialog_id.is_valid()) { - return td::make_unique( - forwarded_message->sender_user_id, forwarded_message->date, forwarded_message->sender_dialog_id, MessageId(), - "", forwarded_message->author_signature, saved_from_dialog_id, saved_from_message_id, "", false); - } else { - LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender"; + if (from_dialog_id != my_dialog_id || content_type == MessageContentType::Dice) { + auto origin = get_forwarded_message_origin(from_dialog_id, m); + if (!origin.is_empty()) { + return td::make_unique(std::move(origin), m->date, saved_from_dialog_id, + saved_from_message_id, "", false); } } return nullptr; @@ -28128,14 +28072,14 @@ Result MessagesManager::get_forwarded_messag } if (is_local_copy) { - auto original_reply_to_message_id = - forwarded_message->reply_in_dialog_id == DialogId() ? forwarded_message->reply_to_message_id : MessageId(); - copied_messages.push_back({std::move(content), input_reply_to, forwarded_message->message_id, - original_reply_to_message_id, std::move(reply_markup), - forwarded_message->media_album_id, - get_message_disable_web_page_preview(forwarded_message), i}); + auto original_reply_to_message_id = forwarded_message->replied_message_info.get_same_chat_reply_to_message_id(); + copied_messages.push_back( + {std::move(content), std::move(input_reply_to), forwarded_message->message_id, original_reply_to_message_id, + std::move(reply_markup), forwarded_message->media_album_id, + get_message_disable_web_page_preview(forwarded_message), forwarded_message->invert_media, i}); } else { - forwarded_message_contents.push_back({std::move(content), forwarded_message->media_album_id, i}); + forwarded_message_contents.push_back( + {std::move(content), forwarded_message->invert_media, forwarded_message->media_album_id, i}); } } result.top_thread_message_id = top_thread_message_id; @@ -28181,14 +28125,14 @@ Result MessagesManager::get_forwarded_messag Result> MessagesManager::forward_messages( DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, vector message_ids, - tl_object_ptr &&options, bool in_game_share, vector &©_options, - bool only_preview) { + tl_object_ptr &&options, bool in_game_share, + vector &©_options) { TRY_RESULT(forwarded_messages_info, get_forwarded_messages(to_dialog_id, top_thread_message_id, from_dialog_id, message_ids, std::move(options), in_game_share, std::move(copy_options))); auto from_dialog = forwarded_messages_info.from_dialog; auto to_dialog = forwarded_messages_info.to_dialog; - auto message_send_options = forwarded_messages_info.message_send_options; + const auto message_send_options = forwarded_messages_info.message_send_options; auto &copied_messages = forwarded_messages_info.copied_messages; auto &forwarded_message_contents = forwarded_messages_info.forwarded_message_contents; auto drop_author = forwarded_messages_info.drop_author; @@ -28208,29 +28152,23 @@ Result> MessagesManager::forward_messages( auto content = std::move(forwarded_message_contents[j].content); auto forward_info = drop_author ? nullptr : create_message_forward_info(from_dialog_id, to_dialog_id, forwarded_message); - if (forward_info != nullptr && !forward_info->is_imported && !is_forward_info_sender_hidden(forward_info.get()) && - !forward_info->message_id.is_valid() && !forward_info->sender_dialog_id.is_valid() && - forward_info->sender_user_id.is_valid()) { - auto private_forward_name = td_->contacts_manager_->get_user_private_forward_name(forward_info->sender_user_id); - if (!private_forward_name.empty()) { - forward_info->sender_user_id = UserId(); - forward_info->sender_name = std::move(private_forward_name); - } - } - MessageId reply_to_message_id; - if (forwarded_message->reply_to_message_id.is_valid() && forwarded_message->reply_in_dialog_id == DialogId()) { - auto it = forwarded_message_id_to_new_message_id.find(forwarded_message->reply_to_message_id); + MessageInputReplyTo input_reply_to; + auto original_reply_to_message_id = forwarded_message->replied_message_info.get_same_chat_reply_to_message_id(); + if (original_reply_to_message_id.is_valid()) { + auto it = forwarded_message_id_to_new_message_id.find(original_reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { - reply_to_message_id = it->second; + input_reply_to = forwarded_message->replied_message_info.get_input_reply_to(); + input_reply_to.set_message_id(it->second); } } unique_ptr message; Message *m; - if (only_preview) { + if (message_send_options.only_preview) { message = create_message_to_send( - to_dialog, top_thread_message_id, {reply_to_message_id, StoryFullId()}, message_send_options, - std::move(content), j + 1 != forwarded_message_contents.size(), std::move(forward_info), false, DialogId()); + to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, std::move(content), + forwarded_message_contents[j].invert_media, j + 1 != forwarded_message_contents.size(), + std::move(forward_info), false, DialogId()); MessageId new_message_id = message_send_options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date) @@ -28238,8 +28176,8 @@ Result> MessagesManager::forward_messages( message->message_id = new_message_id; m = message.get(); } else { - m = get_message_to_send(to_dialog, top_thread_message_id, {reply_to_message_id, StoryFullId()}, - message_send_options, std::move(content), &need_update_dialog_pos, + m = get_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(content), forwarded_message_contents[j].invert_media, &need_update_dialog_pos, j + 1 != forwarded_message_contents.size(), std::move(forward_info)); } fix_forwarded_message(m, to_dialog_id, forwarded_message, forwarded_message_contents[j].media_album_id, @@ -28248,8 +28186,17 @@ Result> MessagesManager::forward_messages( m->real_forward_from_dialog_id = from_dialog_id; m->real_forward_from_message_id = message_id; forwarded_message_id_to_new_message_id.emplace(message_id, m->message_id); + if (forwarded_message->replied_message_info.is_external()) { + if (!message_send_options.only_preview) { + unregister_message_reply(to_dialog_id, m); + } + m->replied_message_info = forwarded_message->replied_message_info.clone(td_); + if (!message_send_options.only_preview) { + register_message_reply(to_dialog_id, m); + } + } - if (!only_preview) { + if (!message_send_options.only_preview) { if (!td_->auth_manager_->is_bot()) { send_update_new_message(to_dialog, m); } @@ -28261,7 +28208,7 @@ Result> MessagesManager::forward_messages( } if (!forwarded_messages.empty()) { - CHECK(!only_preview); + CHECK(!message_send_options.only_preview); do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, forwarded_message_ids, drop_author, drop_media_captions, 0); } @@ -28279,19 +28226,20 @@ Result> MessagesManager::forward_messages( forwarded_message_id_to_new_message_id.emplace(copied_message.original_message_id, MessageId()); } for (auto &copied_message : copied_messages) { - auto input_reply_to = copied_message.input_reply_to; + auto input_reply_to = std::move(copied_message.input_reply_to); if (!input_reply_to.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) { auto it = forwarded_message_id_to_new_message_id.find(copied_message.original_reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { - input_reply_to.message_id_ = it->second; + input_reply_to = MessageInputReplyTo{it->second, DialogId(), FormattedText()}; } } unique_ptr message; Message *m; - if (only_preview) { - message = create_message_to_send(to_dialog, top_thread_message_id, input_reply_to, message_send_options, - std::move(copied_message.content), false, nullptr, is_copy, DialogId()); + if (message_send_options.only_preview) { + message = create_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), + message_send_options, std::move(copied_message.content), + copied_message.invert_media, false, nullptr, is_copy, DialogId()); MessageId new_message_id = message_send_options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date) @@ -28305,15 +28253,16 @@ Result> MessagesManager::forward_messages( extract_authentication_codes(from_dialog_id, forwarded_message, authentication_codes); } - m = get_message_to_send(to_dialog, top_thread_message_id, input_reply_to, message_send_options, - std::move(copied_message.content), &need_update_dialog_pos, false, nullptr, is_copy); + m = get_message_to_send(to_dialog, top_thread_message_id, std::move(input_reply_to), message_send_options, + std::move(copied_message.content), copied_message.invert_media, &need_update_dialog_pos, + false, nullptr, is_copy); } m->disable_web_page_preview = copied_message.disable_web_page_preview; m->media_album_id = copied_message.media_album_id; m->reply_markup = std::move(copied_message.reply_markup); forwarded_message_id_to_new_message_id[copied_message.original_message_id] = m->message_id; - if (!only_preview) { + if (!message_send_options.only_preview) { save_send_message_log_event(to_dialog_id, m); do_send_message(to_dialog_id, m); if (!td_->auth_manager_->is_bot()) { @@ -28325,7 +28274,7 @@ Result> MessagesManager::forward_messages( } if (need_update_dialog_pos) { - CHECK(!only_preview); + CHECK(!message_send_options.only_preview); send_update_chat_last_message(to_dialog, "forward_messages"); } @@ -28336,7 +28285,8 @@ Result> MessagesManager::forward_messages( return get_messages_object(-1, std::move(result), false); } -Result> MessagesManager::resend_messages(DialogId dialog_id, vector message_ids) { +Result> MessagesManager::resend_messages(DialogId dialog_id, vector message_ids, + td_api::object_ptr &"e) { if (message_ids.empty()) { return Status::Error(400, "There are no messages to resend"); } @@ -28425,13 +28375,28 @@ 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"); + auto need_another_reply_quote = + message->send_error_code == 400 && message->send_error_message == CSlice("QUOTE_TEXT_INVALID"); + auto need_drop_reply = + message->send_error_code == 400 && message->send_error_message == CSlice("REPLY_MESSAGE_ID_INVALID"); + if (need_another_reply_quote && message_ids.size() == 1 && quote != nullptr) { + CHECK(message->input_reply_to.is_valid()); + CHECK(message->input_reply_to.has_quote()); // checked in on_send_message_fail + auto r_quote = + get_formatted_text(td_, get_my_dialog_id(), std::move(quote), td_->auth_manager_->is_bot(), true, true, true); + if (r_quote.is_ok()) { + message->input_reply_to.set_quote(r_quote.move_as_ok()); + } + } else if (need_drop_reply) { + message->input_reply_to = {}; + } MessageSendOptions options(message->disable_notification, message->from_background, - message->update_stickersets_order, message->noforwards, + message->update_stickersets_order, message->noforwards, false, get_message_schedule_date(message.get()), message->sending_id); - Message *m = get_message_to_send( - d, message->top_thread_message_id, {message->reply_to_message_id, message->reply_to_story_full_id}, options, - std::move(new_contents[i]), &need_update_dialog_pos, false, nullptr, message->is_copy, - need_another_sender ? DialogId() : get_message_sender(message.get())); + Message *m = + get_message_to_send(d, message->top_thread_message_id, std::move(message->input_reply_to), options, + std::move(new_contents[i]), message->invert_media, &need_update_dialog_pos, false, nullptr, + message->is_copy, need_another_sender ? DialogId() : get_message_sender(message.get())); m->reply_markup = std::move(message->reply_markup); // m->via_bot_user_id = message->via_bot_user_id; m->disable_web_page_preview = message->disable_web_page_preview; @@ -28464,7 +28429,7 @@ void MessagesManager::send_screenshot_taken_notification_message(Dialog *d) { if (dialog_type == DialogType::User) { bool need_update_dialog_pos = false; const Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), - create_screenshot_taken_message_content(), &need_update_dialog_pos); + create_screenshot_taken_message_content(), false, &need_update_dialog_pos); do_send_screenshot_taken_notification_message(d->dialog_id, m, 0); @@ -28561,7 +28526,7 @@ void MessagesManager::share_dialog_with_bot(MessageFullId message_full_id, int32 Result MessagesManager::add_local_message( DialogId dialog_id, td_api::object_ptr &&sender, - td_api::object_ptr &&reply_to, bool disable_notification, + td_api::object_ptr &&reply_to, bool disable_notification, tl_object_ptr &&input_message_content) { if (input_message_content == nullptr) { return Status::Error(400, "Can't add local message without content"); @@ -28642,10 +28607,11 @@ Result MessagesManager::add_local_message( m->sender_dialog_id = sender_dialog_id; } m->date = G()->unix_time(); - m->reply_to_message_id = input_reply_to.message_id_; - m->reply_to_story_full_id = input_reply_to.story_full_id_; - if (m->reply_to_message_id.is_valid() && !message_id.is_scheduled()) { - const Message *reply_m = get_message(d, m->reply_to_message_id); + m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); + m->reply_to_story_full_id = input_reply_to.get_story_full_id(); + if (!message_id.is_scheduled()) { + auto reply_to_message_id = m->replied_message_info.get_same_chat_reply_to_message_id(); + const Message *reply_m = get_message(d, reply_to_message_id); if (reply_m != nullptr) { m->top_thread_message_id = reply_m->top_thread_message_id; if (m->top_thread_message_id.is_valid()) { @@ -28661,6 +28627,7 @@ Result MessagesManager::add_local_message( m->view_count = 0; m->forward_count = 0; m->content = std::move(message_content.content); + m->invert_media = message_content.invert_media; m->disable_web_page_preview = message_content.disable_web_page_preview; m->clear_draft = message_content.clear_draft; if (dialog_type == DialogType::SecretChat) { @@ -28822,7 +28789,7 @@ void MessagesManager::upload_imported_message_attachment(DialogId dialog_id, int bool is_reupload, Promise &&promise, vector bad_parts) { CHECK(file_id.is_valid()); - LOG(INFO) << "Ask to upload improted message attached file " << file_id; + LOG(INFO) << "Ask to upload imported message attached file " << file_id; auto info = td::make_unique(dialog_id, import_id, is_reupload, std::move(promise)); bool is_inserted = being_uploaded_imported_message_attachments_.emplace(file_id, std::move(info)).second; @@ -28858,7 +28825,7 @@ void MessagesManager::on_imported_message_attachments_uploaded(int64 random_id, td_->create_handler(std::move(promise))->send(dialog_id, pending_message_import->import_id); } -bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) { +bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const char *source) { if (!new_message_id.is_valid() && !new_message_id.is_valid_scheduled()) { LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from " << source; @@ -28905,6 +28872,9 @@ bool MessagesManager::on_get_dialog_error(DialogId dialog_id, const Status &stat reload_dialog_info_full(dialog_id, "SEND_AS_PEER_INVALID"); return true; } + if (status.message() == CSlice("QUOTE_TEXT_INVALID") || status.message() == CSlice("REPLY_MESSAGE_ID_INVALID")) { + return true; + } switch (dialog_id.get_type()) { case DialogType::User: @@ -29860,6 +29830,7 @@ bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Me case MessageContentType::PassportDataReceived: case MessageContentType::WebViewDataSent: case MessageContentType::WebViewDataReceived: + case MessageContentType::GiveawayLaunch: VLOG(notifications) << "Disable notification for " << m->message_id << " in " << d->dialog_id << " with content of type " << m->content->get_type(); return true; @@ -30231,12 +30202,10 @@ void MessagesManager::send_update_message_content_impl(DialogId dialog_id, const return; } LOG(INFO) << "Send updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source; - auto content_object = get_message_content_object(m->content.get(), td_, dialog_id, m->is_failed_to_send ? 0 : m->date, - m->is_content_secret, need_skip_bot_commands(dialog_id, m), - get_message_max_media_timestamp(m)); send_closure(G()->td(), &Td::send_update, td_api::make_object(get_chat_id_object(dialog_id, "updateMessageContent"), - m->message_id.get(), std::move(content_object))); + m->message_id.get(), + get_message_message_content_object(dialog_id, m))); } void MessagesManager::send_update_message_edited(DialogId dialog_id, const Message *m) { @@ -30340,12 +30309,11 @@ void MessagesManager::send_update_chat_draft_message(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_draft_message"; - on_dialog_updated(d->dialog_id, "send_update_chat_draft_message"); if (d->draft_message == nullptr || !need_hide_dialog_draft_message(d->dialog_id)) { send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatDraftMessage"), - get_draft_message_object(d->draft_message), get_chat_positions_object(d))); + get_draft_message_object(td_, d->draft_message), get_chat_positions_object(d))); } } @@ -30867,16 +30835,15 @@ void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId o } CHECK(old_message_id.is_yet_unsent()); - Dialog *d = get_dialog(dialog_id); - for (auto message_id : it->second) { - CHECK(message_id.is_yet_unsent()); - MessageFullId message_full_id{dialog_id, message_id}; - auto replied_m = get_message(d, message_id); + for (auto message_full_id : it->second) { + auto reply_d = get_dialog(message_full_id.get_dialog_id()); + CHECK(reply_d != nullptr); + auto replied_m = get_message(reply_d, message_full_id.get_message_id()); CHECK(replied_m != nullptr); - CHECK(replied_m->reply_to_message_id == old_message_id); - CHECK(replied_m->reply_in_dialog_id == DialogId()); - set_message_reply(d, replied_m, new_message_id, true); - // TODO rewrite send message log event + const auto *input_reply_to = get_message_input_reply_to(replied_m); + CHECK(input_reply_to != nullptr); + CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, old_message_id)); + update_message_reply_to_message_id(reply_d, replied_m, new_message_id, true); } if (have_new_message) { CHECK(!new_message_id.is_yet_unsent()); @@ -30962,12 +30929,14 @@ MessageFullId MessagesManager::on_send_message_success(int64 random_id, MessageI LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << new_message_id; } - sent_message->message_id = new_message_id; - - if (sent_message->reply_to_message_id != MessageId() && sent_message->reply_to_message_id.is_yet_unsent()) { - set_message_reply(d, sent_message.get(), MessageId(), false); + const auto *input_reply_to = get_message_input_reply_to(sent_message.get()); + if (input_reply_to != nullptr && input_reply_to->is_valid() && + input_reply_to->get_reply_message_full_id(dialog_id).get_message_id().is_yet_unsent()) { + set_message_reply(d, sent_message.get(), MessageInputReplyTo(), false); } + sent_message->message_id = new_message_id; + send_update_message_send_succeeded(d, old_message_id, sent_message.get()); bool need_update = true; @@ -31268,6 +31237,28 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) { } } else if (error.message() == "PHOTO_EXT_INVALID") { error_message = "Photo has unsupported extension. Use one of .jpg, .jpeg, .gif, .png, .tif or .bmp"; + } else if (error.message() == "QUOTE_TEXT_INVALID") { + auto *input_reply_to = get_message_input_reply_to(m); + if (input_reply_to != nullptr && !input_reply_to->is_empty() && input_reply_to->has_quote()) { + auto reply_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); + if (reply_message_full_id.get_message_id().is_valid()) { + get_message_from_server(reply_message_full_id, Auto(), "QUOTE_TEXT_INVALID"); + } + } else { + error_code = 500; + error_message = "Unexpected QUOTE_TEXT_INVALID error"; + } + } else if (error.message() == "REPLY_MESSAGE_ID_INVALID") { + auto *input_reply_to = get_message_input_reply_to(m); + if (input_reply_to != nullptr && !input_reply_to->is_empty()) { + auto reply_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); + if (reply_message_full_id.get_message_id().is_valid()) { + get_message_from_server(reply_message_full_id, Auto(), "REPLY_MESSAGE_ID_INVALID"); + } + } else { + error_code = 500; + error_message = "Unexpected REPLY_MESSAGE_ID_INVALID error"; + } } break; case 403: @@ -31468,16 +31459,25 @@ void MessagesManager::fail_edit_message_media(MessageFullId message_full_id, Sta } void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id, - tl_object_ptr &&draft_message) { + tl_object_ptr &&draft_message, + bool force) { + if (G()->close_flag()) { + return; + } if (!dialog_id.is_valid()) { - LOG(ERROR) << "Receive update chat draft in invalid " << dialog_id; + LOG(ERROR) << "Receive update of draft message in invalid " << dialog_id; + return; + } + if (td_->auth_manager_->is_bot()) { + if (draft_message != nullptr) { + LOG(ERROR) << "Receive update of draft message in " << dialog_id; + } return; } - auto draft = get_draft_message(td_->contacts_manager_.get(), std::move(draft_message)); auto d = get_dialog_force(dialog_id, "on_update_dialog_draft_message"); if (d == nullptr) { LOG(INFO) << "Ignore update chat draft in unknown " << dialog_id; - if (draft != nullptr) { + if (draft_message != nullptr && draft_message->get_id() != telegram_api::draftMessageEmpty::ID) { if (!have_input_peer(dialog_id, AccessRights::Read)) { LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message"; } else { @@ -31490,17 +31490,51 @@ void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, Message // TODO update thread message draft return; } - update_dialog_draft_message(d, std::move(draft), true, true); + + if (!force && draft_message != nullptr && draft_message->get_id() == telegram_api::draftMessage::ID) { + auto *input_reply_to = static_cast(draft_message.get())->reply_to_.get(); + if (input_reply_to != nullptr) { + InputDialogId input_dialog_id; + switch (input_reply_to->get_id()) { + case telegram_api::inputReplyToStory::ID: { + auto reply_to = static_cast(input_reply_to); + input_dialog_id = InputDialogId(reply_to->user_id_); + break; + } + case telegram_api::inputReplyToMessage::ID: { + auto reply_to = static_cast(input_reply_to); + if (reply_to->reply_to_peer_id_ != nullptr) { + input_dialog_id = InputDialogId(reply_to->reply_to_peer_id_); + } + break; + } + default: + UNREACHABLE(); + } + auto reply_in_dialog_id = input_dialog_id.get_dialog_id(); + if (reply_in_dialog_id.is_valid() && !have_dialog_force(reply_in_dialog_id, "on_update_dialog_draft_message")) { + td_->dialog_filter_manager_->load_input_dialog( + input_dialog_id, [actor_id = actor_id(this), dialog_id, top_thread_message_id, + draft_message = std::move(draft_message)](Unit) mutable { + send_closure(actor_id, &MessagesManager::on_update_dialog_draft_message, dialog_id, top_thread_message_id, + std::move(draft_message), true); + }); + return; + } + } + } + update_dialog_draft_message(d, get_draft_message(td_, std::move(draft_message)), true, true); } bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr &&draft_message, bool from_update, bool need_update_dialog_pos) { CHECK(d != nullptr); - if (need_update_draft_message(d->draft_message, draft_message, from_update)) { + if (!td_->auth_manager_->is_bot() && need_update_draft_message(d->draft_message, draft_message, from_update)) { d->draft_message = std::move(draft_message); if (need_update_dialog_pos) { update_dialog_pos(d, "update_dialog_draft_message", false); } + on_dialog_updated(d->dialog_id, "update_dialog_draft_message"); send_update_chat_draft_message(d); return true; } @@ -31835,7 +31869,7 @@ void MessagesManager::set_dialog_background(Dialog *d, BackgroundInfo &&backgrou d->is_background_inited = true; if (is_changed) { - LOG(INFO) << "Set " << d->dialog_id << " backgroud to " << d->background_info; + LOG(INFO) << "Set " << d->dialog_id << " background to " << d->background_info; send_update_chat_background(d); } else { on_dialog_updated(d->dialog_id, "set_dialog_background"); @@ -32453,6 +32487,26 @@ void MessagesManager::on_dialog_photo_updated(DialogId dialog_id) { } } +void MessagesManager::on_dialog_accent_color_id_updated(DialogId dialog_id) { + auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog + if (d != nullptr && d->is_update_new_chat_sent) { + send_closure( + G()->td(), &Td::send_update, + td_api::make_object(get_chat_id_object(dialog_id, "updateChatAccentColor"), + get_dialog_accent_color_id_object(dialog_id))); + } +} + +void MessagesManager::on_dialog_background_custom_emoji_id_updated(DialogId dialog_id) { + auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog + if (d != nullptr && d->is_update_new_chat_sent) { + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + get_chat_id_object(dialog_id, "updateChatBackgroundCustomEmoji"), + get_dialog_background_custom_emoji_id(dialog_id).get())); + } +} + void MessagesManager::on_dialog_title_updated(DialogId dialog_id) { auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog if (d != nullptr) { @@ -32891,6 +32945,40 @@ const DialogPhoto *MessagesManager::get_dialog_photo(DialogId dialog_id) const { } } +int32 MessagesManager::get_dialog_accent_color_id_object(DialogId dialog_id) const { + switch (dialog_id.get_type()) { + case DialogType::User: + return td_->contacts_manager_->get_user_accent_color_id_object(dialog_id.get_user_id()); + case DialogType::Chat: + return td_->contacts_manager_->get_chat_accent_color_id_object(dialog_id.get_chat_id()); + case DialogType::Channel: + return td_->contacts_manager_->get_channel_accent_color_id_object(dialog_id.get_channel_id()); + case DialogType::SecretChat: + return td_->contacts_manager_->get_secret_chat_accent_color_id_object(dialog_id.get_secret_chat_id()); + case DialogType::None: + default: + UNREACHABLE(); + return 0; + } +} + +CustomEmojiId MessagesManager::get_dialog_background_custom_emoji_id(DialogId dialog_id) const { + switch (dialog_id.get_type()) { + case DialogType::User: + return td_->contacts_manager_->get_user_background_custom_emoji_id(dialog_id.get_user_id()); + case DialogType::Chat: + return td_->contacts_manager_->get_chat_background_custom_emoji_id(dialog_id.get_chat_id()); + case DialogType::Channel: + return td_->contacts_manager_->get_channel_background_custom_emoji_id(dialog_id.get_channel_id()); + case DialogType::SecretChat: + return td_->contacts_manager_->get_secret_chat_background_custom_emoji_id(dialog_id.get_secret_chat_id()); + case DialogType::None: + default: + UNREACHABLE(); + return CustomEmojiId(); + } +} + string MessagesManager::get_dialog_title(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: @@ -32922,7 +33010,7 @@ RestrictedRights MessagesManager::get_dialog_default_permissions(DialogId dialog default: UNREACHABLE(); return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false); + false, false, false, false, ChannelType::Unknown); } } @@ -33103,9 +33191,7 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { auto file_id = get_message_content_upload_file_id(m->content.get()); if (!file_id.is_valid()) { - LOG(ERROR) << "Have no file in " - << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date, m->is_content_secret, - false, -1)); + LOG(ERROR) << "Have no file in " << to_string(get_message_message_content_object(dialog_id, m)); return; } auto file_view = td_->file_manager_->get_file_view(file_id); @@ -33716,6 +33802,33 @@ void MessagesManager::upload_dialog_photo(DialogId dialog_id, FileId file_id, bo td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_dialog_photo_callback_, 32, 0); } +void MessagesManager::set_dialog_accent_color(DialogId dialog_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise) { + if (!have_dialog_force(dialog_id, "set_dialog_accent_color")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + switch (dialog_id.get_type()) { + case DialogType::User: + if (dialog_id == get_my_dialog_id()) { + return td_->contacts_manager_->set_accent_color(accent_color_id, background_custom_emoji_id, + std::move(promise)); + } + break; + case DialogType::Chat: + break; + case DialogType::Channel: + return td_->contacts_manager_->set_channel_accent_color(dialog_id.get_channel_id(), accent_color_id, + background_custom_emoji_id, std::move(promise)); + case DialogType::SecretChat: + break; + case DialogType::None: + default: + UNREACHABLE(); + } + promise.set_error(Status::Error(400, "Can't change accent color in the chat")); +} + void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, Promise &&promise) { if (!have_dialog_force(dialog_id, "set_dialog_title")) { return promise.set_error(Status::Error(400, "Chat not found")); @@ -33864,8 +33977,9 @@ void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Prom td_->create_handler(std::move(promise))->send(dialog_id, ttl); } else { bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), - create_chat_set_ttl_message_content(ttl, UserId()), &need_update_dialog_pos); + Message *m = + get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), + create_chat_set_ttl_message_content(ttl, UserId()), false, &need_update_dialog_pos); send_update_new_message(d, m); if (need_update_dialog_pos) { @@ -33893,6 +34007,7 @@ void MessagesManager::set_dialog_permissions(DialogId dialog_id, return promise.set_error(Status::Error(400, "New permissions must be non-empty")); } + ChannelType channel_type = ChannelType::Unknown; switch (dialog_id.get_type()) { case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat permissions")); @@ -33912,6 +34027,7 @@ void MessagesManager::set_dialog_permissions(DialogId dialog_id, if (!status.can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions")); } + channel_type = ChannelType::Megagroup; break; } case DialogType::SecretChat: @@ -33921,7 +34037,7 @@ void MessagesManager::set_dialog_permissions(DialogId dialog_id, UNREACHABLE(); } - RestrictedRights new_permissions(permissions); + RestrictedRights new_permissions(permissions, channel_type); // TODO this can be wrong if there were previous change permissions requests if (get_dialog_default_permissions(dialog_id) == new_permissions) { @@ -34205,7 +34321,7 @@ const MessagesManager::Message *MessagesManager::get_message(const Dialog *d, Me } else { result = d->messages.get_pointer(message_id); if (result != nullptr) { - auto unix_time = G()->unix_time_cached(); + auto unix_time = G()->unix_time(); if (unix_time > result->last_access_date + 5) { result->last_access_date = unix_time; auto list_node = const_cast(static_cast(result)); @@ -34442,9 +34558,9 @@ void MessagesManager::fix_new_message(const Dialog *d, Message *m, bool from_dat } } - m->last_access_date = G()->unix_time_cached(); + m->last_access_date = G()->unix_time(); - if (m->contains_mention) { + if (!from_database && m->contains_mention) { CHECK(!td_->auth_manager_->is_bot()); auto message_content_type = m->content->get_type(); if (message_content_type == MessageContentType::PinMessage) { @@ -34466,7 +34582,7 @@ void MessagesManager::fix_new_message(const Dialog *d, Message *m, bool from_dat if (dialog_type == DialogType::Channel && !m->contains_unread_mention) { auto channel_read_media_period = td_->option_manager_->get_option_integer("channels_read_media_period", (G()->is_test_dc() ? 300 : 7 * 86400)); - if (m->date < G()->unix_time_cached() - channel_read_media_period) { + if (m->date < G()->unix_time() - channel_read_media_period) { update_opened_message_content(m->content.get()); } } @@ -34978,14 +35094,21 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq add_message_to_dialog_message_list(m, d, from_database, from_update, *need_update, need_update_dialog_pos, source); } - if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) { - CHECK(m->reply_in_dialog_id == DialogId()); - if (!m->reply_to_message_id.is_yet_unsent()) { - if (!m->reply_to_message_id.is_scheduled()) { - replied_by_yet_unsent_messages_[MessageFullId{dialog_id, m->reply_to_message_id}]++; + if (m->message_id.is_yet_unsent()) { + const auto *input_reply_to = get_message_input_reply_to(m); + CHECK(input_reply_to != nullptr); + if (!input_reply_to->is_empty()) { + auto replied_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); + auto replied_message_id = replied_message_full_id.get_message_id(); + if (replied_message_id.is_valid() || replied_message_id.is_valid_scheduled()) { + if (!replied_message_id.is_yet_unsent()) { + if (!replied_message_id.is_scheduled()) { + replied_by_yet_unsent_messages_[replied_message_full_id]++; + } + } else { + replied_yet_unsent_messages_[replied_message_full_id].insert({dialog_id, m->message_id}); + } } - } else { - replied_yet_unsent_messages_[MessageFullId{dialog_id, m->reply_to_message_id}].insert(m->message_id); } } @@ -34994,7 +35117,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } if (from_update && !m->is_failed_to_send && dialog_type == DialogType::Channel) { - auto now = max(G()->unix_time_cached(), m->date); + auto now = max(G()->unix_time(), m->date); if (m->date < now - 2 * 86400 && Slice(source) == Slice("updateNewChannelMessage")) { // if the message is pretty old, we might have missed the update that the message has already been read repair_channel_server_unread_count(d); @@ -35232,14 +35355,21 @@ MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialo LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source; const Message *m = message.get(); - if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) { - CHECK(m->reply_in_dialog_id == DialogId()); - if (!m->reply_to_message_id.is_yet_unsent()) { - if (!m->reply_to_message_id.is_scheduled()) { - replied_by_yet_unsent_messages_[MessageFullId{dialog_id, m->reply_to_message_id}]++; + if (m->message_id.is_yet_unsent()) { + const auto *input_reply_to = get_message_input_reply_to(m); + CHECK(input_reply_to != nullptr); + if (!input_reply_to->is_empty()) { + auto replied_message_full_id = input_reply_to->get_reply_message_full_id(dialog_id); + auto replied_message_id = replied_message_full_id.get_message_id(); + if (replied_message_id.is_valid() || replied_message_id.is_valid_scheduled()) { + if (!replied_message_id.is_yet_unsent()) { + if (!replied_message_id.is_scheduled()) { + replied_by_yet_unsent_messages_[replied_message_full_id]++; + } + } else { + replied_yet_unsent_messages_[replied_message_full_id].insert({dialog_id, m->message_id}); + } } - } else { - replied_yet_unsent_messages_[MessageFullId{dialog_id, m->reply_to_message_id}].insert(m->message_id); } } @@ -35810,24 +35940,20 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } } else { if (new_message->forward_info != nullptr) { - if (old_message->forward_info->author_signature != new_message->forward_info->author_signature) { - old_message->forward_info->author_signature = new_message->forward_info->author_signature; - LOG(DEBUG) << "Change message signature"; - need_send_update = true; - } if (*old_message->forward_info != *new_message->forward_info) { bool need_warning = [&] { if (replace_legacy) { return false; } - if (old_message->forward_info->is_imported != new_message->forward_info->is_imported) { + if (old_message->forward_info->is_imported || new_message->forward_info->is_imported) { return true; } - if (!is_scheduled && !message_id.is_yet_unsent() && !old_message->forward_info->is_imported) { + if (!is_scheduled && !message_id.is_yet_unsent()) { return true; } - return !is_forward_info_sender_hidden(new_message->forward_info.get()) && - !is_forward_info_sender_hidden(old_message->forward_info.get()); + // yet unsent or scheduled messages can change sender name or author signature when being sent + return !old_message->forward_info->origin.has_sender_signature() && + !new_message->forward_info->origin.has_sender_signature(); }(); if (need_warning) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed forward info from " @@ -35873,7 +35999,9 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } } if (new_message->is_mention_notification_disabled) { - old_message->is_mention_notification_disabled = true; + // is_mention_notification_disabled must not be changed, because the message notification will not + // be moved to the other group + // old_message->is_mention_notification_disabled = true; } if (old_message->ttl_period != new_message->ttl_period) { @@ -35887,47 +36015,33 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } } + const bool is_replied_message_info_changed = old_message->replied_message_info != new_message->replied_message_info; const bool is_top_thread_message_id_changed = old_message->top_thread_message_id != new_message->top_thread_message_id; const bool is_is_topic_message_changed = old_message->is_topic_message != new_message->is_topic_message; - if (old_message->reply_to_message_id != new_message->reply_to_message_id || - old_message->reply_in_dialog_id != new_message->reply_in_dialog_id || is_top_thread_message_id_changed || - is_is_topic_message_changed || old_message->reply_to_story_full_id != new_message->reply_to_story_full_id) { + if (is_replied_message_info_changed || is_top_thread_message_id_changed || is_is_topic_message_changed || + old_message->reply_to_story_full_id != new_message->reply_to_story_full_id) { if (!replace_legacy && is_new_available) { - if (old_message->reply_to_message_id != new_message->reply_to_message_id) { + if (is_replied_message_info_changed) { LOG(INFO) << "Update replied message of " << MessageFullId{dialog_id, message_id} << " from " - << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id; - if (message_id.is_yet_unsent() && new_message->reply_to_message_id == MessageId() && - old_message->reply_in_dialog_id == DialogId() && is_deleted_message(d, old_message->reply_to_message_id) && - !is_message_in_dialog) { - // reply to a deleted message, which was available locally - } else if (message_id.is_yet_unsent() && old_message->reply_to_message_id == MessageId() && - new_message->reply_in_dialog_id == DialogId() && - is_deleted_message(d, new_message->reply_to_message_id) && !is_message_in_dialog) { - // reply to a locally deleted yet unsent message, which was available server-side - } else if (old_message->reply_to_message_id.is_valid_scheduled() && - old_message->reply_to_message_id.is_scheduled_server() && - new_message->reply_to_message_id.is_valid_scheduled() && - new_message->reply_to_message_id.is_scheduled_server() && - old_message->reply_to_message_id.get_scheduled_server_message_id() == - new_message->reply_to_message_id.get_scheduled_server_message_id() && - new_message->reply_in_dialog_id == DialogId()) { - // schedule date change - } else if (message_id.is_yet_unsent() && - old_message->top_thread_message_id == new_message->reply_to_message_id && - new_message->reply_in_dialog_id == DialogId()) { - // move of reply to the top thread message after deletion of the replied message - } else { + << old_message->replied_message_info << " to " << new_message->replied_message_info; + auto is_reply_to_deleted_message = [&](const RepliedMessageInfo &replied_message_info) { + auto reply_message_full_id = replied_message_info.get_reply_message_full_id(dialog_id); + auto *d = get_dialog(reply_message_full_id.get_dialog_id()); + if (d == nullptr) { + return false; + } + return is_deleted_message(d, reply_message_full_id.get_message_id()); + }; + if (RepliedMessageInfo::need_reply_changed_warning( + td_, old_message->replied_message_info, new_message->replied_message_info, + old_message->top_thread_message_id, message_id.is_yet_unsent() && !is_message_in_dialog, + is_reply_to_deleted_message)) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed replied message from " - << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id + << old_message->replied_message_info << " to " << new_message->replied_message_info << ", message content type is " << old_content_type << '/' << new_content_type; } } - if (old_message->reply_in_dialog_id != new_message->reply_in_dialog_id) { - LOG(ERROR) << message_id << " in " << dialog_id << " has changed replied message chat from " - << old_message->reply_in_dialog_id << " to " << new_message->reply_in_dialog_id - << ", message content type is " << old_content_type << '/' << new_content_type; - } if (is_top_thread_message_id_changed) { if ((new_message->top_thread_message_id != MessageId() && old_message->top_thread_message_id != MessageId()) || is_message_in_dialog) { @@ -35968,15 +36082,15 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (is_message_in_dialog) { unregister_message_reply(d->dialog_id, old_message); } - old_message->reply_in_dialog_id = new_message->reply_in_dialog_id; - old_message->reply_to_message_id = new_message->reply_to_message_id; + old_message->replied_message_info = std::move(new_message->replied_message_info); old_message->reply_to_story_full_id = new_message->reply_to_story_full_id; old_message->top_thread_message_id = new_message->top_thread_message_id; old_message->reply_to_random_id = 0; - if (old_message->reply_in_dialog_id == DialogId() && old_message->reply_to_message_id != MessageId() && - message_id.is_yet_unsent() && - (dialog_id.get_type() == DialogType::SecretChat || old_message->reply_to_message_id.is_yet_unsent())) { - auto *replied_m = get_message(d, old_message->reply_to_message_id); + + auto same_chat_reply_to_message_id = old_message->replied_message_info.get_same_chat_reply_to_message_id(); + if (same_chat_reply_to_message_id != MessageId() && message_id.is_yet_unsent() && + (dialog_id.get_type() == DialogType::SecretChat || same_chat_reply_to_message_id.is_yet_unsent())) { + auto *replied_m = get_message(d, same_chat_reply_to_message_id); if (replied_m != nullptr) { old_message->reply_to_random_id = replied_m->random_id; } @@ -36071,7 +36185,9 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr if (old_message->legacy_layer != new_message->legacy_layer) { old_message->legacy_layer = new_message->legacy_layer; } - if ((old_message->media_album_id == 0 || td_->auth_manager_->is_bot()) && new_message->media_album_id != 0) { + if (old_message->media_album_id != new_message->media_album_id && + (old_message->media_album_id == 0 || (new_message->media_album_id == 0 && message_id.is_yet_unsent()) || + td_->auth_manager_->is_bot())) { old_message->media_album_id = new_message->media_album_id; LOG(DEBUG) << "Update message media_album_id"; need_send_update = true; @@ -36179,7 +36295,9 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr 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)) { + is_content_changed) || + old_message->invert_media != new_message->invert_media) { + old_message->invert_media = new_message->invert_media; send_update_message_content(d, old_message, is_message_in_dialog, "update_message"); need_send_update = true; } @@ -36272,6 +36390,7 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me } else { merge_message_contents(td_, old_content.get(), new_content.get(), need_message_changed_warning(old_message), dialog_id, need_merge_files, is_content_changed, need_update); + compare_message_contents(td_, old_content.get(), new_content.get(), is_content_changed, need_update); } if (need_finish_upload) { // the file is likely to be already merged with a server file, but if not we need to @@ -36594,6 +36713,8 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di d->is_channel_difference_finished = true; } + auto draft_message = std::move(d->draft_message); + d->draft_message = nullptr; unique_ptr last_database_message; if (!d->messages.empty()) { d->messages.foreach([&](const MessageId &message_id, unique_ptr &message) { @@ -36602,9 +36723,10 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di }); d->messages = {}; } - MessageId last_database_message_id = d->last_database_message_id; + auto last_database_message_id = d->last_database_message_id; d->last_database_message_id = MessageId(); if (td_->auth_manager_->is_bot()) { + draft_message = nullptr; last_database_message = nullptr; d->first_database_message_id = MessageId(); last_database_message_id = MessageId(); @@ -36665,6 +36787,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di if (!is_loaded_from_database) { CHECK(order == DEFAULT_ORDER); + CHECK(draft_message == nullptr); CHECK(last_database_message == nullptr); } @@ -36685,15 +36808,17 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di CHECK(d->messages.empty()); - 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, source); + fix_new_dialog(d, std::move(draft_message), 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, source); return d; } -void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_database_message, - MessageId last_database_message_id, int64 order, int32 last_clear_history_date, +void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft_message, + 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, @@ -36781,7 +36906,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab d->notification_settings.mute_until <= G()->unix_time()) { d->notification_settings.mute_until = 0; } else { - schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until); + schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until, G()->unix_time()); } if (d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid()) { auto pinned_message_id = d->notification_info->pinned_message_notification_message_id_; @@ -36804,19 +36929,20 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab } { - auto it = pending_add_dialog_last_database_message_dependent_dialogs_.find(dialog_id); - if (it != pending_add_dialog_last_database_message_dependent_dialogs_.end()) { + auto it = pending_add_dialog_dependent_dialogs_.find(dialog_id); + if (it != pending_add_dialog_dependent_dialogs_.end()) { auto pending_dialog_ids = std::move(it->second); - pending_add_dialog_last_database_message_dependent_dialogs_.erase(it); + pending_add_dialog_dependent_dialogs_.erase(it); for (auto &pending_dialog_id : pending_dialog_ids) { - auto &counter_message = pending_add_dialog_last_database_message_[pending_dialog_id]; - CHECK(counter_message.first > 0); - counter_message.first--; - if (counter_message.first == 0) { + auto &add_dialog_data = pending_add_dialog_data_[pending_dialog_id]; + CHECK(add_dialog_data.dependent_dialog_count_ > 0); + add_dialog_data.dependent_dialog_count_--; + if (add_dialog_data.dependent_dialog_count_ == 0) { LOG(INFO) << "Add postponed last database message in " << pending_dialog_id; - add_dialog_last_database_message(get_dialog(pending_dialog_id), std::move(counter_message.second)); - pending_add_dialog_last_database_message_.erase(pending_dialog_id); + add_pending_dialog_data(get_dialog(pending_dialog_id), std::move(add_dialog_data.last_message_), + std::move(add_dialog_data.draft_message_)); + pending_add_dialog_data_.erase(pending_dialog_id); } } } @@ -36908,38 +37034,53 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab d->debug_last_database_message_id = d->last_database_message_id; d->debug_last_new_message_id = d->last_new_message_id; - if (last_database_message != nullptr) { + if (last_database_message != nullptr || draft_message != nullptr) { Dependencies dependencies; - add_message_dependencies(dependencies, last_database_message.get()); + if (last_database_message != nullptr) { + add_message_dependencies(dependencies, last_database_message.get()); + } + if (draft_message != nullptr) { + draft_message->add_dependencies(dependencies); + } int32 dependent_dialog_count = 0; for (const auto &other_dialog_id : dependencies.get_dialog_ids()) { if (other_dialog_id.is_valid() && !have_dialog(other_dialog_id)) { - LOG(INFO) << "Postpone adding of last message in " << dialog_id << " because of cyclic dependency with " + LOG(INFO) << "Postpone adding of data in " << dialog_id << " because of cyclic dependency with " << other_dialog_id; - pending_add_dialog_last_database_message_dependent_dialogs_[other_dialog_id].push_back(dialog_id); + pending_add_dialog_dependent_dialogs_[other_dialog_id].push_back(dialog_id); dependent_dialog_count++; } }; - auto last_message_date = last_database_message->date; + auto pending_order = DEFAULT_ORDER; + if (last_database_message != nullptr) { + auto last_message_order = get_dialog_order(last_message_id, last_database_message->date); + if (last_message_order > pending_order) { + pending_order = last_message_order; + } + } + if (draft_message != nullptr && !need_hide_dialog_draft_message(dialog_id)) { + int64 draft_order = get_dialog_order(MessageId(), draft_message->get_date()); + if (draft_order > pending_order) { + pending_order = draft_order; + } + } if (dependent_dialog_count == 0) { - if (!add_dialog_last_database_message(d, std::move(last_database_message))) { + if (!add_pending_dialog_data(d, std::move(last_database_message), std::move(draft_message))) { // failed to add last message; keep the current position and get history from the database - d->pending_last_message_date = last_message_date; - d->pending_last_message_id = last_message_id; + d->pending_order = pending_order; } } else { - // can't add message immediately, because need to notify first about adding of dependent dialogs - d->pending_last_message_date = last_message_date; - d->pending_last_message_id = last_message_id; - pending_add_dialog_last_database_message_[dialog_id] = {dependent_dialog_count, std::move(last_database_message)}; + // can't add data immediately, because need to notify first about adding of dependent dialogs + d->pending_order = pending_order; + pending_add_dialog_data_[dialog_id] = {dependent_dialog_count, std::move(last_database_message), + std::move(draft_message)}; } } else if (last_database_message_id.is_valid()) { auto date = DialogDate(order, dialog_id).get_date(); if (date < MIN_PINNED_DIALOG_DATE) { - d->pending_last_message_date = date; - d->pending_last_message_id = last_message_id; + d->pending_order = get_dialog_order(last_message_id, date); } } @@ -37085,48 +37226,57 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab } } -bool MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptr &&last_database_message) { +bool MessagesManager::add_pending_dialog_data(Dialog *d, unique_ptr &&last_database_message, + unique_ptr &&draft_message) { CHECK(d != nullptr); - CHECK(last_database_message != nullptr); + CHECK(last_database_message != nullptr || draft_message != nullptr); CHECK(!td_->auth_manager_->is_bot()); - auto dialog_id = d->dialog_id; - auto message_id = last_database_message->message_id; - CHECK(message_id.is_valid()); - LOG_CHECK(d->last_database_message_id == message_id) - << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id; - bool need_update_dialog_pos = false; - const Message *m = nullptr; - if (have_input_peer(dialog_id, AccessRights::Read)) { - bool need_update = false; - m = add_message_to_dialog(d, std::move(last_database_message), true, false, &need_update, &need_update_dialog_pos, - "add_dialog_last_database_message 1"); - if (need_update_dialog_pos) { - LOG(ERROR) << "Need to update pos in " << dialog_id; - } - } - if (m != nullptr) { - 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) { - d->pending_last_message_date = 0; - d->pending_last_message_id = MessageId(); - need_update_dialog_pos = true; + bool was_added_last_message = false; + if (last_database_message != nullptr) { + auto dialog_id = d->dialog_id; + auto message_id = last_database_message->message_id; + CHECK(message_id.is_valid()); + LOG_CHECK(d->last_database_message_id == message_id) + << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id; + + const Message *m = nullptr; + if (have_input_peer(dialog_id, AccessRights::Read)) { + bool need_update = false; + m = add_message_to_dialog(d, std::move(last_database_message), true, false, &need_update, &need_update_dialog_pos, + "add_pending_dialog_data 1"); + if (need_update_dialog_pos) { + LOG(ERROR) << "Need to update pos in " << dialog_id; + } } - on_dialog_updated(dialog_id, "add_dialog_last_database_message 4"); // resave without last database message + if (m != nullptr) { + set_dialog_last_message_id(d, m->message_id, "add_pending_dialog_data 2", m); + send_update_chat_last_message(d, "add_pending_dialog_data 3"); + was_added_last_message = true; + } else { + on_dialog_updated(dialog_id, "add_pending_dialog_data 4"); // resave without last database message - if (!td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ && - dialog_id != being_added_by_new_message_dialog_id_ && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { - load_last_dialog_message(d, "add_dialog_last_database_message 5"); + if (!td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ && + dialog_id != being_added_by_new_message_dialog_id_ && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { + load_last_dialog_message(d, "add_pending_dialog_data 5"); + } } } + if (draft_message != nullptr) { + d->draft_message = std::move(draft_message); + need_update_dialog_pos = true; + send_update_chat_draft_message(d); + } + if (d->pending_order != DEFAULT_ORDER) { + d->pending_order = DEFAULT_ORDER; + need_update_dialog_pos = true; + } if (need_update_dialog_pos) { - update_dialog_pos(d, "add_dialog_last_database_message 6"); + update_dialog_pos(d, "add_pending_dialog_data 6"); } - return m != nullptr; + return was_added_last_message; } void MessagesManager::update_dialogs_hints(const Dialog *d) { @@ -37287,11 +37437,10 @@ void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need new_order = clear_order; } } - if (d->pending_last_message_date > 0) { - LOG(INFO) << "Pending last " << d->pending_last_message_id << " at " << d->pending_last_message_date << " found"; - int64 pending_order = get_dialog_order(d->pending_last_message_id, d->pending_last_message_date); - if (pending_order > new_order) { - new_order = pending_order; + if (d->pending_order != DEFAULT_ORDER) { + LOG(INFO) << "Pending order " << d->pending_order << " found"; + if (d->pending_order > new_order) { + new_order = d->pending_order; } } if (d->draft_message != nullptr && !need_hide_dialog_draft_message(d->dialog_id)) { @@ -38794,8 +38943,7 @@ void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 reques set_dialog_is_marked_as_unread(d, is_marked_as_unread); } - update_dialog_draft_message(d, get_draft_message(td_->contacts_manager_.get(), std::move(dialog->draft_)), true, - false); + update_dialog_draft_message(d, get_draft_message(td_, std::move(dialog->draft_)), true, false); on_get_channel_dialog(dialog_id, MessageId(ServerMessageId(dialog->top_message_)), MessageId(ServerMessageId(dialog->read_inbox_max_id_)), dialog->unread_count_, @@ -38966,7 +39114,7 @@ void MessagesManager::reget_message_from_server_if_needed(DialogId dialog_id, co } if (need_reget_message_content(m->content.get()) || (m->legacy_layer != 0 && m->legacy_layer < MTPROTO_LAYER) || - m->reply_info.need_reget(td_)) { + m->reply_info.need_reget(td_) || m->replied_message_info.need_reget()) { MessageFullId message_full_id{dialog_id, m->message_id}; LOG(INFO) << "Reget from server " << message_full_id; get_message_from_server(message_full_id, Auto(), "reget_message_from_server_if_needed"); @@ -39107,19 +39255,26 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { } void MessagesManager::update_forward_count(DialogId dialog_id, const Message *m) { - if (!td_->auth_manager_->is_bot() && m->forward_info != nullptr && m->forward_info->sender_dialog_id.is_valid() && - m->forward_info->message_id.is_valid() && - (!is_discussion_message(dialog_id, m) || m->forward_info->sender_dialog_id != m->forward_info->from_dialog_id || - m->forward_info->message_id != m->forward_info->from_message_id)) { - update_forward_count(m->forward_info->sender_dialog_id, m->forward_info->message_id, m->date); + if (td_->auth_manager_->is_bot() || m->forward_info == nullptr) { + return; + } + auto origin_message_full_id = m->forward_info->origin.get_message_full_id(); + if (!origin_message_full_id.get_message_id().is_valid()) { + return; } + if (is_discussion_message(dialog_id, m) && + origin_message_full_id == MessageFullId(m->forward_info->from_dialog_id, m->forward_info->from_message_id)) { + return; + } + update_forward_count(origin_message_full_id, m->date); } -void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date) { +void MessagesManager::update_forward_count(MessageFullId message_full_id, int32 update_date) { CHECK(!td_->auth_manager_->is_bot()); + auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); - Message *m = get_message_force(d, message_id, "update_forward_count"); + Message *m = get_message_force(d, message_full_id.get_message_id(), "update_forward_count"); if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0 && m->interaction_info_update_date < update_date) { if (m->forward_count == 0) { @@ -39169,39 +39324,68 @@ void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Mes } } -void MessagesManager::set_message_reply(const Dialog *d, Message *m, MessageId reply_to_message_id, +void MessagesManager::set_message_reply(const Dialog *d, Message *m, MessageInputReplyTo input_reply_to, bool is_message_in_dialog) { LOG(INFO) << "Update replied message of " << MessageFullId{d->dialog_id, m->message_id} << " from " - << m->reply_to_message_id << " to " << reply_to_message_id; + << m->replied_message_info << " to " << input_reply_to; if (is_message_in_dialog) { unregister_message_reply(d->dialog_id, m); } - m->reply_in_dialog_id = DialogId(); - m->reply_to_message_id = reply_to_message_id; + m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); m->reply_to_story_full_id = StoryFullId(); m->reply_to_random_id = 0; - if (reply_to_message_id != MessageId() && m->message_id.is_yet_unsent() && - (d->dialog_id.get_type() == DialogType::SecretChat || reply_to_message_id.is_yet_unsent())) { - auto *replied_m = get_message(d, reply_to_message_id); + auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); + if (same_chat_reply_to_message_id != MessageId() && m->message_id.is_yet_unsent() && + (d->dialog_id.get_type() == DialogType::SecretChat || same_chat_reply_to_message_id.is_yet_unsent())) { + auto *replied_m = get_message(d, same_chat_reply_to_message_id); if (replied_m != nullptr) { m->reply_to_random_id = replied_m->random_id; } } + if (!m->message_id.is_any_server()) { + m->input_reply_to = std::move(input_reply_to); + } if (is_message_in_dialog) { register_message_reply(d->dialog_id, m); } update_message_max_reply_media_timestamp(d, m, is_message_in_dialog); } +void MessagesManager::update_message_reply_to_message_id(const Dialog *d, Message *m, MessageId reply_to_message_id, + bool is_message_in_dialog) { + LOG(INFO) << "Update identifier of replied message of " << MessageFullId{d->dialog_id, m->message_id} << " from " + << m->replied_message_info << " to " << reply_to_message_id; + if (is_message_in_dialog) { + unregister_message_reply(d->dialog_id, m); + } + m->replied_message_info.set_message_id(reply_to_message_id); + if (!m->message_id.is_any_server()) { + m->input_reply_to.set_message_id(reply_to_message_id); + } + if (is_message_in_dialog) { + register_message_reply(d->dialog_id, m); + } +} + void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) { - if (m->reply_to_message_id == MessageId() || !m->reply_to_message_id.is_yet_unsent()) { + const auto *input_reply_to = get_message_input_reply_to(m); + CHECK(input_reply_to != nullptr); + if (input_reply_to->is_empty()) { return; } + auto replied_message_full_id = input_reply_to->get_reply_message_full_id(d->dialog_id); + auto replied_message_id = replied_message_full_id.get_message_id(); + if (replied_message_id == MessageId() || !replied_message_id.is_yet_unsent()) { + return; + } + CHECK(replied_message_full_id.get_dialog_id() == d->dialog_id); auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id"); - auto new_reply_to_message_id = - message_id.is_valid() || message_id.is_valid_scheduled() ? message_id : m->top_thread_message_id; - set_message_reply(d, m, new_reply_to_message_id, false); + if (message_id.is_valid() || message_id.is_valid_scheduled()) { + update_message_reply_to_message_id(d, m, message_id, false); + } else { + set_message_reply(d, m, MessageInputReplyTo{m->top_thread_message_id, DialogId(), FormattedText()}, false); + } } MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog_id, unique_ptr &&message, @@ -39329,14 +39513,12 @@ void MessagesManager::on_binlog_events(vector &&events) { CHECK(message->content->get_type() == MessageContentType::Text); + auto bot_user_id = log_event.bot_user_id; Dependencies dependencies; - dependencies.add_dialog_dependencies(dialog_id); + dependencies.add(bot_user_id); + dependencies.add_dialog_and_dependencies(dialog_id); add_message_dependencies(dependencies, message.get()); - dependencies.resolve_force(td_, "SendBotStartMessageLogEvent"); - - auto bot_user_id = log_event.bot_user_id; - if (!td_->contacts_manager_->have_user_force(bot_user_id, "SendBotStartMessageLogEvent")) { - LOG(ERROR) << "Can't find bot " << bot_user_id; + if (!dependencies.resolve_force(td_, "SendBotStartMessageLogEvent")) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } @@ -39441,26 +39623,18 @@ void MessagesManager::on_binlog_events(vector &&events) { auto messages = std::move(log_event.messages_out); Dependencies dependencies; - dependencies.add_dialog_dependencies(to_dialog_id); - dependencies.add_dialog_dependencies(from_dialog_id); + dependencies.add_dialog_and_dependencies(to_dialog_id); + dependencies.add_dialog_and_dependencies(from_dialog_id); for (auto &message : messages) { add_message_dependencies(dependencies, message.get()); } - dependencies.resolve_force(td_, "ForwardMessagesLogEvent"); - - Dialog *to_dialog = get_dialog_force(to_dialog_id, "ForwardMessagesLogEvent to"); - if (to_dialog == nullptr) { - LOG(ERROR) << "Can't find " << to_dialog_id << " to forward messages to"; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - continue; - } - Dialog *from_dialog = get_dialog_force(from_dialog_id, "ForwardMessagesLogEvent from"); - if (from_dialog == nullptr) { - LOG(ERROR) << "Can't find " << from_dialog_id << " to forward messages from"; + if (!dependencies.resolve_force(td_, "ForwardMessagesLogEvent")) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } + Dialog *to_dialog = get_dialog(to_dialog_id); + CHECK(to_dialog != nullptr); to_dialog->was_opened = true; auto now = G()->unix_time(); @@ -39643,18 +39817,14 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event_parse(log_event, event.get_data()).ensure(); auto channel_id = log_event.channel_id_; - if (!td_->contacts_manager_->have_channel_force(channel_id, "DeleteAllChannelMessagesFromSenderOnServer")) { - LOG(ERROR) << "Can't find " << channel_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - break; - } - auto sender_dialog_id = log_event.sender_dialog_id_; - if (!have_dialog_info_force(sender_dialog_id, "DeleteAllChannelMessagesFromSenderOnServer") || + Dependencies dependencies; + dependencies.add(channel_id); + dependencies.add_dialog_dependencies(sender_dialog_id); + if (!dependencies.resolve_force(td_, "DeleteAllChannelMessagesFromSenderOnServer") || !have_input_peer(sender_dialog_id, AccessRights::Know)) { - LOG(ERROR) << "Can't find " << sender_dialog_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); - break; + continue; } delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, event.id_, Auto()); @@ -39711,24 +39881,23 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; CHECK(dialog_id.get_type() == DialogType::SecretChat); - if (!td_->contacts_manager_->have_secret_chat_force(dialog_id.get_secret_chat_id(), - "ReadHistoryInSecretChat")) { - LOG(ERROR) << "Can't read history in unknown " << dialog_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - break; - } - force_create_dialog(dialog_id, "ReadHistoryInSecretChatLogEvent"); - Dialog *d = get_dialog(dialog_id); - if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) { + Dependencies dependencies; + dependencies.add_dialog_and_dependencies(dialog_id); + if (!dependencies.resolve_force(td_, "ReadHistoryInSecretChat") || + !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } + auto &log_event_id = read_history_log_event_ids_[dialog_id][0]; if (log_event_id.log_event_id != 0) { // we need only the latest read history event binlog_erase(G()->td_db()->get_binlog(), log_event_id.log_event_id); } log_event_id.log_event_id = event.id_; + + Dialog *d = get_dialog(dialog_id); + CHECK(d != nullptr); d->last_read_inbox_message_date = log_event.max_date_; read_history_on_server_impl(d, MessageId()); @@ -39870,11 +40039,8 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; - bool have_info = dialog_id.get_type() == DialogType::User - ? td_->contacts_manager_->have_user_force(dialog_id.get_user_id(), - "ToggleDialogIsMarkedAsUnreadOnServerLogEvent") - : have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent"); - if (!have_info || !have_input_peer(dialog_id, AccessRights::Read)) { + if (!have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent") || + !have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -40002,10 +40168,8 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dependencies dependencies; - dependencies.add_dialog_dependencies(dialog_id); // dialog itself may not exist - dependencies.resolve_force(td_, "RegetDialogLogEvent"); - - get_dialog_force(dialog_id, "RegetDialogLogEvent"); // load it if exists + dependencies.add_dialog_and_dependencies(dialog_id); + dependencies.resolve_force(td_, "RegetDialogLogEvent", true); // dialog itself may not exist if (!have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); @@ -40300,6 +40464,24 @@ Result MessagesManager::get_payment_successful_message_id(Messa return m->message_id.get_server_message_id(); } +Result MessagesManager::get_giveaway_message_id(MessageFullId message_full_id) { + auto m = get_message_force(message_full_id, "get_giveaway_message_id"); + if (m == nullptr) { + return Status::Error(400, "Message not found"); + } + if (m->content->get_type() != MessageContentType::Giveaway) { + return Status::Error(400, "Message has wrong type"); + } + if (m->message_id.is_scheduled()) { + return Status::Error(400, "Wrong scheduled message identifier"); + } + if (!m->message_id.is_server()) { + return Status::Error(400, "Wrong message identifier"); + } + + return m->message_id.get_server_message_id(); +} + void MessagesManager::remove_sponsored_dialog() { set_sponsored_dialog(DialogId(), DialogSource()); } @@ -40459,7 +40641,7 @@ void MessagesManager::get_current_state(vector &dialog) { const Dialog *d = dialog.get(); auto update = td_api::make_object(get_chat_object(d)); - if (update->chat_->last_message_ != nullptr && update->chat_->last_message_->forward_info_ != nullptr) { + if (update->chat_->last_message_ != nullptr) { last_message_updates.push_back(td_api::make_object( get_chat_id_object(dialog_id, "updateChatLastMessage"), std::move(update->chat_->last_message_), get_chat_positions_object(d))); diff --git a/lib/tgchat/ext/td/td/telegram/MessagesManager.h b/lib/tgchat/ext/td/td/telegram/MessagesManager.h index 367fa5d0..fe9a7701 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesManager.h +++ b/lib/tgchat/ext/td/td/telegram/MessagesManager.h @@ -6,11 +6,13 @@ // #pragma once +#include "td/telegram/AccentColorId.h" #include "td/telegram/AccessRights.h" #include "td/telegram/AffectedHistory.h" #include "td/telegram/BackgroundInfo.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatReactions.h" +#include "td/telegram/CustomEmojiId.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogDate.h" #include "td/telegram/DialogDb.h" @@ -36,6 +38,7 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/MessageLinkInfo.h" +#include "td/telegram/MessageOrigin.h" #include "td/telegram/MessageReplyHeader.h" #include "td/telegram/MessageReplyInfo.h" #include "td/telegram/MessageSearchFilter.h" @@ -44,7 +47,6 @@ #include "td/telegram/MessageThreadInfo.h" #include "td/telegram/MessageTtl.h" #include "td/telegram/MessageViewer.h" -#include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Notification.h" #include "td/telegram/NotificationGroupFromDatabase.h" @@ -58,6 +60,7 @@ #include "td/telegram/Photo.h" #include "td/telegram/ReactionType.h" #include "td/telegram/RecentDialogList.h" +#include "td/telegram/RepliedMessageInfo.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/RestrictionReason.h" #include "td/telegram/ScheduledServerMessageId.h" @@ -118,33 +121,6 @@ class Td; class MessagesManager final : public Actor { public: - // static constexpr int32 MESSAGE_FLAG_IS_UNREAD = 1 << 0; - static constexpr int32 MESSAGE_FLAG_IS_OUT = 1 << 1; - static constexpr int32 MESSAGE_FLAG_IS_FORWARDED = 1 << 2; - static constexpr int32 MESSAGE_FLAG_IS_REPLY = 1 << 3; - static constexpr int32 MESSAGE_FLAG_HAS_MENTION = 1 << 4; - static constexpr int32 MESSAGE_FLAG_HAS_UNREAD_CONTENT = 1 << 5; - static constexpr int32 MESSAGE_FLAG_HAS_REPLY_MARKUP = 1 << 6; - static constexpr int32 MESSAGE_FLAG_HAS_ENTITIES = 1 << 7; - static constexpr int32 MESSAGE_FLAG_HAS_FROM_ID = 1 << 8; - static constexpr int32 MESSAGE_FLAG_HAS_MEDIA = 1 << 9; - static constexpr int32 MESSAGE_FLAG_HAS_INTERACTION_INFO = 1 << 10; - static constexpr int32 MESSAGE_FLAG_IS_SENT_VIA_BOT = 1 << 11; - static constexpr int32 MESSAGE_FLAG_IS_SILENT = 1 << 13; - static constexpr int32 MESSAGE_FLAG_IS_POST = 1 << 14; - static constexpr int32 MESSAGE_FLAG_HAS_EDIT_DATE = 1 << 15; - static constexpr int32 MESSAGE_FLAG_HAS_AUTHOR_SIGNATURE = 1 << 16; - static constexpr int32 MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID = 1 << 17; - static constexpr int32 MESSAGE_FLAG_IS_FROM_SCHEDULED = 1 << 18; - static constexpr int32 MESSAGE_FLAG_IS_LEGACY = 1 << 19; - static constexpr int32 MESSAGE_FLAG_HAS_REACTIONS = 1 << 20; - static constexpr int32 MESSAGE_FLAG_HIDE_EDIT_DATE = 1 << 21; - static constexpr int32 MESSAGE_FLAG_IS_RESTRICTED = 1 << 22; - static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23; - static constexpr int32 MESSAGE_FLAG_IS_PINNED = 1 << 24; - static constexpr int32 MESSAGE_FLAG_HAS_TTL_PERIOD = 1 << 25; - static constexpr int32 MESSAGE_FLAG_NOFORWARDS = 1 << 26; - static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW = 1 << 1; static constexpr int32 SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP = 1 << 2; static constexpr int32 SEND_MESSAGE_FLAG_HAS_ENTITIES = 1 << 3; @@ -159,6 +135,7 @@ class MessagesManager final : public Actor { 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 SEND_MESSAGE_FLAG_INVERT_MEDIA = 1 << 16; static constexpr int32 ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME = 30 * 60; @@ -171,6 +148,8 @@ class MessagesManager final : public Actor { static bool is_invalid_poll_message(const telegram_api::Message *message); + static int32 get_message_date(const tl_object_ptr &message_ptr); + tl_object_ptr get_input_peer(DialogId dialog_id, AccessRights access_rights) const; static tl_object_ptr get_input_peer_force(DialogId dialog_id); @@ -245,9 +224,6 @@ class MessagesManager final : public Actor { vector> &&messages, Promise> &&promise); - void on_get_message_public_forwards(int32 total_count, vector> &&messages, - int32 next_rate, Promise> &&promise); - // if message is from_update, flags have_previous and have_next are ignored and must be both true MessageFullId on_get_message(tl_object_ptr message_ptr, bool from_update, bool is_channel_message, bool is_scheduled, const char *source); @@ -289,10 +265,10 @@ class MessagesManager final : public Actor { void on_get_common_dialogs(UserId user_id, int64 offset_chat_id, vector> &&chats, int32 total_count); - bool on_update_message_id(int64 random_id, MessageId new_message_id, const string &source); + bool on_update_message_id(int64 random_id, MessageId new_message_id, const char *source); void on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id, - tl_object_ptr &&draft_message); + tl_object_ptr &&draft_message, bool force = false); void on_update_dialog_is_pinned(FolderId folder_id, DialogId dialog_id, bool is_pinned); @@ -455,47 +431,52 @@ class MessagesManager final : public Actor { DialogId get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const; + struct ForwardedMessageInfo { + int32 origin_date_ = 0; + MessageOrigin origin_; + unique_ptr content_; + }; + ForwardedMessageInfo get_forwarded_message_info(MessageFullId message_full_id); + MessageInputReplyTo get_message_input_reply_to(DialogId dialog_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, bool for_draft); + td_api::object_ptr &&reply_to, + bool for_draft); Result> send_message( - DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; Result> send_message_group( - DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, - vector> &&input_message_contents, - bool only_preview) TD_WARN_UNUSED_RESULT; + vector> &&input_message_contents) TD_WARN_UNUSED_RESULT; Result send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter) TD_WARN_UNUSED_RESULT; - Result send_inline_query_result_message(DialogId dialog_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, - tl_object_ptr &&options, - int64 query_id, const string &result_id, - bool hide_via_bot) TD_WARN_UNUSED_RESULT; + Result> send_inline_query_result_message( + DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, + tl_object_ptr &&options, int64 query_id, const string &result_id, + bool hide_via_bot) TD_WARN_UNUSED_RESULT; - Result> forward_messages(DialogId to_dialog_id, MessageId top_thread_message_id, - DialogId from_dialog_id, vector message_ids, - tl_object_ptr &&options, - bool in_game_share, - vector &©_options, - bool only_preview) TD_WARN_UNUSED_RESULT; + Result> forward_messages( + DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, vector message_ids, + tl_object_ptr &&options, bool in_game_share, + vector &©_options) TD_WARN_UNUSED_RESULT; - Result> resend_messages(DialogId dialog_id, vector message_ids) TD_WARN_UNUSED_RESULT; + Result> resend_messages(DialogId dialog_id, vector message_ids, + td_api::object_ptr &"e) TD_WARN_UNUSED_RESULT; void set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise &&promise); void share_dialog_with_bot(MessageFullId message_full_id, int32 button_id, DialogId shared_dialog_id, bool expect_user, bool only_check, Promise &&promise); - Result add_local_message(DialogId dialog_id, td_api::object_ptr &&sender, - td_api::object_ptr &&reply_to, bool disable_notification, - tl_object_ptr &&input_message_content) - TD_WARN_UNUSED_RESULT; + Result add_local_message( + DialogId dialog_id, td_api::object_ptr &&sender, + td_api::object_ptr &&reply_to, bool disable_notification, + tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; void get_message_file_type(const string &message_file_head, Promise> &&promise); @@ -571,6 +552,9 @@ class MessagesManager final : public Actor { void set_dialog_title(DialogId dialog_id, const string &title, Promise &&promise); + void set_dialog_accent_color(DialogId dialog_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise); + void set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise); void set_active_reactions(vector active_reaction_types); @@ -873,9 +857,6 @@ class MessagesManager final : public Actor { void remove_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, Promise &&promise); - void get_message_public_forwards(MessageFullId message_full_id, string offset, int32 limit, - Promise> &&promise); - tl_object_ptr get_dialog_message_by_date_object(int64 random_id); td_api::object_ptr get_dialog_event_log_message_object( @@ -904,6 +885,8 @@ class MessagesManager final : public Actor { void on_dialog_bots_updated(DialogId dialog_id, vector bot_user_ids, bool from_database); void on_dialog_photo_updated(DialogId dialog_id); + void on_dialog_accent_color_id_updated(DialogId dialog_id); + void on_dialog_background_custom_emoji_id_updated(DialogId dialog_id); void on_dialog_title_updated(DialogId dialog_id); void on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames); void on_dialog_usernames_received(DialogId dialog_id, const Usernames &usernames, bool from_database); @@ -1049,6 +1032,8 @@ class MessagesManager final : public Actor { Result get_payment_successful_message_id(MessageFullId message_full_id); + Result get_giveaway_message_id(MessageFullId message_full_id); + bool can_set_game_score(MessageFullId message_full_id) const; void get_current_state(vector> &updates) const; @@ -1088,23 +1073,29 @@ class MessagesManager final : public Actor { int32 forward_count = 0; tl_object_ptr reply_info; tl_object_ptr reactions; - int32 flags = 0; int32 edit_date = 0; vector restriction_reasons; string author_signature; int64 media_album_id = 0; + bool is_outgoing = false; + bool is_silent = false; + bool is_channel_post = false; + bool is_legacy = false; + bool hide_edit_date = false; + bool is_from_scheduled = false; + bool is_pinned = false; + bool noforwards = false; + bool has_mention = false; + bool has_unread_content = false; + bool invert_media = false; unique_ptr content; tl_object_ptr reply_markup; }; struct MessageForwardInfo { - UserId sender_user_id; + MessageOrigin origin; int32 date = 0; - DialogId sender_dialog_id; - MessageId message_id; - string author_signature; - string sender_name; DialogId from_dialog_id; MessageId from_message_id; string psa_type; @@ -1112,15 +1103,10 @@ class MessagesManager final : public Actor { MessageForwardInfo() = default; - MessageForwardInfo(UserId sender_user_id, int32 date, DialogId sender_dialog_id, MessageId message_id, - string author_signature, string sender_name, DialogId from_dialog_id, MessageId from_message_id, - string psa_type, bool is_imported) - : sender_user_id(sender_user_id) + MessageForwardInfo(MessageOrigin &&origin, int32 date, DialogId from_dialog_id, MessageId from_message_id, + string &&psa_type, bool is_imported) + : origin(std::move(origin)) , date(date) - , sender_dialog_id(sender_dialog_id) - , message_id(message_id) - , author_signature(std::move(author_signature)) - , sender_name(std::move(sender_name)) , from_dialog_id(from_dialog_id) , from_message_id(from_message_id) , psa_type(std::move(psa_type)) @@ -1128,9 +1114,7 @@ class MessagesManager final : public Actor { } bool operator==(const MessageForwardInfo &rhs) const { - return sender_user_id == rhs.sender_user_id && date == rhs.date && sender_dialog_id == rhs.sender_dialog_id && - message_id == rhs.message_id && author_signature == rhs.author_signature && - sender_name == rhs.sender_name && from_dialog_id == rhs.from_dialog_id && + return origin == rhs.origin && date == rhs.date && from_dialog_id == rhs.from_dialog_id && from_message_id == rhs.from_message_id && psa_type == rhs.psa_type && is_imported == rhs.is_imported; } @@ -1139,22 +1123,10 @@ class MessagesManager final : public Actor { } friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageForwardInfo &forward_info) { - 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 << ')'; - } + string_builder << "MessageForwardInfo[" << (forward_info.is_imported ? "imported " : "") << forward_info.origin; 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 << MessageFullId(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 " << MessageFullId(forward_info.from_dialog_id, forward_info.from_message_id); } @@ -1176,14 +1148,17 @@ class MessagesManager final : public Actor { unique_ptr forward_info; - MessageId reply_to_message_id; - int64 reply_to_random_id = 0; // for send_message - DialogId reply_in_dialog_id; - MessageId top_thread_message_id; + RepliedMessageInfo replied_message_info; StoryFullId reply_to_story_full_id; + + MessageId top_thread_message_id; MessageId linked_top_thread_message_id; vector local_thread_message_ids; + MessageInputReplyTo input_reply_to; // for send_message + int64 reply_to_random_id = 0; // for send_message + string send_emoji; // for send_message + UserId via_bot_user_id; vector restriction_reasons; @@ -1206,6 +1181,7 @@ class MessagesManager final : public Actor { bool is_pinned = false; bool are_media_timestamp_entities_found = false; bool noforwards = false; + bool invert_media = false; bool has_explicit_sender = false; // for send_message bool is_copy = false; // for send_message @@ -1225,8 +1201,6 @@ class MessagesManager final : public Actor { DialogId real_forward_from_dialog_id; // for resend_message MessageId real_forward_from_message_id; // for resend_message - string send_emoji; // for send_message - NotificationId notification_id; NotificationId removed_notification_id; @@ -1259,6 +1233,7 @@ class MessagesManager final : public Actor { unique_ptr reply_markup; int32 edited_schedule_date = 0; + bool edited_invert_media = false; unique_ptr edited_content; unique_ptr edited_reply_markup; uint64 edit_generation = 0; @@ -1370,13 +1345,12 @@ class MessagesManager final : public Actor { MessageId max_unavailable_message_id; // maximum unavailable message identifier for dialogs with cleared/unavailable history - int32 last_clear_history_date = 0; - MessageId last_clear_history_message_id; int64 order = DEFAULT_ORDER; + int64 pending_order = DEFAULT_ORDER; MessageId deleted_last_message_id; int32 delete_last_message_date = 0; - int32 pending_last_message_date = 0; - MessageId pending_last_message_id; + int32 last_clear_history_date = 0; + MessageId last_clear_history_message_id; MessageId last_edited_message_id; uint32 scheduled_messages_sync_generation = 0; uint32 last_repair_scheduled_messages_generation = 0; @@ -1612,16 +1586,18 @@ class MessagesManager final : public Actor { bool from_background = false; bool update_stickersets_order = false; bool protect_content = false; + bool only_preview = false; int32 schedule_date = 0; int32 sending_id = 0; MessageSendOptions() = default; MessageSendOptions(bool disable_notification, bool from_background, bool update_stickersets_order, - bool protect_content, int32 schedule_date, int32 sending_id) + bool protect_content, bool only_preview, int32 schedule_date, int32 sending_id) : disable_notification(disable_notification) , from_background(from_background) , update_stickersets_order(update_stickersets_order) , protect_content(protect_content) + , only_preview(only_preview) , schedule_date(schedule_date) , sending_id(sending_id) { } @@ -1710,7 +1686,7 @@ class MessagesManager final : public Actor { static constexpr int32 MAX_RECENT_DIALOGS = 50; // some reasonable value static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description - static constexpr size_t MIN_DELETED_ASYNCHRONOUSLY_MESSAGES = 10; + static constexpr size_t MIN_DELETED_ASYNCHRONOUSLY_MESSAGES = 2; static constexpr size_t MAX_UNLOADED_MESSAGES = 5000; static constexpr int64 SPONSORED_DIALOG_ORDER = static_cast(2147483647) << 32; @@ -1752,11 +1728,9 @@ class MessagesManager final : public Actor { static constexpr bool DROP_SEND_MESSAGE_UPDATES = false; - static int32 get_message_date(const tl_object_ptr &message_ptr); - vector get_message_user_ids(const Message *m) const; - static vector get_message_channel_ids(const Message *m); + vector get_message_channel_ids(const Message *m) const; static bool is_dialog_inited(const Dialog *d); @@ -1831,14 +1805,14 @@ class MessagesManager final : public Actor { int64 generate_new_random_id(const Dialog *d); unique_ptr create_message_to_send(Dialog *d, MessageId top_thread_message_id, - MessageInputReplyTo input_reply_to, const MessageSendOptions &options, - unique_ptr &&content, bool suppress_reply_info, - unique_ptr forward_info, bool is_copy, - DialogId send_as_dialog_id) const; + MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, + unique_ptr &&content, bool invert_media, + bool suppress_reply_info, unique_ptr forward_info, + bool is_copy, DialogId send_as_dialog_id) const; - Message *get_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo input_reply_to, + Message *get_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageInputReplyTo &&input_reply_to, const MessageSendOptions &options, unique_ptr &&content, - bool *need_update_dialog_pos, bool suppress_reply_info = false, + bool invert_media, bool *need_update_dialog_pos, bool suppress_reply_info = false, unique_ptr forward_info = nullptr, bool is_copy = false, DialogId sender_dialog_id = DialogId()); @@ -1850,8 +1824,6 @@ class MessagesManager final : public Actor { bool can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup = false) const; - bool has_qts_messages(DialogId dialog_id) const; - bool can_report_dialog(DialogId dialog_id) const; Status can_pin_messages(DialogId dialog_id) const; @@ -1875,10 +1847,10 @@ class MessagesManager final : public Actor { static MessageFullId get_replied_message_id(DialogId dialog_id, const Message *m); MessageInputReplyTo get_message_input_reply_to(Dialog *d, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, bool for_draft); + td_api::object_ptr &&reply_to, + bool for_draft); - void fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id, DialogId reply_in_dialog_id, - MessageId &reply_to_message_id) const; + static const MessageInputReplyTo *get_message_input_reply_to(const Message *m); bool can_set_game_score(DialogId dialog_id, const Message *m) const; @@ -1917,8 +1889,10 @@ class MessagesManager final : public Actor { bool in_game_share, MessageCopyOptions &©_options) TD_WARN_UNUSED_RESULT; + MessageOrigin get_forwarded_message_origin(DialogId dialog_id, const Message *m) const; + unique_ptr create_message_forward_info(DialogId from_dialog_id, DialogId to_dialog_id, - const Message *forwarded_message) const; + const Message *m) const; void fix_forwarded_message(Message *m, DialogId to_dialog_id, const Message *forwarded_message, int64 media_album_id, bool drop_author) const; @@ -1932,12 +1906,14 @@ class MessagesManager final : public Actor { unique_ptr reply_markup; int64 media_album_id; bool disable_web_page_preview; + bool invert_media; size_t index; }; vector copied_messages; struct ForwardedMessageContent { unique_ptr content; + bool invert_media; int64 media_album_id; size_t index; }; @@ -2002,7 +1978,10 @@ class MessagesManager final : public Actor { void do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 log_event_id); - void set_message_reply(const Dialog *d, Message *m, MessageId reply_to_message_id, bool is_message_in_dialog); + void set_message_reply(const Dialog *d, Message *m, MessageInputReplyTo input_reply_to, bool is_message_in_dialog); + + void update_message_reply_to_message_id(const Dialog *d, Message *m, MessageId reply_to_message_id, + bool is_message_in_dialog); void restore_message_reply_to_message_id(Dialog *d, Message *m); @@ -2530,6 +2509,9 @@ class MessagesManager final : public Actor { static tl_object_ptr get_message_scheduling_state_object(int32 send_date); + td_api::object_ptr get_message_message_content_object(DialogId dialog_id, + const Message *m) const; + tl_object_ptr get_message_object(DialogId dialog_id, const Message *m, const char *source) const; static tl_object_ptr get_messages_object(int32 total_count, @@ -2658,7 +2640,7 @@ class MessagesManager final : public Actor { bool update_dialog_notification_settings(DialogId dialog_id, DialogNotificationSettings *current_settings, DialogNotificationSettings &&new_settings); - void schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until); + void schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until, int32 unix_time); void update_dialog_unmute_timeout(Dialog *d, bool &old_use_default, int32 &old_mute_until, bool new_use_default, int32 new_mute_until); @@ -2672,6 +2654,8 @@ class MessagesManager final : public Actor { ChatReactions get_message_available_reactions(const Dialog *d, const Message *m, bool disallow_custom_for_non_premium); + DialogId get_my_reaction_dialog_id(const Dialog *d) const; + void set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise &&promise); void on_set_message_reactions(MessageFullId message_full_id, Result result, Promise promise); @@ -2719,13 +2703,14 @@ class MessagesManager final : public Actor { Dialog *add_new_dialog(unique_ptr &&dialog, bool is_loaded_from_database, const char *source); - 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, - const char *source); + void fix_new_dialog(Dialog *d, unique_ptr &&draft_message, 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, const char *source); - bool add_dialog_last_database_message(Dialog *d, unique_ptr &&last_database_message); + bool add_pending_dialog_data(Dialog *d, unique_ptr &&last_database_message, + unique_ptr &&draft_message); void fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar); @@ -2864,14 +2849,15 @@ class MessagesManager final : public Actor { tl_object_ptr get_send_message_as_input_peer(const Message *m) const; - static bool is_forward_info_sender_hidden(const MessageForwardInfo *forward_info); - unique_ptr get_message_forward_info( - tl_object_ptr &&forward_header, MessageFullId message_full_id); + tl_object_ptr &&forward_header); td_api::object_ptr get_message_forward_info_object( const unique_ptr &forward_info) const; + td_api::object_ptr get_message_import_info_object( + const unique_ptr &forward_info) const; + void ttl_read_history(Dialog *d, bool is_outgoing, MessageId from_message_id, MessageId till_message_id, double view_date); void ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id, MessageId till_message_id, @@ -2977,6 +2963,10 @@ class MessagesManager final : public Actor { const DialogPhoto *get_dialog_photo(DialogId dialog_id) const; + int32 get_dialog_accent_color_id_object(DialogId dialog_id) const; + + CustomEmojiId get_dialog_background_custom_emoji_id(DialogId dialog_id) const; + RestrictedRights get_dialog_default_permissions(DialogId dialog_id) const; bool get_dialog_has_protected_content(DialogId dialog_id) const; @@ -3149,9 +3139,6 @@ class MessagesManager final : public Actor { Status can_import_messages(DialogId dialog_id); - void send_get_message_public_forwards_query(DcId dc_id, MessageFullId message_full_id, string offset, int32 limit, - Promise> &&promise); - void add_sponsored_dialog(const Dialog *d, DialogSource source); void save_sponsored_dialog(); @@ -3186,7 +3173,7 @@ class MessagesManager final : public Actor { void update_forward_count(DialogId dialog_id, const Message *m); - void update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date); + void update_forward_count(MessageFullId message_full_id, int32 update_date); void update_has_outgoing_messages(DialogId dialog_id, const Message *m); @@ -3473,7 +3460,8 @@ class MessagesManager final : public Actor { FlatHashMap get_dialog_query_log_event_id_; FlatHashMap replied_by_yet_unsent_messages_; - FlatHashMap, MessageFullIdHash> replied_yet_unsent_messages_; + FlatHashMap, MessageFullIdHash> + replied_yet_unsent_messages_; // message_full_id -> replies with media timestamps FlatHashMap, MessageFullIdHash> @@ -3608,9 +3596,17 @@ class MessagesManager final : public Actor { FlatHashMap, DialogIdHash> pending_secret_message_ids_; // random_id -> message_id - FlatHashMap, DialogIdHash> pending_add_dialog_last_database_message_dependent_dialogs_; - FlatHashMap>, DialogIdHash> - pending_add_dialog_last_database_message_; // dialog -> dependency counter + message + struct AddDialogData { + int32 dependent_dialog_count_ = 0; + unique_ptr last_message_; + unique_ptr draft_message_; + + AddDialogData() = default; + AddDialogData(int32 dependent_dialog_count, unique_ptr &&last_message, + unique_ptr &&draft_message); + }; + FlatHashMap, DialogIdHash> pending_add_dialog_dependent_dialogs_; + FlatHashMap pending_add_dialog_data_; FlatHashMap, DialogIdHash> pending_add_default_join_group_call_as_dialog_id_; // dialog_id -> dependent dialogs diff --git a/lib/tgchat/ext/td/td/telegram/MinChannel.h b/lib/tgchat/ext/td/td/telegram/MinChannel.h index 5d2b25c6..34201616 100644 --- a/lib/tgchat/ext/td/td/telegram/MinChannel.h +++ b/lib/tgchat/ext/td/td/telegram/MinChannel.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/AccentColorId.h" #include "td/telegram/Photo.h" #include "td/utils/common.h" @@ -15,6 +16,7 @@ namespace td { struct MinChannel { string title_; DialogPhoto photo_; + AccentColorId accent_color_id_; bool is_megagroup_ = false; }; diff --git a/lib/tgchat/ext/td/td/telegram/MinChannel.hpp b/lib/tgchat/ext/td/td/telegram/MinChannel.hpp index b5e40c50..4eadc5f5 100644 --- a/lib/tgchat/ext/td/td/telegram/MinChannel.hpp +++ b/lib/tgchat/ext/td/td/telegram/MinChannel.hpp @@ -18,10 +18,12 @@ template void store(const MinChannel &min_channel, StorerT &storer) { bool has_title = !min_channel.title_.empty(); bool has_photo = min_channel.photo_.small_file_id.is_valid(); + bool has_accent_color_id = min_channel.accent_color_id_.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_title); STORE_FLAG(has_photo); STORE_FLAG(min_channel.is_megagroup_); + STORE_FLAG(has_accent_color_id); END_STORE_FLAGS(); if (has_title) { store(min_channel.title_, storer); @@ -29,16 +31,21 @@ void store(const MinChannel &min_channel, StorerT &storer) { if (has_photo) { store(min_channel.photo_, storer); } + if (has_accent_color_id) { + store(min_channel.accent_color_id_, storer); + } } template void parse(MinChannel &min_channel, ParserT &parser) { bool has_title; bool has_photo; + bool has_accent_color_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_title); PARSE_FLAG(has_photo); PARSE_FLAG(min_channel.is_megagroup_); + PARSE_FLAG(has_accent_color_id); END_PARSE_FLAGS(); if (has_title) { parse(min_channel.title_, parser); @@ -46,6 +53,9 @@ void parse(MinChannel &min_channel, ParserT &parser) { if (has_photo) { parse(min_channel.photo_, parser); } + if (has_accent_color_id) { + parse(min_channel.accent_color_id_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp index 39b7c909..2e1280b1 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp @@ -843,6 +843,7 @@ int32 NotificationManager::get_notification_delay_ms(DialogId dialog_id, const P return MIN_NOTIFICATION_DELAY_MS; } + auto server_time = G()->server_time(); auto delay_ms = [&] { auto online_info = td_->contacts_manager_->get_my_online_status(); if (!online_info.is_online_local && online_info.is_online_remote) { @@ -852,8 +853,8 @@ int32 NotificationManager::get_notification_delay_ms(DialogId dialog_id, const P } if (!online_info.is_online_local && - online_info.was_online_remote > max(static_cast(online_info.was_online_local), - G()->server_time_cached() - online_cloud_timeout_ms_ * 1e-3)) { + online_info.was_online_remote > + max(static_cast(online_info.was_online_local), server_time - online_cloud_timeout_ms_ * 1e-3)) { // If we are offline, but was online from some other client in last 'online_cloud_timeout' seconds // after we had gone offline, then delay notification for 'notification_cloud_delay' seconds. return notification_cloud_delay_ms_; @@ -868,8 +869,7 @@ int32 NotificationManager::get_notification_delay_ms(DialogId dialog_id, const P return 0; }(); - auto passed_time_ms = - static_cast(clamp(G()->server_time_cached() - notification.date - 1, 0.0, 1000000.0) * 1000); + auto passed_time_ms = static_cast(clamp(server_time - notification.date - 1, 0.0, 1000000.0) * 1000); return max(max(min_delay_ms, delay_ms) - passed_time_ms, MIN_NOTIFICATION_DELAY_MS); } @@ -2887,6 +2887,12 @@ string NotificationManager::convert_loc_key(const string &loc_key) { if (loc_key == "MESSAGE_GIF") { return "MESSAGE_ANIMATION"; } + if (loc_key == "MESSAGE_GIFTCODE") { + return "MESSAGE_GIFTCODE"; + } + if (loc_key == "MESSAGE_GIVEAWAY") { + return "MESSAGE_GIVEAWAY"; + } break; case 'H': if (loc_key == "PINNED_PHOTO") { @@ -2900,6 +2906,9 @@ string NotificationManager::convert_loc_key(const string &loc_key) { if (loc_key == "PINNED_GIF") { return "PINNED_MESSAGE_ANIMATION"; } + if (loc_key == "PINNED_GIVEAWAY") { + return "PINNED_MESSAGE_GIVEAWAY"; + } if (loc_key == "MESSAGE_INVOICE") { return "MESSAGE_INVOICE"; } @@ -3134,7 +3143,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo date = now; auto update = telegram_api::make_object( - telegram_api::updateServiceNotification::INBOX_DATE_MASK, false, G()->unix_time(), string(), + telegram_api::updateServiceNotification::INBOX_DATE_MASK, false, false, G()->unix_time(), string(), announcement_message_text, nullptr, vector>()); send_closure(G()->messages_manager(), &MessagesManager::on_update_service_notification, std::move(update), false, std::move(promise)); @@ -3418,6 +3427,21 @@ Status NotificationManager::process_push_notification_payload(string payload, bo arg = PSTRING() << loc_args[1] << ' ' << loc_args[0]; loc_args.clear(); } + if (loc_key == "MESSAGE_GIVEAWAY") { + if (loc_args.size() != 2) { + return Status::Error("Expected 2 arguments for MESSAGE_GIVEAWAY"); + } + TRY_RESULT(user_count, to_integer_safe(loc_args[0])); + if (user_count <= 0) { + return Status::Error("Expected user count to be non-negative"); + } + TRY_RESULT(month_count, to_integer_safe(loc_args[1])); + if (month_count <= 0) { + return Status::Error("Expected month count to be non-negative"); + } + arg = PSTRING() << user_count << ' ' << month_count; + loc_args.clear(); + } if (loc_args.size() > 1) { return Status::Error("Receive too many arguments"); } @@ -3456,7 +3480,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, 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(), nullptr, - vector>(), 0); + vector>(), 0, 0, 0); td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload"); } @@ -3815,7 +3839,8 @@ 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*/, 0, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, sender_user_id.get(), 0, user_name, string(), string(), string(), nullptr, - nullptr, 0, Auto(), string(), string(), nullptr, vector>(), 0); + nullptr, 0, Auto(), string(), string(), nullptr, vector>(), 0, + 0, 0); td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification"); } diff --git a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp index 3801c1ed..c86c3bcf 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp @@ -590,7 +590,7 @@ void NotificationSettingsManager::init() { VLOG(notifications) << "Loaded notification settings in " << scope << ": " << *current_settings; - schedule_scope_unmute(scope, current_settings->mute_until); + schedule_scope_unmute(scope, current_settings->mute_until, G()->unix_time()); send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope)); } @@ -728,11 +728,11 @@ void NotificationSettingsManager::on_scope_unmute(NotificationSettingsScope scop return; } - auto now = G()->unix_time(); - if (notification_settings->mute_until > now) { - LOG(ERROR) << "Failed to unmute " << scope << " in " << now << ", will be unmuted in " - << notification_settings->mute_until; - schedule_scope_unmute(scope, notification_settings->mute_until); + auto unix_time = G()->unix_time(); + if (notification_settings->mute_until > unix_time) { + LOG(INFO) << "Failed to unmute " << scope << " in " << unix_time << ", will be unmuted in " + << notification_settings->mute_until; + schedule_scope_unmute(scope, notification_settings->mute_until, unix_time); return; } @@ -824,10 +824,10 @@ bool NotificationSettingsManager::update_scope_notification_settings(Notificatio return need_update_server; } -void NotificationSettingsManager::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until) { - auto now = G()->unix_time_cached(); - if (mute_until >= now && mute_until < now + 366 * 86400) { - scope_unmute_timeout_.set_timeout_in(static_cast(scope) + 1, mute_until - now + 1); +void NotificationSettingsManager::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until, + int32 unix_time) { + if (mute_until >= unix_time && mute_until < unix_time + 366 * 86400) { + scope_unmute_timeout_.set_timeout_in(static_cast(scope) + 1, mute_until - unix_time + 1); } else { scope_unmute_timeout_.cancel_timeout(static_cast(scope) + 1); } @@ -846,7 +846,7 @@ void NotificationSettingsManager::update_scope_unmute_timeout(NotificationSettin } CHECK(old_mute_until >= 0); - schedule_scope_unmute(scope, new_mute_until); + schedule_scope_unmute(scope, new_mute_until, G()->unix_time()); auto was_muted = old_mute_until != 0; auto is_muted = new_mute_until != 0; diff --git a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.h b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.h index 6a8ea222..a39f58b6 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.h +++ b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.h @@ -188,7 +188,7 @@ class NotificationSettingsManager final : public Actor { void update_scope_notification_settings_on_server(NotificationSettingsScope scope, uint64 log_event_id); - void schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until); + void schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until, int32 unix_time); void update_scope_unmute_timeout(NotificationSettingsScope scope, int32 &old_mute_until, int32 new_mute_until); diff --git a/lib/tgchat/ext/td/td/telegram/NotificationType.cpp b/lib/tgchat/ext/td/td/telegram/NotificationType.cpp index 09e85a66..64a1387e 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationType.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationType.cpp @@ -254,6 +254,22 @@ class NotificationTypePushMessage final : public NotificationType { } return td_api::make_object(title, score, is_pinned); } + if (key == "MESSAGE_GIFTCODE") { + auto month_count = to_integer(arg); + return td_api::make_object(month_count); + } + if (key == "MESSAGE_GIVEAWAY") { + int32 user_count = 0; + int32 month_count = 0; + if (!is_pinned) { + string user_count_str; + string month_count_str; + std::tie(user_count_str, month_count_str) = split(arg); + user_count = to_integer(user_count_str); + month_count = to_integer(month_count_str); + } + return td_api::make_object(user_count, month_count, is_pinned); + } break; case 'I': if (key == "MESSAGE_INVOICE") { diff --git a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp index 593e364a..446e4730 100644 --- a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp @@ -80,6 +80,9 @@ OptionManager::OptionManager(Td *td) if (!have_option("message_caption_length_max")) { set_option_integer("message_caption_length_max", 1024); } + if (!have_option("message_reply_quote_length_max")) { + set_option_integer("message_reply_quote_length_max", 1024); + } if (!have_option("story_caption_length_max")) { set_option_integer("story_caption_length_max", 200); } @@ -128,6 +131,24 @@ OptionManager::OptionManager(Td *td) if (!have_option("story_stealth_mode_cooldown_period")) { set_option_integer("story_stealth_mode_cooldown_period", 3600); } + if (!have_option("giveaway_additional_chat_count_max")) { + set_option_integer("giveaway_additional_chat_count_max", G()->is_test_dc() ? 3 : 10); + } + if (!have_option("giveaway_country_count_max")) { + set_option_integer("giveaway_country_count_max", G()->is_test_dc() ? 3 : 10); + } + if (!have_option("giveaway_boost_count_per_premium")) { + set_option_integer("giveaway_boost_count_per_premium", 4); + } + if (!have_option("giveaway_duration_max")) { + set_option_integer("giveaway_duration_max", 7 * 86400); + } + if (!have_option("channel_custom_accent_color_boost_level_min")) { + set_option_integer("channel_custom_accent_color_boost_level_min", 5); + } + if (!have_option("premium_gift_boost_count")) { + set_option_integer("premium_gift_boost_count", 3); + } set_option_empty("archive_and_mute_new_chats_from_unknown_users"); set_option_empty("chat_filter_count_max"); @@ -569,7 +590,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.19"); + return td_api::make_object("1.8.21"); } break; } diff --git a/lib/tgchat/ext/td/td/telegram/PasswordManager.cpp b/lib/tgchat/ext/td/td/telegram/PasswordManager.cpp index cffe24c6..ba3fbd15 100644 --- a/lib/tgchat/ext/td/td/telegram/PasswordManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PasswordManager.cpp @@ -32,10 +32,11 @@ namespace td { tl_object_ptr TempPasswordState::get_temporary_password_state_object() const { - if (!has_temp_password || valid_until <= G()->unix_time()) { + auto unix_time = G()->unix_time(); + if (!has_temp_password || valid_until <= unix_time) { return make_tl_object(false, 0); } - return make_tl_object(true, valid_until - G()->unix_time_cached()); + return make_tl_object(true, valid_until - unix_time); } static void hash_sha256(Slice data, Slice salt, MutableSlice dest) { diff --git a/lib/tgchat/ext/td/td/telegram/Payments.cpp b/lib/tgchat/ext/td/td/telegram/Payments.cpp index a51458cc..2e5bed47 100644 --- a/lib/tgchat/ext/td/td/telegram/Payments.cpp +++ b/lib/tgchat/ext/td/td/telegram/Payments.cpp @@ -9,6 +9,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" +#include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" #include "td/telegram/InputInvoice.h" #include "td/telegram/MessageEntity.h" @@ -17,6 +18,7 @@ #include "td/telegram/misc.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/Photo.h" +#include "td/telegram/Premium.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" @@ -68,6 +70,55 @@ Result get_input_invoice_info(Td *td, td_api::object_ptr(invoice->name_); break; } + case td_api::inputInvoiceTelegram::ID: { + auto invoice = td_api::move_object_as(input_invoice); + if (invoice->purpose_ == nullptr) { + return Status::Error(400, "Purpose must be non-empty"); + } + switch (invoice->purpose_->get_id()) { + case td_api::telegramPaymentPurposePremiumGiftCodes::ID: { + auto p = static_cast(invoice->purpose_.get()); + vector> input_users; + for (auto user_id : p->user_ids_) { + TRY_RESULT(input_user, td->contacts_manager_->get_input_user(UserId(user_id))); + input_users.push_back(std::move(input_user)); + } + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + DialogId boosted_dialog_id(p->boosted_chat_id_); + TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id)); + int32 flags = 0; + if (boost_input_peer != nullptr) { + flags |= telegram_api::inputStorePaymentPremiumGiftCode::BOOST_PEER_MASK; + } + auto option = telegram_api::make_object( + 0, static_cast(input_users.size()), p->month_count_, string(), 0, p->currency_, p->amount_); + auto purpose = telegram_api::make_object( + flags, std::move(input_users), std::move(boost_input_peer), p->currency_, p->amount_); + + result.dialog_id_ = boosted_dialog_id; + result.input_invoice_ = telegram_api::make_object( + std::move(purpose), std::move(option)); + break; + } + case td_api::telegramPaymentPurposePremiumGiveaway::ID: { + auto p = static_cast(invoice->purpose_.get()); + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get())); + auto option = telegram_api::make_object( + 0, p->winner_count_, p->month_count_, string(), 0, p->currency_, p->amount_); + result.input_invoice_ = telegram_api::make_object( + parameters.get_input_store_payment_premium_giveaway(td, p->currency_, p->amount_), std::move(option)); + break; + } + default: + UNREACHABLE(); + } + break; + } default: UNREACHABLE(); } diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.cpp b/lib/tgchat/ext/td/td/telegram/PollManager.cpp index 4eeebcc5..0e39141e 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PollManager.cpp @@ -213,8 +213,8 @@ class StopPollQuery final : public Td::ResultHandler { auto input_media = telegram_api::make_object(0, std::move(poll), vector(), string(), Auto()); send_query(G()->net_query_creator().create( - telegram_api::messages_editMessage(flags, false /*ignored*/, std::move(input_peer), message_id, string(), - std::move(input_media), std::move(input_reply_markup), + telegram_api::messages_editMessage(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + message_id, string(), std::move(input_media), std::move(input_reply_markup), vector>(), 0), {{poll_id}, {dialog_id_}})); } @@ -274,7 +274,8 @@ void PollManager::tear_down() { PollManager::~PollManager() { Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), polls_, server_poll_messages_, - other_poll_messages_, poll_voters_, loaded_from_database_polls_); + other_poll_messages_, reply_poll_counts_, poll_voters_, + loaded_from_database_polls_); } void PollManager::on_update_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int) { @@ -725,13 +726,36 @@ void PollManager::unregister_poll(PollId poll_id, MessageFullId message_full_id, } } +void PollManager::register_reply_poll(PollId poll_id) { + CHECK(have_poll(poll_id)); + CHECK(!is_local_poll_id(poll_id)); + LOG(INFO) << "Register replied " << poll_id; + reply_poll_counts_[poll_id]++; + if (!G()->close_flag()) { + unload_poll_timeout_.cancel_timeout(poll_id.get()); + } +} + +void PollManager::unregister_reply_poll(PollId poll_id) { + CHECK(have_poll(poll_id)); + CHECK(!is_local_poll_id(poll_id)); + LOG(INFO) << "Unregister replied " << poll_id; + auto &count = reply_poll_counts_[poll_id]; + CHECK(count > 0); + count--; + if (count == 0) { + reply_poll_counts_.erase(poll_id); + schedule_poll_unload(poll_id); + } +} + bool PollManager::can_unload_poll(PollId poll_id) { if (G()->close_flag()) { return false; } if (is_local_poll_id(poll_id) || server_poll_messages_.count(poll_id) != 0 || - other_poll_messages_.count(poll_id) != 0 || pending_answers_.count(poll_id) != 0 || - being_closed_polls_.count(poll_id) != 0) { + other_poll_messages_.count(poll_id) != 0 || reply_poll_counts_.count(poll_id) != 0 || + pending_answers_.count(poll_id) != 0 || being_closed_polls_.count(poll_id) != 0) { return false; } diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.h b/lib/tgchat/ext/td/td/telegram/PollManager.h index 751623b9..356ddbd0 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.h +++ b/lib/tgchat/ext/td/td/telegram/PollManager.h @@ -57,6 +57,10 @@ class PollManager final : public Actor { void unregister_poll(PollId poll_id, MessageFullId message_full_id, const char *source); + void register_reply_poll(PollId poll_id); + + void unregister_reply_poll(PollId poll_id); + bool get_poll_is_closed(PollId poll_id) const; bool get_poll_is_anonymous(PollId poll_id) const; @@ -235,6 +239,8 @@ class PollManager final : public Actor { WaitFreeHashMap, PollIdHash> server_poll_messages_; WaitFreeHashMap, PollIdHash> other_poll_messages_; + WaitFreeHashMap reply_poll_counts_; + struct PendingPollAnswer { vector options_; vector> promises_; diff --git a/lib/tgchat/ext/td/td/telegram/Premium.cpp b/lib/tgchat/ext/td/td/telegram/Premium.cpp index 210a1622..1f9d8cb0 100644 --- a/lib/tgchat/ext/td/td/telegram/Premium.cpp +++ b/lib/tgchat/ext/td/td/telegram/Premium.cpp @@ -6,16 +6,23 @@ // #include "td/telegram/Premium.h" +#include "td/telegram/AccessRights.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/Application.h" +#include "td/telegram/ChannelId.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" +#include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" #include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessageSender.h" +#include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/PremiumGiftOption.h" +#include "td/telegram/ServerMessageId.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" @@ -84,9 +91,32 @@ static td_api::object_ptr get_premium_feature_object(Sli if (premium_feature == "channel_boost") { return td_api::make_object(); } + if (premium_feature == "name_color") { + return td_api::make_object(); + } return nullptr; } +Result> get_boost_input_peer(Td *td, DialogId dialog_id) { + if (dialog_id == DialogId()) { + return nullptr; + } + + if (!td->messages_manager_->have_dialog_force(dialog_id, "get_boost_input_peer")) { + return Status::Error(400, "Chat to boost not found"); + } + if (dialog_id.get_type() != DialogType::Channel || + !td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + return Status::Error(400, "Can't boost the chat"); + } + if (!td->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_administrator()) { + return Status::Error(400, "Not enough rights in the chat"); + } + auto boost_input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(boost_input_peer != nullptr); + return std::move(boost_input_peer); +} + static Result> get_input_store_payment_purpose( Td *td, const td_api::object_ptr &purpose) { if (purpose == nullptr) { @@ -116,6 +146,33 @@ static Result> get_input_s return make_tl_object(std::move(input_user), p->currency_, p->amount_); } + case td_api::storePaymentPurposePremiumGiftCodes::ID: { + auto p = static_cast(purpose.get()); + vector> input_users; + for (auto user_id : p->user_ids_) { + TRY_RESULT(input_user, td->contacts_manager_->get_input_user(UserId(user_id))); + input_users.push_back(std::move(input_user)); + } + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + DialogId boosted_dialog_id(p->boosted_chat_id_); + TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id)); + int32 flags = 0; + if (boost_input_peer != nullptr) { + flags |= telegram_api::inputStorePaymentPremiumGiftCode::BOOST_PEER_MASK; + } + return telegram_api::make_object( + flags, std::move(input_users), std::move(boost_input_peer), p->currency_, p->amount_); + } + case td_api::storePaymentPurposePremiumGiveaway::ID: { + auto p = static_cast(purpose.get()); + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get())); + return parameters.get_input_store_payment_premium_giveaway(td, p->currency_, p->amount_); + } default: UNREACHABLE(); return nullptr; @@ -189,6 +246,263 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { } }; +class GetPremiumGiftCodeOptionsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId boosted_dialog_id_; + + public: + explicit GetPremiumGiftCodeOptionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId boosted_dialog_id) { + auto r_boost_input_peer = get_boost_input_peer(td_, boosted_dialog_id); + if (r_boost_input_peer.is_error()) { + return on_error(r_boost_input_peer.move_as_error()); + } + auto boost_input_peer = r_boost_input_peer.move_as_ok(); + + int32 flags = 0; + if (boost_input_peer != nullptr) { + flags |= telegram_api::payments_getPremiumGiftCodeOptions::BOOST_PEER_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::payments_getPremiumGiftCodeOptions(flags, std::move(boost_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()); + } + + auto results = result_ptr.move_as_ok(); + vector> options; + for (auto &result : results) { + if (result->store_product_.empty()) { + result->store_quantity_ = 0; + } else if (result->store_quantity_ <= 0) { + result->store_quantity_ = 1; + } + options.push_back(td_api::make_object( + result->currency_, result->amount_, result->users_, result->months_, result->store_product_, + result->store_quantity_)); + } + + promise_.set_value(td_api::make_object(std::move(options))); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(boosted_dialog_id_, status, "GetPremiumGiftCodeOptionsQuery"); + promise_.set_error(std::move(status)); + } +}; + +class CheckGiftCodeQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CheckGiftCodeQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &code) { + send_query(G()->net_query_creator().create(telegram_api::payments_checkGiftCode(code))); + } + + 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 result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for CheckGiftCodeQuery: " << to_string(result); + td_->contacts_manager_->on_get_users(std::move(result->users_), "CheckGiftCodeQuery"); + td_->contacts_manager_->on_get_chats(std::move(result->chats_), "CheckGiftCodeQuery"); + + DialogId creator_dialog_id(result->from_id_); + if (!creator_dialog_id.is_valid() || + !td_->messages_manager_->have_dialog_info_force(creator_dialog_id, "CheckGiftCodeQuery") || + result->date_ <= 0 || result->months_ <= 0 || result->used_date_ < 0) { + LOG(ERROR) << "Receive " << to_string(result); + return on_error(Status::Error(500, "Receive invalid response")); + } + if (creator_dialog_id.get_type() != DialogType::User) { + td_->messages_manager_->force_create_dialog(creator_dialog_id, "CheckGiftCodeQuery", true); + } + UserId user_id(result->to_id_); + if (!user_id.is_valid() && user_id != UserId()) { + LOG(ERROR) << "Receive " << to_string(result); + user_id = UserId(); + } + MessageId message_id(ServerMessageId(result->giveaway_msg_id_)); + if (!message_id.is_valid() && message_id != MessageId()) { + LOG(ERROR) << "Receive " << to_string(result); + message_id = MessageId(); + } + promise_.set_value(td_api::make_object( + get_message_sender_object(td_, creator_dialog_id, "premiumGiftCodeInfo"), result->date_, result->via_giveaway_, + message_id.get(), result->months_, td_->contacts_manager_->get_user_id_object(user_id, "premiumGiftCodeInfo"), + result->used_date_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ApplyGiftCodeQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ApplyGiftCodeQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &code) { + send_query(G()->net_query_creator().create(telegram_api::payments_applyGiftCode(code))); + } + + 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 ApplyGiftCodeQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class LaunchPrepaidGiveawayQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit LaunchPrepaidGiveawayQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(int64 giveaway_id, const GiveawayParameters ¶meters) { + auto dialog_id = parameters.get_boosted_dialog_id(); + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create(telegram_api::payments_launchPrepaidGiveaway( + std::move(input_peer), giveaway_id, parameters.get_input_store_payment_premium_giveaway(td_, string(), 0)))); + } + + 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 LaunchPrepaidGiveawayQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetGiveawayInfoQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit GetGiveawayInfoQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, ServerMessageId server_message_id) { + dialog_id_ = dialog_id; + auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + send_query(G()->net_query_creator().create( + telegram_api::payments_getGiveawayInfo(std::move(input_peer), server_message_id.get()))); + } + + 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 GetGiveawayInfoQuery: " << to_string(ptr); + switch (ptr->get_id()) { + case telegram_api::payments_giveawayInfo::ID: { + auto info = telegram_api::move_object_as(ptr); + auto status = [&]() -> td_api::object_ptr { + if (info->joined_too_early_date_ > 0) { + return td_api::make_object( + info->joined_too_early_date_); + } + if (info->admin_disallowed_chat_id_ > 0) { + ChannelId channel_id(info->admin_disallowed_chat_id_); + if (!channel_id.is_valid() || + !td_->contacts_manager_->have_channel_force(channel_id, "GetGiveawayInfoQuery")) { + LOG(ERROR) << "Receive " << to_string(info); + } else { + DialogId dialog_id(channel_id); + td_->messages_manager_->force_create_dialog(dialog_id, "GetGiveawayInfoQuery"); + return td_api::make_object( + td_->messages_manager_->get_chat_id_object(dialog_id, + "premiumGiveawayParticipantStatusAdministrator")); + } + } + if (!info->disallowed_country_.empty()) { + return td_api::make_object( + info->disallowed_country_); + } + if (info->participating_) { + return td_api::make_object(); + } + return td_api::make_object(); + }(); + promise_.set_value(td_api::make_object( + max(0, info->start_date_), std::move(status), info->preparing_results_)); + break; + } + case telegram_api::payments_giveawayInfoResults::ID: { + auto info = telegram_api::move_object_as(ptr); + auto winner_count = info->winners_count_; + auto activated_count = info->activated_count_; + if (activated_count < 0 || activated_count > winner_count) { + LOG(ERROR) << "Receive " << to_string(info); + if (activated_count < 0) { + activated_count = 0; + } + if (winner_count < 0) { + winner_count = 0; + } + if (activated_count > winner_count) { + activated_count = winner_count; + } + } + promise_.set_value(td_api::make_object( + max(0, info->start_date_), max(0, info->finish_date_), info->refunded_, winner_count, activated_count, + info->gift_code_slug_)); + break; + } + default: + UNREACHABLE(); + } + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGiveawayInfoQuery"); + promise_.set_error(std::move(status)); + } +}; + class CanPurchasePremiumQuery final : public Td::ResultHandler { Promise promise_; @@ -408,6 +722,8 @@ static string get_premium_source(const td_api::PremiumFeature *feature) { return "stories"; case td_api::premiumFeatureChatBoost::ID: return "channel_boost"; + case td_api::premiumFeatureAccentColor::ID: + return "name_color"; default: UNREACHABLE(); } @@ -615,6 +931,34 @@ void get_premium_state(Td *td, Promise> td->create_handler(std::move(promise))->send(); } +void get_premium_gift_code_options(Td *td, DialogId boosted_dialog_id, + Promise> &&promise) { + td->create_handler(std::move(promise))->send(boosted_dialog_id); +} + +void check_premium_gift_code(Td *td, const string &code, + Promise> &&promise) { + td->create_handler(std::move(promise))->send(code); +} + +void apply_premium_gift_code(Td *td, const string &code, Promise &&promise) { + td->create_handler(std::move(promise))->send(code); +} + +void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id, + td_api::object_ptr &¶meters, + Promise &&promise) { + TRY_RESULT_PROMISE(promise, giveaway_parameters, GiveawayParameters::get_giveaway_parameters(td, parameters.get())); + td->create_handler(std::move(promise))->send(giveaway_id, giveaway_parameters); +} + +void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, server_message_id, td->messages_manager_->get_giveaway_message_id(message_full_id)); + td->create_handler(std::move(promise)) + ->send(message_full_id.get_dialog_id(), server_message_id); +} + void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise) { td->create_handler(std::move(promise))->send(std::move(purpose)); } diff --git a/lib/tgchat/ext/td/td/telegram/Premium.h b/lib/tgchat/ext/td/td/telegram/Premium.h index 0b6278a5..0bdd7fe8 100644 --- a/lib/tgchat/ext/td/td/telegram/Premium.h +++ b/lib/tgchat/ext/td/td/telegram/Premium.h @@ -6,16 +6,22 @@ // #pragma once +#include "td/telegram/DialogId.h" +#include "td/telegram/MessageFullId.h" #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/Slice.h" +#include "td/utils/Status.h" namespace td { class Td; +Result> get_boost_input_peer(Td *td, DialogId dialog_id); + const vector &get_premium_limit_keys(); void get_premium_limit(const td_api::object_ptr &limit_type, @@ -30,6 +36,21 @@ void click_premium_subscription_button(Td *td, Promise &&promise); void get_premium_state(Td *td, Promise> &&promise); +void get_premium_gift_code_options(Td *td, DialogId boosted_dialog_id, + Promise> &&promise); + +void check_premium_gift_code(Td *td, const string &code, + Promise> &&promise); + +void apply_premium_gift_code(Td *td, const string &code, Promise &&promise); + +void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id, + td_api::object_ptr &¶meters, + Promise &&promise); + +void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, + Promise> &&promise); + void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise); void assign_app_store_transaction(Td *td, const string &receipt, diff --git a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp index 68a08e99..f1f3f101 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp @@ -25,8 +25,6 @@ #include "td/utils/ScopeGuard.h" #include "td/utils/Status.h" -#include - namespace td { class GetAvailableReactionsQuery final : public Td::ResultHandler { @@ -340,16 +338,7 @@ void ReactionManager::add_recent_reaction(const ReactionType &reaction_type) { return; } - auto it = std::find(reactions.begin(), reactions.end(), reaction_type); - if (it == reactions.end()) { - if (static_cast(reactions.size()) == MAX_RECENT_REACTIONS) { - reactions.back() = reaction_type; - } else { - reactions.push_back(reaction_type); - } - it = reactions.end() - 1; - } - std::rotate(reactions.begin(), it, it + 1); + add_to_top(reactions, MAX_RECENT_REACTIONS, reaction_type); recent_reactions_.hash_ = get_reaction_types_hash(reactions); } diff --git a/lib/tgchat/ext/td/td/telegram/ReactionManager.h b/lib/tgchat/ext/td/td/telegram/ReactionManager.h index 1345f050..89ec058b 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionManager.h +++ b/lib/tgchat/ext/td/td/telegram/ReactionManager.h @@ -67,7 +67,7 @@ class ReactionManager final : public Actor { void get_current_state(vector> &updates) const; private: - static constexpr int32 MAX_RECENT_REACTIONS = 100; // some reasonable value + static constexpr size_t MAX_RECENT_REACTIONS = 100; // some reasonable value struct Reaction { ReactionType reaction_type_; diff --git a/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp b/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp index 56fe6299..a4c98603 100644 --- a/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp +++ b/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp @@ -23,8 +23,6 @@ #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" -#include - namespace td { RecentDialogList::RecentDialogList(Td *td, const char *name, size_t max_size) @@ -175,18 +173,8 @@ bool RecentDialogList::do_add_dialog(DialogId dialog_id) { return false; } - // TODO create function - auto it = std::find(dialog_ids_.begin(), dialog_ids_.end(), dialog_id); - if (it == dialog_ids_.end()) { - if (dialog_ids_.size() == max_size_) { - CHECK(!dialog_ids_.empty()); - dialog_ids_.back() = dialog_id; - } else { - dialog_ids_.push_back(dialog_id); - } - it = dialog_ids_.end() - 1; - } - std::rotate(dialog_ids_.begin(), it, it + 1); + add_to_top(dialog_ids_, max_size_, dialog_id); + removed_dialog_ids_.erase(dialog_id); return true; } diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp new file mode 100644 index 00000000..24d5deb6 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp @@ -0,0 +1,429 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/RepliedMessageInfo.h" + +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageContentType.h" +#include "td/telegram/MessageCopyOptions.h" +#include "td/telegram/MessageFullId.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/misc.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/ScheduledServerMessageId.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/Td.h" + +#include "td/utils/algorithm.h" +#include "td/utils/logging.h" + +namespace td { + +static bool has_qts_messages(const Td *td, DialogId dialog_id) { + switch (dialog_id.get_type()) { + case DialogType::User: + case DialogType::Chat: + return td->option_manager_->get_option_integer("session_count") > 1; + case DialogType::Channel: + case DialogType::SecretChat: + return false; + case DialogType::None: + default: + UNREACHABLE(); + return false; + } +} + +RepliedMessageInfo::~RepliedMessageInfo() = default; + +RepliedMessageInfo::RepliedMessageInfo(Td *td, tl_object_ptr &&reply_header, + DialogId dialog_id, MessageId message_id, int32 date) { + CHECK(reply_header != nullptr); + if (reply_header->reply_to_scheduled_) { + message_id_ = MessageId(ScheduledServerMessageId(reply_header->reply_to_msg_id_), date); + if (message_id.is_valid_scheduled()) { + if (reply_header->reply_to_peer_id_ != nullptr) { + dialog_id_ = DialogId(reply_header->reply_to_peer_id_); + LOG(ERROR) << "Receive reply to " << MessageFullId{dialog_id_, message_id_} << " in " + << MessageFullId{dialog_id, message_id}; + message_id_ = MessageId(); + dialog_id_ = DialogId(); + } + if (message_id == message_id_) { + LOG(ERROR) << "Receive reply to " << message_id_ << " in " << MessageFullId{dialog_id, message_id}; + message_id_ = MessageId(); + } + } else { + LOG(ERROR) << "Receive reply to " << message_id_ << " in " << MessageFullId{dialog_id, message_id}; + message_id_ = MessageId(); + } + if (reply_header->reply_from_ != nullptr || reply_header->reply_media_ != nullptr) { + LOG(ERROR) << "Receive reply from other chat " << to_string(reply_header) << " in " + << MessageFullId{dialog_id, message_id}; + } + } else { + if (reply_header->reply_to_msg_id_ != 0) { + message_id_ = MessageId(ServerMessageId(reply_header->reply_to_msg_id_)); + if (reply_header->reply_to_peer_id_ != nullptr) { + dialog_id_ = DialogId(reply_header->reply_to_peer_id_); + if (!dialog_id_.is_valid()) { + LOG(ERROR) << "Receive reply in invalid " << to_string(reply_header->reply_to_peer_id_); + message_id_ = MessageId(); + dialog_id_ = DialogId(); + } + } + if (!message_id_.is_valid()) { + LOG(ERROR) << "Receive " << to_string(reply_header) << " in " << MessageFullId{dialog_id, message_id}; + message_id_ = MessageId(); + dialog_id_ = DialogId(); + } else if (!message_id.is_scheduled() && !dialog_id_.is_valid() && + ((message_id_ > message_id && !has_qts_messages(td, dialog_id)) || message_id_ == message_id)) { + LOG(ERROR) << "Receive reply to " << message_id_ << " in " << MessageFullId{dialog_id, message_id}; + message_id_ = MessageId(); + } + } else if (reply_header->reply_to_peer_id_ != nullptr) { + LOG(ERROR) << "Receive " << to_string(reply_header) << " in " << MessageFullId{dialog_id, message_id}; + } + if (reply_header->reply_from_ != nullptr) { + origin_date_ = reply_header->reply_from_->date_; + if (origin_date_ <= 0) { + LOG(ERROR) << "Receive " << to_string(reply_header) << " in " << MessageFullId{dialog_id, message_id}; + origin_date_ = 0; + } else { + auto r_reply_origin = MessageOrigin::get_message_origin(td, std::move(reply_header->reply_from_)); + if (r_reply_origin.is_error()) { + origin_date_ = 0; + } else { + origin_ = r_reply_origin.move_as_ok(); + } + } + } + if (!origin_.is_empty() && reply_header->reply_media_ != nullptr && + reply_header->reply_media_->get_id() != telegram_api::messageMediaEmpty::ID) { + content_ = get_message_content(td, FormattedText(), std::move(reply_header->reply_media_), dialog_id, true, + UserId(), nullptr, nullptr, "messageReplyHeader"); + CHECK(content_ != nullptr); + switch (content_->get_type()) { + case MessageContentType::Animation: + case MessageContentType::Audio: + case MessageContentType::Contact: + case MessageContentType::Dice: + case MessageContentType::Document: + // case MessageContentType::ExpiredPhoto: + // case MessageContentType::ExpiredVideo: + case MessageContentType::Game: + case MessageContentType::Giveaway: + case MessageContentType::Invoice: + // case MessageContentType::LiveLocation: + case MessageContentType::Location: + case MessageContentType::Photo: + case MessageContentType::Poll: + case MessageContentType::Sticker: + case MessageContentType::Story: + case MessageContentType::Text: + case MessageContentType::Unsupported: + case MessageContentType::Venue: + case MessageContentType::Video: + case MessageContentType::VideoNote: + case MessageContentType::VoiceNote: + break; + default: + LOG(ERROR) << "Receive reply with media of the type " << content_->get_type(); + content_ = nullptr; + } + } + } + if ((!origin_.is_empty() || message_id_ != MessageId()) && !reply_header->quote_text_.empty()) { + is_quote_manual_ = reply_header->quote_; + auto entities = get_message_entities(td->contacts_manager_.get(), std::move(reply_header->quote_entities_), + "RepliedMessageInfo"); + auto status = fix_formatted_text(reply_header->quote_text_, entities, true, true, true, true, false); + if (status.is_error()) { + if (!clean_input_string(reply_header->quote_text_)) { + reply_header->quote_text_.clear(); + } + entities.clear(); + } + quote_ = FormattedText{std::move(reply_header->quote_text_), std::move(entities)}; + remove_unallowed_quote_entities(quote_); + } +} + +RepliedMessageInfo::RepliedMessageInfo(Td *td, const MessageInputReplyTo &input_reply_to) { + if (!input_reply_to.message_id_.is_valid()) { + return; + } + message_id_ = input_reply_to.message_id_; + if (!input_reply_to.quote_.text.empty()) { + quote_ = input_reply_to.quote_; + is_quote_manual_ = true; + } + if (input_reply_to.dialog_id_ != DialogId()) { + auto info = + td->messages_manager_->get_forwarded_message_info({input_reply_to.dialog_id_, input_reply_to.message_id_}); + if (info.origin_date_ == 0 || info.origin_.is_empty() || info.content_ == nullptr) { + *this = {}; + return; + } + origin_date_ = info.origin_date_; + origin_ = std::move(info.origin_); + content_ = std::move(info.content_); + auto content_text = get_message_content_text_mutable(content_.get()); + if (content_text != nullptr) { + if (!is_quote_manual_) { + quote_ = std::move(*content_text); + remove_unallowed_quote_entities(quote_); + truncate_formatted_text( + quote_, static_cast(td->option_manager_->get_option_integer("message_reply_quote_length_max"))); + } + *content_text = {}; + } + auto origin_message_full_id = origin_.get_message_full_id(); + if (origin_message_full_id.get_message_id().is_valid()) { + message_id_ = origin_message_full_id.get_message_id(); + dialog_id_ = origin_message_full_id.get_dialog_id(); + } else if (input_reply_to.dialog_id_.get_type() == DialogType::Channel) { + dialog_id_ = input_reply_to.dialog_id_; + } else { + message_id_ = MessageId(); + } + } +} + +RepliedMessageInfo RepliedMessageInfo::clone(Td *td) const { + RepliedMessageInfo result; + result.message_id_ = message_id_; + result.dialog_id_ = dialog_id_; + result.origin_date_ = origin_date_; + result.origin_ = origin_; + if (content_ != nullptr) { + result.content_ = dup_message_content(td, DialogId(td->contacts_manager_->get_my_id()), content_.get(), + MessageContentDupType::Forward, MessageCopyOptions()); + } + result.quote_ = quote_; + result.is_quote_manual_ = is_quote_manual_; + return result; +} + +bool RepliedMessageInfo::need_reget() const { + return content_ != nullptr && need_reget_message_content(content_.get()); +} + +bool RepliedMessageInfo::need_reply_changed_warning( + const Td *td, const RepliedMessageInfo &old_info, const RepliedMessageInfo &new_info, + MessageId old_top_thread_message_id, bool is_yet_unsent, + std::function is_reply_to_deleted_message) { + if (old_info.origin_date_ != new_info.origin_date_ && old_info.origin_date_ != 0 && new_info.origin_date_ != 0) { + // date of the original message can't change + return true; + } + if (old_info.origin_ != new_info.origin_ && !old_info.origin_.has_sender_signature() && + !new_info.origin_.has_sender_signature() && !old_info.origin_.is_empty() && !new_info.origin_.is_empty()) { + // only signature can change in the message origin + return true; + } + if (old_info.is_quote_manual_ != new_info.is_quote_manual_) { + // quote manual property can't change + return true; + } + if (old_info.quote_ != new_info.quote_) { + auto max_size = static_cast(td->option_manager_->get_option_integer("message_reply_quote_length_max")); + if (old_info.quote_.text.size() < max_size && new_info.quote_.text.size() < max_size) { + // quote can't change, unless truncated differently + return true; + } + } + if (old_info.dialog_id_ != new_info.dialog_id_ && old_info.dialog_id_ != DialogId() && + new_info.dialog_id_ != DialogId()) { + // reply chat can't change + return true; + } + if (old_info.message_id_ == new_info.message_id_ && old_info.dialog_id_ == new_info.dialog_id_) { + if (old_info.message_id_ != MessageId()) { + if (old_info.origin_date_ != new_info.origin_date_) { + // date of the original message can't change + return true; + } + if (old_info.origin_ != new_info.origin_ && !old_info.origin_.has_sender_signature() && + !new_info.origin_.has_sender_signature()) { + // only signature can change in the message origin + return true; + } + } + return false; + } + if (is_yet_unsent && is_reply_to_deleted_message(old_info) && new_info.message_id_ == MessageId()) { + // reply to a deleted message, which was available locally + return false; + } + if (is_yet_unsent && is_reply_to_deleted_message(new_info) && old_info.message_id_ == MessageId()) { + // reply to a locally deleted yet unsent message, which was available server-side + return false; + } + if (old_info.message_id_.is_valid_scheduled() && old_info.message_id_.is_scheduled_server() && + new_info.message_id_.is_valid_scheduled() && new_info.message_id_.is_scheduled_server() && + old_info.message_id_.get_scheduled_server_message_id() == + new_info.message_id_.get_scheduled_server_message_id()) { + // schedule date change + return false; + } + if (is_yet_unsent && old_top_thread_message_id == new_info.message_id_ && new_info.dialog_id_ == DialogId()) { + // move of reply to the top thread message after deletion of the replied message + return false; + } + return true; +} + +vector RepliedMessageInfo::get_file_ids(Td *td) const { + if (content_ != nullptr) { + return get_message_content_file_ids(content_.get(), td); + } + return {}; +} + +vector RepliedMessageInfo::get_min_user_ids(Td *td) const { + vector user_ids; + if (dialog_id_.get_type() == DialogType::User) { + user_ids.push_back(dialog_id_.get_user_id()); + } + origin_.add_user_ids(user_ids); + // not supported server-side: add_formatted_text_user_ids(user_ids, "e_); + if (content_ != nullptr) { + append(user_ids, get_message_content_min_user_ids(td, content_.get())); + } + return user_ids; +} + +vector RepliedMessageInfo::get_min_channel_ids(Td *td) const { + vector channel_ids; + if (dialog_id_.get_type() == DialogType::Channel) { + channel_ids.push_back(dialog_id_.get_channel_id()); + } + origin_.add_channel_ids(channel_ids); + if (content_ != nullptr) { + append(channel_ids, get_message_content_min_channel_ids(td, content_.get())); + } + return channel_ids; +} + +void RepliedMessageInfo::add_dependencies(Dependencies &dependencies, bool is_bot) const { + dependencies.add_dialog_and_dependencies(dialog_id_); + origin_.add_dependencies(dependencies); + add_formatted_text_dependencies(dependencies, "e_); + if (content_ != nullptr) { + add_message_content_dependencies(dependencies, content_.get(), is_bot); + } +} + +td_api::object_ptr RepliedMessageInfo::get_message_reply_to_message_object( + Td *td, DialogId dialog_id) const { + if (dialog_id_.is_valid()) { + dialog_id = dialog_id_; + } else { + CHECK(dialog_id.is_valid()); + } + auto chat_id = td->messages_manager_->get_chat_id_object(dialog_id, "messageReplyToMessage"); + if (message_id_ == MessageId()) { + chat_id = 0; + } + + td_api::object_ptr quote; + if (!quote_.text.empty()) { + quote = get_formatted_text_object(quote_, true, -1); + } + + td_api::object_ptr origin; + if (!origin_.is_empty()) { + origin = origin_.get_message_origin_object(td); + CHECK(origin != nullptr); + } + + td_api::object_ptr content; + if (content_ != nullptr) { + content = get_message_content_object(content_.get(), td, dialog_id, 0, false, true, -1, false, false); + if (content->get_id() == td_api::messageUnsupported::ID || + (content->get_id() == td_api::messageText::ID && + static_cast(content.get())->web_page_ == nullptr)) { + content = nullptr; + } + } + + return td_api::make_object(chat_id, message_id_.get(), std::move(quote), + is_quote_manual_, std::move(origin), origin_date_, + std::move(content)); +} + +MessageInputReplyTo RepliedMessageInfo::get_input_reply_to() const { + CHECK(!is_external()); + if (message_id_.is_valid()) { + return MessageInputReplyTo{message_id_, dialog_id_, FormattedText{quote_}}; + } + return {}; +} + +MessageId RepliedMessageInfo::get_same_chat_reply_to_message_id() const { + return dialog_id_ == DialogId() && origin_.is_empty() ? message_id_ : MessageId(); +} + +MessageFullId RepliedMessageInfo::get_reply_message_full_id(DialogId owner_dialog_id) const { + if (!message_id_.is_valid() && !message_id_.is_valid_scheduled()) { + return {}; + } + return {dialog_id_.is_valid() ? dialog_id_ : owner_dialog_id, message_id_}; +} + +void RepliedMessageInfo::register_content(Td *td) const { + if (content_ != nullptr) { + register_reply_message_content(td, content_.get()); + } +} + +void RepliedMessageInfo::unregister_content(Td *td) const { + if (content_ != nullptr) { + unregister_reply_message_content(td, content_.get()); + } +} + +bool operator==(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs) { + if (!(lhs.message_id_ == rhs.message_id_ && lhs.dialog_id_ == rhs.dialog_id_ && + lhs.origin_date_ == rhs.origin_date_ && lhs.origin_ == rhs.origin_ && lhs.quote_ == rhs.quote_ && + lhs.is_quote_manual_ == rhs.is_quote_manual_)) { + return false; + } + bool need_update = false; + bool is_content_changed = false; + compare_message_contents(nullptr, lhs.content_.get(), rhs.content_.get(), is_content_changed, need_update); + if (need_update || is_content_changed) { + return false; + } + return true; +} + +bool operator!=(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const RepliedMessageInfo &info) { + string_builder << "reply to " << info.message_id_; + if (info.dialog_id_ != DialogId()) { + string_builder << " in " << info.dialog_id_; + } + if (info.origin_date_ != 0) { + string_builder << " sent at " << info.origin_date_ << " by " << info.origin_; + } + if (!info.quote_.text.empty()) { + string_builder << " with " << info.quote_.text.size() << (info.is_quote_manual_ ? " manually" : "") + << " quoted bytes"; + } + if (info.content_ != nullptr) { + string_builder << " and content of the type " << info.content_->get_type(); + } + return string_builder; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h new file mode 100644 index 00000000..776a67a5 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h @@ -0,0 +1,122 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageFullId.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessageInputReplyTo.h" +#include "td/telegram/MessageOrigin.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +#include + +namespace td { + +class Dependencies; + +class Td; + +class RepliedMessageInfo { + MessageId message_id_; + DialogId dialog_id_; // for replies from another known chats + int32 origin_date_ = 0; // for replies in other chats + MessageOrigin origin_; // for replies in other chats + unique_ptr content_; // for replies in other chats + FormattedText quote_; + bool is_quote_manual_ = false; + + friend bool operator==(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const RepliedMessageInfo &info); + + public: + RepliedMessageInfo() = default; + RepliedMessageInfo(const RepliedMessageInfo &) = delete; + RepliedMessageInfo &operator=(const RepliedMessageInfo &) = delete; + RepliedMessageInfo(RepliedMessageInfo &&) = default; + RepliedMessageInfo &operator=(RepliedMessageInfo &&) = default; + ~RepliedMessageInfo(); + + static RepliedMessageInfo legacy(MessageId reply_to_message_id, DialogId reply_in_dialog_id = DialogId()) { + RepliedMessageInfo result; + result.message_id_ = reply_to_message_id; + result.dialog_id_ = reply_in_dialog_id; + return result; + } + + RepliedMessageInfo(Td *td, tl_object_ptr &&reply_header, DialogId dialog_id, + MessageId message_id, int32 date); + + RepliedMessageInfo(Td *td, const MessageInputReplyTo &input_reply_to); + + RepliedMessageInfo clone(Td *td) const; + + bool is_empty() const { + return message_id_ == MessageId() && origin_.is_empty(); + } + + bool is_external() const { + return origin_date_ != 0; + } + + bool need_reget() const; + + static bool need_reply_changed_warning( + const Td *td, const RepliedMessageInfo &old_info, const RepliedMessageInfo &new_info, + MessageId old_top_thread_message_id, bool is_yet_unsent, + std::function is_reply_to_deleted_message); + + vector get_file_ids(Td *td) const; + + vector get_min_user_ids(Td *td) const; + + vector get_min_channel_ids(Td *td) const; + + void add_dependencies(Dependencies &dependencies, bool is_bot) const; + + td_api::object_ptr get_message_reply_to_message_object(Td *td, + DialogId dialog_id) const; + + MessageInputReplyTo get_input_reply_to() const; + + void set_message_id(MessageId new_message_id) { + CHECK(message_id_.is_valid() || message_id_.is_valid_scheduled()); + message_id_ = new_message_id; + } + + MessageId get_same_chat_reply_to_message_id() const; + + MessageFullId get_reply_message_full_id(DialogId owner_dialog_id) const; + + void register_content(Td *td) const; + + void unregister_content(Td *td) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs); + +bool operator!=(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const RepliedMessageInfo &info); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp new file mode 100644 index 00000000..eb597b76 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp @@ -0,0 +1,94 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/RepliedMessageInfo.h" + +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageEntity.hpp" +#include "td/telegram/MessageOrigin.hpp" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void RepliedMessageInfo::store(StorerT &storer) const { + bool has_message_id = message_id_.is_valid() || message_id_.is_valid_scheduled(); + bool has_dialog_id = dialog_id_.is_valid(); + bool has_origin_date = origin_date_ != 0; + bool has_origin = !origin_.is_empty(); + bool has_quote = !quote_.text.empty(); + bool has_content = content_ != nullptr; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_message_id); + STORE_FLAG(has_dialog_id); + STORE_FLAG(has_origin_date); + STORE_FLAG(has_origin); + STORE_FLAG(has_quote); + STORE_FLAG(is_quote_manual_); + STORE_FLAG(has_content); + END_STORE_FLAGS(); + if (has_message_id) { + td::store(message_id_, storer); + } + if (has_dialog_id) { + td::store(dialog_id_, storer); + } + if (has_origin_date) { + td::store(origin_date_, storer); + } + if (has_origin) { + td::store(origin_, storer); + } + if (has_quote) { + td::store(quote_, storer); + } + if (has_content) { + store_message_content(content_.get(), storer); + } +} + +template +void RepliedMessageInfo::parse(ParserT &parser) { + bool has_message_id; + bool has_dialog_id; + bool has_origin_date; + bool has_origin; + bool has_quote; + bool has_content; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_message_id); + PARSE_FLAG(has_dialog_id); + PARSE_FLAG(has_origin_date); + PARSE_FLAG(has_origin); + PARSE_FLAG(has_quote); + PARSE_FLAG(is_quote_manual_); + PARSE_FLAG(has_content); + END_PARSE_FLAGS(); + if (has_message_id) { + td::parse(message_id_, parser); + } + if (has_dialog_id) { + td::parse(dialog_id_, parser); + } + if (has_origin_date) { + td::parse(origin_date_, parser); + } + if (has_origin) { + td::parse(origin_, parser); + } + if (has_quote) { + td::parse(quote_, parser); + remove_unallowed_quote_entities(quote_); + } + if (has_content) { + parse_message_content(content_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SecretChatActor.h b/lib/tgchat/ext/td/td/telegram/SecretChatActor.h index 478b5a4f..c4de675f 100644 --- a/lib/tgchat/ext/td/td/telegram/SecretChatActor.h +++ b/lib/tgchat/ext/td/td/telegram/SecretChatActor.h @@ -485,7 +485,7 @@ class SecretChatActor final : public NetQueryCallback { // 3. Then, we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog. // // Actually the change will be saved to binlog too. - // So we can do it immediatelly after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be + // So we can do it immediately after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be // ordered automatically. // // We will use common ChangesProcessor for all changes (inside one SecretChatActor). diff --git a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp index ecbd7c37..3d278a90 100644 --- a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp @@ -291,7 +291,7 @@ td_api::object_ptr SponsoredMessageManager::get_sponso } return td_api::make_object( sponsored_message.local_id, sponsored_message.is_recommended, - get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1), + get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1, false, false), std::move(sponsor), sponsored_message.additional_info); } diff --git a/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp b/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp index 5198c6f8..79a533de 100644 --- a/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp @@ -9,6 +9,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessagesInfo.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" @@ -17,9 +18,13 @@ #include "td/utils/algorithm.h" #include "td/utils/buffer.h" +#include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" +#include + namespace td { static td_api::object_ptr convert_date_range( @@ -51,7 +56,7 @@ static td_api::object_ptr convert_stats_graph( } } -static double get_percentage_value(double part, double total) { +static double get_percentage_value(double part, double total, bool is_percentage) { if (total < 1e-6 && total > -1e-6) { if (part < 1e-6 && part > -1e-6) { return 0.0; @@ -61,13 +66,18 @@ static double get_percentage_value(double part, double total) { if (part > 1e20) { return 100.0; } - return clamp(0.0, part / total * 100, 100.0); + auto value = part / total * 100; + if (is_percentage) { + return clamp(value, 0.0, 100.0); + } else { + return max(value, -100.0); + } } static td_api::object_ptr convert_stats_absolute_value( const telegram_api::object_ptr &obj) { return td_api::make_object( - obj->current_, obj->previous_, get_percentage_value(obj->current_ - obj->previous_, obj->previous_)); + obj->current_, obj->previous_, get_percentage_value(obj->current_ - obj->previous_, obj->previous_, false)); } static td_api::object_ptr convert_megagroup_stats( @@ -129,7 +139,7 @@ static td_api::object_ptr convert_broadcast_stats return td_api::make_object( convert_date_range(obj->period_), convert_stats_absolute_value(obj->followers_), convert_stats_absolute_value(obj->views_per_post_), convert_stats_absolute_value(obj->shares_per_post_), - get_percentage_value(obj->enabled_notifications_->part_, obj->enabled_notifications_->total_), + get_percentage_value(obj->enabled_notifications_->part_, obj->enabled_notifications_->total_, true), convert_stats_graph(std::move(obj->growth_graph_)), convert_stats_graph(std::move(obj->followers_graph_)), convert_stats_graph(std::move(obj->mute_graph_)), convert_stats_graph(std::move(obj->top_hours_graph_)), convert_stats_graph(std::move(obj->views_by_source_graph_)), @@ -298,6 +308,60 @@ class LoadAsyncGraphQuery final : public Td::ResultHandler { } }; +class GetMessagePublicForwardsQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + int32 limit_; + + public: + explicit GetMessagePublicForwardsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(DcId dc_id, MessageFullId message_full_id, int32 offset_date, DialogId offset_dialog_id, + ServerMessageId offset_message_id, int32 limit) { + dialog_id_ = message_full_id.get_dialog_id(); + limit_ = limit; + + auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id); + CHECK(input_peer != nullptr); + + auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()); + CHECK(input_channel != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::stats_getMessagePublicForwards( + std::move(input_channel), message_full_id.get_message_id().get_server_message_id().get(), offset_date, + std::move(input_peer), offset_message_id.get(), limit), + {}, dc_id)); + } + + 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 info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "GetMessagePublicForwardsQuery"); + td_->messages_manager_->get_channel_differences_if_needed( + std::move(info), PromiseCreator::lambda([actor_id = td_->statistics_manager_actor_.get(), + promise = std::move(promise_)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + auto info = result.move_as_ok(); + send_closure(actor_id, &StatisticsManager::on_get_message_public_forwards, info.total_count, + std::move(info.messages), info.next_rate, std::move(promise)); + } + })); + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePublicForwardsQuery"); + promise_.set_error(std::move(status)); + } +}; + StatisticsManager::StatisticsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } @@ -380,4 +444,111 @@ void StatisticsManager::send_load_async_graph_query(DcId dc_id, string token, in td_->create_handler(std::move(promise))->send(token, x, dc_id); } +void StatisticsManager::get_message_public_forwards(MessageFullId message_full_id, string offset, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), message_full_id, offset = std::move(offset), + limit, promise = std::move(promise)](Result r_dc_id) mutable { + if (r_dc_id.is_error()) { + return promise.set_error(r_dc_id.move_as_error()); + } + send_closure(actor_id, &StatisticsManager::send_get_message_public_forwards_query, r_dc_id.move_as_ok(), + message_full_id, std::move(offset), limit, std::move(promise)); + }); + td_->contacts_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, + std::move(dc_id_promise)); +} + +void StatisticsManager::send_get_message_public_forwards_query( + DcId dc_id, MessageFullId message_full_id, string offset, int32 limit, + Promise> &&promise) { + if (!td_->messages_manager_->have_message_force(message_full_id, "send_get_message_public_forwards_query")) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (!td_->messages_manager_->can_get_message_statistics(message_full_id)) { + return promise.set_error(Status::Error(400, "Message forwards are inaccessible")); + } + + static constexpr int32 MAX_MESSAGE_FORWARDS = 100; // server side limit + if (limit > MAX_MESSAGE_FORWARDS) { + limit = MAX_MESSAGE_FORWARDS; + } + + int32 offset_date = std::numeric_limits::max(); + DialogId offset_dialog_id; + ServerMessageId offset_server_message_id; + + if (!offset.empty()) { + auto parts = full_split(offset, ','); + if (parts.size() != 3) { + return promise.set_error(Status::Error(400, "Invalid offset specified")); + } + auto r_offset_date = to_integer_safe(parts[0]); + auto r_offset_dialog_id = to_integer_safe(parts[1]); + auto r_offset_server_message_id = to_integer_safe(parts[2]); + if (r_offset_date.is_error() || r_offset_dialog_id.is_error() || r_offset_server_message_id.is_error()) { + return promise.set_error(Status::Error(400, "Invalid offset specified")); + } + + offset_date = r_offset_date.ok(); + offset_dialog_id = DialogId(r_offset_dialog_id.ok()); + offset_server_message_id = ServerMessageId(r_offset_server_message_id.ok()); + } + + td_->create_handler(std::move(promise)) + ->send(dc_id, message_full_id, offset_date, offset_dialog_id, offset_server_message_id, limit); +} + +void StatisticsManager::on_get_message_public_forwards( + int32 total_count, vector> &&messages, int32 next_rate, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + LOG(INFO) << "Receive " << messages.size() << " forwarded messages"; + vector> result; + int32 last_message_date = 0; + MessageId last_message_id; + DialogId last_dialog_id; + for (auto &message : messages) { + auto message_date = MessagesManager::get_message_date(message); + auto message_id = MessageId::get_message_id(message, false); + auto dialog_id = DialogId::get_message_dialog_id(message); + if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) { + last_message_date = message_date; + last_message_id = message_id; + last_dialog_id = dialog_id; + } + + auto new_message_full_id = + td_->messages_manager_->on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, + false, "on_get_message_public_forwards"); + if (new_message_full_id != MessageFullId()) { + CHECK(dialog_id == new_message_full_id.get_dialog_id()); + result.push_back( + td_->messages_manager_->get_message_object(new_message_full_id, "on_get_message_public_forwards")); + CHECK(result.back() != nullptr); + } else { + total_count--; + } + } + if (total_count < static_cast(result.size())) { + LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() + << " messages"; + total_count = static_cast(result.size()); + } + string next_offset; + if (!result.empty()) { + if (next_rate > 0) { + last_message_date = next_rate; + } + next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ',' + << last_message_id.get_server_message_id().get(); + } + + promise.set_value(td_api::make_object(total_count, std::move(result), next_offset)); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StatisticsManager.h b/lib/tgchat/ext/td/td/telegram/StatisticsManager.h index e54b144a..dd676c57 100644 --- a/lib/tgchat/ext/td/td/telegram/StatisticsManager.h +++ b/lib/tgchat/ext/td/td/telegram/StatisticsManager.h @@ -11,6 +11,7 @@ #include "td/telegram/MessageFullId.h" #include "td/telegram/net/DcId.h" #include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" #include "td/actor/actor.h" @@ -34,6 +35,13 @@ class StatisticsManager final : public Actor { void load_statistics_graph(DialogId dialog_id, string token, int64 x, Promise> &&promise); + void get_message_public_forwards(MessageFullId message_full_id, string offset, int32 limit, + Promise> &&promise); + + void on_get_message_public_forwards(int32 total_count, + vector> &&messages, + int32 next_rate, Promise> &&promise); + private: void tear_down() final; @@ -46,6 +54,9 @@ class StatisticsManager final : public Actor { void send_load_async_graph_query(DcId dc_id, string token, int64 x, Promise> &&promise); + void send_get_message_public_forwards_query(DcId dc_id, MessageFullId message_full_id, string offset, int32 limit, + Promise> &&promise); + Td *td_; ActorShared<> parent_; }; diff --git a/lib/tgchat/ext/td/td/telegram/StickerListType.cpp b/lib/tgchat/ext/td/td/telegram/StickerListType.cpp new file mode 100644 index 00000000..049f5bce --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StickerListType.cpp @@ -0,0 +1,39 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// 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/StickerListType.h" + +namespace td { + +string get_sticker_list_type_database_key(StickerListType sticker_list_type) { + switch (sticker_list_type) { + case StickerListType::DialogPhoto: + return "default_dialog_photo_custom_emoji_ids"; + case StickerListType::UserProfilePhoto: + return "default_profile_photo_custom_emoji_ids"; + case StickerListType::Background: + return "default_background_custom_emoji_ids"; + default: + UNREACHABLE(); + return string(); + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, StickerListType sticker_list_type) { + switch (sticker_list_type) { + case StickerListType::DialogPhoto: + return string_builder << "default chat photo custom emoji identifiers"; + case StickerListType::UserProfilePhoto: + return string_builder << "default user profile photo custom emoji identifiers"; + case StickerListType::Background: + return string_builder << "default background custom emoji identifiers"; + default: + UNREACHABLE(); + return string_builder; + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StickerListType.h b/lib/tgchat/ext/td/td/telegram/StickerListType.h new file mode 100644 index 00000000..16afdb99 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StickerListType.h @@ -0,0 +1,22 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +enum class StickerListType : int32 { DialogPhoto, UserProfilePhoto, Background }; + +static constexpr int32 MAX_STICKER_LIST_TYPE = 3; + +string get_sticker_list_type_database_key(StickerListType sticker_list_type); + +StringBuilder &operator<<(StringBuilder &string_builder, StickerListType sticker_list_type); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.cpp b/lib/tgchat/ext/td/td/telegram/StickersManager.cpp index ea2462c5..45cc57f0 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.cpp @@ -1446,11 +1446,20 @@ class GetDefaultDialogPhotoEmojisQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(bool for_user, int64 hash) { - if (for_user) { - send_query(G()->net_query_creator().create(telegram_api::account_getDefaultProfilePhotoEmojis(hash))); - } else { - send_query(G()->net_query_creator().create(telegram_api::account_getDefaultGroupPhotoEmojis(hash))); + void send(StickerListType sticker_list_type, int64 hash) { + switch (sticker_list_type) { + case StickerListType::DialogPhoto: + send_query(G()->net_query_creator().create(telegram_api::account_getDefaultGroupPhotoEmojis(hash))); + break; + case StickerListType::UserProfilePhoto: + send_query(G()->net_query_creator().create(telegram_api::account_getDefaultProfilePhotoEmojis(hash))); + break; + case StickerListType::Background: + send_query(G()->net_query_creator().create(telegram_api::account_getDefaultBackgroundEmojis(hash))); + break; + default: + UNREACHABLE(); + break; } } @@ -1458,6 +1467,9 @@ class GetDefaultDialogPhotoEmojisQuery final : public Td::ResultHandler { static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, + ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); @@ -2468,7 +2480,7 @@ tl_object_ptr StickersManager::get_sticker_set_object(Sticke get_sticker_set_minithumbnail_zoom(sticker_set)), sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), - sticker_set->is_viewed_, std::move(stickers), std::move(emojis)); + sticker_set->has_text_color_, sticker_set->is_viewed_, std::move(stickers), std::move(emojis)); } tl_object_ptr StickersManager::get_sticker_sets_object(int32 total_count, @@ -2544,8 +2556,8 @@ tl_object_ptr StickersManager::get_sticker_set_info_obje get_sticker_set_minithumbnail_zoom(sticker_set)), sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), - sticker_set->is_viewed_, sticker_set->was_loaded_ ? actual_count : max(actual_count, sticker_set->sticker_count_), - std::move(stickers)); + sticker_set->has_text_color_, sticker_set->is_viewed_, + sticker_set->was_loaded_ ? actual_count : max(actual_count, sticker_set->sticker_count_), std::move(stickers)); } td_api::object_ptr StickersManager::get_premium_gift_sticker_object(int32 month_count) { @@ -3545,6 +3557,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrflags_ & telegram_api::stickerSet::INSTALLED_DATE_MASK) != 0; bool is_archived = set->archived_; bool is_official = set->official_; + bool has_text_color = set->text_color_; StickerFormat sticker_format = set->videos_ ? StickerFormat::Webm : (set->animated_ ? StickerFormat::Tgs : StickerFormat::Webp); StickerType sticker_type = @@ -3583,6 +3596,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptris_official_ = is_official; s->sticker_format_ = sticker_format; s->sticker_type_ = sticker_type; + s->has_text_color_ = has_text_color; s->is_changed_ = true; } else { CHECK(s->id_ == set_id); @@ -3653,6 +3667,11 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptris_official_ = is_official; s->is_changed_ = true; } + if (s->has_text_color_ != has_text_color) { + LOG(INFO) << "Needs repainting flag of " << set_id << " changed to " << has_text_color; + s->has_text_color_ = has_text_color; + s->is_changed_ = true; + } if (s->sticker_format_ != sticker_format) { LOG(ERROR) << "Format of stickers in " << set_id << '/' << s->short_name_ << " has changed from " << s->sticker_format_ << " to " << sticker_format << " from " << source; @@ -6223,36 +6242,31 @@ void StickersManager::on_get_custom_emoji_documents( promise.set_value(get_custom_emoji_stickers_object(custom_emoji_ids)); } -string StickersManager::get_default_dialog_photo_custom_emoji_ids_database_key(bool for_user) { - return for_user ? "default_profile_photo_custom_emoji_ids" : "default_dialog_photo_custom_emoji_ids"; -} - -void StickersManager::get_default_dialog_photo_custom_emoji_stickers( - bool for_user, bool force_reload, Promise> &&promise) { - if (are_default_dialog_photo_custom_emoji_ids_loaded_[for_user] && !force_reload) { - return get_custom_emoji_stickers_unlimited(default_dialog_photo_custom_emoji_ids_[for_user], std::move(promise)); +void StickersManager::get_default_custom_emoji_stickers(StickerListType sticker_list_type, bool force_reload, + Promise> &&promise) { + auto index = static_cast(sticker_list_type); + if (are_default_custom_emoji_ids_loaded_[index] && !force_reload) { + return get_custom_emoji_stickers_unlimited(default_custom_emoji_ids_[index], std::move(promise)); } - auto &queries = default_dialog_photo_custom_emoji_ids_load_queries_[for_user]; + auto &queries = default_custom_emoji_ids_load_queries_[index]; queries.push_back(std::move(promise)); if (queries.size() != 1) { // query has already been sent, just wait for the result return; } - if (G()->use_sqlite_pmc() && !are_default_dialog_photo_custom_emoji_ids_loaded_[for_user]) { - LOG(INFO) << "Trying to load " << (for_user ? "profile" : "chat") - << " photo custom emoji identifiers from database"; + if (G()->use_sqlite_pmc() && !are_default_custom_emoji_ids_loaded_[index]) { + LOG(INFO) << "Trying to load " << sticker_list_type << " from database"; return G()->td_db()->get_sqlite_pmc()->get( - get_default_dialog_photo_custom_emoji_ids_database_key(for_user), - PromiseCreator::lambda([for_user, force_reload](string value) { - send_closure(G()->stickers_manager(), - &StickersManager::on_load_default_dialog_photo_custom_emoji_ids_from_database, for_user, - force_reload, std::move(value)); + get_sticker_list_type_database_key(sticker_list_type), + PromiseCreator::lambda([sticker_list_type, force_reload](string value) { + send_closure(G()->stickers_manager(), &StickersManager::on_load_default_custom_emoji_ids_from_database, + sticker_list_type, force_reload, std::move(value)); })); } - reload_default_dialog_photo_custom_emoji_ids(for_user); + reload_default_custom_emoji_ids(sticker_list_type); } class StickersManager::CustomEmojiIdsLogEvent { @@ -6279,76 +6293,77 @@ class StickersManager::CustomEmojiIdsLogEvent { } }; -void StickersManager::on_load_default_dialog_photo_custom_emoji_ids_from_database(bool for_user, bool force_reload, - string value) { +void StickersManager::on_load_default_custom_emoji_ids_from_database(StickerListType sticker_list_type, + bool force_reload, string value) { if (G()->close_flag()) { - fail_promises(default_dialog_photo_custom_emoji_ids_load_queries_[for_user], Global::request_aborted_error()); + auto index = static_cast(sticker_list_type); + fail_promises(default_custom_emoji_ids_load_queries_[index], Global::request_aborted_error()); return; } if (value.empty()) { - return reload_default_dialog_photo_custom_emoji_ids(for_user); + return reload_default_custom_emoji_ids(sticker_list_type); } - LOG(INFO) << "Successfully loaded default " << (for_user ? "profile" : "chat") - << " photo custom emoji identifiers of size " << value.size() << " from database"; + LOG(INFO) << "Successfully loaded " << sticker_list_type << " of size " << value.size() << " from database"; CustomEmojiIdsLogEvent log_event; if (log_event_parse(log_event, value).is_error()) { - LOG(ERROR) << "Delete invalid default " << (for_user ? "profile" : "chat") - << " photo custom emoji identifiers from database"; - G()->td_db()->get_sqlite_pmc()->erase(get_default_dialog_photo_custom_emoji_ids_database_key(for_user), Auto()); - return reload_default_dialog_photo_custom_emoji_ids(for_user); + LOG(ERROR) << "Delete invalid " << sticker_list_type << " from database"; + G()->td_db()->get_sqlite_pmc()->erase(get_sticker_list_type_database_key(sticker_list_type), Auto()); + return reload_default_custom_emoji_ids(sticker_list_type); } - on_get_default_dialog_photo_custom_emoji_ids_success(for_user, std::move(log_event.custom_emoji_ids_), - log_event.hash_); + on_get_default_custom_emoji_ids_success(sticker_list_type, std::move(log_event.custom_emoji_ids_), log_event.hash_); if (force_reload) { - reload_default_dialog_photo_custom_emoji_ids(for_user); + reload_default_custom_emoji_ids(sticker_list_type); } } -void StickersManager::reload_default_dialog_photo_custom_emoji_ids(bool for_user) { +void StickersManager::reload_default_custom_emoji_ids(StickerListType sticker_list_type) { if (G()->close_flag()) { - fail_promises(default_dialog_photo_custom_emoji_ids_load_queries_[for_user], Global::request_aborted_error()); + auto index = static_cast(sticker_list_type); + fail_promises(default_custom_emoji_ids_load_queries_[index], Global::request_aborted_error()); return; } CHECK(!td_->auth_manager_->is_bot()); - if (are_default_dialog_photo_custom_emoji_ids_being_loaded_[for_user]) { + auto index = static_cast(sticker_list_type); + if (are_default_custom_emoji_ids_being_loaded_[index]) { return; } - are_default_dialog_photo_custom_emoji_ids_being_loaded_[for_user] = true; + are_default_custom_emoji_ids_being_loaded_[index] = true; auto query_promise = - PromiseCreator::lambda([actor_id = actor_id(this), for_user]( + PromiseCreator::lambda([actor_id = actor_id(this), sticker_list_type]( Result> r_emoji_list) mutable { - send_closure(actor_id, &StickersManager::on_get_default_dialog_photo_custom_emoji_ids, for_user, + send_closure(actor_id, &StickersManager::on_get_default_custom_emoji_ids, sticker_list_type, std::move(r_emoji_list)); }); td_->create_handler(std::move(query_promise)) - ->send(for_user, default_dialog_photo_custom_emoji_ids_hash_[for_user]); + ->send(sticker_list_type, default_custom_emoji_ids_hash_[index]); } -void StickersManager::on_get_default_dialog_photo_custom_emoji_ids( - bool for_user, Result> r_emoji_list) { +void StickersManager::on_get_default_custom_emoji_ids( + StickerListType sticker_list_type, Result> r_emoji_list) { G()->ignore_result_if_closing(r_emoji_list); - CHECK(are_default_dialog_photo_custom_emoji_ids_being_loaded_[for_user]); - are_default_dialog_photo_custom_emoji_ids_being_loaded_[for_user] = false; + auto index = static_cast(sticker_list_type); + CHECK(are_default_custom_emoji_ids_being_loaded_[index]); + are_default_custom_emoji_ids_being_loaded_[index] = false; if (r_emoji_list.is_error()) { - fail_promises(default_dialog_photo_custom_emoji_ids_load_queries_[for_user], r_emoji_list.move_as_error()); + fail_promises(default_custom_emoji_ids_load_queries_[index], r_emoji_list.move_as_error()); return; } auto emoji_list_ptr = r_emoji_list.move_as_ok(); int32 constructor_id = emoji_list_ptr->get_id(); if (constructor_id == telegram_api::emojiListNotModified::ID) { - LOG(INFO) << "Default " << (for_user ? "profile" : "chat") << " photo custom emoji identifiers aren't modified"; - if (!are_default_dialog_photo_custom_emoji_ids_loaded_[for_user]) { - on_get_default_dialog_photo_custom_emoji_ids_success(for_user, {}, 0); + LOG(INFO) << "The " << sticker_list_type << " isn't modified"; + if (!are_default_custom_emoji_ids_loaded_[index]) { + on_get_default_custom_emoji_ids_success(sticker_list_type, {}, 0); } - auto promises = std::move(default_dialog_photo_custom_emoji_ids_load_queries_[for_user]); - reset_to_empty(default_dialog_photo_custom_emoji_ids_load_queries_[for_user]); + auto promises = std::move(default_custom_emoji_ids_load_queries_[index]); + reset_to_empty(default_custom_emoji_ids_load_queries_[index]); for (auto &promise : promises) { CHECK(!promise); } @@ -6361,26 +6376,25 @@ void StickersManager::on_get_default_dialog_photo_custom_emoji_ids( if (G()->use_sqlite_pmc()) { CustomEmojiIdsLogEvent log_event(custom_emoji_ids, hash); - G()->td_db()->get_sqlite_pmc()->set(get_default_dialog_photo_custom_emoji_ids_database_key(for_user), + G()->td_db()->get_sqlite_pmc()->set(get_sticker_list_type_database_key(sticker_list_type), log_event_store(log_event).as_slice().str(), Auto()); } - on_get_default_dialog_photo_custom_emoji_ids_success(for_user, std::move(custom_emoji_ids), hash); + on_get_default_custom_emoji_ids_success(sticker_list_type, std::move(custom_emoji_ids), hash); } -void StickersManager::on_get_default_dialog_photo_custom_emoji_ids_success(bool for_user, - vector custom_emoji_ids, - int64 hash) { - LOG(INFO) << "Load " << custom_emoji_ids.size() << " default " << (for_user ? "profile" : "chat") - << " photo custom emoji identifiers"; - default_dialog_photo_custom_emoji_ids_[for_user] = std::move(custom_emoji_ids); - default_dialog_photo_custom_emoji_ids_hash_[for_user] = hash; - are_default_dialog_photo_custom_emoji_ids_loaded_[for_user] = true; +void StickersManager::on_get_default_custom_emoji_ids_success(StickerListType sticker_list_type, + vector custom_emoji_ids, int64 hash) { + auto index = static_cast(sticker_list_type); + LOG(INFO) << "Load " << custom_emoji_ids.size() << ' ' << sticker_list_type; + default_custom_emoji_ids_[index] = std::move(custom_emoji_ids); + default_custom_emoji_ids_hash_[index] = hash; + are_default_custom_emoji_ids_loaded_[index] = true; - auto promises = std::move(default_dialog_photo_custom_emoji_ids_load_queries_[for_user]); - reset_to_empty(default_dialog_photo_custom_emoji_ids_load_queries_[for_user]); + auto promises = std::move(default_custom_emoji_ids_load_queries_[index]); + reset_to_empty(default_custom_emoji_ids_load_queries_[index]); for (auto &promise : promises) { - get_custom_emoji_stickers_unlimited(default_dialog_photo_custom_emoji_ids_[for_user], std::move(promise)); + get_custom_emoji_stickers_unlimited(default_custom_emoji_ids_[index], std::move(promise)); } } @@ -7480,16 +7494,13 @@ int StickersManager::move_installed_sticker_set_to_top(StickerType sticker_type, } 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()); + if (!current_sticker_set_ids.empty() && sticker_set_id == current_sticker_set_ids[0]) { return 0; } - - std::rotate(current_sticker_set_ids.begin(), it, it + 1); + if (!td::contains(current_sticker_set_ids, sticker_set_id)) { + return -1; + } + add_to_top(current_sticker_set_ids, current_sticker_set_ids.size(), sticker_set_id); need_update_installed_sticker_sets_[type] = true; return 1; @@ -8778,16 +8789,8 @@ void StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_i return promise.set_error(Status::Error(400, "Can't save encrypted stickers")); } - auto it = std::find_if(sticker_ids.begin(), sticker_ids.end(), is_equal); - if (it == sticker_ids.end()) { - if (static_cast(sticker_ids.size()) == recent_stickers_limit_) { - sticker_ids.back() = sticker_id; - } else { - sticker_ids.push_back(sticker_id); - } - it = sticker_ids.end() - 1; - } - std::rotate(sticker_ids.begin(), it, it + 1); + add_to_top_if(sticker_ids, static_cast(recent_stickers_limit_), sticker_id, is_equal); + if (sticker_ids[0].get_remote() == 0 && sticker_id.get_remote() != 0) { sticker_ids[0] = sticker_id; } @@ -9167,16 +9170,8 @@ void StickersManager::add_favorite_sticker_impl(FileId sticker_id, bool add_on_s return promise.set_error(Status::Error(400, "Can't add to favorites encrypted stickers")); } - auto it = std::find_if(favorite_sticker_ids_.begin(), favorite_sticker_ids_.end(), is_equal); - if (it == favorite_sticker_ids_.end()) { - if (static_cast(favorite_sticker_ids_.size()) == favorite_stickers_limit_) { - favorite_sticker_ids_.back() = sticker_id; - } else { - favorite_sticker_ids_.push_back(sticker_id); - } - it = favorite_sticker_ids_.end() - 1; - } - std::rotate(favorite_sticker_ids_.begin(), it, it + 1); + add_to_top_if(favorite_sticker_ids_, static_cast(favorite_stickers_limit_), sticker_id, is_equal); + if (favorite_sticker_ids_[0].get_remote() == 0 && sticker_id.get_remote() != 0) { favorite_sticker_ids_[0] = sticker_id; } diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.h b/lib/tgchat/ext/td/td/telegram/StickersManager.h index 8ace863b..08eec26b 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.h +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.h @@ -19,6 +19,7 @@ #include "td/telegram/SecretInputMedia.h" #include "td/telegram/SpecialStickerSetType.h" #include "td/telegram/StickerFormat.h" +#include "td/telegram/StickerListType.h" #include "td/telegram/StickerMaskPosition.h" #include "td/telegram/StickerSetId.h" #include "td/telegram/StickerType.h" @@ -134,8 +135,8 @@ class StickersManager final : public Actor { void get_custom_emoji_stickers(vector custom_emoji_ids, bool use_database, Promise> &&promise); - void get_default_dialog_photo_custom_emoji_stickers(bool for_user, bool force_reload, - Promise> &&promise); + void get_default_custom_emoji_stickers(StickerListType sticker_list_type, bool force_reload, + Promise> &&promise); void get_premium_gift_option_sticker(int32 month_count, bool is_recursive, Promise> &&promise); @@ -491,6 +492,7 @@ class StickersManager final : public Actor { bool is_installed_ = false; bool is_archived_ = false; bool is_official_ = false; + bool has_text_color_ = false; bool is_viewed_ = true; bool is_thumbnail_reloaded_ = false; // stored in telegram_api::stickerSet bool are_legacy_sticker_thumbnails_reloaded_ = false; // stored in telegram_api::stickerSet @@ -617,15 +619,16 @@ class StickersManager final : public Actor { void on_load_custom_emoji_from_database(CustomEmojiId custom_emoji_id, string value); - void on_load_default_dialog_photo_custom_emoji_ids_from_database(bool for_user, bool force_reload, string value); + void on_load_default_custom_emoji_ids_from_database(StickerListType sticker_list_type, bool force_reload, + string value); - void reload_default_dialog_photo_custom_emoji_ids(bool for_user); + void reload_default_custom_emoji_ids(StickerListType sticker_list_type); - void on_get_default_dialog_photo_custom_emoji_ids( - bool for_user, Result> r_emoji_list); + void on_get_default_custom_emoji_ids(StickerListType sticker_list_type, + Result> r_emoji_list); - void on_get_default_dialog_photo_custom_emoji_ids_success(bool for_user, vector custom_emoji_ids, - int64 hash); + void on_get_default_custom_emoji_ids_success(StickerListType sticker_list_type, + vector custom_emoji_ids, int64 hash); FileId on_get_sticker(unique_ptr new_sticker, bool replace); @@ -908,8 +911,6 @@ class StickersManager final : public Actor { static string get_emoji_language_codes_database_key(const vector &language_codes); - static string get_default_dialog_photo_custom_emoji_ids_database_key(bool for_user); - static string get_emoji_groups_database_key(EmojiGroupType group_type); int32 get_emoji_language_code_version(const string &language_code); @@ -1122,11 +1123,11 @@ class StickersManager final : public Actor { EmojiGroupList emoji_group_list_[MAX_EMOJI_GROUP_TYPE]; vector>> emoji_group_load_queries_[MAX_EMOJI_GROUP_TYPE]; - vector default_dialog_photo_custom_emoji_ids_[2]; - int64 default_dialog_photo_custom_emoji_ids_hash_[2] = {0, 0}; - vector>> default_dialog_photo_custom_emoji_ids_load_queries_[2]; - bool are_default_dialog_photo_custom_emoji_ids_loaded_[2] = {false, false}; - bool are_default_dialog_photo_custom_emoji_ids_being_loaded_[2] = {false, false}; + vector default_custom_emoji_ids_[MAX_STICKER_LIST_TYPE]; + int64 default_custom_emoji_ids_hash_[MAX_STICKER_LIST_TYPE] = {0, 0, 0}; + vector>> default_custom_emoji_ids_load_queries_[MAX_STICKER_LIST_TYPE]; + bool are_default_custom_emoji_ids_loaded_[MAX_STICKER_LIST_TYPE] = {false, false, false}; + bool are_default_custom_emoji_ids_being_loaded_[MAX_STICKER_LIST_TYPE] = {false, false, false}; WaitFreeHashMap custom_emoji_to_sticker_id_; diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.hpp b/lib/tgchat/ext/td/td/telegram/StickersManager.hpp index ad64127d..5b2d4d30 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.hpp @@ -201,6 +201,7 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with STORE_FLAG(has_thumbnail_document_id); STORE_FLAG(sticker_set->are_keywords_loaded_); STORE_FLAG(sticker_set->is_sticker_has_text_color_loaded_); + STORE_FLAG(sticker_set->has_text_color_); END_STORE_FLAGS(); store(sticker_set->id_.get(), storer); store(sticker_set->access_hash_, storer); @@ -264,6 +265,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser bool is_webm; bool is_emojis; bool has_thumbnail_document_id; + bool has_text_color; BEGIN_PARSE_FLAGS(); PARSE_FLAG(sticker_set->is_inited_); PARSE_FLAG(sticker_set->was_loaded_); @@ -284,6 +286,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser PARSE_FLAG(has_thumbnail_document_id); PARSE_FLAG(sticker_set->are_keywords_loaded_); PARSE_FLAG(sticker_set->is_sticker_has_text_color_loaded_); + PARSE_FLAG(has_text_color); END_PARSE_FLAGS(); int64 sticker_set_id; int64 access_hash; @@ -345,6 +348,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser sticker_set->is_official_ = is_official; sticker_set->sticker_type_ = sticker_type; sticker_set->sticker_format_ = sticker_format; + sticker_set->has_text_color_ = has_text_color; auto cleaned_username = clean_username(sticker_set->short_name_); if (!cleaned_username.empty()) { @@ -354,7 +358,7 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser } else { if (sticker_set->title_ != title || sticker_set->minithumbnail_ != minithumbnail || sticker_set->thumbnail_ != thumbnail || sticker_set->thumbnail_document_id_ != thumbnail_document_id || - sticker_set->is_official_ != is_official) { + sticker_set->is_official_ != is_official || sticker_set->has_text_color_ != has_text_color) { sticker_set->is_changed_ = true; } if (sticker_set->short_name_ != short_name) { @@ -401,6 +405,9 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser if (sticker->set_id_ != sticker_set->id_) { LOG_IF(ERROR, sticker->set_id_.is_valid()) << "Sticker " << sticker_id << " set_id has changed"; sticker->set_id_ = sticker_set->id_; + if (sticker->has_text_color_) { + sticker_set->has_text_color_ = true; + } } if (sticker->is_premium_) { sticker_set->premium_sticker_positions_.push_back(static_cast(sticker_set->sticker_ids_.size() - 1)); diff --git a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp index 8495e2f9..975cc218 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp @@ -14,7 +14,6 @@ #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Global.h" -#include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MediaArea.hpp" @@ -54,7 +53,6 @@ #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" -#include #include namespace td { @@ -745,190 +743,6 @@ class ActivateStealthModeQuery final : public Td::ResultHandler { } }; -class GetBoostsStatusQuery final : public Td::ResultHandler { - Promise> promise_; - DialogId dialog_id_; - - public: - explicit GetBoostsStatusQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(DialogId dialog_id) { - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); - CHECK(input_peer != nullptr); - send_query( - G()->net_query_creator().create(telegram_api::stories_getBoostsStatus(std::move(input_peer)), {{dialog_id}})); - } - - 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 result = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for GetBoostsStatusQuery: " << to_string(result); - if (result->level_ < 0 || result->current_level_boosts_ < 0 || result->boosts_ < result->current_level_boosts_ || - (result->next_level_boosts_ != 0 && result->boosts_ >= result->next_level_boosts_)) { - LOG(ERROR) << "Receive invalid " << to_string(result); - if (result->level_ < 0) { - result->level_ = 0; - } - if (result->current_level_boosts_ < 0) { - result->current_level_boosts_ = 0; - } - if (result->boosts_ < result->current_level_boosts_) { - result->boosts_ = result->current_level_boosts_; - } - if (result->next_level_boosts_ != 0 && result->boosts_ >= result->next_level_boosts_) { - result->next_level_boosts_ = result->boosts_ + 1; - } - } - int32 premium_member_count = 0; - double premium_member_percentage = 0.0; - if (result->premium_audience_ != nullptr) { - premium_member_count = max(0, static_cast(result->premium_audience_->part_)); - auto participant_count = max(static_cast(result->premium_audience_->total_), premium_member_count); - if (dialog_id_.get_type() == DialogType::Channel) { - td_->contacts_manager_->on_update_channel_participant_count(dialog_id_.get_channel_id(), participant_count); - } - if (participant_count > 0) { - premium_member_percentage = 100.0 * premium_member_count / participant_count; - } - } - promise_.set_value(td_api::make_object( - result->my_boost_, result->level_, result->boosts_, result->current_level_boosts_, result->next_level_boosts_, - premium_member_count, premium_member_percentage)); - } - - void on_error(Status status) final { - td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBoostsStatusQuery"); - promise_.set_error(std::move(status)); - } -}; - -class CanApplyBoostQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit CanApplyBoostQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(DialogId dialog_id) { - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read); - CHECK(input_peer != nullptr); - auto query = - G()->net_query_creator().create(telegram_api::stories_canApplyBoost(std::move(input_peer)), {{dialog_id}}); - query->total_timeout_limit_ = 4; - send_query(std::move(query)); - } - - 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 result = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for CanApplyBoostQuery: " << to_string(result); - promise_.set_value(td_->story_manager_->get_can_boost_chat_result_object(std::move(result))); - } - - void on_error(Status status) final { - auto result = td_->story_manager_->get_can_boost_chat_result_object(status); - if (result != nullptr) { - promise_.set_value(std::move(result)); - } else { - promise_.set_error(std::move(status)); - } - } -}; - -class ApplyBoostQuery final : public Td::ResultHandler { - Promise promise_; - DialogId dialog_id_; - - public: - explicit ApplyBoostQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(DialogId dialog_id) { - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); - CHECK(input_peer != nullptr); - send_query(G()->net_query_creator().create(telegram_api::stories_applyBoost(std::move(input_peer)), {{dialog_id}})); - } - - 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, "ApplyBoostQuery"); - promise_.set_error(std::move(status)); - } -}; - -class GetBoostersListQuery final : public Td::ResultHandler { - Promise> promise_; - DialogId dialog_id_; - - public: - explicit GetBoostersListQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(DialogId dialog_id, const string &offset, int32 limit) { - dialog_id_ = dialog_id; - auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); - CHECK(input_peer != nullptr); - send_query( - G()->net_query_creator().create(telegram_api::stories_getBoostersList(std::move(input_peer), offset, limit))); - } - - 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 result = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for GetBoostersListQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetBoostersListQuery"); - - auto total_count = result->count_; - vector> boosts; - for (auto &booster : result->boosters_) { - UserId user_id(booster->user_id_); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive " << to_string(booster); - continue; - } - auto expire_date = booster->expires_; - if (expire_date <= G()->unix_time()) { - continue; - } - boosts.push_back(td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "chatBoost"), expire_date)); - } - promise_.set_value( - td_api::make_object(total_count, std::move(boosts), result->next_offset_)); - } - - void on_error(Status status) final { - td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBoostersListQuery"); - promise_.set_error(std::move(status)); - } -}; - class GetChatsToSendStoriesQuery final : public Td::ResultHandler { Promise promise_; @@ -1500,12 +1314,17 @@ void StoryManager::start_up() { load_expired_database_stories(); for (auto story_list_id : {StoryListId::main(), StoryListId::archive()}) { - update_story_list_sent_total_count(story_list_id); + update_story_list_sent_total_count(story_list_id, "start_up"); } } void StoryManager::timeout_expired() { load_expired_database_stories(); + + if (channels_to_send_stories_inited_ && get_dialogs_to_send_stories_queries_.empty() && + Time::now() > next_reload_channels_to_send_stories_time_ && !td_->auth_manager_->is_bot()) { + reload_dialogs_to_send_stories(Auto()); + } } void StoryManager::hangup() { @@ -1575,21 +1394,21 @@ void StoryManager::on_story_expire_timeout(int64 story_global_id) { // timeout used monotonic time instead of wall clock time LOG(INFO) << "Receive timeout for non-expired " << story_full_id << ": expire_date = " << story->expire_date_ << ", current time = " << G()->unix_time(); - return on_story_changed(story_full_id, story, false, false); + return set_story_expire_timeout(story); } LOG(INFO) << "Have expired " << story_full_id; auto owner_dialog_id = story_full_id.get_dialog_id(); CHECK(owner_dialog_id.is_valid()); if (story->content_ != nullptr && !can_access_expired_story(owner_dialog_id, story)) { - on_delete_story(story_full_id); - } - - auto active_stories = get_active_stories(owner_dialog_id); - if (active_stories != nullptr && contains(active_stories->story_ids_, story_full_id.get_story_id())) { - auto story_ids = active_stories->story_ids_; - on_update_active_stories(owner_dialog_id, active_stories->max_read_story_id_, std::move(story_ids), Promise(), - "on_story_expire_timeout"); + on_delete_story(story_full_id); // also updates active stories + } else { + auto active_stories = get_active_stories(owner_dialog_id); + if (active_stories != nullptr && contains(active_stories->story_ids_, story_full_id.get_story_id())) { + auto story_ids = active_stories->story_ids_; + on_update_active_stories(owner_dialog_id, active_stories->max_read_story_id_, std::move(story_ids), + Promise(), "on_story_expire_timeout"); + } } } @@ -1615,13 +1434,13 @@ void StoryManager::on_story_can_get_viewers_timeout(int64 story_global_id) { } LOG(INFO) << "Have expired viewers in " << story_full_id; - if (can_get_story_viewers(story_full_id, story, true).is_ok()) { + if (has_unexpired_viewers(story_full_id, story)) { // timeout used monotonic time instead of wall clock time // also a reaction could have been added on the story LOG(INFO) << "Receive timeout for " << story_full_id << " with available viewers: expire_date = " << story->expire_date_ << ", current time = " << G()->unix_time(); - return on_story_changed(story_full_id, story, false, false); + return set_story_can_get_viewers_timeout(story); } // can_get_viewers flag could have been changed; reload the story to repair it @@ -1630,6 +1449,9 @@ void StoryManager::on_story_can_get_viewers_timeout(int64 story_global_id) { void StoryManager::load_expired_database_stories() { if (!G()->use_message_database()) { + if (!td_->auth_manager_->is_bot()) { + set_timeout_in(Random::fast(300, 420)); + } return; } @@ -1959,7 +1781,7 @@ StoryManager::ActiveStories *StoryManager::on_get_active_stories_from_database(S if (!story_list.is_reloaded_server_total_count_ && story_list.server_total_count_ > static_cast(story_list.ordered_stories_.size())) { story_list.server_total_count_--; - update_story_list_sent_total_count(story_list_id, story_list); + update_story_list_sent_total_count(story_list_id, story_list, "on_get_active_stories_from_database"); save_story_list(story_list_id, story_list.state_, story_list.server_total_count_, story_list.server_has_more_); } } @@ -2014,7 +1836,7 @@ void StoryManager::load_active_stories(StoryListId story_list_id, Promise ++it) { on_dialog_active_stories_order_updated(it->get_dialog_id(), "load_active_stories"); } - update_story_list_sent_total_count(story_list_id, story_list); + update_story_list_sent_total_count(story_list_id, story_list, "load_active_stories"); } return promise.set_error(Status::Error(404, "Not found")); } @@ -2037,11 +1859,19 @@ void StoryManager::on_load_active_stories_from_database(StoryListId story_list_i LOG(INFO) << "Load " << active_story_list.active_stories_.size() << " chats with active stories in " << story_list_id << " from database"; + bool is_bad = false; + FlatHashSet owner_dialog_ids; Dependencies dependencies; for (auto &active_stories_it : active_story_list.active_stories_) { - dependencies.add_dialog_and_dependencies(active_stories_it.first); + auto owner_dialog_id = active_stories_it.first; + if (owner_dialog_id.is_valid()) { + dependencies.add_dialog_and_dependencies(owner_dialog_id); + owner_dialog_ids.insert(owner_dialog_id); + } else { + is_bad = true; + } } - if (!dependencies.resolve_force(td_, "on_load_active_stories_from_database")) { + if (is_bad || !dependencies.resolve_force(td_, "on_load_active_stories_from_database")) { active_story_list.active_stories_.clear(); story_list.state_.clear(); story_list.server_has_more_ = true; @@ -2062,13 +1892,11 @@ void StoryManager::on_load_active_stories_from_database(StoryListId story_list_i if (story_list.list_last_story_date_ < max_story_date) { auto min_story_date = story_list.list_last_story_date_; story_list.list_last_story_date_ = max_story_date; - const auto &owner_dialog_ids = dependencies.get_dialog_ids(); for (auto it = story_list.ordered_stories_.upper_bound(min_story_date); it != story_list.ordered_stories_.end() && *it <= max_story_date; ++it) { auto dialog_id = it->get_dialog_id(); - if (owner_dialog_ids.count(dialog_id) == 0) { - on_dialog_active_stories_order_updated(dialog_id, "on_load_active_stories_from_database 1"); - } + owner_dialog_ids.erase(dialog_id); + on_dialog_active_stories_order_updated(dialog_id, "on_load_active_stories_from_database 1"); } for (auto owner_dialog_id : owner_dialog_ids) { on_dialog_active_stories_order_updated(owner_dialog_id, "on_load_active_stories_from_database 2"); @@ -2077,7 +1905,7 @@ void StoryManager::on_load_active_stories_from_database(StoryListId story_list_i } else { LOG(ERROR) << "Last database story date didn't increase"; } - update_story_list_sent_total_count(story_list_id, story_list); + update_story_list_sent_total_count(story_list_id, story_list, "on_load_active_stories_from_database"); } set_promises(promises); @@ -2205,7 +2033,7 @@ void StoryManager::on_load_active_stories_from_server( "on_load_active_stories_from_server"); load_dialog_expiring_stories(dialog_id, 0, "on_load_active_stories_from_server 1"); } - update_story_list_sent_total_count(story_list_id, story_list); + update_story_list_sent_total_count(story_list_id, story_list, "on_load_active_stories_from_server"); lock.set_value(Unit()); @@ -2251,18 +2079,19 @@ td_api::object_ptr StoryManager::get_update_st story_list.sent_total_count_); } -void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id) { +void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id, const char *source) { if (td_->auth_manager_->is_bot()) { return; } - update_story_list_sent_total_count(story_list_id, get_story_list(story_list_id)); + update_story_list_sent_total_count(story_list_id, get_story_list(story_list_id), source); } -void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id, StoryList &story_list) { +void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id, StoryList &story_list, + const char *source) { if (story_list.server_total_count_ == -1 || td_->auth_manager_->is_bot()) { return; } - LOG(INFO) << "Update story list sent total chat count in " << story_list_id; + LOG(INFO) << "Update story list sent total chat count in " << story_list_id << " from " << source; auto new_total_count = static_cast(story_list.ordered_stories_.size()); auto yet_unsent_total_count = 0; for (const auto &it : yet_unsent_story_ids_) { @@ -2529,6 +2358,7 @@ void StoryManager::on_get_dialog_expiring_stories(DialogId owner_dialog_id, td_->contacts_manager_->on_get_chats(std::move(stories->chats_), "on_get_dialog_expiring_stories"); owner_dialog_id = on_get_dialog_stories(owner_dialog_id, std::move(stories->stories_), Promise()); if (promise) { + CHECK(owner_dialog_id.is_valid()); auto active_stories = get_active_stories(owner_dialog_id); if (updated_active_stories_.insert(owner_dialog_id)) { send_update_chat_active_stories(owner_dialog_id, active_stories, "on_get_dialog_expiring_stories"); @@ -2675,8 +2505,7 @@ bool StoryManager::has_suggested_reaction(const Story *story, const ReactionType return false; } CHECK(story != nullptr); - return std::any_of(story->areas_.begin(), story->areas_.end(), - [&reaction_type](const MediaArea &area) { return area.has_reaction_type(reaction_type); }); + return any_of(story->areas_, [&reaction_type](const auto &area) { return area.has_reaction_type(reaction_type); }); } bool StoryManager::can_use_story_reaction(const Story *story, const ReactionType &reaction_type) const { @@ -2887,7 +2716,7 @@ void StoryManager::read_stories_on_server(DialogId owner_dialog_id, StoryId stor td_->create_handler(get_erase_log_event_promise(log_event_id))->send(owner_dialog_id, story_id); } -Status StoryManager::can_get_story_viewers(StoryFullId story_full_id, const Story *story, bool ignore_premium) const { +Status StoryManager::can_get_story_viewers(StoryFullId story_full_id, const Story *story, int32 unix_time) const { CHECK(story != nullptr); if (!is_my_story(story_full_id.get_dialog_id())) { return Status::Error(400, "Story must be outgoing"); @@ -2898,13 +2727,18 @@ Status StoryManager::can_get_story_viewers(StoryFullId story_full_id, const Stor if (story->interaction_info_.get_reaction_count() > 0) { return Status::OK(); } - if (G()->unix_time() >= get_story_viewers_expire_date(story) && - (ignore_premium || story->interaction_info_.has_hidden_viewers())) { + if (story->interaction_info_.has_hidden_viewers() && unix_time >= get_story_viewers_expire_date(story)) { return Status::Error(400, "Story is too old"); } return Status::OK(); } +bool StoryManager::has_unexpired_viewers(StoryFullId story_full_id, const Story *story) const { + CHECK(story != nullptr); + return is_my_story(story_full_id.get_dialog_id()) && story_full_id.get_story_id().is_server() && + G()->unix_time() < get_story_viewers_expire_date(story); +} + void StoryManager::get_story_viewers(StoryId story_id, const string &query, bool only_contacts, bool prefer_with_reaction, const string &offset, int32 limit, Promise> &&promise) { @@ -2917,7 +2751,8 @@ void StoryManager::get_story_viewers(StoryId story_id, const string &query, bool if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } - if (can_get_story_viewers(story_full_id, story, false).is_error() || story->interaction_info_.get_view_count() == 0) { + if (can_get_story_viewers(story_full_id, story, G()->unix_time()).is_error() || + story->interaction_info_.get_view_count() == 0) { return promise.set_value(td_api::make_object()); } @@ -3005,148 +2840,6 @@ void StoryManager::activate_stealth_mode(Promise &&promise) { td_->create_handler(std::move(promise))->send(); } -void StoryManager::get_dialog_boost_status(DialogId dialog_id, - Promise> &&promise) { - if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - 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")); - } - - td_->create_handler(std::move(promise))->send(dialog_id); -} - -void StoryManager::can_boost_dialog(DialogId dialog_id, - Promise> &&promise) { - if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - 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")); - } - - td_->create_handler(std::move(promise))->send(dialog_id); -} - -td_api::object_ptr StoryManager::get_can_boost_chat_result_object( - telegram_api::object_ptr &&result) const { - CHECK(result != nullptr); - switch (result->get_id()) { - case telegram_api::stories_canApplyBoostOk::ID: - return td_api::make_object(0); - case telegram_api::stories_canApplyBoostReplace::ID: { - auto replace = telegram_api::move_object_as(result); - td_->contacts_manager_->on_get_chats(std::move(replace->chats_), "get_can_boost_chat_result_object"); - DialogId currently_boosted_dialog_id(replace->current_boost_); - td_->messages_manager_->force_create_dialog(currently_boosted_dialog_id, "get_can_boost_chat_result_object"); - return td_api::make_object( - td_->messages_manager_->get_chat_id_object(currently_boosted_dialog_id, "get_can_boost_chat_result_object")); - } - default: - UNREACHABLE(); - return nullptr; - } -} - -td_api::object_ptr StoryManager::get_can_boost_chat_result_object( - const Status &error) const { - CHECK(error.is_error()); - if (error.message() == "PREMIUM_ACCOUNT_REQUIRED") { - return td_api::make_object(); - } - if (error.message() == "PREMIUM_GIFTED_NOT_ALLOWED") { - return td_api::make_object(); - } - if (error.message() == "BOOST_NOT_MODIFIED") { - return td_api::make_object(); - } - if (error.message() == "PEER_ID_INVALID") { - return td_api::make_object(); - } - auto retry_after = Global::get_retry_after(error.code(), error.message()); - if (retry_after > 0) { - return td_api::make_object(retry_after); - } - return nullptr; -} - -void StoryManager::boost_dialog(DialogId dialog_id, Promise &&promise) { - if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - 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")); - } - - td_->create_handler(std::move(promise))->send(dialog_id); -} - -Result> StoryManager::get_dialog_boost_link(DialogId dialog_id) { - if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } - if (dialog_id.get_type() != DialogType::Channel || - !td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { - return Status::Error(400, "Can't boost the chat"); - } - - SliceBuilder sb; - sb << LinkManager::get_t_me_url(); - - auto username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); - bool is_public = !username.empty(); - if (is_public) { - sb << username; - } else { - sb << "c/" << dialog_id.get_channel_id().get(); - } - sb << "?boost"; - - return std::make_pair(sb.as_cslice().str(), is_public); -} - -void StoryManager::get_dialog_boost_link_info(Slice url, Promise &&promise) { - auto r_dialog_boost_link_info = LinkManager::get_dialog_boost_link_info(url); - if (r_dialog_boost_link_info.is_error()) { - return promise.set_error(Status::Error(400, r_dialog_boost_link_info.error().message())); - } - - auto info = r_dialog_boost_link_info.move_as_ok(); - auto query_promise = PromiseCreator::lambda( - [info, promise = std::move(promise)](Result &&result) mutable { promise.set_value(std::move(info)); }); - td_->messages_manager_->resolve_dialog(info.username, info.channel_id, std::move(query_promise)); -} - -td_api::object_ptr StoryManager::get_chat_boost_link_info_object( - const DialogBoostLinkInfo &info) const { - CHECK(info.username.empty() == info.channel_id.is_valid()); - - bool is_public = !info.username.empty(); - DialogId dialog_id = - is_public ? td_->messages_manager_->resolve_dialog_username(info.username) : DialogId(info.channel_id); - return td_api::make_object( - is_public, td_->messages_manager_->get_chat_id_object(dialog_id, "chatBoostLinkInfo")); -} - -void StoryManager::get_dialog_boosts(DialogId dialog_id, const string &offset, int32 limit, - Promise> &&promise) { - if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - 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 (limit <= 0) { - return promise.set_error(Status::Error(400, "Parameter limit must be positive")); - } - - td_->create_handler(std::move(promise))->send(dialog_id, offset, limit); -} - bool StoryManager::have_story(StoryFullId story_full_id) const { return get_story(story_full_id) != nullptr; } @@ -3201,7 +2894,12 @@ void StoryManager::unregister_story(StoryFullId story_full_id, MessageFullId mes StoryManager::StoryInfo StoryManager::get_story_info(StoryFullId story_full_id) const { const auto *story = get_story(story_full_id); auto story_id = story_full_id.get_story_id(); - if (story == nullptr || (story_id.is_server() && !is_active_story(story))) { + if (story == nullptr) { + LOG(INFO) << "Tried to get info about deleted " << story_full_id; + return {}; + } + if (story_id.is_server() && !is_active_story(story)) { + LOG(INFO) << "Tried to get info about expired " << story_full_id; return {}; } StoryInfo story_info; @@ -3283,11 +2981,11 @@ td_api::object_ptr StoryManager::get_story_object(StoryFullId sto bool can_be_replied = story_id.is_server() && owner_dialog_id != changelog_dialog_id && owner_dialog_id.get_type() == DialogType::User; bool can_toggle_is_pinned = can_toggle_story_is_pinned(story_full_id, story); - bool can_get_viewers = can_get_story_viewers(story_full_id, story, false).is_ok(); + auto unix_time = G()->unix_time(); + bool can_get_viewers = can_get_story_viewers(story_full_id, story, unix_time).is_ok(); auto interaction_info = story->interaction_info_.get_story_interaction_info_object(td_); bool has_expired_viewers = is_my_story(owner_dialog_id) && story_id.is_server() && - G()->unix_time_cached() >= get_story_viewers_expire_date(story) && - interaction_info != nullptr && + unix_time >= get_story_viewers_expire_date(story) && interaction_info != nullptr && interaction_info->view_count_ > interaction_info->reaction_count_; const auto &reaction_counts = story->interaction_info_.get_reaction_counts(); auto story_areas = transform(*areas, [&reaction_counts](const MediaArea &media_area) { @@ -3330,6 +3028,9 @@ td_api::object_ptr StoryManager::get_chat_active_stor stories.push_back(std::move(story_info)); } } + if (stories.size() != active_stories->story_ids_.size()) { + send_closure_later(G()->story_manager(), &StoryManager::update_active_stories, owner_dialog_id); + } if (story_list_id.is_valid()) { order = active_stories->public_order_; } @@ -3746,19 +3447,27 @@ void StoryManager::delete_story_from_database(StoryFullId story_full_id) { } } +void StoryManager::set_story_expire_timeout(const Story *story) { + CHECK(story->global_id_ > 0); + story_expire_timeout_.set_timeout_in(story->global_id_, story->expire_date_ - G()->unix_time()); +} + +void StoryManager::set_story_can_get_viewers_timeout(const Story *story) { + CHECK(story->global_id_ > 0); + story_can_get_viewers_timeout_.set_timeout_in(story->global_id_, + get_story_viewers_expire_date(story) - G()->unix_time() + 2); +} + void StoryManager::on_story_changed(StoryFullId story_full_id, const Story *story, bool is_changed, bool need_save_to_database, bool from_database) { if (!story_full_id.get_story_id().is_server()) { return; } if (is_active_story(story)) { - CHECK(story->global_id_ > 0); - story_expire_timeout_.set_timeout_in(story->global_id_, story->expire_date_ - G()->unix_time()); + set_story_expire_timeout(story); } - if (can_get_story_viewers(story_full_id, story, true).is_ok() && story->interaction_info_.get_reaction_count() == 0) { - CHECK(story->global_id_ > 0); - story_can_get_viewers_timeout_.set_timeout_in(story->global_id_, - get_story_viewers_expire_date(story) - G()->unix_time() + 2); + if (has_unexpired_viewers(story_full_id, story)) { + set_story_can_get_viewers_timeout(story); } if (story->content_ == nullptr) { return; @@ -3984,12 +3693,22 @@ void StoryManager::on_update_dialog_stories_hidden(DialogId owner_dialog_id, boo } } +void StoryManager::update_active_stories(DialogId owner_dialog_id) { + auto active_stories = get_active_stories(owner_dialog_id); + if (active_stories != nullptr) { + auto story_ids = active_stories->story_ids_; + on_update_active_stories(owner_dialog_id, active_stories->max_read_story_id_, std::move(story_ids), Promise(), + "update_active_stories"); + } +} + void StoryManager::on_update_active_stories(DialogId owner_dialog_id, StoryId max_read_story_id, vector &&story_ids, Promise &&promise, const char *source, bool from_database) { CHECK(owner_dialog_id.is_valid()); if (td::remove_if(story_ids, [&](StoryId story_id) { if (!story_id.is_server()) { + CHECK(!from_database); return true; } if (!is_active_story(get_story({owner_dialog_id, story_id}))) { @@ -4023,7 +3742,7 @@ void StoryManager::on_update_active_stories(DialogId owner_dialog_id, StoryId ma save_story_list(active_stories->story_list_id_, story_list.state_, story_list.server_total_count_, story_list.server_has_more_); } - update_story_list_sent_total_count(active_stories->story_list_id_, story_list); + update_story_list_sent_total_count(active_stories->story_list_id_, story_list, "on_update_active_stories"); } active_stories_.erase(owner_dialog_id); send_update_chat_active_stories(owner_dialog_id, nullptr, "on_update_active_stories 1"); @@ -4116,13 +3835,13 @@ bool StoryManager::update_active_stories_order(DialogId owner_dialog_id, ActiveS CHECK(is_inserted); if (active_stories->story_list_id_ != story_list_id && active_stories->story_list_id_.is_valid()) { - update_story_list_sent_total_count(active_stories->story_list_id_); + update_story_list_sent_total_count(active_stories->story_list_id_, "update_active_stories_order 1"); } - update_story_list_sent_total_count(story_list_id, story_list); + update_story_list_sent_total_count(story_list_id, story_list, "update_active_stories_order 2"); } } else if (active_stories->story_list_id_.is_valid()) { delete_active_stories_from_story_list(owner_dialog_id, active_stories); - update_story_list_sent_total_count(active_stories->story_list_id_); + update_story_list_sent_total_count(active_stories->story_list_id_, "update_active_stories_order 3"); } if (active_stories->private_order_ != new_private_order || active_stories->public_order_ != new_public_order || @@ -4183,6 +3902,7 @@ void StoryManager::send_update_chat_active_stories(DialogId owner_dialog_id, con LOG(INFO) << "Skip update about active stories in " << owner_dialog_id << " from " << source; return; } + CHECK(owner_dialog_id.is_valid()); updated_active_stories_.insert(owner_dialog_id); } LOG(INFO) << "Send update about active stories in " << owner_dialog_id << " from " << source; @@ -4198,7 +3918,8 @@ void StoryManager::save_active_stories(DialogId owner_dialog_id, const ActiveSto LOG(INFO) << "Delete active stories of " << owner_dialog_id << " from database from " << source; G()->td_db()->get_story_db_async()->delete_active_stories(owner_dialog_id, std::move(promise)); } else { - LOG(INFO) << "Add active stories of " << owner_dialog_id << " to database from " << source; + LOG(INFO) << "Add " << active_stories->story_ids_.size() << " active stories of " << owner_dialog_id + << " to database from " << source; auto order = active_stories->story_list_id_.is_valid() ? active_stories->private_order_ : 0; SavedActiveStories saved_active_stories; saved_active_stories.max_read_story_id_ = active_stories->max_read_story_id_; @@ -4208,8 +3929,16 @@ void StoryManager::save_active_stories(DialogId owner_dialog_id, const ActiveSto saved_active_stories.story_infos_.push_back(std::move(story_info)); } } - G()->td_db()->get_story_db_async()->add_active_stories(owner_dialog_id, active_stories->story_list_id_, order, - log_event_store(saved_active_stories), std::move(promise)); + if (saved_active_stories.story_infos_.size() != active_stories->story_ids_.size()) { + send_closure_later(G()->story_manager(), &StoryManager::update_active_stories, owner_dialog_id); + } + if (saved_active_stories.story_infos_.empty()) { + LOG(INFO) << "Have no active stories to save"; + G()->td_db()->get_story_db_async()->delete_active_stories(owner_dialog_id, std::move(promise)); + } else { + G()->td_db()->get_story_db_async()->add_active_stories(owner_dialog_id, active_stories->story_list_id_, order, + log_event_store(saved_active_stories), std::move(promise)); + } } } @@ -4606,8 +4335,7 @@ void StoryManager::get_dialogs_to_send_stories(Promisetd_db()->get_binlog_pmc()->erase(pmc_key); } else { @@ -4657,6 +4385,8 @@ void StoryManager::finish_get_dialogs_to_send_stories(Result &&result) { return fail_promises(promises, result.move_as_error()); } + next_reload_channels_to_send_stories_time_ = Time::now() + 86400; + CHECK(channels_to_send_stories_inited_); for (auto &promise : promises) { return_dialogs_to_send_stories(std::move(promise), channels_to_send_stories_); @@ -4816,6 +4546,7 @@ void StoryManager::do_send_story(unique_ptr &&pending_story, vecto CHECK(pending_story->story_id_.is_valid()); CHECK(pending_story->story_ != nullptr); CHECK(pending_story->story_->content_ != nullptr); + CHECK(pending_story->story_id_.is_valid()); auto story_full_id = StoryFullId(pending_story->dialog_id_, pending_story->story_id_); if (bad_parts.empty()) { @@ -4845,7 +4576,7 @@ void StoryManager::do_send_story(unique_ptr &&pending_story, vecto updated_active_stories_.insert(pending_story->dialog_id_); send_update_chat_active_stories(pending_story->dialog_id_, active_stories, "do_send_story"); - update_story_list_sent_total_count(StoryListId::main()); + update_story_list_sent_total_count(StoryListId::main(), "do_send_story"); } else { pending_story->story_->content_ = dup_story_content(td_, pending_story->story_->content_.get()); } @@ -5237,7 +4968,7 @@ void StoryManager::delete_pending_story(FileId file_id, unique_ptr if (it->second.empty()) { yet_unsent_stories_.erase(it); yet_unsent_story_ids_.erase(pending_story->dialog_id_); - update_story_list_sent_total_count(StoryListId::main()); + update_story_list_sent_total_count(StoryListId::main(), "delete_pending_story"); } else { auto story_id_it = yet_unsent_story_ids_.find(pending_story->dialog_id_); CHECK(story_id_it != yet_unsent_story_ids_.end()); @@ -5323,6 +5054,7 @@ void StoryManager::delete_story(DialogId owner_dialog_id, StoryId story_id, Prom return promise.set_error(Status::Error(400, "Story not found")); } int64 random_id = random_id_it->second; + CHECK(random_id != 0); LOG(INFO) << "Cancel uploading of " << story_full_id; diff --git a/lib/tgchat/ext/td/td/telegram/StoryManager.h b/lib/tgchat/ext/td/td/telegram/StoryManager.h index aa1f7a2f..4ed8e8c5 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryManager.h +++ b/lib/tgchat/ext/td/td/telegram/StoryManager.h @@ -7,7 +7,6 @@ #pragma once #include "td/telegram/ChannelId.h" -#include "td/telegram/DialogBoostLinkInfo.h" #include "td/telegram/DialogDate.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" @@ -36,7 +35,6 @@ #include "td/utils/FlatHashMap.h" #include "td/utils/FlatHashSet.h" #include "td/utils/Promise.h" -#include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/WaitFreeHashMap.h" #include "td/utils/WaitFreeHashSet.h" @@ -269,26 +267,6 @@ class StoryManager final : public Actor { void activate_stealth_mode(Promise &&promise); - void get_dialog_boost_status(DialogId dialog_id, Promise> &&promise); - - void can_boost_dialog(DialogId dialog_id, Promise> &&promise); - - td_api::object_ptr get_can_boost_chat_result_object( - telegram_api::object_ptr &&result) const; - - td_api::object_ptr get_can_boost_chat_result_object(const Status &error) const; - - void boost_dialog(DialogId dialog_id, Promise &&promise); - - Result> get_dialog_boost_link(DialogId dialog_id); - - void get_dialog_boost_link_info(Slice url, Promise &&promise); - - td_api::object_ptr get_chat_boost_link_info_object(const DialogBoostLinkInfo &info) const; - - void get_dialog_boosts(DialogId dialog_id, const string &offset, int32 limit, - Promise> &&promise); - void remove_story_notifications_by_story_ids(DialogId dialog_id, const vector &story_ids); StoryId on_get_story(DialogId owner_dialog_id, telegram_api::object_ptr &&story_item_ptr); @@ -313,7 +291,9 @@ class StoryManager final : public Actor { void on_dialog_active_stories_order_updated(DialogId owner_dialog_id, const char *source); - Status can_get_story_viewers(StoryFullId story_full_id, const Story *story, bool ignore_premium) const; + Status can_get_story_viewers(StoryFullId story_full_id, const Story *story, int32 unix_time) const; + + bool has_unexpired_viewers(StoryFullId story_full_id, const Story *story) const; void on_get_story_views(DialogId owner_dialog_id, const vector &story_ids, telegram_api::object_ptr &&story_views); @@ -435,6 +415,10 @@ class StoryManager final : public Actor { ActiveStories *on_get_active_stories_from_database(StoryListId story_list_id, DialogId owner_dialog_id, const BufferSlice &value, const char *source); + void set_story_expire_timeout(const Story *story); + + void set_story_can_get_viewers_timeout(const Story *story); + void on_story_changed(StoryFullId story_full_id, const Story *story, bool is_changed, bool need_save_to_database, bool from_database = false); @@ -505,9 +489,9 @@ class StoryManager final : public Actor { td_api::object_ptr get_update_story_list_chat_count_object( StoryListId story_list_id, const StoryList &story_list) const; - void update_story_list_sent_total_count(StoryListId story_list_id); + void update_story_list_sent_total_count(StoryListId story_list_id, const char *source); - void update_story_list_sent_total_count(StoryListId story_list_id, StoryList &story_list); + void update_story_list_sent_total_count(StoryListId story_list_id, StoryList &story_list, const char *source); vector get_story_file_ids(const Story *story) const; @@ -551,6 +535,8 @@ class StoryManager final : public Actor { void on_update_dialog_has_pinned_stories(DialogId owner_dialog_id, bool has_pinned_stories); + void update_active_stories(DialogId owner_dialog_id); + void on_update_active_stories(DialogId owner_dialog_id, StoryId max_read_story_id, vector &&story_ids, Promise &&promise, const char *source, bool from_database = false); @@ -675,6 +661,7 @@ class StoryManager final : public Actor { bool channels_to_send_stories_inited_ = false; vector channels_to_send_stories_; vector>> get_dialogs_to_send_stories_queries_; + double next_reload_channels_to_send_stories_time_ = 0.0; FlatHashMap being_set_story_reactions_; diff --git a/lib/tgchat/ext/td/td/telegram/Td.cpp b/lib/tgchat/ext/td/td/telegram/Td.cpp index cce1fbdf..259d3d80 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.cpp +++ b/lib/tgchat/ext/td/td/telegram/Td.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/Td.h" +#include "td/telegram/AccentColorId.h" #include "td/telegram/AccountManager.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/Application.h" @@ -17,6 +18,7 @@ #include "td/telegram/BackgroundId.h" #include "td/telegram/BackgroundManager.h" #include "td/telegram/BackgroundType.h" +#include "td/telegram/BoostManager.h" #include "td/telegram/BotCommand.h" #include "td/telegram/BotInfoManager.h" #include "td/telegram/BotMenuButton.h" @@ -123,6 +125,7 @@ #include "td/telegram/StateManager.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickerFormat.h" +#include "td/telegram/StickerListType.h" #include "td/telegram/StickerSetId.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickerType.h" @@ -1140,7 +1143,7 @@ class GetDialogBoostLinkInfoRequest final : public RequestActorstory_manager_->get_dialog_boost_link_info(url_, std::move(promise)); + td_->boost_manager_->get_dialog_boost_link_info(url_, std::move(promise)); } void do_set_result(DialogBoostLinkInfo &&result) final { @@ -1148,7 +1151,7 @@ class GetDialogBoostLinkInfoRequest final : public RequestActorstory_manager_->get_chat_boost_link_info_object(dialog_boost_link_info_)); + send_result(td_->boost_manager_->get_chat_boost_link_info_object(dialog_boost_link_info_)); } public: @@ -2848,6 +2851,7 @@ bool Td::is_authentication_request(int32 id) { bool Td::is_synchronous_request(const td_api::Function *function) { switch (function->get_id()) { + case td_api::searchQuote::ID: case td_api::getTextEntities::ID: case td_api::parseTextEntities::ID: case td_api::parseMarkdown::ID: @@ -3271,6 +3275,8 @@ void Td::dec_actor_refcnt() { LOG(DEBUG) << "AutosaveManager was cleared" << timer; background_manager_.reset(); LOG(DEBUG) << "BackgroundManager was cleared" << timer; + boost_manager_.reset(); + LOG(DEBUG) << "BoostManager was cleared" << timer; bot_info_manager_.reset(); LOG(DEBUG) << "BotInfoManager was cleared" << timer; callback_queries_manager_.reset(); @@ -3340,12 +3346,9 @@ void Td::dec_actor_refcnt() { option_manager_.reset(); LOG(DEBUG) << "OptionManager was cleared" << timer; - Promise<> promise = PromiseCreator::lambda([actor_id = create_reference()](Unit) mutable { actor_id.reset(); }); - if (destroy_flag_) { - G()->close_and_destroy_all(std::move(promise)); - } else { - G()->close_all(std::move(promise)); - } + G()->close_all(destroy_flag_, + PromiseCreator::lambda([actor_id = create_reference()](Unit) mutable { actor_id.reset(); })); + // NetQueryDispatcher will be closed automatically close_flag_ = 4; } else if (close_flag_ == 4) { @@ -3483,6 +3486,8 @@ void Td::clear() { LOG(DEBUG) << "AutosaveManager actor was cleared" << timer; background_manager_actor_.reset(); LOG(DEBUG) << "BackgroundManager actor was cleared" << timer; + boost_manager_actor_.reset(); + LOG(DEBUG) << "BoostManager actor was cleared" << timer; bot_info_manager_actor_.reset(); LOG(DEBUG) << "BotInfoManager actor was cleared" << timer; contacts_manager_actor_.reset(); @@ -3783,7 +3788,7 @@ void Td::init(Parameters parameters, Result r_opened_datab // // 3. During replay of binlog some queries may be sent to other actors. They shouldn't process such events before all // their binlog events are processed. So actor may receive some old queries. It must be in its actual state in - // orded to handle them properly. + // order to handle them properly. // // -- Use send_closure_later, so actors don't even start process binlog events, before all binlog events are sent @@ -3968,6 +3973,9 @@ void Td::init_managers() { background_manager_ = make_unique(this, create_reference()); background_manager_actor_ = register_actor("BackgroundManager", background_manager_.get()); G()->set_background_manager(background_manager_actor_.get()); + boost_manager_ = make_unique(this, create_reference()); + boost_manager_actor_ = register_actor("BoostManager", boost_manager_.get()); + G()->set_boost_manager(boost_manager_actor_.get()); bot_info_manager_ = make_unique(this, create_reference()); bot_info_manager_actor_ = register_actor("BotInfoManager", bot_info_manager_.get()); contacts_manager_ = make_unique(this, create_reference()); @@ -5073,7 +5081,7 @@ void Td::on_request(uint64 id, td_api::setAutosaveSettings &request) { void Td::on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - autosave_manager_->clear_autosave_settings_excpetions(std::move(promise)); + autosave_manager_->clear_autosave_settings_exceptions(std::move(promise)); } void Td::on_request(uint64 id, const td_api::getTopChats &request) { @@ -5475,8 +5483,8 @@ void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); - messages_manager_->get_message_public_forwards({DialogId(request.chat_id_), MessageId(request.message_id_)}, - std::move(request.offset_), request.limit_, std::move(promise)); + statistics_manager_->get_message_public_forwards({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(request.offset_), request.limit_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::removeNotification &request) { @@ -5576,7 +5584,7 @@ void Td::on_request(uint64 id, td_api::sendMessage &request) { void Td::on_request(uint64 id, td_api::sendMessageAlbum &request) { auto r_messages = messages_manager_->send_message_group( DialogId(request.chat_id_), MessageId(request.message_thread_id_), std::move(request.reply_to_), - std::move(request.options_), std::move(request.input_message_contents_), request.only_preview_); + std::move(request.options_), std::move(request.input_message_contents_)); if (r_messages.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error()); } else { @@ -5604,18 +5612,14 @@ void Td::on_request(uint64 id, td_api::sendInlineQueryResultMessage &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.result_id_); - DialogId dialog_id(request.chat_id_); - auto r_new_message_id = messages_manager_->send_inline_query_result_message( - dialog_id, MessageId(request.message_thread_id_), std::move(request.reply_to_), std::move(request.options_), - request.query_id_, request.result_id_, request.hide_via_bot_); - if (r_new_message_id.is_error()) { - return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error()); + auto r_sent_message = messages_manager_->send_inline_query_result_message( + DialogId(request.chat_id_), MessageId(request.message_thread_id_), std::move(request.reply_to_), + std::move(request.options_), request.query_id_, request.result_id_, request.hide_via_bot_); + if (r_sent_message.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, r_sent_message.move_as_ok()); } - - CHECK(r_new_message_id.ok().is_valid() || r_new_message_id.ok().is_valid_scheduled()); - send_closure( - actor_id(this), &Td::send_result, id, - messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}, "sendInlineQueryResultMessage")); } void Td::on_request(uint64 id, td_api::addLocalMessage &request) { @@ -5899,8 +5903,7 @@ void Td::on_request(uint64 id, td_api::forwardMessages &request) { MessageId) { return MessageCopyOptions(send_copy, remove_caption); }); auto r_messages = messages_manager_->forward_messages( DialogId(request.chat_id_), MessageId(request.message_thread_id_), DialogId(request.from_chat_id_), - std::move(input_message_ids), std::move(request.options_), false, std::move(message_copy_options), - request.only_preview_); + std::move(input_message_ids), std::move(request.options_), false, std::move(message_copy_options)); if (r_messages.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error()); } else { @@ -5908,9 +5911,10 @@ void Td::on_request(uint64 id, td_api::forwardMessages &request) { } } -void Td::on_request(uint64 id, const td_api::resendMessages &request) { +void Td::on_request(uint64 id, td_api::resendMessages &request) { DialogId dialog_id(request.chat_id_); - auto r_message_ids = messages_manager_->resend_messages(dialog_id, MessageId::get_message_ids(request.message_ids_)); + auto r_message_ids = messages_manager_->resend_messages(dialog_id, MessageId::get_message_ids(request.message_ids_), + std::move(request.quote_)); if (r_message_ids.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_message_ids.move_as_error()); } @@ -5922,7 +5926,8 @@ void Td::on_request(uint64 id, const td_api::resendMessages &request) { void Td::on_request(uint64 id, td_api::getWebPagePreview &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - web_pages_manager_->get_web_page_preview(std::move(request.text_), std::move(promise)); + web_pages_manager_->get_web_page_preview(std::move(request.text_), std::move(request.link_preview_options_), + std::move(promise)); } void Td::on_request(uint64 id, td_api::getWebPageInstantView &request) { @@ -6467,7 +6472,15 @@ void Td::on_request(uint64 id, const td_api::setChatPhoto &request) { messages_manager_->set_dialog_photo(DialogId(request.chat_id_), request.photo_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setChatAccentColor &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->set_dialog_accent_color(DialogId(request.chat_id_), AccentColorId(request.accent_color_id_), + CustomEmojiId(request.background_custom_emoji_id_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::setChatMessageAutoDeleteTime &request) { + CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); messages_manager_->set_dialog_message_ttl(DialogId(request.chat_id_), request.message_auto_delete_time_, std::move(promise)); @@ -6623,26 +6636,26 @@ void Td::on_request(uint64 id, const td_api::activateStoryStealthMode &request) story_manager_->activate_stealth_mode(std::move(promise)); } -void Td::on_request(uint64 id, const td_api::getChatBoostStatus &request) { +void Td::on_request(uint64 id, const td_api::getAvailableChatBoostSlots &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - story_manager_->get_dialog_boost_status(DialogId(request.chat_id_), std::move(promise)); + boost_manager_->get_boost_slots(std::move(promise)); } -void Td::on_request(uint64 id, const td_api::canBoostChat &request) { +void Td::on_request(uint64 id, const td_api::getChatBoostStatus &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - story_manager_->can_boost_dialog(DialogId(request.chat_id_), std::move(promise)); + boost_manager_->get_dialog_boost_status(DialogId(request.chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::boostChat &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); - story_manager_->boost_dialog(DialogId(request.chat_id_), std::move(promise)); + CREATE_REQUEST_PROMISE(); + boost_manager_->boost_dialog(DialogId(request.chat_id_), std::move(request.slot_ids_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getChatBoostLink &request) { - auto r_boost_link = story_manager_->get_dialog_boost_link(DialogId(request.chat_id_)); + auto r_boost_link = boost_manager_->get_dialog_boost_link(DialogId(request.chat_id_)); if (r_boost_link.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_boost_link.move_as_error()); } else { @@ -6660,7 +6673,14 @@ void Td::on_request(uint64 id, td_api::getChatBoosts &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); - story_manager_->get_dialog_boosts(DialogId(request.chat_id_), request.offset_, request.limit_, std::move(promise)); + boost_manager_->get_dialog_boosts(DialogId(request.chat_id_), request.only_gift_codes_, request.offset_, + request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getUserChatBoosts &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + boost_manager_->get_user_dialog_boosts(DialogId(request.chat_id_), UserId(request.user_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getAttachmentMenuBot &request) { @@ -6834,7 +6854,7 @@ void Td::on_request(uint64 id, td_api::searchChatMembers &request) { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { - promise.set_value(result.ok().get_chat_members_object(td)); + promise.set_value(result.ok().get_chat_members_object(td, "searchChatMembers")); } }); contacts_manager_->search_dialog_participants(DialogId(request.chat_id_), request.query_, request.limit_, @@ -7563,6 +7583,13 @@ void Td::on_request(uint64 id, const td_api::getUserProfilePhotos &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setAccentColor &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->set_accent_color(AccentColorId(request.accent_color_id_), + CustomEmojiId(request.background_custom_emoji_id_), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setSupergroupUsername &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); @@ -7675,7 +7702,7 @@ void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { - promise.set_value(result.ok().get_chat_members_object(td)); + promise.set_value(result.ok().get_chat_members_object(td, "getSupergroupMembers")); } }); contacts_manager_->get_channel_participants(ChannelId(request.supergroup_id_), std::move(request.filter_), string(), @@ -7977,13 +8004,21 @@ void Td::on_request(uint64 id, const td_api::getCustomEmojiStickers &request) { } void Td::on_request(uint64 id, const td_api::getDefaultChatPhotoCustomEmojiStickers &request) { + CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - stickers_manager_->get_default_dialog_photo_custom_emoji_stickers(false, false, std::move(promise)); + stickers_manager_->get_default_custom_emoji_stickers(StickerListType::DialogPhoto, false, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getDefaultProfilePhotoCustomEmojiStickers &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->get_default_custom_emoji_stickers(StickerListType::UserProfilePhoto, false, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getDefaultBackgroundCustomEmojiStickers &request) { + CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - stickers_manager_->get_default_dialog_photo_custom_emoji_stickers(true, false, std::move(promise)); + stickers_manager_->get_default_custom_emoji_stickers(StickerListType::Background, false, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getSavedAnimations &request) { @@ -8788,6 +8823,38 @@ void Td::on_request(uint64 id, const td_api::getPremiumState &request) { get_premium_state(this, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getPremiumGiftCodePaymentOptions &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_premium_gift_code_options(this, DialogId(request.boosted_chat_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::checkPremiumGiftCode &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.code_); + CREATE_REQUEST_PROMISE(); + check_premium_gift_code(this, request.code_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::applyPremiumGiftCode &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.code_); + CREATE_OK_REQUEST_PROMISE(); + apply_premium_gift_code(this, request.code_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::launchPrepaidPremiumGiveaway &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + launch_prepaid_premium_giveaway(this, request.giveaway_id_, std::move(request.parameters_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getPremiumGiveawayInfo &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_premium_giveaway_info(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::canPurchasePremium &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -8973,6 +9040,10 @@ void Td::on_request(uint64 id, const td_api::getSupportName &request) { get_support_name(this, std::move(query_promise)); } +void Td::on_request(uint64 id, const td_api::searchQuote &request) { + UNREACHABLE(); +} + void Td::on_request(uint64 id, const td_api::getTextEntities &request) { UNREACHABLE(); } @@ -9065,6 +9136,30 @@ void Td::on_request(uint64 id, const td_api::addLogMessage &request) { UNREACHABLE(); } +td_api::object_ptr Td::do_static_request(td_api::searchQuote &request) { + if (request.text_ == nullptr || request.quote_ == nullptr) { + return make_error(400, "Text and quote must be non-empty"); + } + if (!check_utf8(request.text_->text_) || !check_utf8(request.quote_->text_)) { + return make_error(400, "Strings must be encoded in UTF-8"); + } + auto r_text_entities = get_message_entities(nullptr, std::move(request.text_->entities_), false); + if (r_text_entities.is_error()) { + return make_error(400, r_text_entities.error().message()); + } + auto r_quote_entities = get_message_entities(nullptr, std::move(request.quote_->entities_), false); + if (r_quote_entities.is_error()) { + return make_error(400, r_quote_entities.error().message()); + } + auto position = + search_quote({std::move(request.text_->text_), r_text_entities.move_as_ok()}, + {std::move(request.quote_->text_), r_quote_entities.move_as_ok()}, request.quote_position_); + if (position == -1) { + return make_error(404, "Not Found"); + } + return td_api::make_object(position); +} + td_api::object_ptr Td::do_static_request(const td_api::getTextEntities &request) { if (!check_utf8(request.text_)) { return make_error(400, "Text must be encoded in UTF-8"); @@ -9075,7 +9170,7 @@ td_api::object_ptr Td::do_static_request(const td_api::getTextEn } td_api::object_ptr Td::do_static_request(td_api::parseTextEntities &request) { - if (!check_utf8(request.text_)) { + if (!check_utf8(request.text_)) { // must not use clean_input_string, because \r may be used as a separator return make_error(400, "Text must be encoded in UTF-8"); } if (request.parse_mode_ == nullptr) { @@ -9159,11 +9254,11 @@ td_api::object_ptr Td::do_static_request(td_api::getMarkdownText } td_api::object_ptr Td::do_static_request(td_api::searchStringsByPrefix &request) { - if (!check_utf8(request.query_)) { + if (!clean_input_string(request.query_)) { return make_error(400, "Strings must be encoded in UTF-8"); } for (auto &str : request.strings_) { - if (!check_utf8(str)) { + if (!clean_input_string(str)) { return make_error(400, "Strings must be encoded in UTF-8"); } } diff --git a/lib/tgchat/ext/td/td/telegram/Td.h b/lib/tgchat/ext/td/td/telegram/Td.h index f19a8142..1fb104c1 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.h +++ b/lib/tgchat/ext/td/td/telegram/Td.h @@ -44,6 +44,7 @@ class AudiosManager; class AuthManager; class AutosaveManager; class BackgroundManager; +class BoostManager; class BotInfoManager; class CallManager; class CallbackQueriesManager; @@ -162,6 +163,8 @@ class Td final : public Actor { ActorOwn autosave_manager_actor_; unique_ptr background_manager_; ActorOwn background_manager_actor_; + unique_ptr boost_manager_; + ActorOwn boost_manager_actor_; unique_ptr bot_info_manager_; ActorOwn bot_info_manager_actor_; unique_ptr contacts_manager_; @@ -853,7 +856,7 @@ class Td final : public Actor { void on_request(uint64 id, td_api::forwardMessages &request); - void on_request(uint64 id, const td_api::resendMessages &request); + void on_request(uint64 id, td_api::resendMessages &request); void on_request(uint64 id, td_api::getWebPagePreview &request); @@ -995,6 +998,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::setChatPhoto &request); + void on_request(uint64 id, const td_api::setChatAccentColor &request); + void on_request(uint64 id, const td_api::setChatMessageAutoDeleteTime &request); void on_request(uint64 id, const td_api::setChatPermissions &request); @@ -1043,9 +1048,9 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::activateStoryStealthMode &request); - void on_request(uint64 id, const td_api::getChatBoostStatus &request); + void on_request(uint64 id, const td_api::getAvailableChatBoostSlots &request); - void on_request(uint64 id, const td_api::canBoostChat &request); + void on_request(uint64 id, const td_api::getChatBoostStatus &request); void on_request(uint64 id, const td_api::boostChat &request); @@ -1055,6 +1060,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getChatBoosts &request); + void on_request(uint64 id, const td_api::getUserChatBoosts &request); + void on_request(uint64 id, const td_api::getAttachmentMenuBot &request); void on_request(uint64 id, const td_api::toggleBotIsAddedToAttachmentMenu &request); @@ -1275,6 +1282,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getUserProfilePhotos &request); + void on_request(uint64 id, const td_api::setAccentColor &request); + void on_request(uint64 id, td_api::setSupergroupUsername &request); void on_request(uint64 id, td_api::toggleSupergroupUsernameIsActive &request); @@ -1397,6 +1406,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getDefaultProfilePhotoCustomEmojiStickers &request); + void on_request(uint64 id, const td_api::getDefaultBackgroundCustomEmojiStickers &request); + void on_request(uint64 id, const td_api::getFavoriteStickers &request); void on_request(uint64 id, td_api::addFavoriteSticker &request); @@ -1597,6 +1608,16 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getPremiumState &request); + void on_request(uint64 id, const td_api::getPremiumGiftCodePaymentOptions &request); + + void on_request(uint64 id, td_api::checkPremiumGiftCode &request); + + void on_request(uint64 id, td_api::applyPremiumGiftCode &request); + + void on_request(uint64 id, td_api::launchPrepaidPremiumGiveaway &request); + + void on_request(uint64 id, const td_api::getPremiumGiveawayInfo &request); + void on_request(uint64 id, td_api::canPurchasePremium &request); void on_request(uint64 id, td_api::assignAppStoreTransaction &request); @@ -1643,6 +1664,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getSupportName &request); + void on_request(uint64 id, const td_api::searchQuote &request); + void on_request(uint64 id, const td_api::getTextEntities &request); void on_request(uint64 id, const td_api::parseTextEntities &request); @@ -1709,6 +1732,7 @@ class Td final : public Actor { return td_api::make_object(400, "The method can't be executed synchronously"); } static td_api::object_ptr do_static_request(const td_api::getOption &request); + static td_api::object_ptr do_static_request(td_api::searchQuote &request); static td_api::object_ptr do_static_request(const td_api::getTextEntities &request); static td_api::object_ptr do_static_request(td_api::parseTextEntities &request); static td_api::object_ptr do_static_request(td_api::parseMarkdown &request); diff --git a/lib/tgchat/ext/td/td/telegram/TdDb.cpp b/lib/tgchat/ext/td/td/telegram/TdDb.cpp index 93286568..c8854471 100644 --- a/lib/tgchat/ext/td/td/telegram/TdDb.cpp +++ b/lib/tgchat/ext/td/td/telegram/TdDb.cpp @@ -270,17 +270,19 @@ void TdDb::flush_all() { binlog_->force_flush(); } -void TdDb::close_all(Promise<> on_finished) { - LOG(INFO) << "Close all databases"; - do_close(std::move(on_finished), false /*destroy_flag*/); +void TdDb::close(int32 scheduler_id, bool destroy_flag, Promise on_finished) { + Scheduler::instance()->run_on_scheduler(scheduler_id, + [this, destroy_flag, on_finished = std::move(on_finished)](Unit) mutable { + do_close(destroy_flag, std::move(on_finished)); + }); } -void TdDb::close_and_destroy_all(Promise<> on_finished) { - LOG(INFO) << "Destroy all databases"; - do_close(std::move(on_finished), true /*destroy_flag*/); -} - -void TdDb::do_close(Promise<> on_finished, bool destroy_flag) { +void TdDb::do_close(bool destroy_flag, Promise on_finished) { + if (destroy_flag) { + LOG(INFO) << "Destroy all databases"; + } else { + LOG(INFO) << "Close all databases"; + } MultiPromiseActorSafe mpas{"TdDbCloseMultiPromiseActor"}; mpas.add_promise(PromiseCreator::lambda( [promise = std::move(on_finished), sql_connection = std::move(sql_connection_), destroy_flag](Unit) mutable { diff --git a/lib/tgchat/ext/td/td/telegram/TdDb.h b/lib/tgchat/ext/td/td/telegram/TdDb.h index a74b6aac..409f6ca4 100644 --- a/lib/tgchat/ext/td/td/telegram/TdDb.h +++ b/lib/tgchat/ext/td/td/telegram/TdDb.h @@ -134,8 +134,7 @@ class TdDb { void flush_all(); - void close_all(Promise<> on_finished); - void close_and_destroy_all(Promise<> on_finished); + void close(int32 scheduler_id, bool destroy_flag, Promise on_finished); MessageDbSyncInterface *get_message_db_sync(); MessageDbAsyncInterface *get_message_db_async(); @@ -190,7 +189,7 @@ class TdDb { Status init_sqlite(const Parameters ¶meters, const DbKey &key, const DbKey &old_key, BinlogKeyValue &binlog_pmc); - void do_close(Promise<> on_finished, bool destroy_flag); + void do_close(bool destroy_flag, Promise on_finished); }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp b/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp index c834e090..c0eddc01 100644 --- a/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp @@ -138,14 +138,54 @@ void ThemeManager::ChatThemes::parse(ParserT &parser) { td::parse(themes, parser); } -ThemeManager::ThemeManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +template +void ThemeManager::AccentColors::store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + td::store(static_cast(light_colors_.size()), storer); + for (auto &it : light_colors_) { + td::store(it.first, storer); + td::store(it.second, storer); + } + td::store(static_cast(dark_colors_.size()), storer); + for (auto &it : dark_colors_) { + td::store(it.first, storer); + td::store(it.second, storer); + } + td::store(accent_color_ids_, storer); } -void ThemeManager::start_up() { - init(); +template +void ThemeManager::AccentColors::parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + int32 size; + td::parse(size, parser); + for (int32 i = 0; i < size; i++) { + AccentColorId accent_color_id; + vector colors; + td::parse(accent_color_id, parser); + td::parse(colors, parser); + CHECK(accent_color_id.is_valid()); + light_colors_.emplace(accent_color_id, std::move(colors)); + } + td::parse(size, parser); + for (int32 i = 0; i < size; i++) { + AccentColorId accent_color_id; + vector colors; + td::parse(accent_color_id, parser); + td::parse(colors, parser); + CHECK(accent_color_id.is_valid()); + dark_colors_.emplace(accent_color_id, std::move(colors)); + } + td::parse(accent_color_ids_, parser); } -void ThemeManager::init() { +ThemeManager::ThemeManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + do_init(); +} + +void ThemeManager::do_init() { if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } @@ -161,6 +201,21 @@ void ThemeManager::init() { } } chat_themes_.next_reload_time = Time::now(); + + log_event_string = G()->td_db()->get_binlog_pmc()->get(get_accent_colors_database_key()); + if (!log_event_string.empty()) { + auto status = log_event_parse(accent_colors_, log_event_string); + if (status.is_ok()) { + send_update_accent_colors(); + } else { + LOG(ERROR) << "Failed to parse accent colors from binlog: " << status; + accent_colors_ = AccentColors(); + } + } +} + +void ThemeManager::init() { + do_init(); loop(); } @@ -239,6 +294,35 @@ void ThemeManager::on_update_theme(telegram_api::object_ptr promise.set_value(Unit()); } +void ThemeManager::on_update_accent_colors(FlatHashMap, AccentColorIdHash> light_colors, + FlatHashMap, AccentColorIdHash> dark_colors, + vector accent_color_ids) { + auto are_equal = [](const FlatHashMap, AccentColorIdHash> &lhs, + const FlatHashMap, AccentColorIdHash> &rhs) { + for (auto &lhs_it : lhs) { + auto rhs_it = rhs.find(lhs_it.first); + if (rhs_it == rhs.end() || rhs_it->second != lhs_it.second) { + return false; + } + } + return true; + }; + if (accent_color_ids == accent_colors_.accent_color_ids_ && are_equal(light_colors, accent_colors_.light_colors_) && + are_equal(dark_colors, accent_colors_.dark_colors_)) { + return; + } + for (auto &it : light_colors) { + accent_colors_.light_colors_[it.first] = std::move(it.second); + } + for (auto &it : dark_colors) { + accent_colors_.dark_colors_[it.first] = std::move(it.second); + } + accent_colors_.accent_color_ids_ = std::move(accent_color_ids); + + save_accent_colors(); + send_update_accent_colors(); +} + namespace { template static auto get_color_json(int32 color); @@ -274,6 +358,12 @@ string get_theme_parameters_json_string_impl(const td_api::object_ptrlink_color_)); o("button_color", get_color(theme->button_color_)); o("button_text_color", get_color(theme->button_text_color_)); + o("header_bg_color", get_color(theme->header_background_color_)); + o("section_bg_color", get_color(theme->section_background_color_)); + o("accent_text_color", get_color(theme->accent_text_color_)); + o("section_header_text_color", get_color(theme->section_header_text_color_)); + o("subtitle_text_color", get_color(theme->subtitle_text_color_)); + o("destructive_text_color", get_color(theme->destructive_text_color_)); })); } } // namespace @@ -287,6 +377,19 @@ string ThemeManager::get_theme_parameters_json_string(const td_api::object_ptr ThemeManager::get_theme_settings_object(const ThemeSettings &settings) const { auto fill = [colors = settings.message_colors]() mutable -> td_api::object_ptr { if (colors.size() >= 3) { @@ -315,18 +418,68 @@ td_api::object_ptr ThemeManager::get_update_chat_theme transform(chat_themes_.themes, [this](const ChatTheme &theme) { return get_chat_theme_object(theme); })); } +td_api::object_ptr ThemeManager::get_update_accent_colors_object() const { + return accent_colors_.get_update_accent_colors_object(); +} + +td_api::object_ptr ThemeManager::AccentColors::get_update_accent_colors_object() const { + vector> colors; + int32 base_colors[] = {0xDF2020, 0xDFA520, 0xA040A0, 0x208020, 0x20DFDF, 0x2044DF, 0xDF1493}; + auto get_distance = [](int32 lhs_color, int32 rhs_color) { + auto get_color_distance = [](int32 lhs, int32 rhs) { + auto diff = max(lhs & 255, 0) - max(rhs & 255, 0); + return diff * diff; + }; + return get_color_distance(lhs_color, rhs_color) + get_color_distance(lhs_color >> 8, rhs_color >> 8) + + get_color_distance(lhs_color >> 16, rhs_color >> 16); + }; + for (auto &it : light_colors_) { + auto light_colors = it.second; + auto dark_it = dark_colors_.find(it.first); + auto dark_colors = dark_it != dark_colors_.end() ? dark_it->second : light_colors; + auto first_color = light_colors[0]; + int best_index = 0; + int32 best_distance = get_distance(base_colors[0], first_color); + for (int i = 1; i < 7; i++) { + auto cur_distance = get_distance(base_colors[i], first_color); + if (cur_distance < best_distance) { + best_distance = cur_distance; + best_index = i; + } + } + colors.push_back(td_api::make_object(it.first.get(), best_index, std::move(light_colors), + std::move(dark_colors))); + } + auto available_accent_color_ids = + transform(accent_color_ids_, [](AccentColorId accent_color_id) { return accent_color_id.get(); }); + return td_api::make_object(std::move(colors), std::move(available_accent_color_ids)); +} + string ThemeManager::get_chat_themes_database_key() { return "chat_themes"; } +string ThemeManager::get_accent_colors_database_key() { + return "accent_colors"; +} + void ThemeManager::save_chat_themes() { G()->td_db()->get_binlog_pmc()->set(get_chat_themes_database_key(), log_event_store(chat_themes_).as_slice().str()); } +void ThemeManager::save_accent_colors() { + G()->td_db()->get_binlog_pmc()->set(get_accent_colors_database_key(), + log_event_store(accent_colors_).as_slice().str()); +} + void ThemeManager::send_update_chat_themes() const { send_closure(G()->td(), &Td::send_update, get_update_chat_themes_object()); } +void ThemeManager::send_update_accent_colors() const { + send_closure(G()->td(), &Td::send_update, get_update_accent_colors_object()); +} + void ThemeManager::on_get_chat_themes(Result> result) { if (result.is_error()) { set_timeout_in(Random::fast(40, 60)); @@ -423,11 +576,16 @@ ThemeManager::ThemeSettings ThemeManager::get_chat_theme_settings( } void ThemeManager::get_current_state(vector> &updates) const { - if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || chat_themes_.themes.empty()) { + if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } - updates.push_back(get_update_chat_themes_object()); + if (!chat_themes_.themes.empty()) { + updates.push_back(get_update_chat_themes_object()); + } + if (!accent_colors_.accent_color_ids_.empty()) { + updates.push_back(get_update_accent_colors_object()); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ThemeManager.h b/lib/tgchat/ext/td/td/telegram/ThemeManager.h index 831bc53d..2c150835 100644 --- a/lib/tgchat/ext/td/td/telegram/ThemeManager.h +++ b/lib/tgchat/ext/td/td/telegram/ThemeManager.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/AccentColorId.h" #include "td/telegram/BackgroundInfo.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -13,6 +14,7 @@ #include "td/actor/actor.h" #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" #include "td/utils/Promise.h" #include "td/utils/Status.h" @@ -28,9 +30,16 @@ class ThemeManager final : public Actor { void on_update_theme(telegram_api::object_ptr &&theme, Promise &&promise); + void on_update_accent_colors(FlatHashMap, AccentColorIdHash> light_colors, + FlatHashMap, AccentColorIdHash> dark_colors, + vector accent_color_ids); + static string get_theme_parameters_json_string(const td_api::object_ptr &theme, bool for_web_view); + int32 get_accent_color_id_object(AccentColorId accent_color_id, + AccentColorId fallback_accent_color_id = AccentColorId()) const; + void get_current_state(vector> &updates) const; private: @@ -83,12 +92,26 @@ class ThemeManager final : public Actor { void parse(ParserT &parser); }; - void start_up() final; + struct AccentColors { + FlatHashMap, AccentColorIdHash> light_colors_; + FlatHashMap, AccentColorIdHash> dark_colors_; + vector accent_color_ids_; + + td_api::object_ptr get_update_accent_colors_object() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; void loop() final; void tear_down() final; + void do_init(); + static bool is_dark_base_theme(BaseTheme base_theme); void on_get_chat_themes(Result> result); @@ -101,16 +124,26 @@ class ThemeManager final : public Actor { static string get_chat_themes_database_key(); + string get_accent_colors_database_key(); + void save_chat_themes(); + void save_accent_colors(); + void send_update_chat_themes() const; static BaseTheme get_base_theme(const telegram_api::object_ptr &base_theme); ThemeSettings get_chat_theme_settings(telegram_api::object_ptr settings); + td_api::object_ptr get_update_accent_colors_object() const; + + void send_update_accent_colors() const; + ChatThemes chat_themes_; + AccentColors accent_colors_; + Td *td_; ActorShared<> parent_; }; diff --git a/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp b/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp index 65208dfa..e1c8ff2e 100644 --- a/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp @@ -197,7 +197,8 @@ void TopDialogManager::on_toggle_top_peers(bool is_enabled, Result &&resul } void TopDialogManager::on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) { - if (!is_active_ || !is_enabled_) { + CHECK(!td_->auth_manager_->is_bot()); + if (!is_enabled_) { return; } auto pos = static_cast(category); @@ -240,7 +241,8 @@ void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog if (!td_->messages_manager_->have_dialog_force(dialog_id, "remove_dialog")) { return promise.set_error(Status::Error(400, "Chat not found")); } - if (!is_active_ || !is_enabled_) { + CHECK(!td_->auth_manager_->is_bot()); + if (!is_enabled_) { return promise.set_value(Unit()); } @@ -293,9 +295,7 @@ int TopDialogManager::is_top_dialog(TopDialogCategory category, size_t limit, Di CHECK(category != TopDialogCategory::Size); CHECK(category != TopDialogCategory::ForwardUsers); CHECK(limit > 0); - if (!is_active_) { - return -1; - } + CHECK(!td_->auth_manager_->is_bot()); if (!is_enabled_) { return 0; } @@ -313,7 +313,7 @@ int TopDialogManager::is_top_dialog(TopDialogCategory category, size_t limit, Di } void TopDialogManager::update_rating_e_decay() { - if (!is_active_) { + if (td_->auth_manager_->is_bot()) { return; } rating_e_decay_ = narrow_cast(G()->get_option_integer("rating_e_decay", rating_e_decay_)); @@ -351,14 +351,15 @@ double TopDialogManager::rating_add(double now, double rating_timestamp) const { return std::exp((now - rating_timestamp) / rating_e_decay_); } -double TopDialogManager::current_rating_add(double rating_timestamp) const { - return rating_add(G()->server_time_cached(), rating_timestamp); +double TopDialogManager::current_rating_add(double server_time, double rating_timestamp) const { + return rating_add(server_time, rating_timestamp); } void TopDialogManager::normalize_rating() { + auto server_time = G()->server_time(); for (auto &top_dialogs : by_category_) { - auto div_by = current_rating_add(top_dialogs.rating_timestamp); - top_dialogs.rating_timestamp = G()->server_time_cached(); + auto div_by = current_rating_add(server_time, top_dialogs.rating_timestamp); + top_dialogs.rating_timestamp = server_time; for (auto &dialog : top_dialogs.dialogs) { dialog.rating /= div_by; } @@ -553,7 +554,6 @@ void TopDialogManager::init() { return; } - is_active_ = !td_->auth_manager_->is_bot(); is_enabled_ = !G()->get_option_boolean("disable_top_chats"); update_rating_e_decay(); @@ -571,14 +571,13 @@ void TopDialogManager::try_start() { first_unsync_change_ = Timestamp(); server_sync_state_ = SyncState::None; last_server_sync_ = Timestamp(); - CHECK(pending_get_top_dialogs_.empty()); - LOG(DEBUG) << "Init is enabled: " << is_enabled_; - if (!is_active_) { - G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs"); + if (td_->auth_manager_->is_bot()) { return; } + LOG(DEBUG) << "Init is enabled: " << is_enabled_; + auto di_top_dialogs_ts = G()->td_db()->get_binlog_pmc()->get("top_dialogs_ts"); if (!di_top_dialogs_ts.empty()) { last_server_sync_ = Timestamp::in(to_integer(di_top_dialogs_ts) - Clocks::system()); @@ -616,15 +615,11 @@ void TopDialogManager::try_start() { void TopDialogManager::on_first_sync() { was_first_sync_ = true; - if (!G()->close_flag() && td_->auth_manager_->is_bot()) { - is_active_ = false; - try_start(); - } loop(); } void TopDialogManager::loop() { - if (!is_active_ || G()->close_flag()) { + if (td_->auth_manager_->is_bot() || G()->close_flag()) { return; } diff --git a/lib/tgchat/ext/td/td/telegram/TopDialogManager.h b/lib/tgchat/ext/td/td/telegram/TopDialogManager.h index efa19230..51a7b16e 100644 --- a/lib/tgchat/ext/td/td/telegram/TopDialogManager.h +++ b/lib/tgchat/ext/td/td/telegram/TopDialogManager.h @@ -52,7 +52,6 @@ class TopDialogManager final : public Actor { Td *td_; ActorShared<> parent_; - bool is_active_ = false; bool is_enabled_ = true; bool is_synchronized_ = false; int32 rating_e_decay_ = 241920; @@ -99,7 +98,7 @@ class TopDialogManager final : public Actor { std::array(TopDialogCategory::Size)> by_category_; double rating_add(double now, double rating_timestamp) const; - double current_rating_add(double rating_timestamp) const; + double current_rating_add(double server_time, double rating_timestamp) const; void normalize_rating(); bool set_is_enabled(bool is_enabled); diff --git a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp index 963bac01..06b02953 100644 --- a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp @@ -11,9 +11,11 @@ #include "td/telegram/AttachMenuManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/AutosaveManager.h" +#include "td/telegram/BoostManager.h" #include "td/telegram/CallbackQueriesManager.h" #include "td/telegram/CallManager.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" @@ -54,6 +56,7 @@ #include "td/telegram/ServerMessageId.h" #include "td/telegram/SpecialStickerSetType.h" #include "td/telegram/StateManager.h" +#include "td/telegram/StickerListType.h" #include "td/telegram/StickerSetId.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickerType.h" @@ -257,8 +260,8 @@ UpdatesManager::UpdatesManager(Td *td, ActorShared<> parent) : td_(td), parent_( pending_audio_transcription_timeout_.set_callback(on_pending_audio_transcription_timeout_callback); pending_audio_transcription_timeout_.set_callback_data(static_cast(td_)); - if (td_->option_manager_->get_option_integer("since_last_open") < 3600) { - finished_first_get_difference_ = true; + if (!td_->auth_manager_->is_authorized() || !td_->auth_manager_->is_bot()) { + skipped_postponed_updates_after_start_ = 0; } } @@ -630,14 +633,16 @@ Promise<> UpdatesManager::set_pts(int32 pts, const char *source) { LOG(WARNING) << "PTS decreases from " << old_pts << " to " << pts << " from " << source; } else { LOG(INFO) << "Update PTS from " << old_pts << " to " << pts << " from " << source; - pts_diff_ += pts - old_pts; - if (pts_diff_ >= 1000000) { - LOG(WARNING) << "Fixed " << pts_gap_ << " PTS gaps and " << pts_fixed_short_gap_ << " short gaps by sending " - << pts_short_gap_ << " requests"; - pts_short_gap_ = 0; - pts_fixed_short_gap_ = 0; - pts_gap_ = 0; - pts_diff_ = 0; + if (old_pts > 0) { + pts_diff_ += pts - old_pts; + if (pts_diff_ >= 1000000) { + LOG(WARNING) << "Fixed " << pts_gap_ << " PTS gaps and " << pts_fixed_short_gap_ << " short gaps by sending " + << pts_short_gap_ << " requests"; + pts_short_gap_ = 0; + pts_fixed_short_gap_ = 0; + pts_gap_ = 0; + pts_diff_ = 0; + } } } @@ -774,17 +779,14 @@ bool UpdatesManager::is_acceptable_message_reply_header( switch (header->get_id()) { case telegram_api::messageReplyHeader::ID: { auto reply_header = static_cast(header.get()); - if (!is_acceptable_peer(reply_header->reply_to_peer_id_)) { - return false; - } - return true; + return is_acceptable_peer(reply_header->reply_to_peer_id_) && + is_acceptable_message_forward_header(reply_header->reply_from_) && + is_acceptable_message_entities(reply_header->quote_entities_) && + is_acceptable_message_media(reply_header->reply_media_); } case telegram_api::messageReplyStoryHeader::ID: { auto reply_header = static_cast(header.get()); - if (!is_acceptable_user(UserId(reply_header->user_id_))) { - return false; - } - return true; + return is_acceptable_user(UserId(reply_header->user_id_)); } default: UNREACHABLE(); @@ -807,6 +809,73 @@ bool UpdatesManager::is_acceptable_message_forward_header( return true; } +bool UpdatesManager::is_acceptable_message_media( + const telegram_api::object_ptr &media_ptr) const { + if (media_ptr == nullptr) { + return true; + } + switch (media_ptr->get_id()) { + case telegram_api::messageMediaContact::ID: { + auto message_media = static_cast(media_ptr.get()); + UserId user_id(message_media->user_id_); + return user_id == UserId() || is_acceptable_user(user_id); + } + case telegram_api::messageMediaStory::ID: { + auto message_media = static_cast(media_ptr.get()); + return is_acceptable_peer(message_media->peer_); + } + case telegram_api::messageMediaGiveaway::ID: { + auto message_media = static_cast(media_ptr.get()); + for (auto channel_id : message_media->channels_) { + if (!is_acceptable_channel(ChannelId(channel_id))) { + return false; + } + } + return true; + } + case telegram_api::messageMediaPoll::ID: + /* + // the users and chats are always min, so no need to check + auto message_media_poll = static_cast(media_ptr.get()); + for (auto recent_voter : message_media_poll->results_->recent_voters_) { + if (!is_acceptable_peer(recent_voter)) { + return false; + } + } + */ + return true; + case telegram_api::messageMediaWebPage::ID: + /* + // the channel is always min, so no need to check + auto message_media_web_page = static_cast(media_ptr.get()); + if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) { + auto web_page = static_cast(message_media_web_page->webpage_.get()); + if (web_page->cached_page_ != nullptr) { + const vector> *page_blocks = nullptr; + downcast_call(*web_page->cached_page_, [&page_blocks](auto &page) { page_blocks = &page.blocks_; }); + CHECK(page_blocks != nullptr); + for (auto &page_block : *page_blocks) { + if (page_block->get_id() == telegram_api::pageBlockChannel::ID) { + auto page_block_channel = static_cast(page_block.get()); + auto channel_id = ContactsManager::get_channel_id(page_block_channel->channel_); + if (channel_id.is_valid()) { + if (!is_acceptable_channel(channel_id)) { + return false; + } + } else { + LOG(ERROR) << "Receive wrong channel " << to_string(page_block_channel->channel_); + } + } + } + } + } + */ + return true; + default: + return true; + } +} + bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ptr) const { CHECK(message_ptr != nullptr); int32 constructor_id = message_ptr->get_id(); @@ -831,7 +900,7 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ return false; } - if ((message->flags_ & MessagesManager::MESSAGE_FLAG_IS_SENT_VIA_BOT) && + if ((message->flags_ & telegram_api::message::VIA_BOT_ID_MASK) && !is_acceptable_user(UserId(message->via_bot_id_))) { return false; } @@ -840,61 +909,8 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ return false; } - if (message->media_ != nullptr) { - auto media_id = message->media_->get_id(); - if (media_id == telegram_api::messageMediaContact::ID) { - auto message_media = static_cast(message->media_.get()); - UserId user_id(message_media->user_id_); - if (user_id != UserId() && !is_acceptable_user(user_id)) { - return false; - } - } - if (media_id == telegram_api::messageMediaStory::ID) { - auto message_media = static_cast(message->media_.get()); - if (!is_acceptable_peer(message_media->peer_)) { - return false; - } - } - /* - // the users and chats are always min, so no need to check - if (media_id == telegram_api::messageMediaPoll::ID) { - auto message_media_poll = static_cast(message->media_.get()); - for (auto recent_voter : message_media_poll->results_->recent_voters_) { - if (!is_acceptable_peer(recent_voter)) { - return false; - } - } - } - */ - /* - // the channel is always min, so no need to check - if (media_id == telegram_api::messageMediaWebPage::ID) { - auto message_media_web_page = static_cast(message->media_.get()); - if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) { - auto web_page = static_cast(message_media_web_page->webpage_.get()); - if (web_page->cached_page_ != nullptr) { - const vector> *page_blocks = nullptr; - downcast_call(*web_page->cached_page_, [&page_blocks](auto &page) { page_blocks = &page.blocks_; }); - CHECK(page_blocks != nullptr); - for (auto &page_block : *page_blocks) { - if (page_block->get_id() == telegram_api::pageBlockChannel::ID) { - auto page_block_channel = static_cast(page_block.get()); - auto channel_id = ContactsManager::get_channel_id(page_block_channel->channel_); - if (channel_id.is_valid()) { - if (!is_acceptable_channel(channel_id)) { - return false; - } - } else { - LOG(ERROR) << "Receive wrong channel " << to_string(page_block_channel->channel_); - } - } - } - } - } - } - */ - } else { - CHECK(message->media_ == nullptr); + if (!is_acceptable_message_media(message->media_)) { + return false; } /* @@ -953,6 +969,7 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ case telegram_api::messageActionSuggestProfilePhoto::ID: case telegram_api::messageActionSetChatWallPaper::ID: case telegram_api::messageActionSetSameChatWallPaper::ID: + case telegram_api::messageActionGiveawayLaunch::ID: break; case telegram_api::messageActionChatCreate::ID: { auto chat_create = static_cast(action); @@ -1030,6 +1047,13 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ } break; } + case telegram_api::messageActionGiftCode::ID: { + auto gift_code = static_cast(action); + if (!is_acceptable_peer(gift_code->boost_peer_)) { + return false; + } + break; + } default: UNREACHABLE(); return false; @@ -1069,18 +1093,23 @@ bool UpdatesManager::is_acceptable_update(const telegram_api::Update *update) co return is_acceptable_message(message); } - if (id == telegram_api::updateDraftMessage::ID) { - auto update_draft_message = static_cast(update); - CHECK(update_draft_message->draft_ != nullptr); - if (update_draft_message->draft_->get_id() == telegram_api::draftMessage::ID) { - auto draft_message = static_cast(update_draft_message->draft_.get()); - return is_acceptable_message_entities(draft_message->entities_); - } - } - return true; } +int32 UpdatesManager::fix_short_message_flags(int32 flags) { + static constexpr int32 MESSAGE_FLAG_HAS_REPLY_MARKUP = 1 << 6; + static constexpr int32 MESSAGE_FLAG_HAS_MEDIA = 1 << 9; + static constexpr int32 MESSAGE_FLAG_HAS_REACTIONS = 1 << 20; + static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23; + auto disallowed_flags = + MESSAGE_FLAG_HAS_REPLY_MARKUP | MESSAGE_FLAG_HAS_MEDIA | MESSAGE_FLAG_HAS_REACTIONS | MESSAGE_FLAG_HAS_REPLY_INFO; + if ((flags & disallowed_flags) != 0) { + LOG(ERROR) << "Receive short message with flags " << flags; + flags = flags & ~disallowed_flags; + } + return flags; +} + void UpdatesManager::on_get_updates(tl_object_ptr &&updates_ptr, Promise &&promise) { send_closure_later(actor_id(this), &UpdatesManager::on_get_updates_impl, std::move(updates_ptr), std::move(promise)); } @@ -1132,24 +1161,14 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up break; case telegram_api::updateShortMessage::ID: { auto update = move_tl_object_as(updates_ptr); - if (update->flags_ & MessagesManager::MESSAGE_FLAG_HAS_REPLY_MARKUP) { - LOG(ERROR) << "Receive updateShortMessage with reply_markup"; - update->flags_ ^= MessagesManager::MESSAGE_FLAG_HAS_REPLY_MARKUP; - } - if (update->flags_ & MessagesManager::MESSAGE_FLAG_HAS_MEDIA) { - LOG(ERROR) << "Receive updateShortMessage with media"; - update->flags_ ^= MessagesManager::MESSAGE_FLAG_HAS_MEDIA; - } - auto from_id = update->out_ ? td_->contacts_manager_->get_my_id().get() : update->user_id_; - update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID; - auto message = make_tl_object( - update->flags_, update->out_, update->mentioned_, update->media_unread_, update->silent_, false, false, false, - false, false, false, update->id_, make_tl_object(from_id), - make_tl_object(update->user_id_), std::move(update->fwd_from_), update->via_bot_id_, - std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, std::move(update->entities_), - 0, 0, nullptr, 0, string(), 0, nullptr, Auto(), update->ttl_period_); + fix_short_message_flags(update->flags_), update->out_, update->mentioned_, update->media_unread_, + update->silent_, false, false, false, false, false, false, false, update->id_, + make_tl_object(from_id), make_tl_object(update->user_id_), + std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_, + update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr, + Auto(), update->ttl_period_); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, std::move(promise), "telegram_api::updateShortMessage"); @@ -1157,19 +1176,10 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up } case telegram_api::updateShortChatMessage::ID: { auto update = move_tl_object_as(updates_ptr); - if (update->flags_ & MessagesManager::MESSAGE_FLAG_HAS_REPLY_MARKUP) { - LOG(ERROR) << "Receive updateShortChatMessage with reply_markup"; - update->flags_ ^= MessagesManager::MESSAGE_FLAG_HAS_REPLY_MARKUP; - } - if (update->flags_ & MessagesManager::MESSAGE_FLAG_HAS_MEDIA) { - LOG(ERROR) << "Receive updateShortChatMessage with media"; - update->flags_ ^= MessagesManager::MESSAGE_FLAG_HAS_MEDIA; - } - - update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID; auto message = make_tl_object( - update->flags_, update->out_, update->mentioned_, update->media_unread_, update->silent_, false, false, false, - false, false, false, update->id_, make_tl_object(update->from_id_), + fix_short_message_flags(update->flags_), update->out_, update->mentioned_, update->media_unread_, + update->silent_, false, false, false, false, false, false, false, update->id_, + make_tl_object(update->from_id_), make_tl_object(update->chat_id_), std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr, Auto(), update->ttl_period_); @@ -2111,7 +2121,7 @@ void UpdatesManager::after_get_difference() { retry_timeout_.cancel_timeout(); retry_time_ = 1; - finished_first_get_difference_ = true; + skipped_postponed_updates_after_start_ = 0; td_->option_manager_->set_option_empty("since_last_open"); // cancels qts_gap_timeout_ if needed, can apply some updates received during getDifference, @@ -2263,8 +2273,8 @@ void UpdatesManager::try_reload_data() { td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::generic_animations()); td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::default_statuses()); td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::default_topic_icons()); - td_->stickers_manager_->get_default_dialog_photo_custom_emoji_stickers(false, true, Auto()); - td_->stickers_manager_->get_default_dialog_photo_custom_emoji_stickers(true, true, Auto()); + td_->stickers_manager_->get_default_custom_emoji_stickers(StickerListType::DialogPhoto, true, Auto()); + td_->stickers_manager_->get_default_custom_emoji_stickers(StickerListType::UserProfilePhoto, true, Auto()); td_->story_manager_->reload_active_stories(); td_->story_manager_->reload_all_read_stories(); @@ -3020,6 +3030,12 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up add_qts(qts).set_value(Unit()); break; } + case telegram_api::updateBotChatBoost::ID: { + auto update = move_tl_object_as(update_ptr); + td_->boost_manager_->on_update_dialog_boost(DialogId(update->peer_), std::move(update->boost_)); + add_qts(qts).set_value(Unit()); + break; + } default: UNREACHABLE(); break; @@ -3784,6 +3800,7 @@ bool UpdatesManager::is_qts_update(const telegram_api::Update *update) { case telegram_api::updateChatParticipant::ID: case telegram_api::updateChannelParticipant::ID: case telegram_api::updateBotChatInviteRequester::ID: + case telegram_api::updateBotChatBoost::ID: return true; default: return false; @@ -3804,6 +3821,8 @@ int32 UpdatesManager::get_update_qts(const telegram_api::Update *update) { return static_cast(update)->qts_; case telegram_api::updateBotChatInviteRequester::ID: return static_cast(update)->qts_; + case telegram_api::updateBotChatBoost::ID: + return static_cast(update)->qts_; default: return 0; } @@ -3926,15 +3945,16 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { DialogId dialog_id(update->peer_); - RestrictedRights permissions(update->default_banned_rights_); auto version = update->version_; switch (dialog_id.get_type()) { case DialogType::Chat: - td_->contacts_manager_->on_update_chat_default_permissions(dialog_id.get_chat_id(), permissions, version); + td_->contacts_manager_->on_update_chat_default_permissions( + dialog_id.get_chat_id(), RestrictedRights(update->default_banned_rights_, ChannelType::Unknown), version); break; case DialogType::Channel: LOG_IF(ERROR, version != 0) << "Receive version " << version << " in " << dialog_id; - td_->contacts_manager_->on_update_channel_default_permissions(dialog_id.get_channel_id(), permissions); + td_->contacts_manager_->on_update_channel_default_permissions( + dialog_id.get_channel_id(), RestrictedRights(update->default_banned_rights_, ChannelType::Megagroup)); break; case DialogType::None: case DialogType::User: @@ -4276,6 +4296,11 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->theme_manager_->on_update_theme(std::move(update->theme_), std::move(promise)); } diff --git a/lib/tgchat/ext/td/td/telegram/UpdatesManager.h b/lib/tgchat/ext/td/td/telegram/UpdatesManager.h index 1e04723f..16eec6c6 100644 --- a/lib/tgchat/ext/td/td/telegram/UpdatesManager.h +++ b/lib/tgchat/ext/td/td/telegram/UpdatesManager.h @@ -263,7 +263,7 @@ class UpdatesManager final : public Actor { bool are_sessions_inited_ = false; bool running_get_difference_ = false; - bool finished_first_get_difference_ = false; + int32 skipped_postponed_updates_after_start_ = 50000; int32 last_confirmed_pts_ = 0; int32 last_confirmed_qts_ = 0; int32 min_postponed_update_pts_ = 0; @@ -310,8 +310,12 @@ class UpdatesManager final : public Actor { void on_qts_ack(PtsManager::PtsId ack_token); void save_qts(int32 qts); - bool can_postpone_updates() const { - return finished_first_get_difference_; + bool can_postpone_updates() { + if (skipped_postponed_updates_after_start_ == 0) { + return true; + } + skipped_postponed_updates_after_start_--; + return false; } void set_date(int32 date, bool from_update, string date_source); @@ -457,10 +461,14 @@ class UpdatesManager final : public Actor { bool is_acceptable_message_forward_header( const telegram_api::object_ptr &header) const; + bool is_acceptable_message_media(const telegram_api::object_ptr &media_ptr) const; + bool is_acceptable_message(const telegram_api::Message *message_ptr) const; bool is_acceptable_update(const telegram_api::Update *update) const; + static int32 fix_short_message_flags(int32 flags); + 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); @@ -607,6 +615,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); diff --git a/lib/tgchat/ext/td/td/telegram/Version.h b/lib/tgchat/ext/td/td/telegram/Version.h index df99412b..31e2471b 100644 --- a/lib/tgchat/ext/td/td/telegram/Version.h +++ b/lib/tgchat/ext/td/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 164; +constexpr int32 MTPROTO_LAYER = 166; enum class Version : int32 { Initial, // 0 @@ -62,6 +62,9 @@ enum class Version : int32 { MakeParticipantFlags64Bit, AddDocumentFlags, AddUserFlags2, + AddMessageTextFlags, + AddPageBlockChatLinkFlags, // 50 + SupportRepliesInOtherChats, Next }; diff --git a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp index 3e3f0299..9a31ad83 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/WebPageBlock.h" +#include "td/telegram/AccentColorId.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AnimationsManager.hpp" #include "td/telegram/AudiosManager.h" @@ -25,6 +26,7 @@ #include "td/telegram/PhotoFormat.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" #include "td/telegram/Version.h" #include "td/telegram/VideosManager.h" #include "td/telegram/VideosManager.hpp" @@ -1417,11 +1419,18 @@ class WebPageBlockChatLink final : public WebPageBlock { string title; DialogPhoto photo; string username; + AccentColorId accent_color_id; + ChannelId channel_id; public: WebPageBlockChatLink() = default; - WebPageBlockChatLink(string &&title, DialogPhoto photo, string &&username) - : title(std::move(title)), photo(std::move(photo)), username(std::move(username)) { + WebPageBlockChatLink(string &&title, DialogPhoto photo, string &&username, AccentColorId accent_color_id, + ChannelId channel_id) + : title(std::move(title)) + , photo(std::move(photo)) + , username(std::move(username)) + , accent_color_id(accent_color_id) + , channel_id(channel_id) { } Type get_type() const final { @@ -1434,23 +1443,80 @@ class WebPageBlockChatLink final : public WebPageBlock { td_api::object_ptr get_page_block_object(Context *context) const final { return make_tl_object( - title, get_chat_photo_info_object(context->td_->file_manager_.get(), &photo), username); + title, get_chat_photo_info_object(context->td_->file_manager_.get(), &photo), + context->td_->theme_manager_->get_accent_color_id_object(accent_color_id, AccentColorId(channel_id)), username); } template void store(StorerT &storer) const { using ::td::store; - store(title, storer); - store(photo, storer); - store(username, storer); + bool has_title = !title.empty(); + bool has_photo = photo.small_file_id.is_valid(); + bool has_username = !username.empty(); + bool has_accent_color_id = true; + bool has_channel_id = channel_id.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_title); + STORE_FLAG(has_photo); + STORE_FLAG(has_username); + STORE_FLAG(has_accent_color_id); + STORE_FLAG(has_channel_id); + END_STORE_FLAGS(); + if (has_title) { + store(title, storer); + } + if (has_photo) { + store(photo, storer); + } + if (has_username) { + store(username, storer); + } + store(accent_color_id, storer); + if (has_channel_id) { + store(channel_id, storer); + } } template void parse(ParserT &parser) { using ::td::parse; - parse(title, parser); - parse(photo, parser); - parse(username, parser); + bool has_title; + bool has_photo; + bool has_username; + bool has_accent_color_id = false; + bool has_channel_id = false; + if (parser.version() >= static_cast(Version::AddPageBlockChatLinkFlags)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_title); + PARSE_FLAG(has_photo); + PARSE_FLAG(has_username); + PARSE_FLAG(has_accent_color_id); + PARSE_FLAG(has_channel_id); + END_PARSE_FLAGS(); + } else { + has_title = true; + has_photo = true; + has_username = true; + } + if (has_title) { + parse(title, parser); + } + if (has_photo) { + parse(photo, parser); + } + if (has_username) { + parse(username, parser); + } + if (has_accent_color_id) { + parse(accent_color_id, parser); + } else { + accent_color_id = AccentColorId(5); // blue + } + if (has_channel_id) { + parse(channel_id, parser); + } else { + channel_id = ChannelId(static_cast(5)); // blue + } } }; @@ -2142,14 +2208,18 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptr(td->contacts_manager_->get_channel_title(channel_id), *td->contacts_manager_->get_channel_dialog_photo(channel_id), - td->contacts_manager_->get_channel_first_username(channel_id)); + td->contacts_manager_->get_channel_first_username(channel_id), + td->contacts_manager_->get_channel_accent_color_id(channel_id), + channel_id); } else { bool has_access_hash = (channel->flags_ & telegram_api::channel::ACCESS_HASH_MASK) != 0; + bool has_color = (channel->flags2_ & telegram_api::channel::COLOR_MASK) != 0; return td::make_unique( std::move(channel->title_), get_dialog_photo(td->file_manager_.get(), DialogId(channel_id), has_access_hash ? channel->access_hash_ : 0, std::move(channel->photo_)), - std::move(channel->username_)); + std::move(channel->username_), has_color ? AccentColorId(channel->color_) : AccentColorId(channel_id), + channel_id); } } else { LOG(ERROR) << "Receive wrong channel " << to_string(page_block->channel_); diff --git a/lib/tgchat/ext/td/td/telegram/WebPageId.h b/lib/tgchat/ext/td/td/telegram/WebPageId.h index df82beca..63c92052 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPageId.h +++ b/lib/tgchat/ext/td/td/telegram/WebPageId.h @@ -60,7 +60,7 @@ struct WebPageIdHash { }; inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId web_page_id) { - return string_builder << "web page " << web_page_id.get(); + return string_builder << "link preview " << web_page_id.get(); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp index 9232c5a8..0a3096ac 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp @@ -9,6 +9,7 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Dimensions.h" #include "td/telegram/Document.h" @@ -24,7 +25,6 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/Photo.h" #include "td/telegram/PhotoFormat.h" -#include "td/telegram/secret_api.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/StoryId.h" @@ -61,15 +61,16 @@ namespace td { class GetWebPagePreviewQuery final : public Td::ResultHandler { Promise> promise_; - string url_; + unique_ptr options_; public: explicit GetWebPagePreviewQuery(Promise> &&promise) : promise_(std::move(promise)) { } - void send(const string &text, vector> &&entities, string url) { - url_ = std::move(url); + void send(const string &text, vector> &&entities, + unique_ptr &&options) { + options_ = std::move(options); int32 flags = 0; if (!entities.empty()) { @@ -88,7 +89,7 @@ class GetWebPagePreviewQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebPagePreviewQuery: " << to_string(ptr); - td_->web_pages_manager_->on_get_web_page_preview(url_, std::move(ptr), std::move(promise_)); + td_->web_pages_manager_->on_get_web_page_preview(std::move(options_), std::move(ptr), std::move(promise_)); } void on_error(Status status) final { @@ -123,9 +124,12 @@ class GetWebPageQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebPageQuery: " << to_string(ptr); - if (ptr->get_id() == telegram_api::webPageNotModified::ID) { + td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetWebPageQuery"); + td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetWebPageQuery"); + auto page = std::move(ptr->webpage_); + if (page->get_id() == telegram_api::webPageNotModified::ID) { if (web_page_id_.is_valid()) { - auto web_page = move_tl_object_as(ptr); + auto web_page = move_tl_object_as(page); int32 view_count = web_page->cached_page_views_; td_->web_pages_manager_->on_get_web_page_instant_view_view_count(web_page_id_, view_count); return promise_.set_value(std::move(web_page_id_)); @@ -134,7 +138,7 @@ class GetWebPageQuery final : public Td::ResultHandler { return on_error(Status::Error(500, "Receive webPageNotModified")); } } - auto web_page_id = td_->web_pages_manager_->on_get_web_page(std::move(ptr), DialogId()); + auto web_page_id = td_->web_pages_manager_->on_get_web_page(std::move(page), DialogId()); td_->web_pages_manager_->on_get_web_page_by_url(url_, web_page_id, false); promise_.set_value(std::move(web_page_id)); } @@ -233,6 +237,7 @@ class WebPagesManager::WebPage { Dimensions embed_dimensions_; int32 duration_ = 0; string author_; + bool has_large_media_ = false; Document document_; vector documents_; vector story_full_ids_; @@ -276,6 +281,7 @@ class WebPagesManager::WebPage { STORE_FLAG(is_instant_view_v2); STORE_FLAG(has_documents); STORE_FLAG(has_story_full_ids); + STORE_FLAG(has_large_media_); END_STORE_FLAGS(); store(url_, storer); @@ -353,6 +359,7 @@ class WebPagesManager::WebPage { PARSE_FLAG(is_instant_view_v2); PARSE_FLAG(has_documents); PARSE_FLAG(has_story_full_ids); + PARSE_FLAG(has_large_media_); END_PARSE_FLAGS(); parse(url_, parser); @@ -413,7 +420,8 @@ class WebPagesManager::WebPage { lhs.site_name_ == rhs.site_name_ && lhs.title_ == rhs.title_ && lhs.description_ == rhs.description_ && lhs.photo_ == rhs.photo_ && lhs.type_ == rhs.type_ && lhs.embed_url_ == rhs.embed_url_ && lhs.embed_type_ == rhs.embed_type_ && lhs.embed_dimensions_ == rhs.embed_dimensions_ && - lhs.duration_ == rhs.duration_ && lhs.author_ == rhs.author_ && lhs.document_ == rhs.document_ && + lhs.duration_ == rhs.duration_ && lhs.author_ == rhs.author_ && + lhs.has_large_media_ == rhs.has_large_media_ && lhs.document_ == rhs.document_ && lhs.documents_ == rhs.documents_ && lhs.story_full_ids_ == rhs.story_full_ids_ && lhs.instant_view_.is_empty_ == rhs.instant_view_.is_empty_ && lhs.instant_view_.is_v2_ == rhs.instant_view_.is_v2_; @@ -436,6 +444,24 @@ WebPagesManager::~WebPagesManager() { url_to_web_page_id_, url_to_file_source_id_); } +string WebPagesManager::get_web_page_url(const tl_object_ptr &web_page_ptr) { + CHECK(web_page_ptr != nullptr); + switch (web_page_ptr->get_id()) { + case telegram_api::webPageEmpty::ID: + return static_cast(web_page_ptr.get())->url_; + case telegram_api::webPagePending::ID: + return static_cast(web_page_ptr.get())->url_; + case telegram_api::webPage::ID: + return static_cast(web_page_ptr.get())->url_; + case telegram_api::webPageNotModified::ID: + LOG(ERROR) << "Receive webPageNotModified"; + return string(); + default: + UNREACHABLE(); + return string(); + } +} + WebPageId WebPagesManager::on_get_web_page(tl_object_ptr &&web_page_ptr, DialogId owner_dialog_id) { CHECK(web_page_ptr != nullptr); @@ -518,6 +544,7 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr page->duration_ = 0; } page->author_ = std::move(web_page->author_); + page->has_large_media_ = web_page->has_large_media_; if (web_page->document_ != nullptr) { int32 document_id = web_page->document_->get_id(); if (document_id == telegram_api::document::ID) { @@ -572,10 +599,9 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr update_web_page(std::move(page), web_page_id, false, false); return web_page_id; } - case telegram_api::webPageNotModified::ID: { + case telegram_api::webPageNotModified::ID: LOG(ERROR) << "Receive webPageNotModified"; return WebPageId(); - } default: UNREACHABLE(); return WebPageId(); @@ -751,22 +777,34 @@ void WebPagesManager::on_get_web_page_instant_view_view_count(WebPageId web_page } void WebPagesManager::on_get_web_page_by_url(const string &url, WebPageId web_page_id, bool from_database) { - auto &cached_web_page_id = url_to_web_page_id_[url]; - if (!from_database && G()->use_message_database()) { + if (url.empty()) { + return; + } + auto emplace_result = url_to_web_page_id_.emplace(url, std::make_pair(web_page_id, from_database)); + auto &it = emplace_result.first; + bool is_inserted = emplace_result.second; + if (from_database && !it->second.second) { + // database data can't replace non-database data + CHECK(!is_inserted); + return; + } + auto &cached_web_page_id = it->second.first; + if (!from_database && G()->use_message_database() && (cached_web_page_id != web_page_id || is_inserted)) { if (web_page_id.is_valid()) { - if (cached_web_page_id != web_page_id) { // not already saved - G()->td_db()->get_sqlite_pmc()->set(get_web_page_url_database_key(url), to_string(web_page_id.get()), Auto()); - } + G()->td_db()->get_sqlite_pmc()->set(get_web_page_url_database_key(url), to_string(web_page_id.get()), Auto()); } else { G()->td_db()->get_sqlite_pmc()->erase(get_web_page_url_database_key(url), Auto()); } } - if (cached_web_page_id.is_valid() && web_page_id.is_valid() && web_page_id != cached_web_page_id) { - LOG(ERROR) << "URL \"" << url << "\" preview is changed from " << cached_web_page_id << " to " << web_page_id; + if (!is_inserted) { + if (cached_web_page_id.is_valid() && !it->second.second && web_page_id.is_valid() && + web_page_id != cached_web_page_id) { + LOG(ERROR) << "URL \"" << url << "\" preview is changed from " << cached_web_page_id << " to " << web_page_id; + } + cached_web_page_id = web_page_id; + it->second.second = from_database; } - - cached_web_page_id = web_page_id; } void WebPagesManager::register_web_page(WebPageId web_page_id, MessageFullId message_full_id, const char *source) { @@ -802,14 +840,14 @@ void WebPagesManager::unregister_web_page(WebPageId web_page_id, MessageFullId m } } -void WebPagesManager::on_get_web_page_preview(const string &url, +void WebPagesManager::on_get_web_page_preview(unique_ptr &&options, tl_object_ptr &&message_media_ptr, Promise> &&promise) { CHECK(message_media_ptr != nullptr); int32 constructor_id = message_media_ptr->get_id(); if (constructor_id != telegram_api::messageMediaWebPage::ID) { if (constructor_id == telegram_api::messageMediaEmpty::ID) { - on_get_web_page_preview_success(url, WebPageId(), std::move(promise)); + on_get_web_page_preview_success(std::move(options), WebPageId(), std::move(promise)); return; } @@ -822,44 +860,67 @@ void WebPagesManager::on_get_web_page_preview(const string &url, auto web_page_id = on_get_web_page(std::move(message_media_web_page->webpage_), DialogId()); if (web_page_id.is_valid() && !have_web_page(web_page_id)) { - pending_get_web_pages_[web_page_id].emplace_back(url, std::move(promise)); + pending_get_web_pages_[web_page_id].emplace_back(std::move(options), std::move(promise)); return; } - on_get_web_page_preview_success(url, web_page_id, std::move(promise)); + on_get_web_page_preview_success(std::move(options), web_page_id, std::move(promise)); } -void WebPagesManager::on_get_web_page_preview_success(const string &url, WebPageId web_page_id, +void WebPagesManager::on_get_web_page_preview_success(unique_ptr &&options, + WebPageId web_page_id, Promise> &&promise) { CHECK(web_page_id == WebPageId() || have_web_page(web_page_id)); + CHECK(options != nullptr); + CHECK(options->link_preview_options_ != nullptr); - if (web_page_id.is_valid() && !url.empty()) { - on_get_web_page_by_url(url, web_page_id, true); + if (web_page_id.is_valid() && !options->first_url_.empty()) { + on_get_web_page_by_url(options->first_url_, web_page_id, true); } - promise.set_value(get_web_page_object(web_page_id)); + promise.set_value(get_web_page_object(web_page_id, options->link_preview_options_->force_small_media_, + options->link_preview_options_->force_large_media_, options->skip_confirmation_, + options->link_preview_options_->show_above_text_)); } void WebPagesManager::get_web_page_preview(td_api::object_ptr &&text, + td_api::object_ptr &&link_preview_options, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, formatted_text, - get_formatted_text(td_, DialogId(), std::move(text), false, true, true, true)); + TRY_RESULT_PROMISE( + promise, formatted_text, + get_formatted_text(td_, DialogId(), std::move(text), td_->auth_manager_->is_bot(), true, true, true)); - auto url = get_first_url(formatted_text); + if (link_preview_options == nullptr) { + link_preview_options = td_api::make_object(); + } + if (link_preview_options->is_disabled_) { + return promise.set_value(nullptr); + } + auto url = link_preview_options->url_.empty() ? get_first_url(formatted_text).str() : link_preview_options->url_; if (url.empty()) { return promise.set_value(nullptr); } - LOG(INFO) << "Trying to get web page preview for message \"" << formatted_text.text << '"'; + LOG(INFO) << "Trying to get web page preview for \"" << url << '"'; auto web_page_id = get_web_page_by_url(url); + bool skip_confirmation = is_visible_url(formatted_text, url); if (web_page_id.is_valid()) { - return promise.set_value(get_web_page_object(web_page_id)); + return promise.set_value(get_web_page_object(web_page_id, link_preview_options->force_small_media_, + link_preview_options->force_large_media_, skip_confirmation, + link_preview_options->show_above_text_)); + } + if (!link_preview_options->url_.empty()) { + formatted_text.text = link_preview_options->url_, formatted_text.entities.clear(); } + auto options = make_unique(); + options->first_url_ = std::move(url); + options->skip_confirmation_ = skip_confirmation; + options->link_preview_options_ = std::move(link_preview_options); td_->create_handler(std::move(promise)) ->send(formatted_text.text, get_input_message_entities(td_->contacts_manager_.get(), formatted_text.entities, "get_web_page_preview"), - std::move(url)); + std::move(options)); } void WebPagesManager::get_web_page_instant_view(const string &url, bool force_full, Promise &&promise) { @@ -869,11 +930,12 @@ void WebPagesManager::get_web_page_instant_view(const string &url, bool force_fu } auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { - if (it->second == WebPageId()) { + auto web_page_id = it->second.first; + if (web_page_id == WebPageId()) { // ignore negative caching return reload_web_page_by_url(url, std::move(promise)); } - return get_web_page_instant_view_impl(it->second, force_full, std::move(promise)); + return get_web_page_instant_view_impl(web_page_id, force_full, std::move(promise)); } auto new_promise = PromiseCreator::lambda( @@ -1071,6 +1133,14 @@ void WebPagesManager::update_web_page_instant_view_load_requests(WebPageId web_p } } +string WebPagesManager::get_web_page_url(WebPageId web_page_id) const { + const WebPage *web_page = get_web_page(web_page_id); + if (web_page != nullptr) { + return web_page->url_; + } + return string(); +} + WebPageId WebPagesManager::get_web_page_by_url(const string &url) const { if (url.empty()) { return WebPageId(); @@ -1079,7 +1149,7 @@ WebPageId WebPagesManager::get_web_page_by_url(const string &url) const { auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { LOG(INFO) << "Return " << it->second << " for the URL \"" << url << '"'; - return it->second; + return it->second.first; } LOG(INFO) << "Can't find web page identifier for the URL \"" << url << '"'; @@ -1094,7 +1164,7 @@ void WebPagesManager::get_web_page_by_url(const string &url, Promise auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { - return promise.set_value(WebPageId(it->second)); + return promise.set_value(WebPageId(it->second.first)); } load_web_page_by_url(url, std::move(promise)); @@ -1128,7 +1198,7 @@ void WebPagesManager::on_load_web_page_id_by_url_from_database(string url, strin auto it = url_to_web_page_id_.find(url); if (it != url_to_web_page_id_.end()) { // URL web page has already been loaded - return promise.set_value(WebPageId(it->second)); + return promise.set_value(WebPageId(it->second.first)); } if (!value.empty()) { auto web_page_id = WebPageId(to_integer(value)); @@ -1179,18 +1249,6 @@ void WebPagesManager::reload_web_page_by_url(const string &url, Promisecreate_handler(std::move(promise))->send(WebPageId(), url, 0); } -SecretInputMedia WebPagesManager::get_secret_input_media(WebPageId web_page_id) const { - if (!web_page_id.is_valid()) { - return SecretInputMedia{}; - } - - const WebPage *web_page = get_web_page(web_page_id); - if (web_page == nullptr) { - return SecretInputMedia{}; - } - return SecretInputMedia{nullptr, make_tl_object(web_page->url_)}; -} - bool WebPagesManager::have_web_page(WebPageId web_page_id) const { if (!web_page_id.is_valid()) { return false; @@ -1198,7 +1256,9 @@ bool WebPagesManager::have_web_page(WebPageId web_page_id) const { return get_web_page(web_page_id) != nullptr; } -tl_object_ptr WebPagesManager::get_web_page_object(WebPageId web_page_id) const { +tl_object_ptr WebPagesManager::get_web_page_object(WebPageId web_page_id, bool force_small_media, + bool force_large_media, bool skip_confirmation, + bool invert_media) const { if (!web_page_id.is_valid()) { return nullptr; } @@ -1297,11 +1357,42 @@ tl_object_ptr WebPagesManager::get_web_page_object(WebPageId we story_sender_dialog_id = web_page->story_full_ids_[0].get_dialog_id(); story_id = web_page->story_full_ids_[0].get_story_id(); } + auto show_large_media = [&] { + if (!web_page->has_large_media_) { + return false; + } + if (force_large_media) { + return true; + } + if (force_small_media) { + return false; + } + if (instant_view_version != 0 || web_page->document_.file_id.is_valid() || web_page->photo_.is_empty()) { + return true; + } + Slice type = web_page->type_; + if (type != "app" && type != "article" && type != "profile" && type != "telegram_bot" && + type != "telegram_channel" && type != "telegram_chat" && type != "telegram_channel_boost" && + type != "telegram_livestream" && type != "telegram_megagroup" && type != "telegram_user" && + type != "telegram_voicechat") { + return true; + } + if (web_page->site_name_.empty() && web_page->title_.empty() && web_page->description_.empty() && + web_page->author_.empty()) { + return true; + } + if (web_page->site_name_ == "Twitter" || web_page->site_name_ == "Facebook" || + web_page->site_name_ == "Instagram") { + return true; + } + return false; + }(); return make_tl_object( web_page->url_, web_page->display_url_, web_page->type_, web_page->site_name_, web_page->title_, get_formatted_text_object(description, true, duration == 0 ? std::numeric_limits::max() : duration), get_photo_object(td_->file_manager_.get(), web_page->photo_), web_page->embed_url_, web_page->embed_type_, web_page->embed_dimensions_.width, web_page->embed_dimensions_.height, web_page->duration_, web_page->author_, + web_page->has_large_media_, show_large_media, skip_confirmation, invert_media, web_page->document_.type == Document::Type::Animation ? td_->animations_manager_->get_animation_object(web_page->document_.file_id) : nullptr, @@ -1385,7 +1476,7 @@ void WebPagesManager::on_web_page_changed(WebPageId web_page_id, bool have_web_p auto requests = std::move(get_it->second); pending_get_web_pages_.erase(get_it); for (auto &request : requests) { - on_get_web_page_preview_success(request.first, have_web_page ? web_page_id : WebPageId(), + on_get_web_page_preview_success(std::move(request.first), have_web_page ? web_page_id : WebPageId(), std::move(request.second)); } } @@ -1829,6 +1920,20 @@ vector WebPagesManager::get_web_page_user_ids(WebPageId web_page_id) con return user_ids; } +vector WebPagesManager::get_web_page_channel_ids(WebPageId web_page_id) const { + const WebPage *web_page = get_web_page(web_page_id); + vector channel_ids; + if (web_page != nullptr && !web_page->story_full_ids_.empty()) { + for (auto story_full_id : web_page->story_full_ids_) { + auto dialog_id = story_full_id.get_dialog_id(); + if (dialog_id.get_type() == DialogType::Channel) { + channel_ids.push_back(dialog_id.get_channel_id()); + } + } + } + return channel_ids; +} + vector WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const { if (web_page == nullptr) { return vector(); diff --git a/lib/tgchat/ext/td/td/telegram/WebPagesManager.h b/lib/tgchat/ext/td/td/telegram/WebPagesManager.h index 357be3db..9800308f 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPagesManager.h +++ b/lib/tgchat/ext/td/td/telegram/WebPagesManager.h @@ -6,11 +6,11 @@ // #pragma once +#include "td/telegram/ChannelId.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/MessageFullId.h" -#include "td/telegram/SecretInputMedia.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -46,6 +46,14 @@ class WebPagesManager final : public Actor { WebPagesManager &operator=(WebPagesManager &&) = delete; ~WebPagesManager() final; + struct GetWebPagePreviewOptions { + string first_url_; + bool skip_confirmation_ = false; + td_api::object_ptr link_preview_options_; + }; + + static string get_web_page_url(const tl_object_ptr &web_page_ptr); + WebPageId on_get_web_page(tl_object_ptr &&web_page_ptr, DialogId owner_dialog_id); void on_get_web_page_by_url(const string &url, WebPageId web_page_id, bool from_database); @@ -60,26 +68,30 @@ class WebPagesManager final : public Actor { bool have_web_page_force(WebPageId web_page_id); - tl_object_ptr get_web_page_object(WebPageId web_page_id) const; + tl_object_ptr get_web_page_object(WebPageId web_page_id, bool force_small_media, + bool force_large_media, bool skip_confirmation, + bool invert_media) const; tl_object_ptr get_web_page_instant_view_object(WebPageId web_page_id) const; void get_web_page_preview(td_api::object_ptr &&text, + td_api::object_ptr &&link_preview_options, Promise> &&promise); void get_web_page_instant_view(const string &url, bool force_full, Promise &&promise); + string get_web_page_url(WebPageId web_page_id) const; + WebPageId get_web_page_by_url(const string &url) const; void get_web_page_by_url(const string &url, Promise &&promise); void reload_web_page_by_url(const string &url, Promise &&promise); - void on_get_web_page_preview(const string &url, tl_object_ptr &&message_media_ptr, + void on_get_web_page_preview(unique_ptr &&options, + tl_object_ptr &&message_media_ptr, Promise> &&promise); - SecretInputMedia get_secret_input_media(WebPageId web_page_id) const; - void on_binlog_web_page_event(BinlogEvent &&event); FileSourceId get_url_file_source_id(const string &url); @@ -92,6 +104,8 @@ class WebPagesManager final : public Actor { vector get_web_page_user_ids(WebPageId web_page_id) const; + vector get_web_page_channel_ids(WebPageId web_page_id) const; + void on_story_changed(StoryFullId story_full_id); private: @@ -124,7 +138,7 @@ class WebPagesManager final : public Actor { void on_pending_web_page_timeout(WebPageId web_page_id); - void on_get_web_page_preview_success(const string &url, WebPageId web_page_id, + void on_get_web_page_preview_success(unique_ptr &&options, WebPageId web_page_id, Promise> &&promise); void on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr &&page, int32 hash, @@ -185,12 +199,14 @@ class WebPagesManager final : public Actor { FlatHashMap, WebPageIdHash> web_page_messages_; - FlatHashMap>>>, WebPageIdHash> + FlatHashMap, Promise>>>, + WebPageIdHash> pending_get_web_pages_; FlatHashMap, StoryFullIdHash> story_web_pages_; - FlatHashMap url_to_web_page_id_; + FlatHashMap> url_to_web_page_id_; // URL -> [WebPageId, from_database] FlatHashMap url_to_file_source_id_; diff --git a/lib/tgchat/ext/td/td/telegram/cli.cpp b/lib/tgchat/ext/td/td/telegram/cli.cpp index a3291a1f..b624c41b 100644 --- a/lib/tgchat/ext/td/td/telegram/cli.cpp +++ b/lib/tgchat/ext/td/td/telegram/cli.cpp @@ -765,6 +765,10 @@ class CliClient final : public Actor { return to_integer(trim(std::move(str))); } + static int64 as_custom_emoji_id(Slice str) { + return to_integer(trim(str)); + } + static td_api::object_ptr as_location(const string &latitude, const string &longitude, const string &accuracy) { if (trim(latitude).empty() && trim(longitude).empty()) { @@ -919,40 +923,25 @@ class CliClient final : public Actor { arg.file_id = as_file_id(args); } - struct MessageReplyTo { - int64 message_id = 0; - // or - int64 user_id = 0; - int32 story_id = 0; - - static MessageReplyTo get_default() { - return {}; + td_api::object_ptr get_input_message_reply_to() const { + if (reply_message_id_ != 0) { + return td_api::make_object(reply_chat_id_, reply_message_id_, + as_formatted_text(reply_quote_)); } - - operator td_api::object_ptr() const { - if (message_id == 0 && user_id == 0 && story_id == 0) { - return nullptr; - } - if (message_id != 0) { - return td_api::make_object(-1, message_id); - } else { - return td_api::make_object(user_id, story_id); - } + if (reply_user_id_ != 0 || reply_story_id_ != 0) { + return td_api::make_object(reply_user_id_, reply_story_id_); } - }; + return nullptr; + } - void get_args(string &args, MessageReplyTo &arg) const { - if (!args.empty() && args != "0") { - if (args.find('_') == string::npos) { - arg.message_id = as_message_id(args); - } else { - string user_id; - string story_id; - std::tie(user_id, story_id) = split(args, '_'); - arg.user_id = as_user_id(user_id); - arg.story_id = as_story_id(story_id); - } + td_api::object_ptr get_link_preview_options() const { + if (!link_preview_is_disabled_ && link_preview_url_.empty() && !link_preview_force_small_media_ && + !link_preview_force_large_media_ && !link_preview_show_above_text_) { + return nullptr; } + return td_api::make_object( + link_preview_is_disabled_, link_preview_url_, link_preview_force_small_media_, link_preview_force_large_media_, + link_preview_show_above_text_); } struct ReportReason { @@ -993,6 +982,34 @@ class CliClient final : public Actor { } } + struct PremiumGiveawayParameters { + int64 chat_id = 0; + vector additional_chat_ids; + int32 date; + vector country_codes; + + operator td_api::object_ptr() const { + if (chat_id == 0) { + return nullptr; + } + return td_api::make_object(chat_id, vector(additional_chat_ids), date, + rand_bool(), vector(country_codes)); + } + }; + + void get_args(string &args, PremiumGiveawayParameters &arg) const { + auto parts = autosplit(args); + if (args.size() < 3) { + return; + } + arg.chat_id = as_chat_id(parts[0]); + arg.date = to_integer(parts[parts.size() - 2]); + arg.country_codes.push_back(parts[parts.size() - 1].str()); + for (size_t i = 1; i + 2 < parts.size(); i++) { + arg.additional_chat_ids.push_back(as_chat_id(parts[i])); + } + } + struct ChatPhotoSticker { int64 sticker_set_id = 0; int64 sticker_id = 0; @@ -1073,6 +1090,18 @@ class CliClient final : public Actor { } } + struct CustomEmojiId { + int64 custom_emoji_id = 0; + + operator int64() const { + return custom_emoji_id; + } + }; + + void get_args(string &args, CustomEmojiId &arg) const { + arg.custom_emoji_id = as_custom_emoji_id(args); + } + struct InputBackground { string background_file; // or @@ -1623,8 +1652,16 @@ class CliClient final : public Actor { static td_api::object_ptr as_formatted_text( const string &text, vector> entities = {}) { if (entities.empty() && !text.empty()) { - auto parsed_text = execute( - td_api::make_object(text, td_api::make_object(2))); + Slice unused_reserved_characters("#+-={}.!"); + string new_text; + for (auto c : text) { + if (unused_reserved_characters.find(c) != Slice::npos) { + new_text += '\\'; + } + new_text += c; + } + auto parsed_text = ClientActor::execute(td_api::make_object( + new_text, td_api::make_object(2))); if (parsed_text->get_id() == td_api::formattedText::ID) { return td_api::move_object_as(parsed_text); } @@ -2144,7 +2181,8 @@ class CliClient final : public Actor { } static td_api::object_ptr as_theme_parameters() { - return td_api::make_object(0, 1, -1, 256, 65536, 123456789, 65535); + return td_api::make_object(0, 1, -1, 256, 65536, 123456789, 65535, 5, 55, 555, 5555, 55555, + 555555); } static td_api::object_ptr as_background_fill(int32 color) { @@ -2233,13 +2271,12 @@ class CliClient final : public Actor { } void send_message(int64 chat_id, td_api::object_ptr &&input_message_content, - bool disable_notification = false, bool from_background = false, - MessageReplyTo message_reply_to = MessageReplyTo::get_default()) { + bool disable_notification = false, bool from_background = false) { auto id = send_request(td_api::make_object( - chat_id, message_thread_id_, message_reply_to, + chat_id, message_thread_id_, get_input_message_reply_to(), td_api::make_object(disable_notification, from_background, true, true, as_message_scheduling_state(schedule_date_), - Random::fast(1, 1000)), + Random::fast(1, 1000), only_preview_), nullptr, std::move(input_message_content))); if (id != 0) { query_id_to_send_message_info_[id].start_time = Time::now(); @@ -2248,7 +2285,7 @@ class CliClient final : public Actor { td_api::object_ptr default_message_send_options() const { return td_api::make_object( - false, false, false, true, as_message_scheduling_state(schedule_date_), Random::fast(1, 1000)); + false, false, false, true, as_message_scheduling_state(schedule_date_), Random::fast(1, 1000), only_preview_); } void send_get_background_url(td_api::object_ptr &&background_type) { @@ -3092,18 +3129,48 @@ class CliClient final : public Actor { send_request(td_api::make_object()); } else if (op == "gprs") { send_request(td_api::make_object()); - } else if (op == "cppr") { + } else if (op == "gpgcpo") { + ChatId boosted_chat_id; + get_args(args, boosted_chat_id); + send_request(td_api::make_object(boosted_chat_id)); + } else if (op == "cpgc") { + send_request(td_api::make_object(args)); + } else if (op == "apgc") { + send_request(td_api::make_object(args)); + } else if (op == "lppg") { + int64 giveaway_id; + PremiumGiveawayParameters parameters; + get_args(args, giveaway_id, parameters); + send_request(td_api::make_object(giveaway_id, parameters)); + } else if (op == "gpgi") { + 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 == "cppr" || op == "cpprb") { UserId user_id; string currency; int64 amount; - get_args(args, user_id, currency, amount); + ChatId boosted_chat_id; + get_args(args, user_id, currency, amount, boosted_chat_id); if (currency.empty()) { send_request(td_api::make_object( td_api::make_object(false, false))); - } else { + } else if (op == "cppr") { send_request(td_api::make_object( td_api::make_object(user_id, currency, amount))); + } else { + send_request(td_api::make_object( + td_api::make_object(boosted_chat_id, currency, amount, + vector{user_id}))); } + } else if (op == "cpprg") { + PremiumGiveawayParameters parameters; + string currency; + int64 amount; + get_args(args, parameters, currency, amount); + send_request(td_api::make_object( + td_api::make_object(parameters, currency, amount))); } else if (op == "atos") { send_request(td_api::make_object(args)); } else if (op == "gdli") { @@ -3359,6 +3426,8 @@ class CliClient final : public Actor { send_request(td_api::make_object()); } else if (op == "gdppces") { send_request(td_api::make_object()); + } else if (op == "gdbces") { + send_request(td_api::make_object()); } else if (op == "gsan") { send_request(td_api::make_object()); } else if (op == "asan") { @@ -3634,7 +3703,7 @@ class CliClient final : public Actor { string message_ids; get_args(args, chat_id, message_ids); send_request(td_api::make_object(chat_id, as_message_ids(message_ids), op == "dmr")); - } else if (op == "fm" || op == "cm" || op == "fmp" || op == "cmp") { + } else if (op == "fm" || op == "cm") { ChatId chat_id; ChatId from_chat_id; string message_ids; @@ -3642,12 +3711,14 @@ class CliClient final : public Actor { get_args(args, chat_id, from_chat_id, message_ids, message_thread_id); send_request(td_api::make_object( chat_id, message_thread_id, from_chat_id, as_message_ids(message_ids), default_message_send_options(), - op[0] == 'c', rand_bool(), op.back() == 'p')); + op[0] == 'c', rand_bool())); } else if (op == "resend") { ChatId chat_id; string message_ids; - get_args(args, chat_id, message_ids); - send_request(td_api::make_object(chat_id, as_message_ids(message_ids))); + string quote; + get_args(args, chat_id, message_ids, quote); + send_request( + td_api::make_object(chat_id, as_message_ids(message_ids), as_formatted_text(quote))); } else if (op == "csc" || op == "CreateSecretChat") { send_request(td_api::make_object(as_secret_chat_id(args))); } else if (op == "cnsc" || op == "CreateNewSecretChat") { @@ -3974,6 +4045,13 @@ class CliClient final : public Actor { send_request(td_api::make_object(args)); } else if (op == "jcbil") { send_request(td_api::make_object(args)); + } else if (op == "sq") { + string text; + string quote; + int32 quote_position; + get_args(args, text, quote, quote_position); + execute( + td_api::make_object(as_formatted_text(text), as_formatted_text(quote), quote_position)); } else if (op == "gte") { send_request(td_api::make_object(args)); } else if (op == "gtee") { @@ -4067,21 +4145,23 @@ class CliClient final : public Actor { if (op == "scdm" || op == "scdmt") { ChatId chat_id; MessageThreadId message_thread_id; - string reply_to_message_id; string message; if (op == "scdmt") { get_args(args, message_thread_id, args); } - get_args(args, chat_id, reply_to_message_id, message); + get_args(args, chat_id, message); td_api::object_ptr draft_message; - if (!reply_to_message_id.empty() || !message.empty()) { + auto reply_to = get_input_message_reply_to(); + if (reply_to != nullptr || !message.empty()) { vector> entities; - entities.push_back( - td_api::make_object(0, 1, td_api::make_object())); + if (!message.empty()) { + entities.push_back( + td_api::make_object(0, 1, td_api::make_object())); + } draft_message = td_api::make_object( - as_message_id(reply_to_message_id), 0, - td_api::make_object(as_formatted_text(message, std::move(entities)), true, - false)); + std::move(reply_to), 0, + td_api::make_object(as_formatted_text(message, std::move(entities)), + get_link_preview_options(), false)); } send_request( td_api::make_object(chat_id, message_thread_id, std::move(draft_message))); @@ -4290,18 +4370,17 @@ class CliClient final : public Actor { send_request(td_api::make_object(story_sender_chat_id, story_id, reason, text)); } else if (op == "assm") { send_request(td_api::make_object()); + } else if (op == "gacbs") { + send_request(td_api::make_object()); } else if (op == "gcbs") { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); - } else if (op == "cbc") { - ChatId chat_id; - get_args(args, chat_id); - send_request(td_api::make_object(chat_id)); } else if (op == "bc") { ChatId chat_id; - get_args(args, chat_id); - send_request(td_api::make_object(chat_id)); + string slot_ids; + get_args(args, chat_id, slot_ids); + send_request(td_api::make_object(chat_id, to_integers(slot_ids))); } else if (op == "gcbl") { ChatId chat_id; get_args(args, chat_id); @@ -4310,10 +4389,16 @@ class CliClient final : public Actor { send_request(td_api::make_object(args)); } else if (op == "gcb") { ChatId chat_id; + bool only_gift_codes; string offset; string limit; - get_args(args, chat_id); - send_request(td_api::make_object(chat_id, offset, as_limit(limit))); + get_args(args, chat_id, only_gift_codes, offset, limit); + send_request(td_api::make_object(chat_id, only_gift_codes, offset, as_limit(limit))); + } else if (op == "gucb") { + ChatId chat_id; + UserId user_id; + get_args(args, chat_id, user_id); + send_request(td_api::make_object(chat_id, user_id)); } else if (op == "gamb") { UserId user_id; get_args(args, user_id); @@ -4353,11 +4438,10 @@ class CliClient final : public Actor { ChatId chat_id; UserId bot_user_id; string url; - MessageReplyTo message_reply_to; MessageThreadId message_thread_id; - get_args(args, chat_id, bot_user_id, url, message_reply_to, message_thread_id); + get_args(args, chat_id, bot_user_id, url, message_thread_id); send_request(td_api::make_object(chat_id, bot_user_id, url, as_theme_parameters(), "android", - message_thread_id, message_reply_to)); + message_thread_id, get_input_message_reply_to())); } else if (op == "cwa") { int64 launch_id; get_args(args, launch_id); @@ -4381,7 +4465,8 @@ class CliClient final : public Actor { as_local_file("rgb.jpg"), nullptr, Auto(), 0, 0, as_caption(message), get_message_self_destruct_type(), has_spoiler_)); } else { - send_message(chat_id, td_api::make_object(as_formatted_text(message), false, true)); + send_message(chat_id, td_api::make_object(as_formatted_text(message), + get_link_preview_options(), true)); } } } else if (op == "ssm") { @@ -4394,6 +4479,8 @@ class CliClient final : public Actor { as_search_messages_filter(filter))); } else if (op == "ssd") { schedule_date_ = std::move(args); + } else if (op == "sop") { + only_preview_ = as_bool(args); } else if (op == "smti") { get_args(args, message_thread_id_); } else if (op == "shs") { @@ -4409,19 +4496,26 @@ class CliClient final : public Actor { string sender_id; get_args(args, chat_id, sender_id); send_request(td_api::make_object(chat_id, as_message_sender(sender_id))); - } else if (op == "sm" || op == "sms" || op == "smr" || op == "smf") { + } else if (op == "smr") { + get_args(args, reply_message_id_, reply_chat_id_); + } else if (op == "smrq") { + reply_quote_ = args; + } else if (op == "smrs") { + get_args(args, reply_user_id_, reply_story_id_); + } else if (op == "slpo") { + get_args(args, link_preview_is_disabled_, link_preview_url_, link_preview_force_small_media_, + link_preview_force_large_media_, link_preview_show_above_text_); + } else if (op == "sm" || op == "sms" || op == "smf") { ChatId chat_id; - MessageReplyTo message_reply_to; string message; get_args(args, chat_id, message); - if (op == "smr") { - get_args(message, message_reply_to, message); - } if (op == "smf") { message = string(5097, 'a'); } - send_message(chat_id, td_api::make_object(as_formatted_text(message), false, true), - op == "sms", false, message_reply_to); + send_message( + chat_id, + td_api::make_object(as_formatted_text(message), get_link_preview_options(), true), + op == "sms", false); } else if (op == "smce") { ChatId chat_id; get_args(args, chat_id); @@ -4433,26 +4527,19 @@ class CliClient final : public Actor { entities.push_back(td_api::make_object( 6, 5, td_api::make_object(5368324170671202286))); auto text = as_formatted_text("👍 😉 🧑‍🚒", std::move(entities)); - send_message(chat_id, td_api::make_object(std::move(text), false, true)); - } else if (op == "alm" || op == "almr") { + send_message(chat_id, + td_api::make_object(std::move(text), get_link_preview_options(), true)); + } else if (op == "alm") { ChatId chat_id; string sender_id; - MessageReplyTo message_reply_to; string message; get_args(args, chat_id, sender_id, message); - if (op == "almr") { - get_args(message, message_reply_to, message); - } send_request(td_api::make_object( - chat_id, as_message_sender(sender_id), message_reply_to, false, - td_api::make_object(as_formatted_text(message), false, true))); - } else if (op == "smap" || op == "smapr" || op == "smapp" || op == "smaprp") { + chat_id, as_message_sender(sender_id), get_input_message_reply_to(), false, + td_api::make_object(as_formatted_text(message), get_link_preview_options(), true))); + } else if (op == "smap") { ChatId chat_id; - MessageReplyTo message_reply_to; get_args(args, chat_id, args); - if (op == "smapr" || op == "smaprp") { - get_args(args, message_reply_to, args); - } auto input_message_contents = transform(full_split(args), [this](const string &photo) { td_api::object_ptr content = td_api::make_object( as_input_file(photo), nullptr, Auto(), 0, 0, as_caption(""), @@ -4460,9 +4547,9 @@ class CliClient final : public Actor { return content; }); send_request(td_api::make_object( - chat_id, message_thread_id_, message_reply_to, default_message_send_options(), - std::move(input_message_contents), op == "smapp" || op == "smaprp")); - } else if (op == "smad" || op == "smadp") { + chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(), + std::move(input_message_contents))); + } else if (op == "smad") { ChatId chat_id; get_args(args, chat_id, args); auto input_message_contents = transform(full_split(args), [](const string &document) { @@ -4470,9 +4557,8 @@ class CliClient final : public Actor { td_api::make_object(as_input_file(document), nullptr, true, as_caption("")); return content; }); - send_request(td_api::make_object(chat_id, message_thread_id_, nullptr, - default_message_send_options(), - std::move(input_message_contents), op.back() == 'p')); + send_request(td_api::make_object( + chat_id, message_thread_id_, nullptr, default_message_send_options(), std::move(input_message_contents))); } else if (op == "gmft") { auto r_message_file_head = read_file_str(args, 2 << 10); if (r_message_file_head.is_error()) { @@ -4503,7 +4589,7 @@ class CliClient final : public Actor { get_args(args, chat_id, message_id, message); send_request(td_api::make_object( chat_id, message_id, nullptr, - td_api::make_object(as_formatted_text(message), true, true))); + td_api::make_object(as_formatted_text(message), get_link_preview_options(), true))); } else if (op == "eman") { ChatId chat_id; MessageId message_id; @@ -4580,7 +4666,7 @@ class CliClient final : public Actor { MessageThreadId message_thread_id; string name; bool edit_icon_custom_emoji; - int64 icon_custom_emoji_id; + CustomEmojiId icon_custom_emoji_id; get_args(args, chat_id, message_thread_id, name, edit_icon_custom_emoji, icon_custom_emoji_id); send_request(td_api::make_object(chat_id, message_thread_id, name, edit_icon_custom_emoji, icon_custom_emoji_id)); @@ -5185,6 +5271,13 @@ class CliClient final : public Actor { InputChatPhoto input_chat_photo; get_args(args, chat_id, input_chat_photo); send_request(td_api::make_object(chat_id, input_chat_photo)); + } else if (op == "scac") { + ChatId chat_id; + int32 accent_color_id; + CustomEmojiId background_custom_emoji_id; + get_args(args, chat_id, accent_color_id, background_custom_emoji_id); + send_request( + td_api::make_object(chat_id, accent_color_id, background_custom_emoji_id)); } else if (op == "scmt") { ChatId chat_id; int32 auto_delete_time; @@ -5396,7 +5489,7 @@ class CliClient final : public Actor { } else if (op == "sese") { send_request(td_api::make_object(nullptr)); } else if (op == "ses") { - int64 custom_emoji_id; + CustomEmojiId custom_emoji_id; int32 expiration_date; get_args(args, custom_emoji_id, expiration_date); send_request(td_api::make_object( @@ -5585,7 +5678,7 @@ class CliClient final : public Actor { } else if (op == "groc") { send_request(td_api::make_object(as_limit(args))); } else if (op == "gwpp") { - send_request(td_api::make_object(as_formatted_text(args))); + send_request(td_api::make_object(as_formatted_text(args), get_link_preview_options())); } else if (op == "gwpiv") { string url; bool force_full; @@ -5751,6 +5844,11 @@ class CliClient final : public Actor { int64 profile_photo_id; get_args(args, profile_photo_id); send_request(td_api::make_object(profile_photo_id)); + } else if (op == "sac") { + int32 accent_color_id; + CustomEmojiId background_custom_emoji_id; + get_args(args, accent_color_id, background_custom_emoji_id); + send_request(td_api::make_object(accent_color_id, background_custom_emoji_id)); } else if (op == "gns") { int64 notification_sound_id; get_args(args, notification_sound_id); @@ -6184,11 +6282,23 @@ class CliClient final : public Actor { int64 my_id_ = 0; td_api::object_ptr authorization_state_; string schedule_date_; + bool only_preview_ = false; MessageThreadId message_thread_id_; bool has_spoiler_ = false; int32 message_self_destruct_time_ = 0; int64 opened_chat_id_ = 0; + ChatId reply_chat_id_; + MessageId reply_message_id_; + string reply_quote_; + UserId reply_user_id_; + StoryId reply_story_id_; + string link_preview_url_; + bool link_preview_is_disabled_ = false; + bool link_preview_force_small_media_ = false; + bool link_preview_force_large_media_ = false; + bool link_preview_show_above_text_ = false; + ConcurrentScheduler *scheduler_{nullptr}; bool use_test_dc_ = false; diff --git a/lib/tgchat/ext/td/td/telegram/files/FileStatsWorker.cpp b/lib/tgchat/ext/td/td/telegram/files/FileStatsWorker.cpp index 0dbc7f3d..562bb127 100644 --- a/lib/tgchat/ext/td/td/telegram/files/FileStatsWorker.cpp +++ b/lib/tgchat/ext/td/td/telegram/files/FileStatsWorker.cpp @@ -200,6 +200,7 @@ void FileStatsWorker::get_stats(bool need_all_files, bool split_by_owner_dialog_ return; } // LOG(INFO) << "Match! " << db_info.path << " from " << db_info.owner_dialog_id; + CHECK(it->second < full_infos.size()); auto &full_info = full_infos[it->second]; full_info.owner_dialog_id = db_info.owner_dialog_id; full_info.file_type = db_info.file_type; // database file_type is the correct one diff --git a/lib/tgchat/ext/td/td/telegram/misc.cpp b/lib/tgchat/ext/td/td/telegram/misc.cpp index e950bbf7..c543b251 100644 --- a/lib/tgchat/ext/td/td/telegram/misc.cpp +++ b/lib/tgchat/ext/td/td/telegram/misc.cpp @@ -172,7 +172,7 @@ string strip_empty_characters(string str, size_t max_length, bool strip_rtlo) { }(); CHECK(can_be_first_inited); - // replace all occurences of space characters with a space + // replace all occurrences of space characters with a space size_t i = 0; while (i < str.size() && !can_be_first[static_cast(str[i])]) { i++; diff --git a/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.cpp b/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.cpp index 81e013dc..0939d512 100644 --- a/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.cpp @@ -34,8 +34,8 @@ class AuthDataSharedImpl final : public AuthDataShared { return public_rsa_key_; } - mtproto::AuthKey get_auth_key() final { - string dc_key = G()->td_db()->get_binlog_pmc()->get(auth_key_key()); + static mtproto::AuthKey get_auth_key_for_dc(DcId dc_id) { + string dc_key = G()->td_db()->get_binlog_pmc()->get(get_auth_key_binlog_key(dc_id)); mtproto::AuthKey res; if (!dc_key.empty()) { @@ -44,8 +44,12 @@ class AuthDataSharedImpl final : public AuthDataShared { return res; } + mtproto::AuthKey get_auth_key() final { + return get_auth_key_for_dc(dc_id_); + } + void set_auth_key(const mtproto::AuthKey &auth_key) final { - G()->td_db()->get_binlog_pmc()->set(auth_key_key(), serialize(auth_key)); + G()->td_db()->get_binlog_pmc()->set(get_auth_key_binlog_key(dc_id_), serialize(auth_key)); log_auth_key(auth_key); notify(); @@ -69,11 +73,11 @@ class AuthDataSharedImpl final : public AuthDataShared { } void set_future_salts(const std::vector &future_salts) final { - G()->td_db()->get_binlog_pmc()->set(future_salts_key(), serialize(future_salts)); + G()->td_db()->get_binlog_pmc()->set(get_future_salts_binlog_key(dc_id_), serialize(future_salts)); } std::vector get_future_salts() final { - string future_salts = G()->td_db()->get_binlog_pmc()->get(future_salts_key()); + string future_salts = G()->td_db()->get_binlog_pmc()->get(get_future_salts_binlog_key(dc_id_)); std::vector res; if (!future_salts.empty()) { unserialize(res, future_salts).ensure(); @@ -88,11 +92,12 @@ class AuthDataSharedImpl final : public AuthDataShared { std::shared_ptr guard_; RwMutex rw_mutex_; - string auth_key_key() const { - return PSTRING() << "auth" << dc_id_.get_raw_id(); + static string get_auth_key_binlog_key(DcId dc_id) { + return PSTRING() << "auth" << dc_id.get_raw_id(); } - string future_salts_key() const { - return PSTRING() << "salt" << dc_id_.get_raw_id(); + + static string get_future_salts_binlog_key(DcId dc_id) { + return PSTRING() << "salt" << dc_id.get_raw_id(); } void notify() { @@ -114,6 +119,10 @@ class AuthDataSharedImpl final : public AuthDataShared { } }; +mtproto::AuthKey AuthDataShared::get_auth_key_for_dc(DcId dc_id) { + return AuthDataSharedImpl::get_auth_key_for_dc(dc_id); +} + std::shared_ptr AuthDataShared::create(DcId dc_id, std::shared_ptr public_rsa_key, std::shared_ptr guard) { return std::make_shared(dc_id, std::move(public_rsa_key), std::move(guard)); diff --git a/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.h b/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.h index 02290931..570e55bc 100644 --- a/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.h +++ b/lib/tgchat/ext/td/td/telegram/net/AuthDataShared.h @@ -43,6 +43,8 @@ class AuthDataShared { virtual void set_future_salts(const std::vector &future_salts) = 0; virtual std::vector get_future_salts() = 0; + static mtproto::AuthKey get_auth_key_for_dc(DcId dc_id); + static std::shared_ptr create(DcId dc_id, std::shared_ptr public_rsa_key, std::shared_ptr guard); }; diff --git a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp index 3e3bae64..57eb829e 100644 --- a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp @@ -1090,6 +1090,21 @@ void ConnectionCreator::start_up() { on_dc_options(std::move(dc_options)); } + if (G()->td_db()->get_binlog_pmc()->get("proxy_max_id") != "2" || + !G()->td_db()->get_binlog_pmc()->get(get_proxy_database_key(1)).empty()) { + // don't need to init proxies if they have never been added + init_proxies(); + } else { + max_proxy_id_ = 2; + } + + ref_cnt_guard_ = create_reference(-1); + + is_inited_ = true; + loop(); +} + +void ConnectionCreator::init_proxies() { auto proxy_info = G()->td_db()->get_binlog_pmc()->prefix_get("proxy"); auto it = proxy_info.find("_max_id"); if (it != proxy_info.end()) { @@ -1117,6 +1132,8 @@ void ConnectionCreator::start_up() { log_event_parse(proxies_[proxy_id], info.second).ensure(); if (proxies_[proxy_id].type() == Proxy::Type::None) { LOG_IF(ERROR, proxy_id != 1) << "Have empty proxy " << proxy_id; + G()->td_db()->get_binlog_pmc()->erase(get_proxy_database_key(proxy_id)); + G()->td_db()->get_binlog_pmc()->erase(get_proxy_used_database_key(proxy_id)); proxies_.erase(proxy_id); if (active_proxy_id_ == proxy_id) { set_active_proxy_id(0); @@ -1145,11 +1162,6 @@ void ConnectionCreator::start_up() { on_proxy_changed(true); } - - ref_cnt_guard_ = create_reference(-1); - - is_inited_ = true; - loop(); } void ConnectionCreator::hangup_shared() { diff --git a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h index 02014f89..ca489d33 100644 --- a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h +++ b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h @@ -205,6 +205,7 @@ class ConnectionCreator final : public NetQueryCallback { void hangup() final; void loop() final; + void init_proxies(); void save_dc_options(); Result do_request_connection(DcId dc_id, bool allow_media_only); Result, bool>> do_request_raw_connection(DcId dc_id, diff --git a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp index 43735a86..65112e27 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp @@ -187,13 +187,14 @@ void DcAuthManager::dc_loop(DcInfo &dc) { } } -void DcAuthManager::destroy(Promise<> promise) { +void DcAuthManager::destroy(Promise promise) { + need_destroy_auth_key_ = true; destroy_promise_ = std::move(promise); loop(); } void DcAuthManager::destroy_loop() { - if (!destroy_promise_) { + if (!need_destroy_auth_key_) { return; } bool is_ready{true}; @@ -204,6 +205,7 @@ void DcAuthManager::destroy_loop() { if (is_ready) { VLOG(dc) << "Destroy auth keys loop is ready, all keys are destroyed"; destroy_promise_.set_value(Unit()); + need_destroy_auth_key_ = false; } else { VLOG(dc) << "DC is not ready for destroying auth key"; } diff --git a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.h b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.h index 5d36efab..32847184 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.h +++ b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.h @@ -53,7 +53,8 @@ class DcAuthManager final : public NetQueryCallback { DcId main_dc_id_; bool need_check_authorization_is_ok_{false}; bool close_flag_{false}; - Promise<> destroy_promise_; + bool need_destroy_auth_key_{false}; + Promise destroy_promise_; DcInfo &get_dc(int32 dc_id); DcInfo *find_dc(int32 dc_id); diff --git a/lib/tgchat/ext/td/td/telegram/net/DcId.h b/lib/tgchat/ext/td/td/telegram/net/DcId.h index 205bc627..d73b42b0 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcId.h +++ b/lib/tgchat/ext/td/td/telegram/net/DcId.h @@ -17,8 +17,10 @@ class DcId { public: DcId() = default; + static constexpr int32 MAX_RAW_DC_ID = 1000; + static bool is_valid(int32 dc_id) { - return 1 <= dc_id && dc_id <= 1000; + return 1 <= dc_id && dc_id <= MAX_RAW_DC_ID; } static DcId main() { return DcId{MainDc, false}; diff --git a/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.cpp b/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.cpp index e9816cd1..cc55d2f4 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.cpp @@ -110,7 +110,7 @@ vector DcOptionsSet::find_all_connections(DcId dc_ if (!static_options.empty()) { options = std::move(static_options); } else { - bool have_ipv4 = std::any_of(options.begin(), options.end(), [](auto &v) { return !v.option->is_ipv6(); }); + bool have_ipv4 = any_of(options, [](const auto &v) { return !v.option->is_ipv6(); }); if (have_ipv4) { td::remove_if(options, [](auto &v) { return v.option->is_ipv6(); }); } @@ -122,13 +122,13 @@ vector DcOptionsSet::find_all_connections(DcId dc_ } if (prefer_ipv6) { - bool have_ipv6 = std::any_of(options.begin(), options.end(), [](auto &v) { return v.option->is_ipv6(); }); + bool have_ipv6 = any_of(options, [](const auto &v) { return v.option->is_ipv6(); }); if (have_ipv6) { td::remove_if(options, [](auto &v) { return !v.option->is_ipv6(); }); } } - bool have_media_only = std::any_of(options.begin(), options.end(), [](auto &v) { return v.option->is_media_only(); }); + bool have_media_only = any_of(options, [](const auto &v) { return v.option->is_media_only(); }); if (have_media_only) { td::remove_if(options, [](auto &v) { return !v.option->is_media_only(); }); } diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp index 0d31a257..de475f4f 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp @@ -151,7 +151,6 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) { dc.id_ = dc_id; decltype(common_public_rsa_key_) public_rsa_key; bool is_cdn = false; - bool need_destroy_key = false; if (dc_id.is_internal()) { public_rsa_key = common_public_rsa_key_; } else { @@ -173,16 +172,16 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) { int32 main_session_scheduler_id = G()->use_sqlite_pmc() ? -1 : G()->get_database_scheduler_id(); dc.main_session_ = create_actor_on_scheduler( PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":main", main_session_scheduler_id, session_count, auth_data, - true, raw_dc_id == main_dc_id_, use_pfs, false, false, is_cdn, need_destroy_key); + true, raw_dc_id == main_dc_id_, use_pfs, false, false, is_cdn); dc.upload_session_ = create_actor_on_scheduler( PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":upload", slow_net_scheduler_id, upload_session_count, - auth_data, false, false, use_pfs, false, true, is_cdn, need_destroy_key); + auth_data, false, false, use_pfs, false, true, is_cdn); dc.download_session_ = create_actor_on_scheduler( PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download", slow_net_scheduler_id, download_session_count, - auth_data, false, false, use_pfs, true, true, is_cdn, need_destroy_key); + auth_data, false, false, use_pfs, true, true, is_cdn); dc.download_small_session_ = create_actor_on_scheduler( PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download_small", slow_net_scheduler_id, - download_small_session_count, auth_data, false, false, use_pfs, true, true, is_cdn, need_destroy_key); + download_small_session_count, auth_data, false, false, use_pfs, true, true, is_cdn); dc.is_inited_ = true; if (dc_id.is_internal()) { send_closure_later(dc_auth_manager_, &DcAuthManager::add_dc, std::move(auth_data)); @@ -225,9 +224,10 @@ void NetQueryDispatcher::update_session_count() { std::lock_guard guard(main_dc_id_mutex_); int32 session_count = get_session_count(); bool use_pfs = get_use_pfs(); - for (size_t i = 1; i < MAX_DC_COUNT; i++) { - if (is_dc_inited(narrow_cast(i))) { - send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_options, session_count, use_pfs); + for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { + if (is_dc_inited(i)) { + send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_options, session_count, use_pfs, + need_destroy_auth_key_); send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_use_pfs, use_pfs); send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_use_pfs, use_pfs); send_closure_later(dcs_[i - 1].download_small_session_, &SessionMultiProxy::update_use_pfs, use_pfs); @@ -235,13 +235,19 @@ void NetQueryDispatcher::update_session_count() { } } void NetQueryDispatcher::destroy_auth_keys(Promise<> promise) { + for (int32 i = 1; i < DcId::MAX_RAW_DC_ID && i <= 5; i++) { + auto dc_id = DcId::internal(i); + if (!is_dc_inited(i) && !AuthDataShared::get_auth_key_for_dc(dc_id).empty()) { + wait_dc_init(dc_id, true).ignore(); + } + } + std::lock_guard guard(main_dc_id_mutex_); LOG(INFO) << "Destroy auth keys"; need_destroy_auth_key_ = true; - for (size_t i = 1; i < MAX_DC_COUNT; i++) { - if (is_dc_inited(narrow_cast(i)) && dcs_[i - 1].id_.is_internal()) { - send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_destroy_auth_key, - need_destroy_auth_key_); + for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { + if (is_dc_inited(i) && dcs_[i - 1].id_.is_internal()) { + send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::destroy_auth_key); } } send_closure_later(dc_auth_manager_, &DcAuthManager::destroy, std::move(promise)); @@ -250,8 +256,8 @@ void NetQueryDispatcher::destroy_auth_keys(Promise<> promise) { void NetQueryDispatcher::update_use_pfs() { std::lock_guard guard(main_dc_id_mutex_); bool use_pfs = get_use_pfs(); - for (size_t i = 1; i < MAX_DC_COUNT; i++) { - if (is_dc_inited(narrow_cast(i))) { + for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { + if (is_dc_inited(i)) { send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_use_pfs, use_pfs); send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_use_pfs, use_pfs); send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_use_pfs, use_pfs); @@ -262,8 +268,8 @@ void NetQueryDispatcher::update_use_pfs() { void NetQueryDispatcher::update_mtproto_header() { std::lock_guard guard(main_dc_id_mutex_); - for (size_t i = 1; i < MAX_DC_COUNT; i++) { - if (is_dc_inited(narrow_cast(i))) { + for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { + if (is_dc_inited(i)) { send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_mtproto_header); send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_mtproto_header); send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_mtproto_header); diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h index 36300029..ff07c30c 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h @@ -74,8 +74,7 @@ class NetQueryDispatcher { ActorOwn download_small_session_; ActorOwn upload_session_; }; - static constexpr size_t MAX_DC_COUNT = 1000; - std::array dcs_; + std::array dcs_; #if TD_EMSCRIPTEN // FIXME std::atomic main_dc_id_{2}; #else diff --git a/lib/tgchat/ext/td/td/telegram/net/Session.cpp b/lib/tgchat/ext/td/td/telegram/net/Session.cpp index dc483cb8..7cd34f99 100644 --- a/lib/tgchat/ext/td/td/telegram/net/Session.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/Session.cpp @@ -233,7 +233,7 @@ bool Session::PriorityQueue::empty() const { Session::Session(unique_ptr callback, std::shared_ptr shared_auth_data, int32 raw_dc_id, int32 dc_id, bool is_primary, bool is_main, bool use_pfs, bool persist_tmp_auth_key, bool is_cdn, - bool need_destroy, const mtproto::AuthKey &tmp_auth_key, + bool need_destroy_auth_key, const mtproto::AuthKey &tmp_auth_key, const vector &server_salts) : raw_dc_id_(raw_dc_id) , dc_id_(dc_id) @@ -241,9 +241,9 @@ Session::Session(unique_ptr callback, std::shared_ptr , is_main_(is_main) , persist_tmp_auth_key_(use_pfs && persist_tmp_auth_key) , is_cdn_(is_cdn) - , need_destroy_(need_destroy) { - VLOG(dc) << "Start connection " << tag("need_destroy", need_destroy_); - if (need_destroy_) { + , need_destroy_auth_key_(need_destroy_auth_key) { + VLOG(dc) << "Start connection " << tag("need_destroy_auth_key", need_destroy_auth_key_); + if (need_destroy_auth_key_) { use_pfs = false; CHECK(!is_cdn); } @@ -292,7 +292,7 @@ bool Session::is_high_loaded() { } bool Session::can_destroy_auth_key() const { - return need_destroy_; + return need_destroy_auth_key_; } void Session::start_up() { @@ -590,10 +590,8 @@ Status Session::on_pong() { break; } } - if (status.is_error()) { - return status; - } } + return status; } return Status::OK(); } @@ -637,7 +635,7 @@ void Session::on_closed(Status status) { auth_data_.drop_main_auth_key(); on_auth_key_updated(); on_session_failed(status.clone()); - } else if (need_destroy_) { + } else if (need_destroy_auth_key_) { LOG(WARNING) << "Session connection was closed, because main auth_key has been successfully destroyed"; auth_data_.drop_main_auth_key(); on_auth_key_updated(); @@ -1513,7 +1511,7 @@ void Session::loop() { if (cached_connection_timestamp_ < now - 10) { cached_connection_.reset(); } - if (!is_main_ && !has_queries() && !need_destroy_ && last_activity_timestamp_ < now - ACTIVITY_TIMEOUT) { + if (!is_main_ && !has_queries() && !need_destroy_auth_key_ && last_activity_timestamp_ < now - ACTIVITY_TIMEOUT) { on_session_failed(Status::OK()); } diff --git a/lib/tgchat/ext/td/td/telegram/net/Session.h b/lib/tgchat/ext/td/td/telegram/net/Session.h index 12d47789..ebf9860d 100644 --- a/lib/tgchat/ext/td/td/telegram/net/Session.h +++ b/lib/tgchat/ext/td/td/telegram/net/Session.h @@ -68,8 +68,9 @@ class Session final }; Session(unique_ptr callback, std::shared_ptr shared_auth_data, int32 raw_dc_id, int32 dc_id, - bool is_primary, bool is_main, bool use_pfs, bool persist_tmp_auth_key, bool is_cdn, bool need_destroy, - const mtproto::AuthKey &tmp_auth_key, const vector &server_salts); + bool is_primary, bool is_main, bool use_pfs, bool persist_tmp_auth_key, bool is_cdn, + bool need_destroy_auth_key, const mtproto::AuthKey &tmp_auth_key, + const vector &server_salts); void send(NetQueryPtr &&query); @@ -115,7 +116,7 @@ class Session final const bool is_main_; // true only for the primary Session(s) to the main DC const bool persist_tmp_auth_key_; const bool is_cdn_; - const bool need_destroy_; + const bool need_destroy_auth_key_; bool was_on_network_ = false; bool network_flag_ = false; bool online_flag_ = false; @@ -210,8 +211,9 @@ class Session final void on_online(bool online_flag); void on_logging_out(bool logging_out_flag); - void on_auth_key_updated() final; - void on_tmp_auth_key_updated() final; + void on_auth_key_updated(); + void on_tmp_auth_key_updated(); + void on_server_salt_updated() final; void on_server_time_difference_updated(bool force) final; diff --git a/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.cpp b/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.cpp index 75a12ef2..f8e6d768 100644 --- a/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.cpp @@ -22,7 +22,7 @@ SessionMultiProxy::~SessionMultiProxy() = default; SessionMultiProxy::SessionMultiProxy(int32 session_count, std::shared_ptr shared_auth_data, bool is_primary, bool is_main, bool use_pfs, bool allow_media_only, bool is_media, - bool is_cdn, bool need_destroy_auth_key) + bool is_cdn) : session_count_(session_count) , auth_data_(std::move(shared_auth_data)) , is_primary_(is_primary) @@ -30,8 +30,7 @@ SessionMultiProxy::SessionMultiProxy(int32 session_count, std::shared_ptr 100) { session_count_ = 100; } - LOG(INFO) << "Update " << get_name() << " session_count to " << session_count_; + LOG(INFO) << "Update session_count to " << session_count_; changed = true; } @@ -93,10 +96,16 @@ void SessionMultiProxy::update_options(int32 session_count, bool use_pfs) { bool old_pfs_flag = get_pfs_flag(); use_pfs_ = use_pfs; if (old_pfs_flag != get_pfs_flag()) { - LOG(INFO) << "Update " << get_name() << " use_pfs to " << use_pfs_; + LOG(INFO) << "Update use_pfs to " << use_pfs_; changed = true; } } + + if (need_destroy_auth_key) { + need_destroy_auth_key_ = need_destroy_auth_key; + LOG(WARNING) << "Destroy auth key"; + } + if (changed) { init(); } diff --git a/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.h b/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.h index 16f3f042..8dcf2903 100644 --- a/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.h +++ b/lib/tgchat/ext/td/td/telegram/net/SessionMultiProxy.h @@ -20,8 +20,7 @@ class SessionProxy; class SessionMultiProxy final : public Actor { public: SessionMultiProxy(int32 session_count, std::shared_ptr shared_auth_data, bool is_primary, - bool is_main, bool use_pfs, bool allow_media_only, bool is_media, bool is_cdn, - bool need_destroy_auth_key); + bool is_main, bool use_pfs, bool allow_media_only, bool is_media, bool is_cdn); SessionMultiProxy(const SessionMultiProxy &) = delete; SessionMultiProxy &operator=(const SessionMultiProxy &) = delete; ~SessionMultiProxy() final; @@ -31,10 +30,10 @@ class SessionMultiProxy final : public Actor { void update_session_count(int32 session_count); void update_use_pfs(bool use_pfs); - void update_options(int32 session_count, bool use_pfs); + void update_options(int32 session_count, bool use_pfs, bool need_destroy_auth_key); void update_mtproto_header(); - void update_destroy_auth_key(bool need_destroy_auth_key); + void destroy_auth_key(); private: int32 session_count_ = 0; diff --git a/lib/tgchat/ext/td/td/telegram/net/SessionProxy.cpp b/lib/tgchat/ext/td/td/telegram/net/SessionProxy.cpp index e46a609c..eb3158e2 100644 --- a/lib/tgchat/ext/td/td/telegram/net/SessionProxy.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/SessionProxy.cpp @@ -93,7 +93,7 @@ class SessionCallback final : public Session::Callback { SessionProxy::SessionProxy(unique_ptr callback, std::shared_ptr shared_auth_data, bool is_primary, bool is_main, bool allow_media_only, bool is_media, bool use_pfs, - bool persist_tmp_auth_key, bool is_cdn, bool need_destroy) + bool persist_tmp_auth_key, bool is_cdn, bool need_destroy_auth_key) : callback_(std::move(callback)) , auth_data_(std::move(shared_auth_data)) , is_primary_(is_primary) @@ -103,7 +103,7 @@ SessionProxy::SessionProxy(unique_ptr callback, std::shared_ptr( name, make_unique(actor_shared(this, session_generation_), dc_id, allow_media_only_, is_media_, hash), - auth_data_, raw_dc_id, int_dc_id, is_primary_, is_main_, use_pfs_, persist_tmp_auth_key_, is_cdn_, need_destroy_, - tmp_auth_key_, server_salts_); + auth_data_, raw_dc_id, int_dc_id, is_primary_, is_main_, use_pfs_, persist_tmp_auth_key_, is_cdn_, + need_destroy_auth_key_, tmp_auth_key_, server_salts_); } void SessionProxy::update_auth_key_state() { diff --git a/lib/tgchat/ext/td/td/telegram/net/SessionProxy.h b/lib/tgchat/ext/td/td/telegram/net/SessionProxy.h index af11c015..a30b4ff9 100644 --- a/lib/tgchat/ext/td/td/telegram/net/SessionProxy.h +++ b/lib/tgchat/ext/td/td/telegram/net/SessionProxy.h @@ -34,12 +34,13 @@ class SessionProxy final : public Actor { SessionProxy(unique_ptr callback, std::shared_ptr shared_auth_data, bool is_primary, bool is_main, bool allow_media_only, bool is_media, bool use_pfs, bool persist_tmp_auth_key, bool is_cdn, - bool need_destroy); + bool need_destroy_auth_key); void send(NetQueryPtr query); + void update_main_flag(bool is_main); + void update_mtproto_header(); - void update_destroy(bool need_destroy); private: unique_ptr callback_; @@ -54,7 +55,7 @@ class SessionProxy final : public Actor { mtproto::AuthKey tmp_auth_key_; std::vector server_salts_; bool is_cdn_; - bool need_destroy_; + bool need_destroy_auth_key_; ActorOwn session_; std::vector pending_queries_; uint64 session_generation_ = 1; diff --git a/lib/tgchat/ext/td/td/tl/TlObject.h b/lib/tgchat/ext/td/td/tl/TlObject.h index ed05f089..177d8f8d 100644 --- a/lib/tgchat/ext/td/td/tl/TlObject.h +++ b/lib/tgchat/ext/td/td/tl/TlObject.h @@ -191,7 +191,7 @@ using tl_object_ptr = tl::unique_ptr; * auto message_text = td::make_tl_object("Hello, world!!!", * td::td_api::array>()); * auto send_message_request = td::make_tl_object(chat_id, 0, nullptr, nullptr, nullptr, - * td::make_tl_object(std::move(message_text), false, true)); + * td::make_tl_object(std::move(message_text), nullptr, true)); * \endcode * * \tparam Type Type of the TL-object to construct. diff --git a/lib/tgchat/ext/td/tdactor/CMakeLists.txt b/lib/tgchat/ext/td/tdactor/CMakeLists.txt index a156384c..cf316b2f 100644 --- a/lib/tgchat/ext/td/tdactor/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdactor/CMakeLists.txt @@ -56,7 +56,7 @@ if (NOT CMAKE_CROSSCOMPILING) target_link_libraries(example PRIVATE tdactor) endif() -install(TARGETS tdactor EXPORT TdTargets +install(TARGETS tdactor EXPORT TdStaticTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) diff --git a/lib/tgchat/ext/td/tdactor/test/actors_simple.cpp b/lib/tgchat/ext/td/tdactor/test/actors_simple.cpp index e01b356e..d4825064 100644 --- a/lib/tgchat/ext/td/tdactor/test/actors_simple.cpp +++ b/lib/tgchat/ext/td/tdactor/test/actors_simple.cpp @@ -166,7 +166,7 @@ TEST(Actors, simple_pass_event_arguments) { // Var-->LvalueRef // Var-->LvalueRef (Delayed) - // CE or stange behaviour + // CE or strange behaviour // Var-->Value sb.clear(); @@ -292,7 +292,6 @@ class OpenClose final : public td::Actor { cnt_--; yield(); } else { - td::unlink(file_name).ignore(); td::Scheduler::instance()->finish(); } } @@ -310,6 +309,7 @@ TEST(Actors, open_close) { while (scheduler.run_main(10)) { } scheduler.finish(); + td::unlink("server").ignore(); } class MsgActor : public td::Actor { diff --git a/lib/tgchat/ext/td/tddb/CMakeLists.txt b/lib/tgchat/ext/td/tddb/CMakeLists.txt index 036f0ca6..04325f56 100644 --- a/lib/tgchat/ext/td/tddb/CMakeLists.txt +++ b/lib/tgchat/ext/td/tddb/CMakeLists.txt @@ -56,7 +56,7 @@ if (NOT CMAKE_CROSSCOMPILING) target_link_libraries(binlog_dump PRIVATE tddb) endif() -install(TARGETS tddb EXPORT TdTargets +install(TARGETS tddb EXPORT TdStaticTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) diff --git a/lib/tgchat/ext/td/tddb/td/db/BinlogKeyValue.h b/lib/tgchat/ext/td/tddb/td/db/BinlogKeyValue.h index 6b6e6842..0665aefa 100644 --- a/lib/tgchat/ext/td/tddb/td/db/BinlogKeyValue.h +++ b/lib/tgchat/ext/td/tddb/td/db/BinlogKeyValue.h @@ -14,6 +14,7 @@ #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" #include "td/utils/HashTableUtils.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -83,6 +84,10 @@ class BinlogKeyValue final : public KeyValueSyncInterface { [&](const BinlogEvent &binlog_event) { Event event; event.parse(TlParser(binlog_event.get_data())); + if (event.key.empty()) { + LOG(ERROR) << "Have event with empty key"; + return; + } map_.emplace(event.key.str(), std::make_pair(event.value.str(), binlog_event.id_)); }, std::move(db_key), DbKey::empty(), scheduler_id)); @@ -104,6 +109,10 @@ class BinlogKeyValue final : public KeyValueSyncInterface { void external_init_handle(const BinlogEvent &binlog_event) { Event event; event.parse(TlParser(binlog_event.get_data())); + if (event.key.empty()) { + LOG(ERROR) << "Have external event with empty key"; + return; + } map_.emplace(event.key.str(), std::make_pair(event.value.str(), binlog_event.id_)); } @@ -121,6 +130,7 @@ class BinlogKeyValue final : public KeyValueSyncInterface { SeqNo set(string key, string value) final { auto lock = rw_mutex_.lock_write().move_as_ok(); uint64 old_event_id = 0; + CHECK(!key.empty()); auto it_ok = map_.emplace(key, std::make_pair(value, 0)); if (!it_ok.second) { if (it_ok.first->second.first == value) { @@ -221,9 +231,10 @@ class BinlogKeyValue final : public KeyValueSyncInterface { return res; } - std::unordered_map> get_all() final { + FlatHashMap get_all() final { auto lock = rw_mutex_.lock_write().move_as_ok(); - std::unordered_map> res; + FlatHashMap res; + res.reserve(map_.size()); for (const auto &kv : map_) { res.emplace(kv.first, kv.second.first); } @@ -248,6 +259,7 @@ class BinlogKeyValue final : public KeyValueSyncInterface { seq_no++; } } + template friend class BinlogKeyValue; @@ -256,7 +268,7 @@ class BinlogKeyValue final : public KeyValueSyncInterface { } private: - std::unordered_map, Hash> map_; + FlatHashMap> map_; std::shared_ptr binlog_; RwMutex rw_mutex_; int32 magic_ = MAGIC; diff --git a/lib/tgchat/ext/td/tddb/td/db/KeyValueSyncInterface.h b/lib/tgchat/ext/td/tddb/td/db/KeyValueSyncInterface.h index 111882fb..ed29c6b9 100644 --- a/lib/tgchat/ext/td/tddb/td/db/KeyValueSyncInterface.h +++ b/lib/tgchat/ext/td/tddb/td/db/KeyValueSyncInterface.h @@ -7,6 +7,7 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" #include "td/utils/HashTableUtils.h" #include "td/utils/Promise.h" #include "td/utils/Slice.h" @@ -36,7 +37,7 @@ class KeyValueSyncInterface { virtual std::unordered_map> prefix_get(Slice prefix) = 0; - virtual std::unordered_map> get_all() = 0; + virtual FlatHashMap get_all() = 0; virtual SeqNo erase(const string &key) = 0; diff --git a/lib/tgchat/ext/td/tdnet/CMakeLists.txt b/lib/tgchat/ext/td/tdnet/CMakeLists.txt index e14f3500..d490f87f 100644 --- a/lib/tgchat/ext/td/tdnet/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdnet/CMakeLists.txt @@ -84,7 +84,7 @@ if (APPLE_WATCH) target_link_libraries(tdnet PRIVATE ${FOUNDATION_LIBRARY}) endif() -install(TARGETS tdnet EXPORT TdTargets +install(TARGETS tdnet EXPORT TdStaticTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) diff --git a/lib/tgchat/ext/td/tdnet/td/net/HttpConnectionBase.cpp b/lib/tgchat/ext/td/tdnet/td/net/HttpConnectionBase.cpp index df8a7e13..6d6ba13d 100644 --- a/lib/tgchat/ext/td/tdnet/td/net/HttpConnectionBase.cpp +++ b/lib/tgchat/ext/td/tdnet/td/net/HttpConnectionBase.cpp @@ -102,7 +102,7 @@ void HttpConnectionBase::loop() { LOG(DEBUG) << "Can read from the connection"; auto r = fd_.flush_read(); if (r.is_error()) { - if (!begins_with(r.error().message(), "SSL error {336134278")) { // if error is not yet outputed + if (!begins_with(r.error().message(), "SSL error {336134278")) { // if error is not yet outputted LOG(INFO) << "Receive flush_read error: " << r.error(); } on_error(Status::Error(r.error().public_message())); diff --git a/lib/tgchat/ext/td/tdutils/CMakeLists.txt b/lib/tgchat/ext/td/tdutils/CMakeLists.txt index e7b4b788..54f363e5 100644 --- a/lib/tgchat/ext/td/tdutils/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdutils/CMakeLists.txt @@ -24,6 +24,9 @@ if (ZLIB_FOUND) endif() endif() +if (NOT CRC32C_FOUND) + find_package(Crc32c QUIET) +endif() if (CRC32C_FOUND) set(TD_HAVE_CRC32C 1) endif() @@ -406,7 +409,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") target_link_libraries(tdutils PUBLIC atomic) endif() -install(TARGETS tdutils EXPORT TdTargets +install(TARGETS tdutils EXPORT TdStaticTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) diff --git a/lib/tgchat/ext/td/tdutils/td/utils/FloodControlStrict.h b/lib/tgchat/ext/td/tdutils/td/utils/FloodControlStrict.h index e2168871..56ba71c4 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/FloodControlStrict.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/FloodControlStrict.h @@ -12,7 +12,7 @@ namespace td { -// More strict implementaions of flood control than FloodControlFast. +// More strict implementations of flood control than FloodControlFast. // Should be just fine for small counters. class FloodControlStrict { public: diff --git a/lib/tgchat/ext/td/tdutils/td/utils/Hash.h b/lib/tgchat/ext/td/tdutils/td/utils/Hash.h index 58c10a73..85ad60d2 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/Hash.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/Hash.h @@ -15,7 +15,7 @@ #include namespace td { -// A simple wrapper for absl::flat_hash_map, std::unordered_map and probably some our implementaion of hash map in +// A simple wrapper for absl::flat_hash_map, std::unordered_map and probably some our implementation of hash map in // the future // We will introduce out own Hashing utility like an absl one. diff --git a/lib/tgchat/ext/td/tdutils/td/utils/ObjectPool.h b/lib/tgchat/ext/td/tdutils/td/utils/ObjectPool.h index 8f6f4c28..a535bb57 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/ObjectPool.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/ObjectPool.h @@ -14,7 +14,7 @@ #include namespace td { -// It is draft object pool implementaion +// It is draft object pool implementation // // Compared with std::shared_ptr: // + WeakPtr are much faster. Just pointer copy. No barriers, no atomics. diff --git a/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.cpp b/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.cpp index 941394e1..2d86f111 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.cpp @@ -8,7 +8,6 @@ #include "td/utils/misc.h" #include "td/utils/port/thread_local.h" -#include "td/utils/Slice.h" #include #include @@ -51,6 +50,23 @@ StringBuilder &StringBuilder::operator<<(Slice slice) { return *this; } +void StringBuilder::append_char(size_t count, char c) { + if (unlikely(!reserve(count))) { + if (end_ptr_ < current_ptr_) { + on_error(); + return; + } + auto available_size = static_cast(end_ptr_ + RESERVED_SIZE - 1 - current_ptr_); + if (count > available_size) { + error_flag_ = true; + count = available_size; + } + } + + MutableSlice(current_ptr_, count).fill(c); + current_ptr_ += count; +} + template static char *print_uint(char *current_ptr, T x) { if (x < 100) { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.h b/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.h index 3431a01a..ce82bd99 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/StringBuilder.h @@ -44,6 +44,8 @@ class StringBuilder { *current_ptr_++ = c; } + void append_char(size_t count, char c); + MutableCSlice as_cslice() { if (current_ptr_ >= end_ptr_ + RESERVED_SIZE) { std::abort(); // shouldn't happen diff --git a/lib/tgchat/ext/td/tdutils/td/utils/TlStorerToString.h b/lib/tgchat/ext/td/tdutils/td/utils/TlStorerToString.h index 7b487313..3e033086 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/TlStorerToString.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/TlStorerToString.h @@ -9,171 +9,137 @@ #include "td/utils/common.h" #include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" -#include "td/utils/SliceBuilder.h" +#include "td/utils/StackAllocator.h" +#include "td/utils/StringBuilder.h" #include "td/utils/UInt.h" namespace td { class TlStorerToString { - string result; - size_t shift = 0; - - void store_field_begin(const char *name) { - result.append(shift, ' '); - if (name && name[0]) { - result += name; - result += " = "; + decltype(StackAllocator::alloc(0)) buffer_ = StackAllocator::alloc(1 << 14); + StringBuilder sb_ = StringBuilder(buffer_.as_slice(), true); + size_t shift_ = 0; + + void store_field_begin(Slice name) { + sb_.append_char(shift_, ' '); + if (!name.empty()) { + sb_ << name << " = "; } } void store_field_end() { - result += '\n'; - } - - void store_long(int64 value) { - result += (PSLICE() << value).c_str(); + sb_.push_back('\n'); } void store_binary(Slice data) { static const char *hex = "0123456789ABCDEF"; - result.append("{ ", 2); + sb_ << "{ "; for (auto c : data) { unsigned char byte = c; - result += hex[byte >> 4]; - result += hex[byte & 15]; - result += ' '; + sb_.push_back(hex[byte >> 4]); + sb_.push_back(hex[byte & 15]); + sb_.push_back(' '); } - result += '}'; + sb_.push_back('}'); } public: TlStorerToString() = default; TlStorerToString(const TlStorerToString &) = delete; TlStorerToString &operator=(const TlStorerToString &) = delete; + TlStorerToString(TlStorerToString &&) = delete; + TlStorerToString &operator=(TlStorerToString &&) = delete; - void store_field(const char *name, bool value) { + void store_field(Slice name, const string &value) { store_field_begin(name); - result += (value ? "true" : "false"); + sb_.push_back('"'); + sb_ << value; + sb_.push_back('"'); store_field_end(); } - void store_field(const char *name, int32 value) { - store_field(name, static_cast(value)); - } - - void store_field(const char *name, int64 value) { + void store_field(Slice name, const SecureString &value) { store_field_begin(name); - store_long(value); - store_field_end(); - } - - void store_field(const char *name, double value) { - store_field_begin(name); - result += (PSLICE() << value).c_str(); - store_field_end(); - } - - void store_field(const char *name, const char *value) { - store_field_begin(name); - result += value; - store_field_end(); - } - - void store_field(const char *name, const string &value) { - store_field_begin(name); - result += '"'; - result += value; - result += '"'; - store_field_end(); - } - - void store_field(const char *name, const SecureString &value) { - store_field_begin(name); - result.append(""); + sb_ << ""; store_field_end(); } template - void store_field(const char *name, const T &value) { + void store_field(Slice name, const T &value) { store_field_begin(name); - result.append(value.data(), value.size()); + sb_ << value; store_field_end(); } - void store_bytes_field(const char *name, const SecureString &value) { + void store_bytes_field(Slice name, const SecureString &value) { store_field_begin(name); - result.append(""); + sb_ << ""; store_field_end(); } template - void store_bytes_field(const char *name, const BytesT &value) { + void store_bytes_field(Slice name, const BytesT &value) { static const char *hex = "0123456789ABCDEF"; store_field_begin(name); - result.append("bytes ["); - store_long(static_cast(value.size())); - result.append("] { "); + sb_ << "bytes [" << value.size() << "] { "; size_t len = min(static_cast(64), value.size()); for (size_t i = 0; i < len; i++) { int b = value[static_cast(i)] & 0xff; - result += hex[b >> 4]; - result += hex[b & 15]; - result += ' '; + sb_.push_back(hex[b >> 4]); + sb_.push_back(hex[b & 15]); + sb_.push_back(' '); } if (len < value.size()) { - result.append("..."); + sb_ << "..."; } - result += '}'; + sb_.push_back('}'); store_field_end(); } template - void store_object_field(const char *name, const ObjectT *value) { + void store_object_field(CSlice name, const ObjectT *value) { if (value == nullptr) { - store_field(name, "null"); + store_field(name, Slice("null")); } else { - value->store(*this, name); + value->store(*this, name.c_str()); } } - void store_field(const char *name, const UInt128 &value) { + void store_field(Slice name, const UInt128 &value) { store_field_begin(name); store_binary(as_slice(value)); store_field_end(); } - void store_field(const char *name, const UInt256 &value) { + void store_field(Slice name, const UInt256 &value) { store_field_begin(name); store_binary(as_slice(value)); store_field_end(); } - void store_vector_begin(const char *field_name, size_t vector_size) { + void store_vector_begin(Slice field_name, size_t vector_size) { store_field_begin(field_name); - result += "vector["; - result += (PSLICE() << vector_size).c_str(); - result += "] {\n"; - shift += 2; + sb_ << "vector[" << vector_size << "] {\n"; + shift_ += 2; } - void store_class_begin(const char *field_name, const char *class_name) { - store_field_begin(field_name); - result += class_name; - result += " {\n"; - shift += 2; + void store_class_begin(const char *field_name, Slice class_name) { + store_field_begin(Slice(field_name)); + sb_ << class_name << " {\n"; + shift_ += 2; } void store_class_end() { - CHECK(shift >= 2); - shift -= 2; - result.append(shift, ' '); - result += "}\n"; + CHECK(shift_ >= 2); + shift_ -= 2; + sb_.append_char(shift_, ' '); + sb_ << "}\n"; } string move_as_string() { - return std::move(result); + return sb_.as_cslice().str(); } }; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/algorithm.h b/lib/tgchat/ext/td/tdutils/td/utils/algorithm.h index 00aae8f6..1b4f0949 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/algorithm.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/algorithm.h @@ -86,6 +86,54 @@ bool remove(V &v, const T &value) { return true; } +template +void add_to_top(V &v, size_t max_size, T value) { + size_t size = v.size(); + size_t i; + for (i = 0; i < size; i++) { + if (v[i] == value) { + value = std::move(v[i]); + break; + } + } + if (i == size) { + if (size < max_size || i == 0) { + v.emplace_back(value); + } else { + i--; + } + } + while (i > 0) { + v[i] = std::move(v[i - 1]); + i--; + } + v[0] = std::move(value); +} + +template +void add_to_top_if(V &v, size_t max_size, T value, const F &is_equal_to_value) { + size_t size = v.size(); + size_t i; + for (i = 0; i < size; i++) { + if (is_equal_to_value(v[i])) { + value = std::move(v[i]); + break; + } + } + if (i == size) { + if (size < max_size || i == 0) { + v.emplace_back(value); + } else { + i--; + } + } + while (i > 0) { + v[i] = std::move(v[i - 1]); + i--; + } + v[0] = std::move(value); +} + template void unique(V &v) { if (v.empty()) { @@ -118,6 +166,16 @@ bool contains(const V &v, const T &value) { return false; } +template +bool any_of(const V &v, F &&f) { + for (const auto &x : v) { + if (f(x)) { + return true; + } + } + return false; +} + template bool all_of(const V &v, F &&f) { for (const auto &x : v) { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/filesystem.h b/lib/tgchat/ext/td/tdutils/td/utils/filesystem.h index f036674f..dd48bff4 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/filesystem.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/filesystem.h @@ -28,7 +28,7 @@ Status write_file(CSlice to, Slice data, WriteFileOptions options = {}) TD_WARN_ string clean_filename(CSlice name); -// writes data to file and ensures that the file is either fully overriden, or is left intact +// writes data to file and ensures that the file is either fully overridden, or is left intact // uses path_tmp to temporary store data, then calls rename Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp = {}); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/logging.cpp b/lib/tgchat/ext/td/tdutils/td/utils/logging.cpp index cf73f5df..b31c0450 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/logging.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/logging.cpp @@ -191,8 +191,7 @@ class DefaultLog final : public LogInterface { #elif TD_EMSCRIPTEN switch (log_level) { case VERBOSITY_NAME(FATAL): - emscripten_log(EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_FUNC_PARAMS, "%s", - slice.c_str()); + emscripten_log(EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK, "%s", slice.c_str()); EM_ASM(throw(UTF8ToString($0)), slice.c_str()); break; case VERBOSITY_NAME(ERROR): diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp index 68c62ad1..550a7caa 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp @@ -578,7 +578,7 @@ Status get_socket_pending_error(const NativeFd &fd, WSAOVERLAPPED *overlapped, S DWORD flags = 0; BOOL success = WSAGetOverlappedResult(fd.socket(), overlapped, &num_bytes, false, &flags); if (success) { - LOG(ERROR) << "WSAGetOverlappedResult succeded after " << iocp_error; + LOG(ERROR) << "WSAGetOverlappedResult succeeded after " << iocp_error; return iocp_error; } return OS_SOCKET_ERROR(PSLICE() << "Error on " << fd); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/unicode.cpp b/lib/tgchat/ext/td/tdutils/td/utils/unicode.cpp index df9a22a9..6d809e19 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/unicode.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/unicode.cpp @@ -12,137 +12,137 @@ namespace td { // list of [(range_begin << 5) + range_type] static const uint32 unicode_simple_category_ranges[] = { - 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441, - 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880, - 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032, - 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024, - 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744, - 43809, 43840, 44033, 45344, 47617, 48480, 48609, 48736, 50177, 51552, 52226, 52544, - 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248, - 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809, - 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817, - 66848, 67585, 68384, 68609, 68960, 69121, 69888, 69921, 70112, 70657, 72000, 73857, - 75584, 75681, 75712, 76289, 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985, - 78240, 78305, 78368, 78433, 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777, - 79808, 80321, 80352, 80769, 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728, - 81793, 81824, 82081, 82272, 82401, 82464, 82529, 83232, 83265, 83488, 83521, 83584, - 83617, 83680, 83713, 83776, 84769, 84896, 84929, 84960, 85186, 85504, 85569, 85664, - 86177, 86464, 86497, 86592, 86625, 87328, 87361, 87584, 87617, 87680, 87713, 87872, - 87969, 88000, 88577, 88608, 89089, 89152, 89282, 89600, 89889, 89920, 90273, 90528, - 90593, 90656, 90721, 91424, 91457, 91680, 91713, 91776, 91809, 91968, 92065, 92096, - 93057, 93120, 93153, 93248, 93378, 93696, 93729, 93763, 93952, 94305, 94336, 94369, - 94560, 94657, 94752, 94785, 94912, 95009, 95072, 95105, 95136, 95169, 95232, 95329, - 95392, 95489, 95584, 95681, 96064, 96769, 96800, 97474, 97795, 97888, 98465, 98720, - 98753, 98848, 98881, 99616, 99649, 100160, 100257, 100288, 101121, 101216, 101281, 101312, - 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432, 102561, 102816, 102849, 102944, - 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384, 105377, 105440, 105473, 105536, - 105666, 105984, 106017, 106080, 106625, 106912, 106945, 107040, 107073, 108384, 108449, 108480, - 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762, 110083, 110368, 110401, 110592, - 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576, 112641, 112864, 113858, 114176, - 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568, 118817, 118880, 118913, 118944, - 118977, 119136, 119169, 119936, 119969, 120000, 120033, 120352, 120385, 120448, 120737, 120768, - 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856, 122881, 122912, 123906, 124227, - 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073, 132448, 133089, 133122, 133440, - 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368, 134593, 134688, 134817, 135232, - 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472, 137633, 137664, 137729, 139104, - 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304, 150337, 150464, 150529, 151840, - 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568, 153601, 153632, 153665, 153792, - 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560, 159011, 159648, 159745, 160256, - 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324, 184353, 185184, 185345, 187744, - 187843, 187937, 188192, 188417, 188992, 189409, 190016, 190465, 191040, 191489, 191904, 191937, - 192032, 192513, 194176, 195297, 195328, 195457, 195488, 195586, 195904, 196099, 196416, 197122, - 197440, 197633, 200480, 200705, 200864, 200929, 202016, 202049, 202080, 202241, 204480, 204801, - 205792, 207042, 207361, 208320, 208385, 208544, 208897, 210304, 210433, 211264, 211458, 211779, - 211808, 212993, 213728, 214017, 215712, 217090, 217408, 217602, 217920, 218337, 218368, 221345, - 222848, 223393, 223648, 223746, 224064, 225377, 226336, 226753, 226818, 227137, 228544, 229377, - 230528, 231426, 231744, 231841, 231938, 232257, 233408, 233473, 233760, 233985, 235360, 235425, - 235520, 236833, 236960, 236993, 237184, 237217, 237280, 237377, 237408, 237569, 243712, 245761, - 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865, - 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161, - 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825, - 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792, - 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657, - 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681, - 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595, - 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 367776, 367969, 368096, 368193, - 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081, 370112, 370177, 371968, 372193, - 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273, 374496, 374529, 374752, 374785, - 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337, 378368, 393220, 393248, 393377, - 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105, 395168, 395297, 398048, 398241, - 398336, 398369, 401248, 401281, 401408, 401569, 402944, 402977, 405984, 406083, 406208, 406529, - 407552, 409089, 409600, 410627, 410944, 411907, 412160, 412195, 412672, 413699, 414016, 415267, - 415744, 425985, 636928, 638977, 1348000, 1350145, 1351616, 1351681, 1360288, 1360385, 1360898, 1361217, - 1361280, 1361921, 1363424, 1363937, 1364928, 1364993, 1367235, 1367552, 1368801, 1369088, 1369153, 1372448, - 1372513, 1374560, 1374721, 1374784, 1374817, 1374848, 1374881, 1375040, 1375809, 1376320, 1376353, 1376448, - 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016, 1382914, 1383232, - 1384001, 1384192, 1384289, 1384320, 1384353, 1384416, 1384450, 1384769, 1385664, 1385985, 1386720, 1387521, - 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130, 1392449, 1392608, - 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448, 1396545, 1396576, - 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816, 1398849, 1398880, - 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312, 1401377, 1401568, - 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404224, 1404417, 1408096, 1408514, 1408832, - 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976, 2056193, 2056416, - 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144, 2058177, 2058208, - 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184, 2077249, 2078976, - 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848, 2091041, 2091872, - 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032, 2097153, 2097536, - 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160, 2101249, 2105184, - 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224, 2120739, 2121600, - 2121729, 2122755, 2122880, 2123169, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504, 2125825, 2126784, - 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360, 2135553, 2136704, - 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2141697, 2142048, 2142081, 2142560, 2142593, 2142816, - 2142849, 2142912, 2142945, 2143296, 2143329, 2143808, 2143841, 2144064, 2144097, 2144160, 2146305, 2156256, - 2156545, 2157248, 2157569, 2157824, 2158593, 2158784, 2158817, 2160160, 2160193, 2160480, 2162689, 2162880, - 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640, 2164705, 2165440, 2165507, 2165761, - 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464, 2170497, 2170560, 2170723, 2170881, - 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961, 2177027, 2177536, 2177603, 2179073, - 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180800, 2181123, 2181408, 2182145, 2183075, 2183136, - 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595, 2186752, 2187265, 2188992, 2189313, - 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675, 2192896, 2195457, 2197792, 2199553, - 2201184, 2201601, 2203232, 2203459, 2203649, 2204800, 2205186, 2205504, 2214915, 2215904, 2215937, 2217280, - 2217473, 2217536, 2220033, 2220963, 2221281, 2221312, 2221569, 2222272, 2222627, 2222752, 2223617, 2224192, - 2225665, 2226339, 2226560, 2227201, 2227936, 2228321, 2230016, 2230851, 2231490, 2231808, 2231841, 2231904, - 2231969, 2232000, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664, 2238146, 2238464, - 2238593, 2238624, 2238689, 2238720, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720, - 2243074, 2243393, 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2246625, - 2246688, 2248705, 2248928, 2248961, 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241, - 2251744, 2252290, 2252608, 2252961, 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401, - 2254464, 2254497, 2254656, 2254753, 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265, - 2263392, 2263554, 2263872, 2264033, 2264128, 2265089, 2266624, 2267265, 2267328, 2267361, 2267392, 2267650, - 2267968, 2273281, 2274784, 2276097, 2276224, 2277377, 2278912, 2279553, 2279584, 2279938, 2280256, 2281473, - 2282848, 2283265, 2283296, 2283522, 2283840, 2285569, 2286432, 2287106, 2287427, 2287488, 2287617, 2287840, - 2293761, 2295168, 2298881, 2300930, 2301251, 2301536, 2301921, 2302176, 2302241, 2302272, 2302337, 2302592, - 2302625, 2302688, 2302721, 2303488, 2303969, 2304000, 2304033, 2304064, 2304514, 2304832, 2307073, 2307328, - 2307393, 2308640, 2309153, 2309184, 2309217, 2309248, 2310145, 2310176, 2310497, 2311776, 2312001, 2312032, - 2312705, 2312736, 2313089, 2314560, 2315169, 2315200, 2315777, 2318112, 2326529, 2326816, 2326849, 2328032, - 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2334721, 2334944, 2334977, 2335040, 2335073, - 2336288, 2336961, 2336992, 2337282, 2337600, 2337793, 2337984, 2338017, 2338080, 2338113, 2339136, 2339585, - 2339616, 2339842, 2340160, 2350081, 2350688, 2351169, 2351200, 2351233, 2351648, 2351681, 2352768, 2353666, - 2353984, 2356737, 2356768, 2357251, 2357920, 2359297, 2388800, 2392067, 2395616, 2396161, 2402432, 2486785, - 2489888, 2490369, 2524672, 2525217, 2525408, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578, - 2968896, 2969089, 2971616, 2971650, 2971968, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258, - 2976576, 2976611, 2976832, 2976865, 2977536, 2977697, 2978304, 3000321, 3002371, 3003104, 3006465, 3008864, - 3009025, 3009056, 3011169, 3011584, 3013633, 3013696, 3013729, 3013760, 3014657, 3211008, 3211265, 3250880, - 3252225, 3252512, 3538433, 3538560, 3538593, 3538816, 3538849, 3538912, 3538945, 3548256, 3548737, 3548768, - 3549697, 3549792, 3549857, 3549888, 3550337, 3550464, 3550721, 3563392, 3637249, 3640672, 3640833, 3641248, - 3641345, 3641632, 3641857, 3642176, 3823619, 3824256, 3824643, 3825280, 3828739, 3829536, 3833857, 3836576, - 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137, 3839200, 3839265, 3839392, 3839425, 3839808, - 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273, 3842400, 3842465, 3842720, 3842753, 3842976, - 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289, 3844320, 3844417, 3844640, 3844673, 3855552, - 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305, 3859104, 3859137, 3860128, 3860161, 3860960, - 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873, 3864672, 3864705, 3864960, 3865026, 3866624, - 3923969, 3924960, 3925153, 3925344, 3933697, 3935680, 3940353, 3941792, 3942113, 3942336, 3942402, 3942720, - 3942849, 3942880, 3953153, 3954112, 3954689, 3956096, 3956226, 3956544, 3971585, 3972480, 3972610, 3972928, - 3996673, 3996896, 3996929, 3997056, 3997089, 3997152, 3997185, 3997664, 3997697, 4004000, 4004067, 4004352, - 4005889, 4008064, 4008289, 4008320, 4008450, 4008768, 4034083, 4035968, 4036003, 4036096, 4036131, 4036256, - 4038691, 4040128, 4040163, 4040640, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032, - 4048097, 4048128, 4048161, 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992, - 4049121, 4049152, 4049185, 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568, - 4049633, 4049664, 4049697, 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016, - 4050049, 4050080, 4050145, 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912, - 4050945, 4051264, 4051297, 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840, - 4161026, 4161344, 4194305, 5561344, 5562369, 5695296, 5695489, 5702592, 5702657, 5887040, 5887489, 6126624, - 6225921, 6243264, 6291457, 6449504, 6449665, 6583808, 4294967295}; + 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441, + 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880, + 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032, + 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024, + 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744, + 43809, 43840, 44033, 45344, 47617, 48480, 48609, 48736, 50177, 51552, 52226, 52544, + 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248, + 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809, + 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817, + 66848, 67585, 68384, 68609, 68960, 69121, 69888, 69921, 70112, 70657, 72000, 73857, + 75584, 75681, 75712, 76289, 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985, + 78240, 78305, 78368, 78433, 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777, + 79808, 80321, 80352, 80769, 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728, + 81793, 81824, 82081, 82272, 82401, 82464, 82529, 83232, 83265, 83488, 83521, 83584, + 83617, 83680, 83713, 83776, 84769, 84896, 84929, 84960, 85186, 85504, 85569, 85664, + 86177, 86464, 86497, 86592, 86625, 87328, 87361, 87584, 87617, 87680, 87713, 87872, + 87969, 88000, 88577, 88608, 89089, 89152, 89282, 89600, 89889, 89920, 90273, 90528, + 90593, 90656, 90721, 91424, 91457, 91680, 91713, 91776, 91809, 91968, 92065, 92096, + 93057, 93120, 93153, 93248, 93378, 93696, 93729, 93763, 93952, 94305, 94336, 94369, + 94560, 94657, 94752, 94785, 94912, 95009, 95072, 95105, 95136, 95169, 95232, 95329, + 95392, 95489, 95584, 95681, 96064, 96769, 96800, 97474, 97795, 97888, 98465, 98720, + 98753, 98848, 98881, 99616, 99649, 100160, 100257, 100288, 101121, 101216, 101281, 101312, + 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432, 102561, 102816, 102849, 102944, + 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384, 105377, 105440, 105473, 105536, + 105666, 105984, 106017, 106080, 106625, 106912, 106945, 107040, 107073, 108384, 108449, 108480, + 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762, 110083, 110368, 110401, 110592, + 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576, 112641, 112864, 113858, 114176, + 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568, 118817, 118880, 118913, 118944, + 118977, 119136, 119169, 119936, 119969, 120000, 120033, 120352, 120385, 120448, 120737, 120768, + 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856, 122881, 122912, 123906, 124227, + 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073, 132448, 133089, 133122, 133440, + 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368, 134593, 134688, 134817, 135232, + 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472, 137633, 137664, 137729, 139104, + 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304, 150337, 150464, 150529, 151840, + 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568, 153601, 153632, 153665, 153792, + 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560, 159011, 159648, 159745, 160256, + 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324, 184353, 185184, 185345, 187744, + 187843, 187937, 188192, 188417, 188992, 189409, 190016, 190465, 191040, 191489, 191904, 191937, + 192032, 192513, 194176, 195297, 195328, 195457, 195488, 195586, 195904, 196099, 196416, 197122, + 197440, 197633, 200480, 200705, 200864, 200929, 202016, 202049, 202080, 202241, 204480, 204801, + 205792, 207042, 207361, 208320, 208385, 208544, 208897, 210304, 210433, 211264, 211458, 211779, + 211808, 212993, 213728, 214017, 215712, 217090, 217408, 217602, 217920, 218337, 218368, 221345, + 222848, 223393, 223648, 223746, 224064, 225377, 226336, 226753, 226818, 227137, 228544, 229377, + 230528, 231426, 231744, 231841, 231938, 232257, 233408, 233473, 233760, 233985, 235360, 235425, + 235520, 236833, 236960, 236993, 237184, 237217, 237280, 237377, 237408, 237569, 243712, 245761, + 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865, + 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161, + 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825, + 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792, + 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657, + 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681, + 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595, + 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 367776, 367969, 368096, 368193, + 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081, 370112, 370177, 371968, 372193, + 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273, 374496, 374529, 374752, 374785, + 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337, 378368, 393220, 393248, 393377, + 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105, 395168, 395297, 398048, 398241, + 398336, 398369, 401248, 401281, 401408, 401569, 402944, 402977, 405984, 406083, 406208, 406529, + 407552, 409089, 409600, 410627, 410944, 411907, 412160, 412195, 412672, 413699, 414016, 415267, + 415744, 425985, 636928, 638977, 1348000, 1350145, 1351616, 1351681, 1360288, 1360385, 1360898, 1361217, + 1361280, 1361921, 1363424, 1363937, 1364928, 1364993, 1367235, 1367552, 1368801, 1369088, 1369153, 1372448, + 1372513, 1374560, 1374721, 1374784, 1374817, 1374848, 1374881, 1375040, 1375809, 1376320, 1376353, 1376448, + 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016, 1382914, 1383232, + 1384001, 1384192, 1384289, 1384320, 1384353, 1384416, 1384450, 1384769, 1385664, 1385985, 1386720, 1387521, + 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130, 1392449, 1392608, + 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448, 1396545, 1396576, + 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816, 1398849, 1398880, + 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312, 1401377, 1401568, + 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404224, 1404417, 1408096, 1408514, 1408832, + 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976, 2056193, 2056416, + 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144, 2058177, 2058208, + 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184, 2077249, 2078976, + 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848, 2091041, 2091872, + 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032, 2097153, 2097536, + 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160, 2101249, 2105184, + 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224, 2120739, 2121600, + 2121729, 2122755, 2122880, 2123169, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504, 2125825, 2126784, + 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360, 2135553, 2136704, + 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2141697, 2142048, 2142081, 2142560, 2142593, 2142816, + 2142849, 2142912, 2142945, 2143296, 2143329, 2143808, 2143841, 2144064, 2144097, 2144160, 2146305, 2156256, + 2156545, 2157248, 2157569, 2157824, 2158593, 2158784, 2158817, 2160160, 2160193, 2160480, 2162689, 2162880, + 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640, 2164705, 2165440, 2165507, 2165761, + 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464, 2170497, 2170560, 2170723, 2170881, + 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961, 2177027, 2177536, 2177603, 2179073, + 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180800, 2181123, 2181408, 2182145, 2183075, 2183136, + 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595, 2186752, 2187265, 2188992, 2189313, + 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675, 2192896, 2195457, 2197792, 2199553, + 2201184, 2201601, 2203232, 2203459, 2203649, 2204800, 2205186, 2205504, 2214915, 2215904, 2215937, 2217280, + 2217473, 2217536, 2220033, 2220963, 2221281, 2221312, 2221569, 2222272, 2222627, 2222752, 2223617, 2224192, + 2225665, 2226339, 2226560, 2227201, 2227936, 2228321, 2230016, 2230851, 2231490, 2231808, 2231841, 2231904, + 2231969, 2232000, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664, 2238146, 2238464, + 2238593, 2238624, 2238689, 2238720, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720, + 2243074, 2243393, 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2246625, + 2246688, 2248705, 2248928, 2248961, 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241, + 2251744, 2252290, 2252608, 2252961, 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401, + 2254464, 2254497, 2254656, 2254753, 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265, + 2263392, 2263554, 2263872, 2264033, 2264128, 2265089, 2266624, 2267265, 2267328, 2267361, 2267392, 2267650, + 2267968, 2273281, 2274784, 2276097, 2276224, 2277377, 2278912, 2279553, 2279584, 2279938, 2280256, 2281473, + 2282848, 2283265, 2283296, 2283522, 2283840, 2285569, 2286432, 2287106, 2287427, 2287488, 2287617, 2287840, + 2293761, 2295168, 2298881, 2300930, 2301251, 2301536, 2301921, 2302176, 2302241, 2302272, 2302337, 2302592, + 2302625, 2302688, 2302721, 2303488, 2303969, 2304000, 2304033, 2304064, 2304514, 2304832, 2307073, 2307328, + 2307393, 2308640, 2309153, 2309184, 2309217, 2309248, 2310145, 2310176, 2310497, 2311776, 2312001, 2312032, + 2312705, 2312736, 2313089, 2314560, 2315169, 2315200, 2315777, 2318112, 2326529, 2326816, 2326849, 2328032, + 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2334721, 2334944, 2334977, 2335040, 2335073, + 2336288, 2336961, 2336992, 2337282, 2337600, 2337793, 2337984, 2338017, 2338080, 2338113, 2339136, 2339585, + 2339616, 2339842, 2340160, 2350081, 2350688, 2351169, 2351200, 2351233, 2351648, 2351681, 2352768, 2353666, + 2353984, 2356737, 2356768, 2357251, 2357920, 2359297, 2388800, 2392067, 2395616, 2396161, 2402432, 2486785, + 2489888, 2490369, 2524672, 2525217, 2525408, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578, + 2968896, 2969089, 2971616, 2971650, 2971968, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258, + 2976576, 2976611, 2976832, 2976865, 2977536, 2977697, 2978304, 3000321, 3002371, 3003104, 3006465, 3008864, + 3009025, 3009056, 3011169, 3011584, 3013633, 3013696, 3013729, 3013760, 3014657, 3211008, 3211265, 3250880, + 3252225, 3252512, 3538433, 3538560, 3538593, 3538816, 3538849, 3538912, 3538945, 3548256, 3548737, 3548768, + 3549697, 3549792, 3549857, 3549888, 3550337, 3550464, 3550721, 3563392, 3637249, 3640672, 3640833, 3641248, + 3641345, 3641632, 3641857, 3642176, 3823619, 3824256, 3824643, 3825280, 3828739, 3829536, 3833857, 3836576, + 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137, 3839200, 3839265, 3839392, 3839425, 3839808, + 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273, 3842400, 3842465, 3842720, 3842753, 3842976, + 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289, 3844320, 3844417, 3844640, 3844673, 3855552, + 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305, 3859104, 3859137, 3860128, 3860161, 3860960, + 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873, 3864672, 3864705, 3864960, 3865026, 3866624, + 3923969, 3924960, 3925153, 3925344, 3933697, 3935680, 3940353, 3941792, 3942113, 3942336, 3942402, 3942720, + 3942849, 3942880, 3953153, 3954112, 3954689, 3956096, 3956226, 3956544, 3971585, 3972480, 3972610, 3972928, + 3996673, 3996896, 3996929, 3997056, 3997089, 3997152, 3997185, 3997664, 3997697, 4004000, 4004067, 4004352, + 4005889, 4008064, 4008289, 4008320, 4008450, 4008768, 4034083, 4035968, 4036003, 4036096, 4036131, 4036256, + 4038691, 4040128, 4040163, 4040640, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032, + 4048097, 4048128, 4048161, 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992, + 4049121, 4049152, 4049185, 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568, + 4049633, 4049664, 4049697, 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016, + 4050049, 4050080, 4050145, 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912, + 4050945, 4051264, 4051297, 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840, + 4161026, 4161344, 4194305, 5561344, 5562369, 5695296, 5695489, 5702592, 5702657, 5887040, 5887489, 6126624, + 6127105, 6147008, 6225921, 6243264, 6291457, 6449504, 6449665, 6583808, 4294967295}; static const uint16 unicode_simple_category_jump_pos[] = { 1, 9, 27, 27, 27, 27, 36, 44, 55, 55, 57, 63, 68, 75, 86, 91, 102, 114, 119, @@ -198,8 +198,8 @@ static const uint16 unicode_simple_category_jump_pos[] = { 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1429, 1432, 1432, 1434, 1435, 1442, 1442, 1442, 1448, 1448, 1448, 1448, 1452, 1452, 1452, 1452, 1452, 1452, 1461, 1461, 1465, 1470, 1470, 1470, 1470, 1470, 1470, 1471, 1476, 1480, 1481, 1537, 1546, 1546, 1546, 1546, 1547, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, - 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1551, 1563, - 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566}; + 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1551, 1565, + 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568, 1568}; static const char *unicode_simple_category_table = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" @@ -362,62 +362,63 @@ static const int32 prepare_search_character_ranges[] = { 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, 8080, -8081, 8088, -8081, 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, -8049, 8124, 8115, 8125, 32, 8126, 953, 8127, 32, 8130, -8131, 8136, -8051, 8140, 8131, - 8141, 32, 8144, -8145, 8152, -8145, 8154, -8055, 8156, 8156, 8157, 32, - 8160, -8161, 8168, -8161, 8170, -8059, 8172, 8165, 8173, 32, 8176, -8177, - 8184, -8057, 8186, -8061, 8188, 8179, 8189, 32, 8191, 8191, 8192, 32, - 8203, 0, 8208, 32, 8234, 0, 8239, 32, 8288, 0, 8293, 8293, - 8294, 0, 8304, -8305, 8314, 32, 8319, -8320, 8330, 32, 8335, -8336, - 8352, 32, 8385, -8386, 8400, 0, 8433, -8434, 8448, 32, 8450, 99, - 8452, 32, 8455, 603, 8456, 32, 8457, 102, 8458, 8458, 8459, 104, - 8462, -8463, 8464, 105, 8466, 108, 8467, 8467, 8468, 32, 8469, 110, - 8470, 32, 8473, -113, 8476, 114, 8478, 32, 8484, 122, 8485, 32, - 8486, 969, 8487, 32, 8488, 122, 8489, 32, 8490, 107, 8491, 229, - 8492, -99, 8494, 32, 8495, 8495, 8496, -102, 8498, 8526, 8499, 109, - 8500, -8501, 8506, 32, 8508, -8509, 8510, 947, 8511, 960, 8512, 32, - 8517, 100, 8518, -8519, 8522, 32, 8526, 8526, 8527, 32, 8528, -8529, - 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, 8586, 32, 8588, -8589, - 8592, 32, 9255, -9256, 9280, 32, 9291, -9292, 9372, 32, 9398, -9425, - 9424, -9425, 9472, 32, 10102, -10103, 10132, 32, 11124, -11125, 11126, 32, - 11158, 11158, 11159, 32, 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619, - 11363, 7549, 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, - 11373, 593, 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, - 11381, 11382, 11383, -11384, 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492, - 11493, 32, 11499, 11500, 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509, - 11513, 32, 11517, 11517, 11518, 32, 11520, -11521, 11632, 32, 11633, -11634, - 11647, 0, 11648, -11649, 11744, 0, 11776, 32, 11823, 11823, 11824, 32, - 11870, -11871, 11904, 32, 11930, 11930, 11931, 32, 11935, 11935, 11936, 32, - 12019, -12020, 12272, 32, 12284, -12285, 12288, 32, 12293, -12294, 12296, 32, - 12321, -12322, 12330, 0, 12336, 32, 12337, -12338, 12342, 32, 12344, -12345, - 12349, 32, 12352, -12353, 12441, 0, 12443, 32, 12445, -12446, 12448, 32, - 12449, -12450, 12539, 32, 12540, 0, 12541, -12542, 12688, 32, 12690, -12691, - 12736, 32, 12772, -12773, 12800, 32, 12831, -12832, 12842, 32, 12868, -12869, - 12880, 32, 12881, -12882, 12910, 32, 12928, -12929, 12992, 32, 13008, -13009, - 13055, 32, 13312, -13313, 19904, 32, 19968, -19969, 42128, 32, 42183, -42184, - 42238, 32, 42240, -42241, 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606, - 42607, 0, 42611, 32, 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653, - 42654, 0, 42656, -42657, 42736, 0, 42738, 32, 42744, -42745, 42752, 32, - 42775, -42776, 42784, 32, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, - 42873, 42874, 42875, 42876, 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32, - 42891, 42892, 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153, - 42922, 614, 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927, - 42928, 670, 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900, - 42949, 642, 42950, 7566, 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961, - 42962, -42963, 42966, 2097153, 42970, -42971, 42994, 99, 42995, 102, 42996, 113, - 42997, 42998, 42999, 42999, 43000, 295, 43001, -43002, 43010, 0, 43011, -43012, - 43014, 0, 43015, -43016, 43019, 0, 43020, -43021, 43043, 0, 43048, 32, - 43052, 0, 43053, -43054, 43062, 32, 43066, -43067, 43124, 32, 43128, -43129, - 43136, 0, 43138, -43139, 43188, 0, 43206, -43207, 43214, 32, 43216, -43217, - 43232, 0, 43250, -43251, 43256, 32, 43259, 43259, 43260, 32, 43261, -43262, - 43263, 0, 43264, -43265, 43302, 0, 43310, 32, 43312, -43313, 43335, 0, - 43348, -43349, 43359, 32, 43360, -43361, 43392, 0, 43396, -43397, 43443, 0, - 43457, 32, 43470, -43471, 43486, 32, 43488, -43489, 43493, 0, 43494, -43495, - 43561, 0, 43575, -43576, 43587, 0, 43588, -43589, 43596, 0, 43598, -43599, - 43612, 32, 43616, -43617, 43639, 32, 43642, 43642, 43643, 0, 43646, -43647, - 43696, 0, 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, - 43710, 0, 43712, 43712, 43713, 0, 43714, -43715, 43742, 32, 43744, -43745, - 43755, 0, 43760, 32, 43762, -43763, 43765, 0, 43767, -43768, 43867, 32, - 43868, -43869, 43882, 32, 43884, -43885, 43888, -5025, 43968, -43969, 44003, 0, - 44011, 32, 44012, 0, 44014, -44015, 55296, 0, 57344, -57345, 64286, 0, + 8141, 32, 8144, -8145, 8147, 912, 8148, -8149, 8152, -8145, 8154, -8055, + 8156, 8156, 8157, 32, 8160, -8161, 8163, 944, 8164, -8165, 8168, -8161, + 8170, -8059, 8172, 8165, 8173, 32, 8176, -8177, 8184, -8057, 8186, -8061, + 8188, 8179, 8189, 32, 8191, 8191, 8192, 32, 8203, 0, 8208, 32, + 8234, 0, 8239, 32, 8288, 0, 8293, 8293, 8294, 0, 8304, -8305, + 8314, 32, 8319, -8320, 8330, 32, 8335, -8336, 8352, 32, 8385, -8386, + 8400, 0, 8433, -8434, 8448, 32, 8450, 99, 8452, 32, 8455, 603, + 8456, 32, 8457, 102, 8458, 8458, 8459, 104, 8462, -8463, 8464, 105, + 8466, 108, 8467, 8467, 8468, 32, 8469, 110, 8470, 32, 8473, -113, + 8476, 114, 8478, 32, 8484, 122, 8485, 32, 8486, 969, 8487, 32, + 8488, 122, 8489, 32, 8490, 107, 8491, 229, 8492, -99, 8494, 32, + 8495, 8495, 8496, -102, 8498, 8526, 8499, 109, 8500, -8501, 8506, 32, + 8508, -8509, 8510, 947, 8511, 960, 8512, 32, 8517, 100, 8518, -8519, + 8522, 32, 8526, 8526, 8527, 32, 8528, -8529, 8544, -8561, 8560, -8561, + 8579, 8580, 8581, -8582, 8586, 32, 8588, -8589, 8592, 32, 9255, -9256, + 9280, 32, 9291, -9292, 9372, 32, 9398, -9425, 9424, -9425, 9472, 32, + 10102, -10103, 10132, 32, 11124, -11125, 11126, 32, 11158, 11158, 11159, 32, + 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619, 11363, 7549, 11364, 637, + 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 593, 11374, 625, + 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, 11381, 11382, 11383, -11384, + 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492, 11493, 32, 11499, 11500, + 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509, 11513, 32, 11517, 11517, + 11518, 32, 11520, -11521, 11632, 32, 11633, -11634, 11647, 0, 11648, -11649, + 11744, 0, 11776, 32, 11823, 11823, 11824, 32, 11870, -11871, 11904, 32, + 11930, 11930, 11931, 32, 11935, 11935, 11936, 32, 12019, -12020, 12272, 32, + 12293, -12294, 12296, 32, 12321, -12322, 12330, 0, 12336, 32, 12337, -12338, + 12342, 32, 12344, -12345, 12349, 32, 12352, -12353, 12441, 0, 12443, 32, + 12445, -12446, 12448, 32, 12449, -12450, 12539, 32, 12540, 0, 12541, -12542, + 12688, 32, 12690, -12691, 12736, 32, 12772, -12773, 12783, 32, 12784, -12785, + 12800, 32, 12831, -12832, 12842, 32, 12868, -12869, 12880, 32, 12881, -12882, + 12910, 32, 12928, -12929, 12992, 32, 13008, -13009, 13055, 32, 13312, -13313, + 19904, 32, 19968, -19969, 42128, 32, 42183, -42184, 42238, 32, 42240, -42241, + 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606, 42607, 0, 42611, 32, + 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653, 42654, 0, 42656, -42657, + 42736, 0, 42738, 32, 42744, -42745, 42752, 32, 42775, -42776, 42784, 32, + 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, 42873, 42874, 42875, 42876, + 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32, 42891, 42892, 42893, 613, + 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153, 42922, 614, 42923, 604, + 42924, 609, 42925, 620, 42926, 618, 42927, 42927, 42928, 670, 42929, 647, + 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900, 42949, 642, 42950, 7566, + 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961, 42962, -42963, 42966, 2097153, + 42970, -42971, 42994, 99, 42995, 102, 42996, 113, 42997, 42998, 42999, 42999, + 43000, 295, 43001, -43002, 43010, 0, 43011, -43012, 43014, 0, 43015, -43016, + 43019, 0, 43020, -43021, 43043, 0, 43048, 32, 43052, 0, 43053, -43054, + 43062, 32, 43066, -43067, 43124, 32, 43128, -43129, 43136, 0, 43138, -43139, + 43188, 0, 43206, -43207, 43214, 32, 43216, -43217, 43232, 0, 43250, -43251, + 43256, 32, 43259, 43259, 43260, 32, 43261, -43262, 43263, 0, 43264, -43265, + 43302, 0, 43310, 32, 43312, -43313, 43335, 0, 43348, -43349, 43359, 32, + 43360, -43361, 43392, 0, 43396, -43397, 43443, 0, 43457, 32, 43470, -43471, + 43486, 32, 43488, -43489, 43493, 0, 43494, -43495, 43561, 0, 43575, -43576, + 43587, 0, 43588, -43589, 43596, 0, 43598, -43599, 43612, 32, 43616, -43617, + 43639, 32, 43642, 43642, 43643, 0, 43646, -43647, 43696, 0, 43697, 43697, + 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, 43710, 0, 43712, 43712, + 43713, 0, 43714, -43715, 43742, 32, 43744, -43745, 43755, 0, 43760, 32, + 43762, -43763, 43765, 0, 43767, -43768, 43867, 32, 43868, -43869, 43882, 32, + 43884, -43885, 43888, -5025, 43968, -43969, 44003, 0, 44011, 32, 44012, 0, + 44014, -44015, 55296, 0, 57344, -57345, 64261, 64262, 64263, -64264, 64286, 0, 64287, -64288, 64297, 32, 64298, -64299, 64434, 32, 64451, -64452, 64830, 32, 64848, -64849, 64975, 32, 65008, -65009, 65020, 32, 65024, 0, 65040, 32, 65050, -65051, 65056, 0, 65072, 32, 65107, 65107, 65108, 32, 65127, 65127, diff --git a/lib/tgchat/ext/td/tdutils/test/ChainScheduler.cpp b/lib/tgchat/ext/td/tdutils/test/ChainScheduler.cpp index ba4cdbd1..5418cd13 100644 --- a/lib/tgchat/ext/td/tdutils/test/ChainScheduler.cpp +++ b/lib/tgchat/ext/td/tdutils/test/ChainScheduler.cpp @@ -151,7 +151,7 @@ TEST(ChainScheduler, Stress) { }; auto check_parents_ok = [&](const QueryWithParents &query_with_parents) -> bool { - return td::all_of(query_with_parents.parents, [](auto &parent) { return parent->is_ok; }); + return td::all_of(query_with_parents.parents, [](const auto &parent) { return parent->is_ok; }); }; auto to_query_ptr = [&](TaskId task_id) { diff --git a/lib/tgchat/ext/td/tdutils/test/misc.cpp b/lib/tgchat/ext/td/tdutils/test/misc.cpp index aaea9fa7..92e63233 100644 --- a/lib/tgchat/ext/td/tdutils/test/misc.cpp +++ b/lib/tgchat/ext/td/tdutils/test/misc.cpp @@ -409,6 +409,49 @@ TEST(Misc, remove) { test_remove(v, 1, v); } +static void test_add_to_top(td::vector v, size_t max_size, int new_value, const td::vector &expected) { + auto u = v; + td::add_to_top(v, max_size, new_value); + ASSERT_EQ(expected, v); + + td::add_to_top_if(u, max_size, new_value, [new_value](int value) { return value == new_value; }); + ASSERT_EQ(expected, u); +} + +static void test_add_to_top_if(td::vector v, int max_size, int new_value, const td::vector &expected) { + td::add_to_top_if(v, max_size, new_value, [new_value](int value) { return value % 10 == new_value % 10; }); + ASSERT_EQ(expected, v); +} + +TEST(Misc, add_to_top) { + test_add_to_top({}, 0, 1, {1}); + test_add_to_top({}, 1, 1, {1}); + test_add_to_top({}, 6, 1, {1}); + + test_add_to_top({1, 2, 3, 4, 5, 6}, 3, 2, {2, 1, 3, 4, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 6, 1, {1, 2, 3, 4, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 7, 1, {1, 2, 3, 4, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 6, 2, {2, 1, 3, 4, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 7, 2, {2, 1, 3, 4, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 6, 4, {4, 1, 2, 3, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 7, 4, {4, 1, 2, 3, 5, 6}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 6, 6, {6, 1, 2, 3, 4, 5}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 7, 6, {6, 1, 2, 3, 4, 5}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 6, 7, {7, 1, 2, 3, 4, 5}); + test_add_to_top({1, 2, 3, 4, 5, 6}, 7, 7, {7, 1, 2, 3, 4, 5, 6}); + + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 6, 11, {1, 2, 3, 4, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 7, 21, {1, 2, 3, 4, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 6, 32, {2, 1, 3, 4, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 7, 42, {2, 1, 3, 4, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 6, 54, {4, 1, 2, 3, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 7, 64, {4, 1, 2, 3, 5, 6}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 6, 76, {6, 1, 2, 3, 4, 5}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 7, 86, {6, 1, 2, 3, 4, 5}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 6, 97, {97, 1, 2, 3, 4, 5}); + test_add_to_top_if({1, 2, 3, 4, 5, 6}, 7, 87, {87, 1, 2, 3, 4, 5, 6}); +} + static void test_unique(td::vector v, const td::vector &expected) { auto v_str = td::transform(v, &td::to_string); auto expected_str = td::transform(expected, &td::to_string); diff --git a/lib/tgchat/ext/td/test/link.cpp b/lib/tgchat/ext/td/test/link.cpp index b1203af6..1bd138bb 100644 --- a/lib/tgchat/ext/td/test/link.cpp +++ b/lib/tgchat/ext/td/test/link.cpp @@ -291,6 +291,10 @@ static auto premium_features(const td::string &referrer) { return td::td_api::make_object(referrer); } +static auto premium_gift_code(const td::string &code) { + return td::td_api::make_object(code); +} + static auto privacy_and_security_settings() { return td::td_api::make_object(); } @@ -400,6 +404,14 @@ TEST(Link, parse_internal_link_part1) { parse_internal_link("t.me/c/123456789012?boost", chat_boost("tg://boost?channel=123456789012")); parse_internal_link("t.me/c/123456789012?boost=12312&domain=123", chat_boost("tg://boost?channel=123456789012")); + parse_internal_link("t.me/boost/s/12345", story("boost", 12345)); + parse_internal_link("t.me/boost/s", chat_boost("tg://boost?domain=s")); + parse_internal_link("t.me/boost/12", message("tg://resolve?domain=boost&post=12")); + parse_internal_link("t.me/boost?cc=1#c=1", public_chat("boost")); + parse_internal_link("t.me/boost?c=-1", public_chat("boost")); + parse_internal_link("t.me/boost?c=12telegram", chat_boost("tg://boost?channel=12")); + parse_internal_link("t.me/bOoSt?c=12telegram", chat_boost("tg://boost?channel=12")); + parse_internal_link("tg:boost?domain=username/12345&single", chat_boost("tg://boost?domain=username%2F12345")); parse_internal_link("tg:boost?domain=username&channel=12345", chat_boost("tg://boost?domain=username")); parse_internal_link("tg:boost?channel=12345&domain=username", chat_boost("tg://boost?domain=username")); @@ -632,6 +644,24 @@ TEST(Link, parse_internal_link_part2) { parse_internal_link("tg:invoice?slug=abc%30ef", invoice("abc0ef")); parse_internal_link("tg://invoice?slug=", unknown_deep_link("tg://invoice?slug=")); + parse_internal_link("t.me/giftcode?slug=abcdef", nullptr); + parse_internal_link("t.me/giftcode", nullptr); + parse_internal_link("t.me/giftcode/", nullptr); + parse_internal_link("t.me/giftcode//abcdef", nullptr); + parse_internal_link("t.me/giftcode?/abcdef", nullptr); + parse_internal_link("t.me/giftcode/?abcdef", nullptr); + parse_internal_link("t.me/giftcode/#abcdef", nullptr); + parse_internal_link("t.me/giftcode/abacaba", premium_gift_code("abacaba")); + parse_internal_link("t.me/giftcode/aba%20aba", premium_gift_code("aba aba")); + parse_internal_link("t.me/giftcode/123456a", premium_gift_code("123456a")); + parse_internal_link("t.me/giftcode/12345678901", premium_gift_code("12345678901")); + parse_internal_link("t.me/giftcode/123456", premium_gift_code("123456")); + parse_internal_link("t.me/giftcode/123456/123123/12/31/a/s//21w/?asdas#test", premium_gift_code("123456")); + + parse_internal_link("tg:giftcode?slug=abcdef", premium_gift_code("abcdef")); + parse_internal_link("tg:giftcode?slug=abc%30ef", premium_gift_code("abc0ef")); + parse_internal_link("tg://giftcode?slug=", unknown_deep_link("tg://giftcode?slug=")); + parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true)); parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false)); parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false)); @@ -1240,12 +1270,20 @@ TEST(Link, parse_internal_link_part4) { 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("a.t.me", nullptr); + parse_internal_link("b.t.me", nullptr); + parse_internal_link("k.t.me", nullptr); + parse_internal_link("z.t.me", nullptr); + parse_internal_link("web.t.me", nullptr); parse_internal_link("addemoji.t.me", nullptr); parse_internal_link("addlist.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("boost.t.me", nullptr); parse_internal_link("confirmphone.t.me", nullptr); + parse_internal_link("contact.t.me", nullptr); + parse_internal_link("giftcode.t.me", nullptr); parse_internal_link("invoice.t.me", nullptr); parse_internal_link("joinchat.t.me", nullptr); parse_internal_link("login.t.me", nullptr); diff --git a/lib/tgchat/ext/td/test/message_entities.cpp b/lib/tgchat/ext/td/test/message_entities.cpp index 378e76b2..ef9768ee 100644 --- a/lib/tgchat/ext/td/test/message_entities.cpp +++ b/lib/tgchat/ext/td/test/message_entities.cpp @@ -1134,7 +1134,7 @@ TEST(MessageEntities, fix_formatted_text) { auto n = td::Random::fast(1, 20); td::vector entities; for (int j = 0; j < n; j++) { - td::int32 type = td::Random::fast(4, 16); + td::int32 type = td::Random::fast(4, static_cast(td::MessageEntity::Type::Size) - 1); td::int32 offset = td::Random::fast(0, static_cast(str.size()) - 1); auto max_length = static_cast(str.size() - offset); if ((test_n & 1) != 0 && max_length > 4) { @@ -1156,7 +1156,7 @@ TEST(MessageEntities, fix_formatted_text) { auto old_type_mask = get_type_mask(str.size(), entities); ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, true, false).is_ok()); auto new_type_mask = get_type_mask(str.size(), entities); - auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15); + auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15) | (1 << 19); auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9); for (std::size_t pos = 0; pos < str.size(); pos++) { if ((new_type_mask[pos] & pre_mask) != 0) { @@ -1217,6 +1217,24 @@ TEST(MessageEntities, fix_formatted_text) { {}); } +TEST(MessageEntities, is_visible_url) { + td::string str = "a telegram.org telegran.org telegrao.org telegram.orc telegrap.org c"; + td::vector entities; + entities.emplace_back(td::MessageEntity::Type::TextUrl, 0, 1, "telegrab.org"); + entities.emplace_back(td::MessageEntity::Type::TextUrl, static_cast(str.size()) - 1, 1, "telegrax.org"); + td::fix_formatted_text(str, entities, false, false, false, false, true).ensure(); + td::FormattedText text{std::move(str), std::move(entities)}; + ASSERT_EQ(td::get_first_url(text), "telegrab.org"); + ASSERT_TRUE(!td::is_visible_url(text, "telegrab.org")); + ASSERT_TRUE(td::is_visible_url(text, "telegram.org")); + ASSERT_TRUE(td::is_visible_url(text, "telegran.org")); + ASSERT_TRUE(td::is_visible_url(text, "telegrao.org")); + ASSERT_TRUE(!td::is_visible_url(text, "telegram.orc")); + ASSERT_TRUE(td::is_visible_url(text, "telegrap.org")); + ASSERT_TRUE(!td::is_visible_url(text, "telegraf.org")); + ASSERT_TRUE(!td::is_visible_url(text, "telegrax.org")); +} + static void check_parse_html(td::string text, const td::string &result, const td::vector &entities) { auto r_entities = td::parse_html(text); ASSERT_TRUE(r_entities.is_ok()); @@ -1265,6 +1283,7 @@ TEST(MessageEntities, parse_html) { check_parse_html("➡️ ➡️➡️ ➡️", "➡️ ➡️➡️ ➡️", {{td::MessageEntity::Type::Strikethrough, 5, 5}}); check_parse_html("➡️ ➡️➡️ ➡️", "➡️ ➡️➡️ ➡️", {{td::MessageEntity::Type::Strikethrough, 5, 5}}); check_parse_html("➡️ ➡️➡️ ➡️", "➡️ ➡️➡️ ➡️", {{td::MessageEntity::Type::Strikethrough, 5, 5}}); + check_parse_html("➡️ ➡️
➡️ ➡️
", "➡️ ➡️➡️ ➡️", {{td::MessageEntity::Type::BlockQuote, 5, 5}}); check_parse_html("➡️ ➡️➡️ ➡️➡️ ➡️", "➡️ ➡️➡️ ➡️➡️ ➡️", {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}}); check_parse_html("🏟 🏟🏟 <🏟", "🏟 🏟🏟 <🏟", {{td::MessageEntity::Type::Italic, 5, 6}}); @@ -1349,11 +1368,16 @@ TEST(MessageEntities, parse_html) { check_parse_html("🏟 🏟🏟1", "🏟 🏟🏟1", {{td::MessageEntity::Type::Bold, 5, 3}, {td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast(1))}}); + check_parse_html("
a<
b;", "a &entities) {
   auto r_entities = td::parse_markdown_v2(text);
+  if (r_entities.is_error()) {
+    LOG(ERROR) << r_entities.error();
+  }
   ASSERT_TRUE(r_entities.is_ok());
   ASSERT_EQ(entities, r_entities.ok());
   ASSERT_STREQ(result, text);
@@ -1368,7 +1392,7 @@ static void check_parse_markdown(td::string text, td::Slice error_message) {
 
 TEST(MessageEntities, parse_markdown) {
   td::Slice reserved_characters("]()>#+-=|{}.!");
-  td::Slice begin_characters("_*[~`");
+  td::Slice begin_characters("_*[~`>");
   for (char c = 1; c < 126; c++) {
     if (begin_characters.find(c) != td::Slice::npos) {
       continue;
@@ -1405,6 +1429,7 @@ TEST(MessageEntities, parse_markdown) {
   check_parse_markdown("🏟 🏟__", "Can't find end of Underline entity at byte offset 9");
   check_parse_markdown("🏟 🏟||test\\|", "Can't find end of Spoiler entity at byte offset 9");
   check_parse_markdown("🏟 🏟!", "Character '!' is reserved and must be escaped with the preceding '\\'");
+  check_parse_markdown("🏟 🏟>", "Character '>' is reserved and must be escaped with the preceding '\\'");
   check_parse_markdown("🏟 🏟![", "Can't find end of CustomEmoji entity at byte offset 9");
   check_parse_markdown("🏟 🏟![👍", "Can't find end of CustomEmoji entity at byte offset 9");
   check_parse_markdown("🏟 🏟![👍]", "Custom emoji entity must contain a tg://emoji URL");
@@ -1414,6 +1439,7 @@ TEST(MessageEntities, parse_markdown) {
   check_parse_markdown("🏟 🏟![👍](tg://emoji#test)", "Custom emoji URL must have an emoji identifier");
   check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1#&id=25)", "Custom emoji URL must have an emoji identifier");
   check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1231&id=025)", "Invalid custom emoji identifier specified");
+  check_parse_markdown(">*b\n>ld \n>bo\nld*\nasd\ndef", "Can't find end of Bold entity at byte offset 1");
 
   check_parse_markdown("", "", {});
   check_parse_markdown("\\\\", "\\", {});
@@ -1478,6 +1504,29 @@ TEST(MessageEntities, parse_markdown) {
                        {{0, 12, td::UserId(static_cast(123456))}});
   check_parse_markdown("🏟 🏟![👍](TG://EMoJI/?test=1231&id=25#id=32)a", "🏟 🏟👍a",
                        {{td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast(25))}});
+  check_parse_markdown("> \n> \n>", " \n \n", {{td::MessageEntity::Type::BlockQuote, 0, 4}});
+  check_parse_markdown("> \\>\n \\> \n>", " >\n > \n", {{td::MessageEntity::Type::BlockQuote, 0, 3}});
+  check_parse_markdown("abc\n> \n> \n>\ndef", "abc\n \n \n\ndef", {{td::MessageEntity::Type::BlockQuote, 4, 5}});
+  check_parse_markdown(">", "", {});
+  check_parse_markdown(">a", "a", {{td::MessageEntity::Type::BlockQuote, 0, 1}});
+  check_parse_markdown(
+      ">*bold _italic bold ~italic bold strikethrough ||italic bold strikethrough spoiler||~ __underline italic "
+      "bold___ bold*",
+      "bold italic bold italic bold strikethrough italic bold strikethrough spoiler underline italic bold bold",
+      {{td::MessageEntity::Type::BlockQuote, 0, 103},
+       {td::MessageEntity::Type::Bold, 0, 103},
+       {td::MessageEntity::Type::Italic, 5, 93},
+       {td::MessageEntity::Type::Strikethrough, 17, 59},
+       {td::MessageEntity::Type::Spoiler, 43, 33},
+       {td::MessageEntity::Type::Underline, 77, 21}});
+  check_parse_markdown(">*b\n>ld \n>bo\n>ld*\nasd\ndef", "b\nld \nbo\nld\nasd\ndef",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 12}, {td::MessageEntity::Type::Bold, 0, 11}});
+  check_parse_markdown("*a\n>b\n>ld \n>bo\n>ld\nasd*\ndef", "a\nb\nld \nbo\nld\nasd\ndef",
+                       {{td::MessageEntity::Type::Bold, 0, 17}, {td::MessageEntity::Type::BlockQuote, 2, 12}});
+  check_parse_markdown(">`b\n>ld \n>bo\nld`\n>asd\ndef", "b\n>ld \n>bo\nld\nasd\ndef",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 18}, {td::MessageEntity::Type::Code, 0, 13}});
+  check_parse_markdown("`>b\n>ld \n>bo\nld`\n>asd\ndef", ">b\n>ld \n>bo\nld\nasd\ndef",
+                       {{td::MessageEntity::Type::Code, 0, 14}, {td::MessageEntity::Type::BlockQuote, 15, 4}});
 }
 
 static void check_parse_markdown_v3(td::string text, td::vector entities,
@@ -1764,9 +1813,10 @@ TEST(MessageEntities, parse_markdown_v3) {
   check_parse_markdown_v3(
       "__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold "
       "italic__bold**__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) "
-      "__italic**bold italic__bold** ||spoiler||",
+      "__italic**bold italic__bold** ||spoiler|| ```pre\nprecode``` init",
+      {{td::MessageEntity::Type::Italic, 271, 4}},
       "italic strikethrough bold code pre italic text_url italicbold italicbolditalic strikethrough bold code pre "
-      "italic text_url italicbold italicbold spoiler",
+      "italic text_url italicbold italicbold spoiler precode init",
       {{td::MessageEntity::Type::Italic, 0, 6},
        {td::MessageEntity::Type::Strikethrough, 7, 13},
        {td::MessageEntity::Type::Bold, 21, 4},
@@ -1785,7 +1835,22 @@ TEST(MessageEntities, parse_markdown_v3) {
        {td::MessageEntity::Type::Italic, 107, 6},
        {td::MessageEntity::Type::Italic, 123, 17},
        {td::MessageEntity::Type::Bold, 129, 15},
-       {td::MessageEntity::Type::Spoiler, 145, 7}});
+       {td::MessageEntity::Type::Spoiler, 145, 7},
+       {td::MessageEntity::Type::PreCode, 153, 7, "pre"},
+       {td::MessageEntity::Type::Italic, 161, 4}});
+  check_parse_markdown_v3("```\nsome code\n```", "some code\n", {{td::MessageEntity::Type::Pre, 0, 10}});
+  check_parse_markdown_v3("asd\n```\nsome code\n```cabab", "asd\nsome code\ncabab",
+                          {{td::MessageEntity::Type::Pre, 4, 10}});
+  check_parse_markdown_v3("asd\naba```\nsome code\n```cabab", "asd\nabasome code\ncabab",
+                          {{td::MessageEntity::Type::Pre, 7, 10}});
+  check_parse_markdown_v3("asd\naba```\nsome code\n```\ncabab", "asd\nabasome code\n\ncabab",
+                          {{td::MessageEntity::Type::Pre, 7, 10}});
+  check_parse_markdown_v3("asd\naba```a b\nsome code\n```\ncabab", "asd\nabaa b\nsome code\n\ncabab",
+                          {{td::MessageEntity::Type::Pre, 7, 14}});
+  check_parse_markdown_v3("asd\naba```a!@#$%^&*(b\nsome code\n```\ncabab", "asd\nabasome code\n\ncabab",
+                          {{td::MessageEntity::Type::PreCode, 7, 10, "a!@#$%^&*(b"}});
+  check_parse_markdown_v3("```aba\n```", "aba\n", {{td::MessageEntity::Type::Pre, 0, 4}});
+  check_parse_markdown_v3("```\n```", "\n", {{td::MessageEntity::Type::Pre, 0, 1}});
 
   td::vector parts{"a", " #test__a", "__", "**", "~~", "||", "[", "](t.me)", "`"};
   td::vector types{
@@ -1836,7 +1901,7 @@ static void check_get_markdown_v3(const td::string &result_text, const td::vecto
 }
 
 TEST(MessageEntities, get_markdown_v3) {
-  check_get_markdown_v3("``` ```", {}, " ", {{td::MessageEntity::Type::Pre, 0, 1}});
+  check_get_markdown_v3("```\n ```", {}, " ", {{td::MessageEntity::Type::Pre, 0, 1}});
   check_get_markdown_v3("` `", {}, " ", {{td::MessageEntity::Type::Code, 0, 1}});
   check_get_markdown_v3("`\n`", {}, "\n", {{td::MessageEntity::Type::Code, 0, 1}});
   check_get_markdown_v3("ab", {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}}, "ab",
@@ -1851,16 +1916,18 @@ TEST(MessageEntities, get_markdown_v3) {
   check_get_markdown_v3("** **", {}, " ", {{td::MessageEntity::Type::Bold, 0, 1}});
   check_get_markdown_v3("~~ ~~", {}, " ", {{td::MessageEntity::Type::Strikethrough, 0, 1}});
   check_get_markdown_v3("|| ||", {}, " ", {{td::MessageEntity::Type::Spoiler, 0, 1}});
-  check_get_markdown_v3("__a__ **b** ~~c~~ ||d|| e", {{td::MessageEntity::Type::PreCode, 24, 1, "C++"}}, "a b c d e",
+  check_get_markdown_v3("__a__ **b** ~~c~~ ||d|| e", {{td::MessageEntity::Type::PreCode, 24, 1, " C++"}}, "a b c d e",
                         {{td::MessageEntity::Type::Italic, 0, 1},
                          {td::MessageEntity::Type::Bold, 2, 1},
                          {td::MessageEntity::Type::Strikethrough, 4, 1},
                          {td::MessageEntity::Type::Spoiler, 6, 1},
-                         {td::MessageEntity::Type::PreCode, 8, 1, "C++"}});
-  check_get_markdown_v3("`ab` ```cd``` ef", {{td::MessageEntity::Type::PreCode, 14, 2, "C++"}}, "ab cd ef",
-                        {{td::MessageEntity::Type::Code, 0, 2},
-                         {td::MessageEntity::Type::Pre, 3, 2},
-                         {td::MessageEntity::Type::PreCode, 6, 2, "C++"}});
+                         {td::MessageEntity::Type::PreCode, 8, 1, " C++"}});
+  check_get_markdown_v3("```cpp\ngh```\n`ab`\n```\ncd```\nef", {{td::MessageEntity::Type::PreCode, 28, 2, " C++"}},
+                        "gh\nab\ncd\nef",
+                        {{td::MessageEntity::Type::PreCode, 0, 2, "cpp"},
+                         {td::MessageEntity::Type::Code, 3, 2},
+                         {td::MessageEntity::Type::Pre, 6, 2},
+                         {td::MessageEntity::Type::PreCode, 9, 2, " C++"}});
   check_get_markdown_v3("__asd__[__ab__cd](http://t.me/)", {}, "asdabcd",
                         {{td::MessageEntity::Type::Italic, 0, 3},
                          {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"},
@@ -1879,4 +1946,19 @@ TEST(MessageEntities, get_markdown_v3) {
                         {{td::MessageEntity::Type::TextUrl, 0, 16, "http://example.com/"},
                          {td::MessageEntity::Type::Bold, 0, 16},
                          {td::MessageEntity::Type::Italic, 0, 16}});
+  check_get_markdown_v3("```\nsome code\n```", {}, "some code\n", {{td::MessageEntity::Type::Pre, 0, 10}});
+  check_get_markdown_v3("asd\n```\nsome code\n```cabab", {}, "asd\nsome code\ncabab",
+                        {{td::MessageEntity::Type::Pre, 4, 10}});
+  check_get_markdown_v3("asd\naba```\nsome code\n```cabab", {}, "asd\nabasome code\ncabab",
+                        {{td::MessageEntity::Type::Pre, 7, 10}});
+  check_get_markdown_v3("asd\naba```\nsome code\n```\ncabab", {}, "asd\nabasome code\n\ncabab",
+                        {{td::MessageEntity::Type::Pre, 7, 10}});
+  check_get_markdown_v3("asd\naba```\na b\nsome code\n```\ncabab", {}, "asd\nabaa b\nsome code\n\ncabab",
+                        {{td::MessageEntity::Type::Pre, 7, 14}});
+  check_get_markdown_v3("asd\n```\na b\nsome code\n```\ncabab", {}, "asd\na b\nsome code\n\ncabab",
+                        {{td::MessageEntity::Type::Pre, 4, 14}});
+  check_get_markdown_v3("asd\naba```a!@#$%^&*(b\nsome code\n```\ncabab", {}, "asd\nabasome code\n\ncabab",
+                        {{td::MessageEntity::Type::PreCode, 7, 10, "a!@#$%^&*(b"}});
+  check_get_markdown_v3("```\naba\n```", {}, "aba\n", {{td::MessageEntity::Type::Pre, 0, 4}});
+  check_get_markdown_v3("```\n```", {}, "\n", {{td::MessageEntity::Type::Pre, 0, 1}});
 }
diff --git a/lib/tgchat/ext/td/test/tdclient.cpp b/lib/tgchat/ext/td/test/tdclient.cpp
index 1917bf2e..215cbfd0 100644
--- a/lib/tgchat/ext/td/test/tdclient.cpp
+++ b/lib/tgchat/ext/td/test/tdclient.cpp
@@ -314,7 +314,7 @@ class SetUsername final : public TestClinetTask {
                            chat->id_, 0, nullptr, nullptr, nullptr,
                            td::make_tl_object(
                                td::make_tl_object(PSTRING() << tag_ << " INIT", td::Auto()),
-                               false, false)),
+                               nullptr, false)),
                        [](auto res) {});
     });
   }
@@ -385,7 +385,7 @@ class TestA final : public TestClinetTask {
                 chat->id_, 0, nullptr, nullptr, nullptr,
                 td::make_tl_object(
                     td::make_tl_object(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
-                    false, false)),
+                    nullptr, false)),
             [&](auto res) { this->stop(); });
       }
     });
@@ -434,7 +434,7 @@ class TestSecretChat final : public TestClinetTask {
                 chat_id_, 0, nullptr, nullptr, nullptr,
                 td::make_tl_object(
                     td::make_tl_object(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
-                    false, false)),
+                    nullptr, false)),
             [](auto res) {});
       }
     }
@@ -615,7 +615,7 @@ class CheckTestC final : public TestClinetTask {
                    chat_id_, 0, nullptr, nullptr, nullptr,
                    td::make_tl_object(
                        td::make_tl_object(PSTRING() << tag_ << " ONE_FILE", td::Auto()),
-                       false, false)),
+                       nullptr, false)),
                [](auto res) { check_td_error(res); });
   }
 
diff --git a/lib/tgchat/src/tgchat.cpp b/lib/tgchat/src/tgchat.cpp
index 95361c49..aa699077 100644
--- a/lib/tgchat/src/tgchat.cpp
+++ b/lib/tgchat/src/tgchat.cpp
@@ -40,7 +40,7 @@
 
 // #define SIMULATED_SPONSORED_MESSAGES
 
-static const int s_TdlibDate = 20230922;
+static const int s_TdlibDate = 20231106;
 
 namespace detail
 {
@@ -161,6 +161,7 @@ class TgChat::Impl
   bool IsSelf(int64_t p_UserId);
   std::string GetContactName(int64_t p_UserId);
   void GetChatHistory(int64_t p_ChatId, int64_t p_FromMsgId, int32_t p_Offset, int32_t p_Limit, bool p_Sequence);
+  td::td_api::object_ptr GetFormattedText(const std::string& p_Text);
   td::td_api::object_ptr GetMessageText(const std::string& p_Text);
   std::string ConvertMarkdownV2ToV1(const std::string& p_Str);
 
@@ -782,8 +783,9 @@ void TgChat::Impl::PerformRequest(std::shared_ptr p_RequestMessa
           auto message_content = GetMessageText(sendMessageRequest->chatMessage.text);
           send_message->input_message_content_ = std::move(message_content);
           send_message->reply_to_ =
-            td::td_api::make_object(StrUtil::NumFromHex(sendMessageRequest->chatId),
-                                                                       StrUtil::NumFromHex(sendMessageRequest->chatMessage.quotedId));
+            td::td_api::make_object(StrUtil::NumFromHex(sendMessageRequest->chatId),
+                                                                            StrUtil::NumFromHex(sendMessageRequest->chatMessage.quotedId),
+                                                                            GetFormattedText(sendMessageRequest->chatMessage.quotedText));
         }
         else
         {
@@ -2639,9 +2641,9 @@ void TgChat::Impl::GetChatHistory(int64_t p_ChatId, int64_t p_FromMsgId, int32_t
   // *INDENT-ON*
 }
 
-td::td_api::object_ptr TgChat::Impl::GetMessageText(const std::string& p_Text)
+td::td_api::object_ptr TgChat::Impl::GetFormattedText(const std::string& p_Text)
 {
-  auto message_content = td::td_api::make_object();
+  td::td_api::object_ptr formatted_text;
 
   static const bool markdownEnabled = (m_Config.Get("markdown_enabled") == "1");
   static const int32_t markdownVersion = (m_Config.Get("markdown_version") == "1") ? 1 : 2;
@@ -2656,18 +2658,24 @@ td::td_api::object_ptr TgChat::Impl::GetMessageTex
     auto parseResponse = td::Client::execute(std::move(parseRequest));
     if (parseResponse.object->get_id() == td::td_api::formattedText::ID)
     {
-      auto formattedText =
+      formatted_text =
         td::td_api::move_object_as(parseResponse.object);
-      message_content->text_ = std::move(formattedText);
     }
   }
 
-  if (!message_content->text_)
+  if (!formatted_text)
   {
-    message_content->text_ = td::td_api::make_object();
-    message_content->text_->text_ = p_Text;
+    formatted_text = td::td_api::make_object();
+    formatted_text->text_ = p_Text;
   }
 
+  return formatted_text;
+}
+
+td::td_api::object_ptr TgChat::Impl::GetMessageText(const std::string& p_Text)
+{
+  auto message_content = td::td_api::make_object();
+  message_content->text_ = GetFormattedText(p_Text);
   return message_content;
 }
 
diff --git a/src/nchat.1 b/src/nchat.1
index 227e76a3..087d1ab3 100644
--- a/src/nchat.1
+++ b/src/nchat.1
@@ -1,5 +1,5 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man.
-.TH NCHAT "1" "November 2023" "nchat v4.02" "User Commands"
+.TH NCHAT "1" "November 2023" "nchat v4.03" "User Commands"
 .SH NAME
 nchat \- ncurses chat
 .SH SYNOPSIS