diff --git a/CMake/TdSetUpCompiler.cmake b/CMake/TdSetUpCompiler.cmake index 536c5efb299b..76f3555e10f0 100644 --- a/CMake/TdSetUpCompiler.cmake +++ b/CMake/TdSetUpCompiler.cmake @@ -121,6 +121,7 @@ function(td_set_up_compiler) add_cxx_compiler_flag("-Wdeprecated") add_cxx_compiler_flag("-Wno-unused-command-line-argument") add_cxx_compiler_flag("-Qunused-arguments") + add_cxx_compiler_flag("-Wno-unknown-warning-option") add_cxx_compiler_flag("-Wodr") add_cxx_compiler_flag("-flto-odr-type-merging") @@ -135,7 +136,7 @@ function(td_set_up_compiler) # add_cxx_compiler_flag("-Wzero-as-null-pointer-constant") endif() - if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)) + if (GCC) add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too many false positives endif() if (WIN32 AND GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0)) diff --git a/CMake/iOS.cmake b/CMake/iOS.cmake index 55e3f42c18be..00e5411cebb6 100644 --- a/CMake/iOS.cmake +++ b/CMake/iOS.cmake @@ -195,13 +195,19 @@ set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS su # Set the architectures unless specified manually with IOS_ARCH if (NOT DEFINED IOS_ARCH) if (IOS_PLATFORM STREQUAL "OS") - set (IOS_ARCH "armv7;armv7s;arm64") + set (IOS_ARCH "arm64") elseif (IOS_PLATFORM STREQUAL "SIMULATOR") - set (IOS_ARCH "i386;x86_64;arm64") + set (IOS_ARCH "x86_64;arm64") elseif (IOS_PLATFORM STREQUAL "WATCHOS") set (IOS_ARCH "armv7k;arm64_32;arm64") + + # Include C++ Standard Library for Xcode 15 builds. + include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1") elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR") - set (IOS_ARCH "i386;x86_64;arm64") + set (IOS_ARCH "x86_64;arm64") + + # Include C++ Standard Library for Xcode 15 builds. + include_directories(SYSTEM "${CMAKE_IOS_SDK_ROOT}/usr/include/c++/v1") elseif (IOS_PLATFORM STREQUAL "TVOS") set (IOS_ARCH "arm64") elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR") diff --git a/CMakeLists.txt b/CMakeLists.txt index 5756b5687c8a..3ff10a9d911d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.14 LANGUAGES CXX C) +project(TDLib VERSION 1.8.18 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -285,7 +285,7 @@ set(TDLIB_SOURCE td/mtproto/Transport.cpp td/mtproto/utils.cpp - td/telegram/Account.cpp + td/telegram/AccountManager.cpp td/telegram/AnimationsManager.cpp td/telegram/Application.cpp td/telegram/AttachMenuManager.cpp @@ -369,6 +369,7 @@ set(TDLIB_SOURCE td/telegram/Game.cpp td/telegram/GameManager.cpp td/telegram/Global.cpp + td/telegram/GlobalPrivacySettings.cpp td/telegram/GroupCallManager.cpp td/telegram/GroupCallParticipant.cpp td/telegram/GroupCallParticipantOrder.cpp @@ -385,12 +386,15 @@ set(TDLIB_SOURCE td/telegram/Location.cpp td/telegram/logevent/LogEventHelper.cpp td/telegram/Logging.cpp + td/telegram/MediaArea.cpp + td/telegram/MediaAreaCoordinates.cpp td/telegram/MessageContent.cpp td/telegram/MessageContentType.cpp td/telegram/MessageDb.cpp td/telegram/MessageEntity.cpp td/telegram/MessageExtendedMedia.cpp td/telegram/MessageId.cpp + td/telegram/MessageInputReplyTo.cpp td/telegram/MessageReaction.cpp td/telegram/MessageReplyHeader.cpp td/telegram/MessageReplyInfo.cpp @@ -422,6 +426,8 @@ set(TDLIB_SOURCE td/telegram/net/SessionProxy.cpp td/telegram/net/SessionMultiProxy.cpp td/telegram/NewPasswordState.cpp + td/telegram/NotificationGroupInfo.cpp + td/telegram/NotificationGroupType.cpp td/telegram/NotificationManager.cpp td/telegram/NotificationSettingsScope.cpp td/telegram/NotificationSettingsManager.cpp @@ -442,6 +448,8 @@ set(TDLIB_SOURCE td/telegram/PremiumGiftOption.cpp td/telegram/QueryCombiner.cpp td/telegram/QueryMerger.cpp + td/telegram/ReactionManager.cpp + td/telegram/ReactionType.cpp td/telegram/RecentDialogList.cpp td/telegram/ReplyMarkup.cpp td/telegram/ReportReason.cpp @@ -468,6 +476,13 @@ set(TDLIB_SOURCE td/telegram/StickersManager.cpp td/telegram/StickerType.cpp td/telegram/StorageManager.cpp + td/telegram/StoryContent.cpp + td/telegram/StoryContentType.cpp + td/telegram/StoryDb.cpp + td/telegram/StoryInteractionInfo.cpp + td/telegram/StoryManager.cpp + td/telegram/StoryStealthMode.cpp + td/telegram/StoryViewer.cpp td/telegram/SuggestedAction.cpp td/telegram/Support.cpp td/telegram/Td.cpp @@ -480,6 +495,8 @@ set(TDLIB_SOURCE td/telegram/TranslationManager.cpp td/telegram/UpdatesManager.cpp td/telegram/Usernames.cpp + td/telegram/UserPrivacySetting.cpp + td/telegram/UserPrivacySettingRule.cpp td/telegram/Venue.cpp td/telegram/VideoNotesManager.cpp td/telegram/VideosManager.cpp @@ -518,7 +535,7 @@ set(TDLIB_SOURCE td/mtproto/utils.h td/telegram/AccessRights.h - td/telegram/Account.h + td/telegram/AccountManager.h td/telegram/AffectedHistory.h td/telegram/AnimationsManager.h td/telegram/Application.h @@ -531,6 +548,7 @@ set(TDLIB_SOURCE td/telegram/BackgroundInfo.h td/telegram/BackgroundManager.h td/telegram/BackgroundType.h + td/telegram/BlockListId.h td/telegram/BotCommand.h td/telegram/BotCommandScope.h td/telegram/BotInfoManager.h @@ -626,6 +644,7 @@ set(TDLIB_SOURCE td/telegram/GameManager.h td/telegram/GitCommitHash.h td/telegram/Global.h + td/telegram/GlobalPrivacySettings.h td/telegram/GroupCallId.h td/telegram/GroupCallManager.h td/telegram/GroupCallParticipant.h @@ -646,6 +665,8 @@ set(TDLIB_SOURCE td/telegram/logevent/LogEventHelper.h td/telegram/logevent/SecretChatEvent.h td/telegram/Logging.h + td/telegram/MediaArea.h + td/telegram/MediaAreaCoordinates.h td/telegram/MessageContent.h td/telegram/MessageContentType.h td/telegram/MessageCopyOptions.h @@ -653,6 +674,7 @@ set(TDLIB_SOURCE td/telegram/MessageEntity.h td/telegram/MessageExtendedMedia.h td/telegram/MessageId.h + td/telegram/MessageInputReplyTo.h td/telegram/MessageLinkInfo.h td/telegram/MessageReaction.h td/telegram/MessageReplyHeader.h @@ -694,11 +716,15 @@ set(TDLIB_SOURCE td/telegram/net/TempAuthKeyWatchdog.h td/telegram/NewPasswordState.h td/telegram/Notification.h + td/telegram/NotificationGroupFromDatabase.h td/telegram/NotificationGroupId.h + td/telegram/NotificationGroupInfo.h td/telegram/NotificationGroupKey.h td/telegram/NotificationGroupType.h td/telegram/NotificationId.h td/telegram/NotificationManager.h + td/telegram/NotificationObjectFullId.h + td/telegram/NotificationObjectId.h td/telegram/NotificationSettingsScope.h td/telegram/NotificationSettingsManager.h td/telegram/NotificationSound.h @@ -723,6 +749,8 @@ set(TDLIB_SOURCE td/telegram/PublicDialogType.h td/telegram/QueryCombiner.h td/telegram/QueryMerger.h + td/telegram/ReactionManager.h + td/telegram/ReactionType.h td/telegram/RecentDialogList.h td/telegram/ReplyMarkup.h td/telegram/ReportReason.h @@ -755,6 +783,17 @@ set(TDLIB_SOURCE td/telegram/StickersManager.h td/telegram/StickerType.h td/telegram/StorageManager.h + td/telegram/StoryContent.h + td/telegram/StoryContentType.h + td/telegram/StoryDb.h + td/telegram/StoryFullId.h + td/telegram/StoryId.h + td/telegram/StoryInteractionInfo.h + td/telegram/StoryListId.h + td/telegram/StoryManager.h + td/telegram/StoryNotificationSettings.h + td/telegram/StoryStealthMode.h + td/telegram/StoryViewer.h td/telegram/SuggestedAction.h td/telegram/Support.h td/telegram/Td.h @@ -770,6 +809,8 @@ set(TDLIB_SOURCE td/telegram/UpdatesManager.h td/telegram/UserId.h td/telegram/Usernames.h + td/telegram/UserPrivacySetting.h + td/telegram/UserPrivacySettingRule.h td/telegram/Venue.h td/telegram/Version.h td/telegram/VideoNotesManager.h @@ -785,6 +826,7 @@ set(TDLIB_SOURCE td/telegram/AuthManager.hpp td/telegram/BackgroundInfo.hpp td/telegram/BackgroundType.hpp + td/telegram/ChatReactions.hpp td/telegram/DialogNotificationSettings.hpp td/telegram/DialogFilter.hpp td/telegram/Dimensions.hpp @@ -805,11 +847,14 @@ set(TDLIB_SOURCE td/telegram/Game.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/MessageReaction.hpp td/telegram/MessageReplyInfo.hpp td/telegram/MinChannel.hpp + td/telegram/NotificationGroupInfo.hpp td/telegram/OrderInfo.hpp td/telegram/Photo.hpp td/telegram/PhotoSize.hpp @@ -817,6 +862,8 @@ set(TDLIB_SOURCE td/telegram/PollId.hpp td/telegram/PollManager.hpp td/telegram/PremiumGiftOption.hpp + td/telegram/ReactionManager.hpp + td/telegram/ReactionType.hpp td/telegram/ReplyMarkup.hpp td/telegram/RequestedDialogType.hpp td/telegram/ScopeNotificationSettings.hpp @@ -825,6 +872,8 @@ set(TDLIB_SOURCE td/telegram/StickerMaskPosition.hpp td/telegram/StickerPhotoSize.hpp td/telegram/StickersManager.hpp + td/telegram/StoryInteractionInfo.hpp + td/telegram/StoryStealthMode.hpp td/telegram/TranscriptionInfo.hpp td/telegram/VideoNotesManager.hpp td/telegram/VideosManager.hpp @@ -874,7 +923,7 @@ target_link_libraries(tdapi PRIVATE tdutils) if (TD_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android if (NOT JNI_FOUND) - find_package(JNI REQUIRED) + find_package(JNI REQUIRED COMPONENTS JVM) endif() message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}") target_include_directories(tdapi PUBLIC ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}) diff --git a/README.md b/README.md index 0a128da640a2..261fc32ad504 100644 --- a/README.md +++ b/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.14 REQUIRED) +find_package(Td 1.8.18 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/SplitSource.php b/SplitSource.php index 702e657a6665..7bb4df74a108 100644 --- a/SplitSource.php +++ b/SplitSource.php @@ -276,6 +276,7 @@ function ($matches) use ($needed_std_headers) { if (!preg_match('/Td::~?Td/', $new_content)) { // destructor Td::~Td needs to see definitions of all forward-declared classes $td_methods = array( + 'account_manager[_(-][^.]|AccountManager[^;>]' => "AccountManager", 'animations_manager[_(-][^.]|AnimationsManager[^;>]' => "AnimationsManager", 'attach_menu_manager[_(-][^.]|AttachMenuManager[^;>]' => "AttachMenuManager", 'audios_manager[_(-][^.]|AudiosManager' => "AudiosManager", @@ -306,11 +307,14 @@ function ($matches) use ($needed_std_headers) { 'option_manager[_(-][^.]|OptionManager' => "OptionManager", 'phone_number_manager[_(-][^.]|PhoneNumberManager' => "PhoneNumberManager", 'poll_manager[_(-][^.]|PollManager' => "PollManager", + 'privacy_manager[_(-][^.]|PrivacyManager' => "PrivacyManager", 'PublicDialogType|get_public_dialog_type' => 'PublicDialogType', + 'reaction_manager[_(-][^.]|ReactionManager' => 'ReactionManager', 'SecretChatActor' => 'SecretChatActor', 'secret_chats_manager[_(-]|SecretChatsManager' => 'SecretChatsManager', 'sponsored_message_manager[_(-][^.]|SponsoredMessageManager' => 'SponsoredMessageManager', 'stickers_manager[_(-][^.]|StickersManager' => 'StickersManager', + 'story_manager[_(-][^.]|StoryManager' => 'StoryManager', '[>](td_db[(][)]|get_td_db_impl[(])|TdDb[^A-Za-z]' => 'TdDb', 'theme_manager[_(-][^.]|ThemeManager' => "ThemeManager", 'TopDialogCategory|get_top_dialog_category' => 'TopDialogCategory', diff --git a/benchmark/bench_misc.cpp b/benchmark/bench_misc.cpp index 9089890fb93a..7cb175699f6e 100644 --- a/benchmark/bench_misc.cpp +++ b/benchmark/bench_misc.cpp @@ -46,7 +46,7 @@ class F { template void operator()(const T &x) const { - sum += static_cast(x.get_id()); + sum += static_cast(reinterpret_cast(&x)); } }; diff --git a/example/README.md b/example/README.md index ab1140921860..99efe9df083f 100644 --- a/example/README.md +++ b/example/README.md @@ -118,8 +118,6 @@ See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface, provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples. -You can also use [TDLibCore](https://github.com/ph09nix/TDLibCore) library. - Also, see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months, [egram.tel](https://github.com/egramtel/egram.tel) – a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or [telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches. diff --git a/example/cpp/CMakeLists.txt b/example/cpp/CMakeLists.txt index 19c4d11011af..fe5013035ddc 100644 --- a/example/cpp/CMakeLists.txt +++ b/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.8.14 REQUIRED) +find_package(Td 1.8.18 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/example/csharp/TdExample.cs b/example/csharp/TdExample.cs index 3cb1949d6af5..c09b0fb3d3b2 100644 --- a/example/csharp/TdExample.cs +++ b/example/csharp/TdExample.cs @@ -211,7 +211,7 @@ private static void sendMessage(long chatId, string message) 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); - _client.Send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), _defaultHandler); + _client.Send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), _defaultHandler); } static void Main() diff --git a/example/ios/Python-Apple-support.patch b/example/ios/Python-Apple-support.patch index 3650d008bc6a..a8a217a47614 100644 --- a/example/ios/Python-Apple-support.patch +++ b/example/ios/Python-Apple-support.patch @@ -1,9 +1,9 @@ diff --git a/Makefile b/Makefile -index 695be54..eda7b0d 100644 +index a1d13e9..a0841cf 100644 --- a/Makefile +++ b/Makefile -@@ -7,8 +7,11 @@ - # - watchOS - build everything for watchOS +@@ -18,8 +18,11 @@ + # - OpenSSL - build OpenSSL for all platforms # - OpenSSL-macOS - build OpenSSL for macOS # - OpenSSL-iOS - build OpenSSL for iOS +# - OpenSSL-iOS-simulator - build OpenSSL for iOS-simulator @@ -11,95 +11,88 @@ index 695be54..eda7b0d 100644 +# - OpenSSL-tvOS-simulator - build OpenSSL for tvOS-simulator # - OpenSSL-watchOS - build OpenSSL for watchOS +# - OpenSSL-watchOS-simulator - build OpenSSL for watchOS-simulator - # - BZip2-macOS - build BZip2 for macOS - # - BZip2-iOS - build BZip2 for iOS - # - BZip2-tvOS - build BZip2 for tvOS -@@ -30,37 +33,52 @@ PYTHON_VERSION=2.7.14 - PYTHON_VER=$(basename $(PYTHON_VERSION)) + # - libFFI - build libFFI for all platforms (except macOS) + # - libFFI-iOS - build libFFI for iOS + # - libFFI-tvOS - build libFFI for tvOS +@@ -50,7 +53,7 @@ XZ_VERSION=5.4.2 + # Preference is to use OpenSSL 3; however, Cryptography 3.4.8 (and + # probably some other packages as well) only works with 1.1.1, so + # we need to preserve the ability to build the older OpenSSL (for now...) +-OPENSSL_VERSION=3.1.0 ++OPENSSL_VERSION=3.1.2 + # OPENSSL_VERSION_NUMBER=1.1.1 + # OPENSSL_REVISION=q + # OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION) +@@ -59,7 +62,7 @@ LIBFFI_VERSION=3.4.2 - OPENSSL_VERSION_NUMBER=1.0.2 --OPENSSL_REVISION=n -+OPENSSL_REVISION=u - OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION) + # Supported OS and dependencies + DEPENDENCIES=BZip2 XZ OpenSSL libFFI +-OS_LIST=macOS iOS tvOS watchOS ++OS_LIST=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator - BZIP2_VERSION=1.0.6 + CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar - # Supported OS --OS=macOS iOS tvOS watchOS -+OS=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator - - # macOS targets --TARGETS-macOS=macosx.x86_64 -+TARGETS-macOS=macosx.arm64 macosx.x86_64 -+PYTHON_TARGETS-macOS=macOS - CFLAGS-macOS=-mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET) +@@ -69,22 +72,34 @@ VERSION_MIN-macOS=10.15 + CFLAGS-macOS=-mmacosx-version-min=$(VERSION_MIN-macOS) # iOS targets --TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.i386 iphoneos.armv7 iphoneos.armv7s iphoneos.arm64 -+TARGETS-iOS=iphoneos.armv7 iphoneos.armv7s iphoneos.arm64 - CFLAGS-iOS=-mios-version-min=7.0 --CFLAGS-iphoneos.armv7=-fembed-bitcode --CFLAGS-iphoneos.armv7s=-fembed-bitcode --CFLAGS-iphoneos.arm64=-fembed-bitcode -+CFLAGS-iphoneos.armv7= -+CFLAGS-iphoneos.armv7s= -+CFLAGS-iphoneos.arm64= -+ -+# iOS-simulator targets -+TARGETS-iOS-simulator=iphonesimulator.x86_64 iphonesimulator.i386 iphonesimulator.arm64 -+CFLAGS-iOS-simulator=-mios-simulator-version-min=7.0 +-TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64 ++TARGETS-iOS=iphoneos.arm64 + VERSION_MIN-iOS=12.0 + CFLAGS-iOS=-mios-version-min=$(VERSION_MIN-iOS) ++# iOS-simulator targets ++TARGETS-iOS-simulator=iphonesimulator.x86_64 iphonesimulator.arm64 ++CFLAGS-iOS-simulator=-mios-simulator-version-min=$(VERSION_MIN-iOS) ++ # tvOS targets --TARGETS-tvOS=appletvsimulator.x86_64 appletvos.arm64 +-TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64 +TARGETS-tvOS=appletvos.arm64 - CFLAGS-tvOS=-mtvos-version-min=9.0 --CFLAGS-appletvos.arm64=-fembed-bitcode -+CFLAGS-appletvos.arm64= + VERSION_MIN-tvOS=9.0 + CFLAGS-tvOS=-mtvos-version-min=$(VERSION_MIN-tvOS) PYTHON_CONFIGURE-tvOS=ac_cv_func_sigaltstack=no +# tvOS-simulator targets +TARGETS-tvOS-simulator=appletvsimulator.x86_64 appletvsimulator.arm64 -+CFLAGS-tvOS-simulator=-mtvos-simulator-version-min=9.0 ++CFLAGS-tvOS-simulator=-mtvos-simulator-version-min=$(VERSION_MIN-tvOS) + # watchOS targets --TARGETS-watchOS=watchsimulator.i386 watchos.armv7k +-TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32 +TARGETS-watchOS=watchos.armv7k watchos.arm64_32 watchos.arm64 - CFLAGS-watchOS=-mwatchos-version-min=4.0 --CFLAGS-watchos.armv7k=-fembed-bitcode -+CFLAGS-watchos.armv7k= -+CFLAGS-watchos.arm64_32= -+CFLAGS-watchos.arm64= + VERSION_MIN-watchOS=4.0 + CFLAGS-watchOS=-mwatchos-version-min=$(VERSION_MIN-watchOS) PYTHON_CONFIGURE-watchOS=ac_cv_func_sigaltstack=no +# watchOS-simulator targets +TARGETS-watchOS-simulator=watchsimulator.i386 watchsimulator.x86_64 watchsimulator.arm64 -+CFLAGS-watchOS-simulator=-mwatchos-simulator-version-min=4.0 ++CFLAGS-watchOS-simulator=-mwatchos-simulator-version-min=$(VERSION_MIN-watchOS) + - # override machine types for arm64 - MACHINE_DETAILED-arm64=aarch64 - MACHINE_SIMPLE-arm64=arm -@@ -194,9 +212,11 @@ endif + # The architecture of the machine doing the build + HOST_ARCH=$(shell uname -m) + HOST_PYTHON=install/macOS/macosx/python-$(PYTHON_VERSION) +@@ -662,7 +677,7 @@ BZIP2_FATLIB-$(sdk)=$$(BZIP2_MERGE-$(sdk))/lib/libbz2.a + XZ_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/$(sdk)/xz-$(XZ_VERSION) + XZ_FATLIB-$(sdk)=$$(XZ_MERGE-$(sdk))/lib/liblzma.a + +-OPENSSL_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION) ++OPENSSL_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/openssl + OPENSSL_FATINCLUDE-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/include + OPENSSL_SSL_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libssl.a + OPENSSL_CRYPTO_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libcrypto.a +@@ -716,14 +731,14 @@ $$(OPENSSL_SSL_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENS + mkdir -p $$(OPENSSL_MERGE-$(sdk))/lib + lipo -create -output $$@ \ + $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_SSL_LIB-$$(target))) \ +- 2>&1 | tee -a merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION).ssl.lipo.log ++ 2>&1 | tee -a merge/$(os)/openssl-$(OPENSSL_VERSION).ssl.lipo.log - # Configure the build - ifeq ($2,macOS) -+ # Patch openssl-darwin-arm64 -+ cd $$(OPENSSL_DIR-$1) && git apply ../../../../openssl-1.0.2n-darwin-arm64.patch - cd $$(OPENSSL_DIR-$1) && \ - CC="$$(CC-$1)" MACOSX_DEPLOYMENT_TARGET=$$(MACOSX_DEPLOYMENT_TARGET) \ -- ./Configure darwin64-x86_64-cc --openssldir=$(PROJECT_DIR)/build/$2/openssl -+ ./Configure darwin64-$$(ARCH-$1)-cc --openssldir=$(PROJECT_DIR)/build/$2/openssl - else - cd $$(OPENSSL_DIR-$1) && \ - CC="$$(CC-$1)" \ -@@ -216,7 +235,10 @@ $$(OPENSSL_DIR-$1)/libssl.a $$(OPENSSL_DIR-$1)/libcrypto.a: $$(OPENSSL_DIR-$1)/M - CC="$$(CC-$1)" \ - CROSS_TOP="$$(dir $$(SDK_ROOT-$1)).." \ - CROSS_SDK="$$(notdir $$(SDK_ROOT-$1))" \ -- make all && make install -+ make build_libs && \ -+ mkdir -p "$(PROJECT_DIR)/build/$2/openssl/lib" && \ -+ cp libcrypto.a libssl.a "$(PROJECT_DIR)/build/$2/openssl/lib" -+ -cd $$(OPENSSL_DIR-$1) && make install_sw 2> /dev/null + $$(OPENSSL_CRYPTO_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_CRYPTO_LIB-$$(target))) + @echo ">>> Build OpenSSL crypto fat library for $(sdk)" + mkdir -p $$(OPENSSL_MERGE-$(sdk))/lib + lipo -create -output $$@ \ + $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_CRYPTO_LIB-$$(target))) \ +- 2>&1 | tee -a merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION).crypto.lipo.log ++ 2>&1 | tee -a merge/$(os)/openssl-$(OPENSSL_VERSION).crypto.lipo.log - # Unpack BZip2 - $$(BZIP2_DIR-$1)/Makefile: downloads/bzip2-$(BZIP2_VERSION).tgz + ########################################################################### + # SDK: libFFI diff --git a/example/ios/build-openssl.sh b/example/ios/build-openssl.sh index e31f565691b8..7c3c6cd975bc 100755 --- a/example/ios/build-openssl.sh +++ b/example/ios/build-openssl.sh @@ -3,12 +3,11 @@ cd $(dirname $0) git clone https://github.com/beeware/Python-Apple-support cd Python-Apple-support -git checkout 60b990128d5f1f04c336ff66594574515ab56604 || exit 1 +git checkout 6f43aba0ddd5a9f52f39775d0141bd4363614020 || exit 1 git reset --hard || exit 1 git apply ../Python-Apple-support.patch || exit 1 cd .. -#TODO: change openssl version platforms="macOS iOS watchOS tvOS" for platform in $platforms; @@ -31,8 +30,8 @@ do cd .. rm -rf third_party/openssl/$platform || exit 1 mkdir -p third_party/openssl/$platform/lib || exit 1 - cp ./Python-Apple-support/build/$platform/libcrypto.a third_party/openssl/$platform/lib/ || exit 1 - cp ./Python-Apple-support/build/$platform/libssl.a third_party/openssl/$platform/lib/ || exit 1 - cp -r ./Python-Apple-support/build/$platform/openssl/include/ third_party/openssl/$platform/include || exit 1 + cp ./Python-Apple-support/merge/$platform/openssl/lib/libcrypto.a third_party/openssl/$platform/lib/ || exit 1 + cp ./Python-Apple-support/merge/$platform/openssl/lib/libssl.a third_party/openssl/$platform/lib/ || exit 1 + cp -r ./Python-Apple-support/merge/$platform/openssl/include/ third_party/openssl/$platform/include || exit 1 done done diff --git a/example/ios/openssl-1.0.2n-darwin-arm64.patch b/example/ios/openssl-1.0.2n-darwin-arm64.patch deleted file mode 100644 index 5239d94c3233..000000000000 --- a/example/ios/openssl-1.0.2n-darwin-arm64.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- Configure 2019-12-20 14:02:41.000000000 +0100 -+++ Configure 2020-11-22 16:23:13.000000000 +0100 -@@ -650,7 +650,9 @@ - "darwin-i386-cc","cc:-arch i386 -O3 -fomit-frame-pointer -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:".eval{my $asm=$x86_asm;$asm=~s/cast\-586\.o//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - "debug-darwin-i386-cc","cc:-arch i386 -g3 -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:${x86_asm}:macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - "darwin64-x86_64-cc","cc:-arch x86_64 -O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch x86_64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"darwin64-arm64-cc","cc:-arch arm64 -O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-arch arm64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - "debug-darwin64-x86_64-cc","cc:-arch x86_64 -ggdb -g2 -O0 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch x86_64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", -+"debug-darwin64-arm64-cc","cc:-arch arm64 -ggdb -g2 -O0 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-arch arm64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - "debug-darwin-ppc-cc","cc:-DBN_DEBUG -DREF_CHECK -DCONF_DEBUG -DCRYPTO_MDEBUG -DB_ENDIAN -g -Wall -O::-D_REENTRANT:MACOSX::BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc32_asm}:osx32:dlfcn:darwin-shared:-fPIC:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", - # iPhoneOS/iOS - "iphoneos-cross","llvm-gcc:-O3 -isysroot \$(CROSS_TOP)/SDKs/\$(CROSS_SDK) -fomit-frame-pointer -fno-common::-D_REENTRANT:macOS:-Wl,-search_paths_first%:BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib", diff --git a/example/java/CMakeLists.txt b/example/java/CMakeLists.txt index 576eee2cfcfd..e3ef3eb9bd37 100644 --- a/example/java/CMakeLists.txt +++ b/example/java/CMakeLists.txt @@ -24,7 +24,7 @@ endif() find_package(Td REQUIRED) if (NOT JNI_FOUND) - find_package(JNI REQUIRED) + find_package(JNI REQUIRED COMPONENTS JVM) endif() message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}") diff --git a/example/java/org/drinkless/tdlib/example/Example.java b/example/java/org/drinkless/tdlib/example/Example.java index ce0ebe113fdd..bff44efd727d 100644 --- a/example/java/org/drinkless/tdlib/example/Example.java +++ b/example/java/org/drinkless/tdlib/example/Example.java @@ -294,7 +294,7 @@ private static void sendMessage(long chatId, String message) { 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); - client.send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), defaultHandler); + client.send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), defaultHandler); } public static void main(String[] args) throws InterruptedException { @@ -429,6 +429,14 @@ public void onResult(TdApi.Object object) { } break; } + case TdApi.UpdateChatPermissions.CONSTRUCTOR: { + TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.permissions = update.permissions; + } + break; + } case TdApi.UpdateChatLastMessage.CONSTRUCTOR: { TdApi.UpdateChatLastMessage updateChat = (TdApi.UpdateChatLastMessage) object; TdApi.Chat chat = chats.get(updateChat.chatId); @@ -485,44 +493,44 @@ public void onResult(TdApi.Object object) { } break; } - case TdApi.UpdateChatUnreadMentionCount.CONSTRUCTOR: { - TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object; + case TdApi.UpdateChatActionBar.CONSTRUCTOR: { + TdApi.UpdateChatActionBar updateChat = (TdApi.UpdateChatActionBar) object; TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.unreadMentionCount = updateChat.unreadMentionCount; + chat.actionBar = updateChat.actionBar; } break; } - case TdApi.UpdateMessageMentionRead.CONSTRUCTOR: { - TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object; + case TdApi.UpdateChatAvailableReactions.CONSTRUCTOR: { + TdApi.UpdateChatAvailableReactions updateChat = (TdApi.UpdateChatAvailableReactions) object; TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.unreadMentionCount = updateChat.unreadMentionCount; + chat.availableReactions = updateChat.availableReactions; } break; } - case TdApi.UpdateChatReplyMarkup.CONSTRUCTOR: { - TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object; + case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: { + TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object; TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.replyMarkupMessageId = updateChat.replyMarkupMessageId; + chat.draftMessage = updateChat.draftMessage; + setChatPositions(chat, updateChat.positions); } break; } - case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: { - TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object; + case TdApi.UpdateChatMessageSender.CONSTRUCTOR: { + TdApi.UpdateChatMessageSender updateChat = (TdApi.UpdateChatMessageSender) object; TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.draftMessage = updateChat.draftMessage; - setChatPositions(chat, updateChat.positions); + chat.messageSenderId = updateChat.messageSenderId; } break; } - case TdApi.UpdateChatPermissions.CONSTRUCTOR: { - TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object; - TdApi.Chat chat = chats.get(update.chatId); + case TdApi.UpdateChatMessageAutoDeleteTime.CONSTRUCTOR: { + TdApi.UpdateChatMessageAutoDeleteTime updateChat = (TdApi.UpdateChatMessageAutoDeleteTime) object; + TdApi.Chat chat = chats.get(updateChat.chatId); synchronized (chat) { - chat.permissions = update.permissions; + chat.messageAutoDeleteTime = updateChat.messageAutoDeleteTime; } break; } @@ -534,6 +542,62 @@ public void onResult(TdApi.Object object) { } break; } + case TdApi.UpdateChatPendingJoinRequests.CONSTRUCTOR: { + TdApi.UpdateChatPendingJoinRequests update = (TdApi.UpdateChatPendingJoinRequests) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.pendingJoinRequests = update.pendingJoinRequests; + } + break; + } + case TdApi.UpdateChatReplyMarkup.CONSTRUCTOR: { + TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.replyMarkupMessageId = updateChat.replyMarkupMessageId; + } + break; + } + case TdApi.UpdateChatBackground.CONSTRUCTOR: { + TdApi.UpdateChatBackground updateChat = (TdApi.UpdateChatBackground) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.background = updateChat.background; + } + break; + } + case TdApi.UpdateChatTheme.CONSTRUCTOR: { + TdApi.UpdateChatTheme updateChat = (TdApi.UpdateChatTheme) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.themeName = updateChat.themeName; + } + break; + } + case TdApi.UpdateChatUnreadMentionCount.CONSTRUCTOR: { + TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.unreadMentionCount = updateChat.unreadMentionCount; + } + break; + } + case TdApi.UpdateChatUnreadReactionCount.CONSTRUCTOR: { + TdApi.UpdateChatUnreadReactionCount updateChat = (TdApi.UpdateChatUnreadReactionCount) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.unreadReactionCount = updateChat.unreadReactionCount; + } + break; + } + case TdApi.UpdateChatVideoChat.CONSTRUCTOR: { + TdApi.UpdateChatVideoChat updateChat = (TdApi.UpdateChatVideoChat) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.videoChat = updateChat.videoChat; + } + break; + } case TdApi.UpdateChatDefaultDisableNotification.CONSTRUCTOR: { TdApi.UpdateChatDefaultDisableNotification update = (TdApi.UpdateChatDefaultDisableNotification) object; TdApi.Chat chat = chats.get(update.chatId); @@ -542,6 +606,22 @@ public void onResult(TdApi.Object object) { } break; } + case TdApi.UpdateChatHasProtectedContent.CONSTRUCTOR: { + TdApi.UpdateChatHasProtectedContent updateChat = (TdApi.UpdateChatHasProtectedContent) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.hasProtectedContent = updateChat.hasProtectedContent; + } + break; + } + case TdApi.UpdateChatIsTranslatable.CONSTRUCTOR: { + TdApi.UpdateChatIsTranslatable update = (TdApi.UpdateChatIsTranslatable) object; + TdApi.Chat chat = chats.get(update.chatId); + synchronized (chat) { + chat.isTranslatable = update.isTranslatable; + } + break; + } case TdApi.UpdateChatIsMarkedAsUnread.CONSTRUCTOR: { TdApi.UpdateChatIsMarkedAsUnread update = (TdApi.UpdateChatIsMarkedAsUnread) object; TdApi.Chat chat = chats.get(update.chatId); @@ -550,11 +630,11 @@ public void onResult(TdApi.Object object) { } break; } - case TdApi.UpdateChatIsBlocked.CONSTRUCTOR: { - TdApi.UpdateChatIsBlocked update = (TdApi.UpdateChatIsBlocked) object; + case TdApi.UpdateChatBlockList.CONSTRUCTOR: { + TdApi.UpdateChatBlockList update = (TdApi.UpdateChatBlockList) object; TdApi.Chat chat = chats.get(update.chatId); synchronized (chat) { - chat.isBlocked = update.isBlocked; + chat.blockList = update.blockList; } break; } @@ -567,6 +647,23 @@ public void onResult(TdApi.Object object) { break; } + case TdApi.UpdateMessageMentionRead.CONSTRUCTOR: { + TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.unreadMentionCount = updateChat.unreadMentionCount; + } + break; + } + case TdApi.UpdateMessageUnreadReactions.CONSTRUCTOR: { + TdApi.UpdateMessageUnreadReactions updateChat = (TdApi.UpdateMessageUnreadReactions) object; + TdApi.Chat chat = chats.get(updateChat.chatId); + synchronized (chat) { + chat.unreadReactionCount = updateChat.unreadReactionCount; + } + break; + } + case TdApi.UpdateUserFullInfo.CONSTRUCTOR: TdApi.UpdateUserFullInfo updateUserFullInfo = (TdApi.UpdateUserFullInfo) object; usersFullInfo.put(updateUserFullInfo.userId, updateUserFullInfo.userFullInfo); diff --git a/example/java/td_jni.cpp b/example/java/td_jni.cpp index fb7554c747e3..993e36222e00 100644 --- a/example/java/td_jni.cpp +++ b/example/java/td_jni.cpp @@ -168,7 +168,7 @@ static jint register_native(JavaVM *vm) { #undef TD_OBJECT td::jni::init_vars(env, PACKAGE_NAME); - td::td_api::set_package_name(PACKAGE_NAME); + td::td_api::get_package_name_ref() = PACKAGE_NAME; return JAVA_VERSION; } diff --git a/example/uwp/Telegram.Td.UWP.nuspec b/example/uwp/Telegram.Td.UWP.nuspec index 039ec2adaf0a..45975ed777da 100644 --- a/example/uwp/Telegram.Td.UWP.nuspec +++ b/example/uwp/Telegram.Td.UWP.nuspec @@ -2,7 +2,7 @@ Telegram.Td.UWP - 1.8.14 + 1.8.18 TDLib for Universal Windows Platform Telegram Telegram @@ -12,5 +12,8 @@ https://github.com/tdlib/td/blob/master/CHANGELOG.md TDLib for Universal Windows Platform © Telegram FZ-LLC. All rights reserved. + + + \ No newline at end of file diff --git a/example/uwp/extension.vsixmanifest b/example/uwp/extension.vsixmanifest index 66c434a16459..db21234412ac 100644 --- a/example/uwp/extension.vsixmanifest +++ b/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/sqlite/CMakeLists.txt b/sqlite/CMakeLists.txt index 1072a5a52ca0..cc698b2be802 100644 --- a/sqlite/CMakeLists.txt +++ b/sqlite/CMakeLists.txt @@ -61,7 +61,7 @@ endif() if (GCC OR CLANG) target_compile_options(tdsqlite PRIVATE -Wno-deprecated-declarations -Wno-unused-variable -Wno-unused-const-variable -Wno-unused-function -Wno-maybe-uninitialized -Wno-return-local-addr) if (CLANG) - target_compile_options(tdsqlite PRIVATE -Wno-parentheses-equality -Wno-unused-value -Wno-unused-command-line-argument -Qunused-arguments) + target_compile_options(tdsqlite PRIVATE -Wno-parentheses-equality -Wno-unused-value -Wno-unknown-warning-option -Wno-unused-command-line-argument -Qunused-arguments) endif() if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0)) target_compile_options(tdsqlite PRIVATE -Wno-return-local-addr -Wno-stringop-overflow) diff --git a/td/generate/CMakeLists.txt b/td/generate/CMakeLists.txt index e7f6aea06741..d822357c560b 100644 --- a/td/generate/CMakeLists.txt +++ b/td/generate/CMakeLists.txt @@ -101,7 +101,7 @@ if (NOT CMAKE_CROSSCOMPILING) endif() if (PHP_EXECUTABLE AND NOT TD_ENABLE_DOTNET) - set(GENERATE_COMMON_CMD generate_common && ${PHP_EXECUTABLE} DoxygenTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/td_api.h) + set(GENERATE_COMMON_CMD generate_common && ${PHP_EXECUTABLE} ../DoxygenTlDocumentationGenerator.php ../scheme/td_api.tl td/telegram/td_api.h) else() set(GENERATE_COMMON_CMD generate_common) endif() @@ -132,7 +132,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(generate_common ${TL_GENERATE_COMMON_SOURCE}) target_link_libraries(generate_common PRIVATE tdtl) add_custom_target(tl_generate_common - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/auto COMMAND ${GENERATE_COMMON_CMD} COMMENT "Generate common TL source files" DEPENDS generate_common tl_generate_tlo ${TLO_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenTlDocumentationGenerator.php @@ -147,7 +147,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(generate_c ${TL_GENERATE_C_SOURCE}) target_link_libraries(generate_c PRIVATE tdtl) add_custom_target(tl_generate_c - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/auto COMMAND generate_c COMMENT "Generate C TL source files" DEPENDS generate_c tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl @@ -159,7 +159,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(generate_json ${TL_GENERATE_JSON_SOURCE}) target_link_libraries(generate_json PRIVATE tdtl tdutils) add_custom_target(tl_generate_json - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/auto COMMAND generate_json COMMENT "Generate JSON TL source files" DEPENDS generate_json tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl @@ -173,7 +173,7 @@ if (NOT CMAKE_CROSSCOMPILING) if (TD_ENABLE_DOTNET) if (PHP_EXECUTABLE) - set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TD_API_TLO_FILE} && ${PHP_EXECUTABLE} DotnetTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/TdDotNetApi.h) + set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TD_API_TLO_FILE} && ${PHP_EXECUTABLE} ../DotnetTlDocumentationGenerator.php ../scheme/td_api.tl td/telegram/TdDotNetApi.h) else() set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TD_API_TLO_FILE}) endif() @@ -181,7 +181,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_executable(td_generate_dotnet_api generate_dotnet.cpp tl_writer_dotnet.h) target_link_libraries(td_generate_dotnet_api PRIVATE tdtl) add_custom_target(generate_dotnet_api - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/auto COMMAND ${GENERATE_DOTNET_API_CMD} COMMENT "Generate .NET API files" DEPENDS td_generate_dotnet_api tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DotnetTlDocumentationGenerator.php diff --git a/td/generate/DoxygenTlDocumentationGenerator.php b/td/generate/DoxygenTlDocumentationGenerator.php index bb945fd14959..95475e647328 100644 --- a/td/generate/DoxygenTlDocumentationGenerator.php +++ b/td/generate/DoxygenTlDocumentationGenerator.php @@ -130,7 +130,7 @@ protected function needSkipLine($line) strpos($tline, 'result += ') === 0 || strpos($tline, 'result = ') || strpos($tline, ' : values') || strpos($line, 'JNIEnv') || strpos($line, 'jfieldID') || $tline === 'virtual ~Object() {' || $tline === 'virtual void store(TlStorerToString &s, const char *field_name) const = 0;' || - $tline === 'void set_package_name(const char *new_package_name);'; + $tline === 'const char *&get_package_name_ref();'; } protected function isHeaderLine($line) @@ -235,7 +235,7 @@ protected function addGlobalDocumentation() * auto get_me_request = td::td_api::make_object(); * 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, 0, nullptr, nullptr, + * 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)); * \\endcode * diff --git a/td/generate/TlDocumentationGenerator.php b/td/generate/TlDocumentationGenerator.php index 09c0f58c702d..e46deafacbae 100644 --- a/td/generate/TlDocumentationGenerator.php +++ b/td/generate/TlDocumentationGenerator.php @@ -42,6 +42,15 @@ final protected function addDot($str) { return ''; } + $brackets = preg_replace("/[^[\\](){}'\"]/", '', preg_replace("/[a-z]'/", '', $str)); + while (strlen($brackets)) { + $brackets = preg_replace(array('/[[]]/', '/[(][)]/', '/[{][}]/', "/''/", '/""/'), '', $brackets, -1, $replaced_bracket_count); + if ($replaced_bracket_count == 0) { + $this->printError('Unmatched bracket in '.$str); + break; + } + } + $len = strlen($str); if ($str[$len - 1] === '.') { return $str; @@ -66,7 +75,7 @@ final protected function addDot($str) { return substr($str, 0, -1).'.)'; } } else { - $this->printError("Unmatched bracket"); + $this->printError('Unmatched bracket'); } } return $str.'.'; diff --git a/td/generate/generate_c.cpp b/td/generate/generate_c.cpp index ecd7ca058fb6..1e8f83ecc24f 100644 --- a/td/generate/generate_c.cpp +++ b/td/generate/generate_c.cpp @@ -10,11 +10,8 @@ #include "td/tl/tl_generate.h" int main() { - td::tl::tl_config config_td = td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo"); - td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api.h", - td::TlWriterCCommon("TdApi", 1, "#include \"td/telegram/td_api.h\"\n")); - td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api_inner.h", - td::TlWriterCCommon("TdApi", -1, "#include \"td/telegram/td_api.h\"\n")); - td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api.cpp", - td::TlWriterCCommon("TdApi", 0, "#include \"td/telegram/td_api.h\"\n")); + td::tl::tl_config config_td = td::tl::read_tl_config_from_file("tlo/td_api.tlo"); + td::tl::write_tl_to_file(config_td, "td/telegram/td_tdc_api.h", td::TlWriterCCommon("TdApi", 1)); + td::tl::write_tl_to_file(config_td, "td/telegram/td_tdc_api_inner.h", td::TlWriterCCommon("TdApi", -1)); + td::tl::write_tl_to_file(config_td, "td/telegram/td_tdc_api.cpp", td::TlWriterCCommon("TdApi", 0)); } diff --git a/td/generate/generate_common.cpp b/td/generate/generate_common.cpp index f295f95cfa5f..32602b4b8a30 100644 --- a/td/generate/generate_common.cpp +++ b/td/generate/generate_common.cpp @@ -16,33 +16,37 @@ #include #include -template +template static void generate_cpp(const std::string &directory, const std::string &tl_name, const std::string &string_type, const std::string &bytes_type, const std::vector &ext_cpp_includes, const std::vector &ext_h_includes) { std::string path = directory + "/" + tl_name; - td::tl::tl_config config = td::tl::read_tl_config_from_file("auto/tlo/" + tl_name + ".tlo"); + td::tl::tl_config config = td::tl::read_tl_config_from_file("tlo/" + tl_name + ".tlo"); td::tl::write_tl_to_file(config, path + ".cpp", WriterCpp(tl_name, string_type, bytes_type, ext_cpp_includes)); - td::tl::write_tl_to_file(config, path + ".h", WriterH(tl_name, string_type, bytes_type, ext_h_includes)); + if (generate_multiple_headers) { + td::tl::write_tl_to_multiple_files(config, path, ".h", WriterH(tl_name, string_type, bytes_type, ext_h_includes)); + } else { + td::tl::write_tl_to_file(config, path + ".h", WriterH(tl_name, string_type, bytes_type, ext_h_includes)); + } td::tl::write_tl_to_file(config, path + ".hpp", WriterHpp(tl_name, string_type, bytes_type)); } int main() { - generate_cpp<>("auto/td/telegram", "telegram_api", "std::string", "BufferSlice", + generate_cpp<>("td/telegram", "telegram_api", "std::string", "BufferSlice", {"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""}, {"\"td/utils/buffer.h\""}); - generate_cpp<>("auto/td/telegram", "secret_api", "std::string", "BufferSlice", + generate_cpp<>("td/telegram", "secret_api", "std::string", "BufferSlice", {"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""}, {"\"td/utils/buffer.h\""}); - generate_cpp<>("auto/td/mtproto", "mtproto_api", "Slice", "Slice", + generate_cpp<>("td/mtproto", "mtproto_api", "Slice", "Slice", {"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""}, {"\"td/utils/Slice.h\"", "\"td/utils/UInt.h\""}); #ifdef TD_ENABLE_JNI - generate_cpp( - "auto/td/telegram", "td_api", "std::string", "std::string", {"\"td/tl/tl_jni_object.h\""}, {""}); + generate_cpp( + "td/telegram", "td_api", "std::string", "std::string", {"\"td/tl/tl_jni_object.h\""}, {""}); #else - generate_cpp<>("auto/td/telegram", "td_api", "std::string", "std::string", {}, {""}); + generate_cpp<>("td/telegram", "td_api", "std::string", "std::string", {}, {""}); #endif } diff --git a/td/generate/generate_dotnet.cpp b/td/generate/generate_dotnet.cpp index 1452e0a691cd..c194d51e7d5a 100644 --- a/td/generate/generate_dotnet.cpp +++ b/td/generate/generate_dotnet.cpp @@ -16,7 +16,7 @@ int main(int argc, char *argv[]) { td::tl::tl_config config_td = td::tl::read_tl_config_from_file(argv[1]); - td::tl::write_tl_to_file(config_td, "auto/td/telegram/TdDotNetApi.cpp", + td::tl::write_tl_to_file(config_td, "td/telegram/TdDotNetApi.cpp", td::tl::TlWriterDotNet("TdApi", false, "#include \"td/telegram/TdDotNetApi.h\"\n\n")); - td::tl::write_tl_to_file(config_td, "auto/td/telegram/TdDotNetApi.h", td::tl::TlWriterDotNet("TdApi", true, "")); + td::tl::write_tl_to_file(config_td, "td/telegram/TdDotNetApi.h", td::tl::TlWriterDotNet("TdApi", true, "")); } diff --git a/td/generate/generate_json.cpp b/td/generate/generate_json.cpp index bff13683519d..f178cedb8ff3 100644 --- a/td/generate/generate_json.cpp +++ b/td/generate/generate_json.cpp @@ -10,6 +10,6 @@ #include "td/tl/tl_generate.h" int main() { - td::gen_json_converter(td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo"), "td/telegram/td_api_json", + td::gen_json_converter(td::tl::read_tl_config_from_file("tlo/td_api.tlo"), "td/telegram/td_api_json", td::tl::TL_writer::Server); } diff --git a/td/generate/remove_documentation.cpp b/td/generate/remove_documentation.cpp index 435a905562f2..f6cb017e6db4 100644 --- a/td/generate/remove_documentation.cpp +++ b/td/generate/remove_documentation.cpp @@ -13,9 +13,9 @@ int main(int argc, char *argv[]) { for (int i = 1; i < argc; i++) { std::string file_name = argv[i]; - std::string old_contents = td::tl::get_file_contents(file_name, "rb"); + std::string old_contents = td::tl::get_file_contents(file_name); std::string new_contents = td::tl::remove_documentation(old_contents); - if (new_contents != old_contents && !td::tl::put_file_contents(file_name, "wb", new_contents)) { + if (!td::tl::put_file_contents(file_name, new_contents, true)) { std::fprintf(stderr, "Can't write file %s\n", file_name.c_str()); std::abort(); } diff --git a/td/generate/scheme/mtproto_api.tl b/td/generate/scheme/mtproto_api.tl index de2e94bd6213..b2508b478b9e 100644 --- a/td/generate/scheme/mtproto_api.tl +++ b/td/generate/scheme/mtproto_api.tl @@ -25,8 +25,7 @@ dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer; dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer; -bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int -= BindAuthKeyInner; +bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; //rpc_result#f35c6d01 req_msg_id:long result:string = RpcResult; rpc_error#2144ca19 error_code:int error_message:string = RpcError; diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index b6493936200e..22b56d73beb4 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -195,7 +195,7 @@ localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_ //@description Represents a remote file //@id Remote file identifier; may be empty. Can be used by the current user across application restarts or even from other devices. Uniquely identifies a file, but a file can have a lot of different valid identifiers. -//-If the ID starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known. +//-If the identifier starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known. //-If downloadFile/addFileToDownloads is called on such a file or if it is sent to a secret chat, TDLib starts a file generation process by sending updateFileGenerationStart to the application with the HTTP URL in the original_path and "#url#" as the conversion string. //-Application must generate the file by downloading it to the specified location //@unique_id Unique file identifier; may be empty if unknown. The unique file identifier which is the same for the same file even for different users and is persistent over time @@ -215,10 +215,10 @@ file id:int32 size:int53 expected_size:int53 local:localFile remote:remoteFile = //@class InputFile @description Points to a file -//@description A file defined by its unique ID @id Unique file identifier +//@description A file defined by its unique identifier @id Unique file identifier inputFileId id:int32 = InputFile; -//@description A file defined by its remote ID. The remote ID is guaranteed to be usable only if the corresponding file is still accessible to the user and known to TDLib. +//@description A file defined by its remote identifier. The remote identifier is guaranteed to be usable only if the corresponding file is still accessible to the user and known to TDLib. //-For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application //@id Remote file identifier inputFileRemote id:string = InputFile; @@ -250,7 +250,7 @@ minithumbnail width:int32 height:int32 data:bytes = Minithumbnail; //@description The thumbnail is in JPEG format thumbnailFormatJpeg = ThumbnailFormat; -//@description The thumbnail is in static GIF format. It will be used only for some bot inline results +//@description The thumbnail is in static GIF format. It will be used only for some bot inline query results thumbnailFormatGif = ThumbnailFormat; //@description The thumbnail is in MPEG4 format. It will be used only for some animations and videos @@ -497,13 +497,13 @@ webApp short_name:string title:string description:string photo:photo animation:a //@question Poll question; 1-300 characters //@options List of poll answer options //@total_voter_count Total number of voters, participating in the poll -//@recent_voter_user_ids User identifiers of recent voters, if the poll is non-anonymous +//@recent_voter_ids Identifiers of recent voters, if the poll is non-anonymous //@is_anonymous True, if the poll is anonymous //@type Type of the poll //@open_period Amount of time the poll will be active after creation, in seconds //@close_date Point in time (Unix timestamp) when the poll will automatically be closed //@is_closed True, if the poll is closed -poll id:int64 question:string options:vector total_voter_count:int32 recent_voter_user_ids:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = Poll; +poll id:int64 question:string options:vector total_voter_count:int32 recent_voter_ids:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = Poll; //@description Describes a chat background @@ -555,7 +555,7 @@ userTypeDeleted = UserType; //@is_inline True, if the bot supports inline queries //@inline_query_placeholder Placeholder for inline queries (displayed on the application input field) //@need_location True, if the location of the user is expected to be sent with every inline query to this bot -//@can_be_added_to_attachment_menu True, if the bot can be added to attachment menu +//@can_be_added_to_attachment_menu True, if the bot can be added to attachment or side menu userTypeBot can_be_edited:Bool can_join_groups:Bool can_read_all_group_messages:Bool is_inline:Bool inline_query_placeholder:string need_location:Bool can_be_added_to_attachment_menu:Bool = UserType; //@description No information on the user besides the user identifier is available, yet this user has not been deleted. This object is extremely rare and must be handled like a deleted user. It is not possible to perform any actions on users of this type @@ -681,11 +681,13 @@ premiumPaymentOption currency:string amount:int53 discount_percentage:int32 mont premiumStatePaymentOption payment_option:premiumPaymentOption is_current:Bool is_upgrade:Bool last_transaction_id:string = PremiumStatePaymentOption; -//@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 -emojiStatus custom_emoji_id:int64 = EmojiStatus; +//@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 +//@expiration_date Point in time (Unix timestamp) when the status will expire; 0 if never +emojiStatus custom_emoji_id:int64 expiration_date:int32 = EmojiStatus; -//@description Contains a list of emoji statuses @emoji_statuses The list of emoji statuses -emojiStatuses emoji_statuses:vector = EmojiStatuses; +//@description Contains a list of custom emoji identifiers, which can be set as emoji statuses @custom_emoji_ids The list of custom emoji identifiers +emojiStatuses custom_emoji_ids:vector = EmojiStatuses; //@description Describes usernames assigned to a user, a supergroup, or a channel @@ -706,17 +708,20 @@ usernames active_usernames:vector disabled_usernames:vector edit //@emoji_status Emoji status to be shown instead of the default Telegram Premium badge; may be null. For Telegram Premium users only //@is_contact The user is a contact of the current user //@is_mutual_contact The user is a contact of the current user and the current user is a contact of the user +//@is_close_friend The user is a close friend of the current user; implies that the user is a contact //@is_verified True, if the user is verified //@is_premium True, if the user is a Telegram Premium user //@is_support True, if the user is Telegram support account //@restriction_reason If non-empty, it contains a human-readable description of the reason why access to this user must be restricted //@is_scam True, if many users reported this user as a scam //@is_fake True, if many users reported this user as a fake account +//@has_active_stories True, if the user has non-expired stories available to the current user +//@has_unread_active_stories True, if the user has unread non-expired stories available to the current user //@have_access If false, the user is inaccessible, and the only information known about the user is inside this class. Identifier of the user can't be passed to any method //@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_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User; +user id:int53 first_name:string last_name:string 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; //@description Contains information about a bot @@ -741,18 +746,19 @@ botInfo short_description:string description:string photo:photo animation:animat //-If non-null and personal_photo is null, then it is the same photo as in user.profile_photo and chat.photo //@public_photo User profile photo visible if the main photo is hidden by privacy settings; may be null. If null and user.profile_photo is null, then the photo is empty; otherwise, it is unknown. //-If non-null and both photo and personal_photo are null, then it is the same photo as in user.profile_photo and chat.photo. This photo isn't returned in the list of user photos -//@is_blocked True, if the user is blocked by the current user +//@block_list Block list to which the user is added; may be null if none //@can_be_called True, if the user can be called //@supports_video_calls True, if a video call can be created with the user //@has_private_calls True, if the user can't be called due to their privacy settings //@has_private_forwards True, if the user can't be linked in forwarded messages due to their privacy settings //@has_restricted_voice_and_video_note_messages True, if voice and video notes can't be sent or forwarded to the user +//@has_pinned_stories True, if the user has pinned stories //@need_phone_number_privacy_exception True, if the current user needs to explicitly allow to share their phone number with the user when the method addContact is used //@bio A short user bio; may be null for bots //@premium_gift_options The list of available options for gifting Telegram Premium to the user //@group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user -//@bot_info For bots, information about the bot; may be null -userFullInfo personal_photo:chatPhoto photo:chatPhoto public_photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool need_phone_number_privacy_exception:Bool bio:formattedText premium_gift_options:vector group_in_common_count:int32 bot_info:botInfo = UserFullInfo; +//@bot_info For bots, information about the bot; may be null if the user isn't a bot +userFullInfo personal_photo:chatPhoto photo:chatPhoto public_photo:chatPhoto block_list:BlockList can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool has_pinned_stories:Bool need_phone_number_privacy_exception:Bool bio:formattedText premium_gift_options:vector group_in_common_count:int32 bot_info:botInfo = UserFullInfo; //@description Represents a list of users @total_count Approximate total number of users found @user_ids A list of user identifiers users total_count:int32 user_ids:vector = Users; @@ -896,6 +902,19 @@ chatInviteLinkMember user_id:int53 joined_chat_date:int32 via_chat_folder_invite //@description Contains a list of chat members joined a chat via an invite link @total_count Approximate total number of chat members found @members List of chat members, joined a chat via an invite link chatInviteLinkMembers total_count:int32 members:vector = ChatInviteLinkMembers; + +//@class InviteLinkChatType @description Describes the type of a chat to which points an invite link + +//@description The link is an invite link for a basic group +inviteLinkChatTypeBasicGroup = InviteLinkChatType; + +//@description The link is an invite link for a supergroup +inviteLinkChatTypeSupergroup = InviteLinkChatType; + +//@description The link is an invite link for a channel +inviteLinkChatTypeChannel = InviteLinkChatType; + + //@description Contains information about a chat invite link //@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining //@accessible_for If non-zero, the amount of time for which read access to the chat will remain available, in seconds @@ -907,7 +926,11 @@ chatInviteLinkMembers total_count:int32 members:vector = C //@member_user_ids User identifiers of some chat members that may be known to the current user //@creates_join_request True, if the link only creates join request //@is_public True, if the chat is a public supergroup or channel, i.e. it has a username or it is a location-based supergroup -chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:ChatType title:string photo:chatPhotoInfo description:string member_count:int32 member_user_ids:vector creates_join_request:Bool is_public:Bool = ChatInviteLinkInfo; +//@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; + //@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 chatJoinRequest user_id:int53 date:int32 bio:string = ChatJoinRequest; @@ -976,7 +999,6 @@ supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus membe //@can_get_members True, if members of the chat can be retrieved via getSupergroupMembers or searchChatMembers //@has_hidden_members True, if non-administrators can receive only administrators and bots using getSupergroupMembers or searchChatMembers //@can_hide_members True, if non-administrators and non-bots can be hidden in responses to getSupergroupMembers and searchChatMembers for non-administrators -//@can_set_username True, if the chat username can be changed //@can_set_sticker_set True, if the supergroup sticker set can be changed //@can_set_location True, if the supergroup location can be changed //@can_get_statistics True, if the supergroup or channel statistics are available @@ -985,12 +1007,12 @@ supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus membe //-so this option affects only private non-forum supergroups without a linked chat. The value of this field is only available to chat administrators //@has_aggressive_anti_spam_enabled True, if aggressive anti-spam checks are enabled in the supergroup. The value of this field is only available to chat administrators //@sticker_set_id Identifier of the supergroup sticker set; 0 if none -//@location Location to which the supergroup is connected; may be null +//@location Location to which the supergroup is connected; may be null if none //@invite_link Primary invite link for the chat; may be null. For chat administrators with can_invite_users right only //@bot_commands List of commands of bots in the group //@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none //@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none -supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool has_hidden_members:Bool can_hide_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool can_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool has_aggressive_anti_spam_enabled:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink bot_commands:vector upgraded_from_basic_group_id:int53 upgraded_from_max_message_id:int53 = SupergroupFullInfo; +supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool has_hidden_members:Bool can_hide_members:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool can_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool has_aggressive_anti_spam_enabled:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink bot_commands:vector upgraded_from_basic_group_id:int53 upgraded_from_max_message_id:int53 = SupergroupFullInfo; //@class SecretChatState @description Describes the current secret chat state @@ -1127,12 +1149,23 @@ messageSendingStatePending sending_id:int32 = MessageSendingState; messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool need_another_sender: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 +messageReplyToStory story_sender_chat_id:int53 story_id:int32 = MessageReplyTo; + + //@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 //@chat_id Chat identifier -//@sending_state The sending state of the message; may be null -//@scheduling_state The scheduling state of the message; may be null +//@sending_state The sending state of the message; may be null if the message isn't being sent and didn't fail to be sent +//@scheduling_state The scheduling state of the message; may be null if the message isn't scheduled //@is_outgoing True, if the message is outgoing //@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 @@ -1152,22 +1185,21 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool n //@contains_unread_mention True, if the message contains an unread mention for the current user //@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 -//@interaction_info Information about interactions with the message; may be null +//@forward_info Information about the initial message sender; may be null if none or unknown +//@interaction_info Information about interactions with the message; may be null if none //@unread_reactions Information about unread reactions added to the message -//@reply_in_chat_id If non-zero, the identifier of the chat to which the replied message belongs; Currently, only messages in the Replies chat can have different reply_in_chat_id and chat_id -//@reply_to_message_id If non-zero, the identifier of the message this message is replying to; can be the identifier of a deleted message +//@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_time The message's self-destruct time, in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the time expires -//@self_destruct_in Time left before the message self-destruct timer expires, in seconds. If the self-destruct timer isn't started yet, equals to the value of the self_destruct_time field -//@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. TDLib will send updateDeleteMessages or updateMessageContent once the time expires +//@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 +//@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 //@media_album_id Unique identifier of an album this message belongs to. Only audios, documents, photos and videos can be grouped together in albums //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@content Content of the message -//@reply_markup Reply markup for the message; may be null -message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_added_reactions:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool 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_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 self_destruct_time:int32 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; +//@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; //@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; @@ -1217,21 +1249,41 @@ messageSourceChatEventLog = MessageSource; //@description The message is from a notification messageSourceNotification = MessageSource; +//@description The message was screenshotted; the source must be used only if the message content was visible during the screenshot +messageSourceScreenshot = MessageSource; + //@description The message is from some other source messageSourceOther = MessageSource; +//@class MessageSponsorType @description Describes type of a message sponsor + +//@description The sponsor is a bot @bot_user_id User identifier of the bot @link An internal link to be opened when the sponsored message is clicked +messageSponsorTypeBot bot_user_id:int53 link:InternalLinkType = MessageSponsorType; + +//@description The sponsor is a public channel chat @chat_id Sponsor chat identifier @link An internal link to be opened when the sponsored message is clicked; may be null if the sponsor chat needs to be opened instead +messageSponsorTypePublicChannel chat_id:int53 link:InternalLinkType = MessageSponsorType; + +//@description The sponsor is a private channel chat @title Title of the chat @invite_link Invite link for the channel +messageSponsorTypePrivateChannel title:string invite_link:string = MessageSponsorType; + +//@description The sponsor is a website @url URL of the website @name Name of the website +messageSponsorTypeWebsite url:string name:string = MessageSponsorType; + + +//@description Information about the sponsor of a message +//@type Type of the sponsor +//@photo Photo of the sponsor; may be null if must not be shown +//@info Additional optional information about the sponsor to be shown along with the message +messageSponsor type:MessageSponsorType photo:chatPhotoInfo info:string = MessageSponsor; + //@description Describes a sponsored message //@message_id Message identifier; unique for the chat to which the sponsored message belongs among both ordinary and sponsored messages //@is_recommended True, if the message needs to be labeled as "recommended" instead of "sponsored" -//@sponsor_chat_id Sponsor chat identifier; 0 if the sponsor chat is accessible through an invite link -//@sponsor_chat_info Information about the sponsor chat; may be null unless sponsor_chat_id == 0 -//@show_chat_photo True, if the sponsor's chat photo must be shown -//@link An internal link to be opened when the sponsored message is clicked; may be null if the sponsor chat needs to be opened instead //@content Content of the message. Currently, can be only of the type messageText -//@sponsor_info If non-empty, information about the sponsor to be shown along with the message +//@sponsor Information about the sponsor of the message //@additional_info If non-empty, additional information about the sponsored message to be shown along with the message -sponsoredMessage message_id:int53 is_recommended:Bool sponsor_chat_id:int53 sponsor_chat_info:chatInviteLinkInfo show_chat_photo:Bool link:InternalLinkType content:MessageContent sponsor_info:string additional_info:string = SponsoredMessage; +sponsoredMessage message_id:int53 is_recommended:Bool content:MessageContent sponsor:messageSponsor additional_info:string = SponsoredMessage; //@description Contains a list of sponsored messages @messages List of sponsored messages @messages_between The minimum number of messages between shown sponsored messages, or 0 if only one sponsored message must be shown after all ordinary messages sponsoredMessages messages:vector messages_between:int32 = SponsoredMessages; @@ -1274,22 +1326,32 @@ notificationSettingsScopeChannelChats = NotificationSettingsScope; //@use_default_mute_for If true, mute_for is ignored and the value for the relevant type of chat or the forum chat is used instead //@mute_for Time left before notifications will be unmuted, in seconds //@use_default_sound If true, the value for the relevant type of chat or the forum chat is used instead of sound_id -//@sound_id Identifier of the notification sound to be played; 0 if sound is disabled +//@sound_id Identifier of the notification sound to be played for messages; 0 if sound is disabled //@use_default_show_preview If true, show_preview is ignored and the value for the relevant type of chat or the forum chat is used instead //@show_preview True, if message content must be displayed in notifications +//@use_default_mute_stories If true, mute_stories is ignored and the value for the relevant type of chat is used instead +//@mute_stories True, if story notifications are disabled for the chat +//@use_default_story_sound If true, the value for the relevant type of chat is used instead of story_sound_id +//@story_sound_id Identifier of the notification sound to be played for stories; 0 if sound is disabled +//@use_default_show_story_sender If true, show_story_sender is ignored and the value for the relevant type of chat is used instead +//@show_story_sender True, if the sender of stories must be displayed in notifications //@use_default_disable_pinned_message_notifications If true, disable_pinned_message_notifications is ignored and the value for the relevant type of chat or the forum chat is used instead //@disable_pinned_message_notifications If true, notifications for incoming pinned messages will be created as for an ordinary unread message //@use_default_disable_mention_notifications If true, disable_mention_notifications is ignored and the value for the relevant type of chat or the forum chat is used instead //@disable_mention_notifications If true, notifications for messages with mentions will be created as for an ordinary unread message -chatNotificationSettings use_default_mute_for:Bool mute_for:int32 use_default_sound:Bool sound_id:int64 use_default_show_preview:Bool show_preview:Bool use_default_disable_pinned_message_notifications:Bool disable_pinned_message_notifications:Bool use_default_disable_mention_notifications:Bool disable_mention_notifications:Bool = ChatNotificationSettings; +chatNotificationSettings use_default_mute_for:Bool mute_for:int32 use_default_sound:Bool sound_id:int64 use_default_show_preview:Bool show_preview:Bool use_default_mute_stories:Bool mute_stories:Bool use_default_story_sound:Bool story_sound_id:int64 use_default_show_story_sender:Bool show_story_sender:Bool use_default_disable_pinned_message_notifications:Bool disable_pinned_message_notifications:Bool use_default_disable_mention_notifications:Bool disable_mention_notifications:Bool = ChatNotificationSettings; //@description Contains information about notification settings for several chats //@mute_for Time left before notifications will be unmuted, in seconds //@sound_id Identifier of the notification sound to be played; 0 if sound is disabled //@show_preview True, if message content must be displayed in notifications +//@use_default_mute_stories If true, mute_stories is ignored and story notifications are received only for the first 5 chats from topChatCategoryUsers +//@mute_stories True, if story notifications are disabled for the chat +//@story_sound_id Identifier of the notification sound to be played for stories; 0 if sound is disabled +//@show_story_sender True, if the sender of stories must be displayed in notifications //@disable_pinned_message_notifications True, if notifications for incoming pinned messages will be created as for an ordinary unread message //@disable_mention_notifications True, if notifications for messages with mentions will be created as for an ordinary unread message -scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool disable_pinned_message_notifications:Bool disable_mention_notifications:Bool = ScopeNotificationSettings; +scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool use_default_mute_stories:Bool mute_stories:Bool story_sound_id:int64 show_story_sender:Bool disable_pinned_message_notifications:Bool disable_mention_notifications:Bool = ScopeNotificationSettings; //@description Contains information about a message draft @@ -1339,8 +1401,9 @@ chatFolder title:string icon:chatFolderIcon is_shareable:Bool pinned_chat_ids:ve //@id Unique chat folder identifier //@title The title of the folder; 1-12 characters without line feeds //@icon The chosen or default icon for the chat folder +//@is_shareable True, if at least one link has been created for the folder //@has_my_invite_links True, if the chat folder has invite links created by the current user -chatFolderInfo id:int32 title:string icon:chatFolderIcon has_my_invite_links:Bool = ChatFolderInfo; +chatFolderInfo id:int32 title:string icon:chatFolderIcon is_shareable:Bool has_my_invite_links:Bool = ChatFolderInfo; //@description Contains a chat folder invite link //@invite_link The chat folder invite link @@ -1363,6 +1426,12 @@ recommendedChatFolder folder:chatFolder description:string = RecommendedChatFold //@description Contains a list of recommended chat folders @chat_folders List of recommended chat folders recommendedChatFolders chat_folders:vector = RecommendedChatFolders; +//@description Contains settings for automatic moving of chats to and from the Archive chat lists +//@archive_and_mute_new_chats_from_unknown_users True, if new chats from non-contacts will be automatically archived and muted. Can be set to true only if the option "can_archive_and_mute_new_chats_from_unknown_users" is true +//@keep_unmuted_chats_archived True, if unmuted chats will be kept in the Archive chat list when they get a new message +//@keep_chats_from_folders_archived True, if unmuted chats, that are always included or pinned in a folder, will be kept in the Archive chat list when they get a new message. Ignored if keep_unmuted_chats_archived == true +archiveChatListSettings archive_and_mute_new_chats_from_unknown_users:Bool keep_unmuted_chats_archived:Bool keep_chats_from_folders_archived:Bool = ArchiveChatListSettings; + //@class ChatList @description Describes a list of chats @@ -1418,13 +1487,13 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@title Chat title //@photo Chat photo; may be null //@permissions Actions that non-administrator chat members are allowed to take in the chat -//@last_message Last message in the chat; may be null +//@last_message Last message in the chat; may be null if none or unknown //@positions Positions of the chat in chat lists //@message_sender_id Identifier of a user or chat that is selected to send messages in the chat; may be null if the user can't change message sender +//@block_list Block list to which the chat is added; may be null if none //@has_protected_content True, if chat content can't be saved locally, forwarded, or copied //@is_translatable True, if translation of all messages in the chat must be suggested to the user //@is_marked_as_unread True, if the chat is marked as unread -//@is_blocked True, if the chat is blocked by the current user and private messages from the chat can't be received //@has_scheduled_messages True, if the chat has scheduled messages //@can_be_deleted_only_for_self True, if the chat messages can be deleted only for the current user while other users will continue to see the messages //@can_be_deleted_for_all_users True, if the chat messages can be deleted for all users @@ -1440,13 +1509,13 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@message_auto_delete_time Current message auto-delete or self-destruct timer setting for the chat, in seconds; 0 if disabled. Self-destruct timer in secret chats starts after the message or its content is viewed. Auto-delete timer in other chats starts from the send date //@background Background set for the chat; may be null if none //@theme_name If non-empty, name of a theme, set for the chat -//@action_bar Information about actions which must be possible to do through the chat action bar; may be null +//@action_bar Information about actions which must be possible to do through the chat action bar; may be null if none //@video_chat Information about video chat of the chat -//@pending_join_requests Information about pending join requests; may be null +//@pending_join_requests Information about pending join requests; may be null if none //@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 +//@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 has_protected_content:Bool is_translatable:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_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 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; @@ -1470,17 +1539,17 @@ publicChatTypeIsLocationBased = PublicChatType; //@class ChatActionBar @description Describes actions which must be possible to do through a chat action bar -//@description The chat can be reported as spam using the method reportChat with the reason chatReportReasonSpam. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown +//@description The chat can be reported as spam using the method reportChat with the reason reportReasonSpam. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown //@can_unarchive If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings chatActionBarReportSpam can_unarchive:Bool = ChatActionBar; -//@description The chat is a location-based supergroup, which can be reported as having unrelated location using the method reportChat with the reason chatReportReasonUnrelatedLocation +//@description The chat is a location-based supergroup, which can be reported as having unrelated location using the method reportChat with the reason reportReasonUnrelatedLocation chatActionBarReportUnrelatedLocation = ChatActionBar; //@description The chat is a recently created group chat to which new members can be invited chatActionBarInviteMembers = ChatActionBar; -//@description The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method toggleMessageSenderIsBlocked, +//@description The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method setMessageSenderBlockList, //-or the other user can be added to the contact list using the method addContact. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown //@can_unarchive If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings //@distance If non-negative, the current user was found by the peer through searchChatsNearby and this is the distance between the users @@ -1616,9 +1685,10 @@ 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 request_write_access:Bool skip_confirmation:Bool = FoundWebApp; +foundWebApp web_app:webApp supports_settings:Bool 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; @@ -1630,7 +1700,7 @@ webAppInfo launch_id:int64 url:string = WebAppInfo; //@reply_info Information about the message thread; may be null for forum topic threads //@unread_message_count Approximate number of unread messages in the message thread //@messages The messages from which the thread starts. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id) -//@draft_message A draft of a message in the message thread; may be null +//@draft_message A draft of a message in the message thread; may be null if none messageThreadInfo chat_id:int53 message_thread_id:int53 reply_info:messageReplyInfo unread_message_count:int32 messages:vector draft_message:draftMessage = MessageThreadInfo; @@ -1641,7 +1711,7 @@ forumTopicIcon color:int32 custom_emoji_id:int64 = ForumTopicIcon; //@message_thread_id Message thread identifier of the topic //@name Name of the topic //@icon Icon of the topic -//@creation_date Date the topic was created +//@creation_date Point in time (Unix timestamp) when the topic was created //@creator_id Identifier of the creator of the topic //@is_general True, if the topic is the General topic list //@is_outgoing True, if the topic was created by the current user @@ -1659,7 +1729,7 @@ forumTopicInfo message_thread_id:int53 name:string icon:forumTopicIcon creation_ //@unread_mention_count Number of unread messages with a mention/reply in the topic //@unread_reaction_count Number of messages with unread reactions in the topic //@notification_settings Notification settings for the topic -//@draft_message A draft of a message in the topic; may be null +//@draft_message A draft of a message in the topic; may be null if none forumTopic info:forumTopicInfo last_message:message is_pinned: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 draft_message:draftMessage = ForumTopic; //@description Describes a list of forum topics @@ -1950,8 +2020,10 @@ webPageInstantView page_blocks:vector view_count:int32 version:int32 //@video Preview of the content as a video, if available; may be null //@video_note Preview of the content as a video note, if available; may be null //@voice_note Preview of the content as a voice note, if available; may be null +//@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 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 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 @@ -2203,21 +2275,21 @@ personalDetails first_name:string middle_name:string last_name:string native_fir //@description An identity document //@number Document number; 1-24 characters -//@expiry_date Document expiry date; may be null if not applicable +//@expiration_date Document expiration date; may be null if not applicable //@front_side Front side of the document //@reverse_side Reverse side of the document; only for driver license and identity card; may be null //@selfie Selfie with the document; may be null //@translation List of files containing a certified English translation of the document -identityDocument number:string expiry_date:date front_side:datedFile reverse_side:datedFile selfie:datedFile translation:vector = IdentityDocument; +identityDocument number:string expiration_date:date front_side:datedFile reverse_side:datedFile selfie:datedFile translation:vector = IdentityDocument; //@description An identity document to be saved to Telegram Passport //@number Document number; 1-24 characters -//@expiry_date Document expiry date; pass null if not applicable +//@expiration_date Document expiration date; pass null if not applicable //@front_side Front side of the document //@reverse_side Reverse side of the document; only for driver license and identity card; pass null otherwise //@selfie Selfie with the document; pass null if unavailable //@translation List of files containing a certified English translation of the document -inputIdentityDocument number:string expiry_date:date front_side:InputFile reverse_side:InputFile selfie:InputFile translation:vector = InputIdentityDocument; +inputIdentityDocument number:string expiration_date:date front_side:InputFile reverse_side:InputFile selfie:InputFile translation:vector = InputIdentityDocument; //@description A personal document, containing some information about a user @files List of files containing the pages of the document @translation List of files containing a certified English translation of the document personalDocument files:vector translation:vector = PersonalDocument; @@ -2497,6 +2569,12 @@ messageGame game:game = MessageContent; //@description A message with a poll @poll The poll description messagePoll poll:poll = MessageContent; +//@description A message with a forwarded story +//@story_sender_chat_id Identifier of the chat that posted the story +//@story_id Story identifier +//@via_mention True, if the story was automatically forwarded because of a mention of the user +messageStory story_sender_chat_id:int53 story_id:int32 via_mention:Bool = MessageContent; + //@description A message with an invoice from a bot. Use getInternalLink with internalLinkTypeBotStart to share the invoice //@title Product title //@param_description Product description @@ -2641,8 +2719,10 @@ 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 -messageBotWriteAccessAllowed web_app:webApp = 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 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; @@ -2659,7 +2739,7 @@ messagePassportDataReceived elements:vector credential //@description A user in the chat came within proximity alert range @traveler_id The identifier of a user or chat that triggered the proximity alert @watcher_id The identifier of a user or chat that subscribed for the proximity alert @distance The distance between the users messageProximityAlertTriggered traveler_id:MessageSender watcher_id:MessageSender distance:int32 = MessageContent; -//@description Message content that is not supported in the current TDLib version +//@description A message content that is not supported in the current TDLib version messageUnsupported = MessageContent; @@ -2735,13 +2815,22 @@ inputThumbnail thumbnail:InputFile width:int32 height:int32 = InputThumbnail; //@class MessageSchedulingState @description Contains information about the time when a scheduled message will be sent -//@description The message will be sent at the specified date @send_date Date the message will be sent. The date must be within 367 days in the future +//@description The message will be sent at the specified date @send_date Point in time (Unix timestamp) when the message will be sent. The date must be within 367 days in the future messageSchedulingStateSendAtDate send_date:int32 = MessageSchedulingState; //@description The message will be sent when the peer will be online. Applicable to private chats only and when the exact online status of the peer is known messageSchedulingStateSendWhenOnline = MessageSchedulingState; +//@class MessageSelfDestructType @description Describes when a message will be self-destructed + +//@description The message will be self-destructed in the specified time after its content was opened @self_destruct_time The message's self-destruct time, in seconds; must be between 0 and 60 in private chats +messageSelfDestructTypeTimer self_destruct_time:int32 = MessageSelfDestructType; + +//@description The message can be opened only once and will be self-destructed once closed +messageSelfDestructTypeImmediately = MessageSelfDestructType; + + //@description Options to be used when a message is sent //@disable_notification Pass true to disable notification for the message //@from_background Pass true if the message is sent from the background @@ -2800,9 +2889,10 @@ inputMessageDocument document:InputFile thumbnail:inputThumbnail disable_content //@width Photo width //@height Photo height //@caption Photo caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters -//@self_destruct_time Photo self-destruct time, in seconds (0-60). A non-zero self-destruct time can be specified only in private chats +//@self_destruct_type Photo self-destruct type; pass null if none; private chats only //@has_spoiler True, if the photo preview must be covered by a spoiler animation; not supported in secret chats -inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:formattedText self_destruct_time:int32 has_spoiler:Bool = InputMessageContent; +inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:formattedText self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; + //@description A sticker message //@sticker Sticker to be sent @@ -2821,9 +2911,9 @@ inputMessageSticker sticker:InputFile thumbnail:inputThumbnail width:int32 heigh //@height Video height //@supports_streaming True, if the video is supposed to be streamed //@caption Video caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters -//@self_destruct_time Video self-destruct time, in seconds (0-60). A non-zero self-destruct time can be specified only in private chats +//@self_destruct_type Video self-destruct type; pass null if none; private chats only //@has_spoiler True, if the video preview must be covered by a spoiler animation; not supported in secret chats -inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 supports_streaming:Bool caption:formattedText self_destruct_time:int32 has_spoiler:Bool = InputMessageContent; +inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 supports_streaming:Bool caption:formattedText self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; //@description A video note message //@video_note Video note to be sent @@ -2883,6 +2973,11 @@ inputMessageInvoice invoice:invoice title:string description:string photo_url:st //@is_closed True, if the poll needs to be sent already closed; for bots only inputMessagePoll question:string options:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = InputMessageContent; +//@description A message with a forwarded story. Stories can't be sent to secret chats. A story can be forwarded only if story.can_be_forwarded +//@story_sender_chat_id Identifier of the chat that posted the story +//@story_id Story identifier +inputMessageStory story_sender_chat_id:int53 story_id:int32 = InputMessageContent; + //@description A forwarded message //@from_chat_id Identifier for the chat this forwarded message came from //@message_id Identifier of the message to forward @@ -3081,6 +3176,160 @@ emojiCategoryTypeEmojiStatus = EmojiCategoryType; emojiCategoryTypeChatPhoto = EmojiCategoryType; +//@description Represents a viewer of a story +//@user_id User identifier of the viewer +//@view_date Approximate point in time (Unix timestamp) when the story was viewed +//@block_list Block list to which the user is added; may be null if none +//@chosen_reaction_type Type of the reaction that was chosen by the user; may be null if none +storyViewer user_id:int53 view_date:int32 block_list:BlockList chosen_reaction_type:ReactionType = StoryViewer; + +//@description Represents a list of story viewers +//@total_count Approximate total number of story viewers found +//@total_reaction_count Approximate total number of reactions set by found story viewers +//@viewers List of story viewers +//@next_offset The offset for the next request. If empty, there are no more results +storyViewers total_count:int32 total_reaction_count:int32 viewers:vector next_offset:string = StoryViewers; + + +//@description Describes position of a clickable rectangle area on a story media +//@x_percentage The abscissa of the rectangle's center, as a percentage of the media width +//@y_percentage The ordinate of the rectangle's center, as a percentage of the media height +//@width_percentage The width of the rectangle, as a percentage of the media width +//@height_percentage The ordinate of the rectangle's center, as a percentage of the media height +//@rotation_angle Clockwise rotation angle of the rectangle, in degrees; 0-360 +storyAreaPosition x_percentage:double y_percentage:double width_percentage:double height_percentage:double rotation_angle:double = StoryAreaPosition; + + +//@class StoryAreaType @description Describes type of a clickable rectangle area on a story media + +//@description An area pointing to a location @location The location +storyAreaTypeLocation location:location = StoryAreaType; + +//@description An area pointing to a venue @venue Information about the venue +storyAreaTypeVenue venue:venue = StoryAreaType; + + +//@description Describes a clickable rectangle area on a story media @position Position of the area @type Type of the area +storyArea position:storyAreaPosition type:StoryAreaType = StoryArea; + + +//@class InputStoryAreaType @description Describes type of a clickable rectangle area on a story media to be added + +//@description An area pointing to a location @location The location +inputStoryAreaTypeLocation location:location = InputStoryAreaType; + +//@description An area pointing to a venue found by the bot getOption("venue_search_bot_username") +//@query_id Identifier of the inline query, used to found the venue +//@result_id Identifier of the inline query result +inputStoryAreaTypeFoundVenue query_id:int64 result_id:string = InputStoryAreaType; + +//@description An area pointing to a venue already added to the story +//@venue_provider Provider of the venue +//@venue_id Identifier of the venue in the provider database +inputStoryAreaTypePreviousVenue venue_provider:string venue_id:string = InputStoryAreaType; + + +//@description Describes a clickable rectangle area on a story media to be added @position Position of the area @type Type of the area +inputStoryArea position:storyAreaPosition type:InputStoryAreaType = InputStoryArea; + +//@description Contains a list of story areas to be added @areas List of 0-10 input story areas +inputStoryAreas areas:vector = InputStoryAreas; + + +//@description Describes a video file sent in a story +//@duration Duration of the video, in seconds +//@width Video width +//@height Video height +//@has_stickers True, if stickers were added to the video. The list of corresponding sticker sets can be received using getAttachedStickerSets +//@is_animation True, if the video has no sound +//@minithumbnail Video minithumbnail; may be null +//@thumbnail Video thumbnail in JPEG or MPEG4 format; may be null +//@preload_prefix_size Size of file prefix, which is supposed to be preloaded, in bytes +//@video File containing the video +storyVideo duration:double width:int32 height:int32 has_stickers:Bool is_animation:Bool minithumbnail:minithumbnail thumbnail:thumbnail preload_prefix_size:int32 video:file = StoryVideo; + + +//@class StoryContent @description Contains the content of a story + +//@description A photo story @photo The photo +storyContentPhoto photo:photo = StoryContent; + +//@description A video story @video The video in MPEG4 format @alternative_video Alternative version of the video in MPEG4 format, encoded by x264 codec; may be null +storyContentVideo video:storyVideo alternative_video:storyVideo = StoryContent; + +//@description A story content that is not supported in the current TDLib version +storyContentUnsupported = StoryContent; + + +//@class InputStoryContent @description The content of a story to send + +//@description A photo story +//@photo Photo to send. The photo must be at most 10 MB in size. The photo size must be 1080x1920 +//@added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable +inputStoryContentPhoto photo:InputFile added_sticker_file_ids:vector = InputStoryContent; + +//@description A video story +//@video Video to be sent. The video size must be 720x1280. The video must be streamable and stored in MPEG4 format, after encoding with x265 codec and key frames added each second +//@added_sticker_file_ids File identifiers of the stickers added to the video, if applicable +//@duration Precise duration of the video, in seconds; 0-60 +//@is_animation True, if the video has no sound +inputStoryContentVideo video:InputFile added_sticker_file_ids:vector duration:double is_animation:Bool = InputStoryContent; + + +//@class StoryList @description Describes a list of stories + +//@description The list of stories, shown in the main chat list and folder chat lists +storyListMain = StoryList; + +//@description The list of stories, shown in the Arvhive chat list +storyListArchive = StoryList; + + +//@description Contains information about interactions with a story +//@view_count Number of times the story was viewed +//@reaction_count Number of reactions added to the story +//@recent_viewer_user_ids Identifiers of at most 3 recent viewers of the story +storyInteractionInfo view_count:int32 reaction_count:int32 recent_viewer_user_ids:vector = StoryInteractionInfo; + +//@description Represents a story +//@id Unique story identifier among stories of the given sender +//@sender_chat_id Identifier of the chat that posted the story +//@date Point in time (Unix timestamp) when the story was published +//@is_being_sent True, if the story is being sent by the current user +//@is_being_edited True, if the story is being edited by the current user +//@is_edited True, if the story was edited +//@is_pinned True, if the story is saved in the sender's profile and will be available there after expiration +//@is_visible_only_for_self True, if the story is visible only for the current user +//@can_be_forwarded True, if the story can be forwarded as a message. Otherwise, screenshots and saving of the story content must be also forbidden +//@can_be_replied True, if the story can be replied in the chat with the story sender +//@can_get_viewers True, if users viewed the story can be received through getStoryViewers +//@has_expired_viewers True, if users viewed the story can't be received, because the story has expired more than getOption("story_viewers_expiration_delay") seconds ago +//@interaction_info Information about interactions with the story; may be null if the story isn't owned or there were no interactions +//@chosen_reaction_type Type of the chosen reaction; may be null if none +//@privacy_settings Privacy rules affecting story visibility; may be approximate for non-owned stories +//@content Content of the story +//@areas Clickable areas to be shown on the story content +//@caption Caption of the story +story id:int32 sender_chat_id:int53 date:int32 is_being_sent:Bool is_being_edited:Bool is_edited:Bool is_pinned:Bool is_visible_only_for_self:Bool can_be_forwarded:Bool can_be_replied:Bool can_get_viewers:Bool has_expired_viewers:Bool interaction_info:storyInteractionInfo chosen_reaction_type:ReactionType privacy_settings:StoryPrivacySettings content:StoryContent areas:vector caption:formattedText = Story; + +//@description Represents a list of stories @total_count Approximate total number of stories found @stories The list of stories +stories total_count:int32 stories:vector = Stories; + +//@description Contains basic information about a story +//@story_id Unique story identifier among stories of the given sender +//@date Point in time (Unix timestamp) when the story was published +//@is_for_close_friends True, if the story is available only to close friends +storyInfo story_id:int32 date:int32 is_for_close_friends:Bool = StoryInfo; + +//@description Describes active stories posted by a chat +//@chat_id Identifier of the chat that posted the stories +//@list Identifier of the story list in which the stories are shown; may be null if the stories aren't shown in a story list +//@order A parameter used to determine order of the stories in the story list; 0 if the stories doesn't need to be shown in the story list. Stories must be sorted by the pair (order, story_sender_chat_id) in descending order +//@max_read_story_id Identifier of the last read active story +//@stories Basic information about the stories; use getStory to get full information about the stories. The stories are in a chronological order (i.e., in order of increasing story identifiers) +chatActiveStories chat_id:int53 list:StoryList order:int53 max_read_story_id:int32 stories:vector = ChatActiveStories; + + //@class CallDiscardReason @description Describes the reason why a call was discarded //@description The call wasn't discarded, or the reason is unknown @@ -3103,7 +3352,7 @@ callDiscardReasonHungUp = CallDiscardReason; //@udp_p2p True, if UDP peer-to-peer connections are supported //@udp_reflector True, if connection through UDP reflectors is supported //@min_layer The minimum supported API layer; use 65 -//@max_layer The maximum supported API layer; use 65 +//@max_layer The maximum supported API layer; use 92 //@library_versions List of supported tgcalls versions callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 library_versions:vector = CallProtocol; @@ -3378,25 +3627,32 @@ speechRecognitionResultError error:error = SpeechRecognitionResult; //@description Describes a color to highlight a bot added to attachment menu @light_color Color in the RGB24 format for light themes @dark_color Color in the RGB24 format for dark themes attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotColor; -//@description Represents a bot, which can be added to attachment menu -//@bot_user_id User identifier of the bot added to attachment menu +//@description Represents a bot, which can be added to attachment or side menu +//@bot_user_id User identifier of the bot //@supports_self_chat True, if the bot supports opening from attachment menu in the chat with the bot //@supports_user_chats True, if the bot supports opening from attachment menu in private chats with ordinary users //@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 the bot to send them messages +//@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 +//@show_in_side_menu True, if the bot must be shown in the side menu +//@show_disclaimer_in_side_menu True, if a disclaimer, why the bot is shown in the side menu, is needed //@name Name for the bot in attachment menu //@name_color Color to highlight selected name of the bot if appropriate; may be null -//@default_icon Default attachment menu icon for the bot in SVG format; may be null -//@ios_static_icon Attachment menu icon for the bot in SVG format for the official iOS app; may be null -//@ios_animated_icon Attachment menu icon for the bot in TGS format for the official iOS app; may be null -//@android_icon Attachment menu icon for the bot in TGS format for the official Android app; may be null -//@macos_icon Attachment menu icon for the bot in TGS format for the official native macOS app; may be null +//@default_icon Default icon for the bot in SVG format; may be null +//@ios_static_icon Icon for the bot in SVG format for the official iOS app; may be null +//@ios_animated_icon Icon for the bot in TGS format for the official iOS app; may be null +//@ios_side_menu_icon Icon for the bot in PNG format for the official iOS app side menu; may be null +//@android_icon Icon for the bot in TGS format for the official Android app; may be null +//@android_side_menu_icon Icon for the bot in SVG format for the official Android app side menu; may be null +//@macos_icon Icon for the bot in TGS format for the official native macOS app; may be null +//@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 name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file android_icon:file macos_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 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; //@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; @@ -3916,6 +4172,18 @@ premiumLimitTypeChatFolderInviteLinkCount = PremiumLimitType; //@description The maximum number of added shareable chat folders premiumLimitTypeShareableChatFolderCount = PremiumLimitType; +//@description The maximum number of active stories +premiumLimitTypeActiveStoryCount = PremiumLimitType; + +//@description The maximum number of stories sent per week +premiumLimitTypeWeeklySentStoryCount = PremiumLimitType; + +//@description The maximum number of stories sent per month +premiumLimitTypeMonthlySentStoryCount = PremiumLimitType; + +//@description The maximum length of captions of sent stories +premiumLimitTypeStoryCaptionLength = PremiumLimitType; + //@class PremiumFeature @description Describes a feature available to Premium users @@ -3949,7 +4217,7 @@ premiumFeatureAdvancedChatManagement = PremiumFeature; //@description A badge in the user's profile premiumFeatureProfileBadge = PremiumFeature; -//@description A emoji status shown along with the user's name +//@description An emoji status shown along with the user's name premiumFeatureEmojiStatus = PremiumFeature; //@description Profile photo animation on message and chat screens @@ -3964,6 +4232,30 @@ premiumFeatureAppIcons = PremiumFeature; //@description Allowed to translate chat messages real-time premiumFeatureRealTimeChatTranslation = PremiumFeature; +//@description Allowed to use many additional features for stories +premiumFeatureUpgradedStories = PremiumFeature; + + +//@class PremiumStoryFeature @description Describes a story feature available to Premium users + +//@description User stories are displayed before stories of non-premium contacts +premiumStoryFeaturePriorityOrder = PremiumStoryFeature; + +//@description The ability to hide the fact that the user viewed other's stories +premiumStoryFeatureStealthMode = PremiumStoryFeature; + +//@description The ability to check who opened the current user's stories after they expire +premiumStoryFeaturePermanentViewsHistory = PremiumStoryFeature; + +//@description The ability to set custom expiration duration for stories +premiumStoryFeatureCustomExpirationDuration = PremiumStoryFeature; + +//@description The ability to save other's unprotected stories +premiumStoryFeatureSaveStories = PremiumStoryFeature; + +//@description The ability to use links and formatting in story caption +premiumStoryFeatureLinksAndFormatting = PremiumStoryFeature; + //@description Contains information about a limit, increased for Premium users @type The type of the limit @default_value Default value of the limit @premium_value Value of the limit for Premium users premiumLimit type:PremiumLimitType default_value:int32 premium_value:int32 = PremiumLimit; @@ -3983,6 +4275,9 @@ premiumSourceLimitExceeded limit_type:PremiumLimitType = PremiumSource; //@description A user tried to use a Premium feature @feature The used feature premiumSourceFeature feature:PremiumFeature = PremiumSource; +//@description A user tried to use a Premium story feature @feature The used feature +premiumSourceStoryFeature feature:PremiumStoryFeature = PremiumSource; + //@description A user opened an internal link of the type internalLinkTypePremiumFeatures @referrer The referrer from the link premiumSourceLink referrer:string = PremiumSource; @@ -4122,6 +4417,24 @@ chatTheme name:string light_settings:themeSettings dark_settings:themeSettings = hashtags hashtags:vector = Hashtags; +//@class CanSendStoryResult @description Represents result of checking whether the current user can send a story + +//@description A story can be sent +canSendStoryResultOk = CanSendStoryResult; + +//@description The user must subscribe to Telegram Premium to be able to post stories +canSendStoryResultPremiumNeeded = CanSendStoryResult; + +//@description The limit for the number of active stories exceeded. The user can buy Telegram Premium, delete an active story, or wait for the oldest story to expire +canSendStoryResultActiveStoryLimitExceeded = CanSendStoryResult; + +//@description The weekly limit for the number of posted stories exceeded. The user needs to buy Telegram Premium or wait specified time @retry_after Time left before the user can send the next story +canSendStoryResultWeeklyLimitExceeded retry_after:int32 = CanSendStoryResult; + +//@description The monthly limit for the number of posted stories exceeded. The user needs to buy Telegram Premium or wait specified time @retry_after Time left before the user can send the next story +canSendStoryResultMonthlyLimitExceeded retry_after:int32 = CanSendStoryResult; + + //@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 @@ -4248,6 +4561,9 @@ pushMessageContentScreenshotTaken = PushMessageContent; //@is_pinned True, if the message is a pinned message with the specified content pushMessageContentSticker sticker:sticker emoji:string is_pinned:Bool = PushMessageContent; +//@description A message with a story @is_pinned True, if the message is a pinned message with the specified content +pushMessageContentStory is_pinned:Bool = PushMessageContent; + //@description A text message @text Message text @is_pinned True, if the message is a pinned message with the specified content pushMessageContentText text:string is_pinned:Bool = PushMessageContent; @@ -4327,7 +4643,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 ID can be used in viewMessages, or as reply_to_message_id +//@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 //@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 @@ -4418,12 +4734,27 @@ jsonValueArray values:vector = JsonValue; jsonValueObject members:vector = JsonValue; -//@class UserPrivacySettingRule @description Represents a single rule for managing privacy settings +//@class StoryPrivacySettings @description Describes privacy settings of a story + +//@description The story can be viewed by everyone @except_user_ids Identifiers of the users that can't see the story; always unknown and empty for non-owned stories +storyPrivacySettingsEveryone except_user_ids:vector = StoryPrivacySettings; + +//@description The story can be viewed by all contacts except chosen users @except_user_ids User identifiers of the contacts that can't see the story; always unknown and empty for non-owned stories +storyPrivacySettingsContacts except_user_ids:vector = StoryPrivacySettings; + +//@description The story can be viewed by all close friends +storyPrivacySettingsCloseFriends = StoryPrivacySettings; + +//@description The story can be viewed by certain specified users @user_ids Identifiers of the users; always unknown and empty for non-owned stories +storyPrivacySettingsSelectedUsers user_ids:vector = StoryPrivacySettings; + + +//@class UserPrivacySettingRule @description Represents a single rule for managing user privacy settings //@description A rule to allow all users to do something userPrivacySettingRuleAllowAll = UserPrivacySettingRule; -//@description A rule to allow all of a user's contacts to do something +//@description A rule to allow all contacts of the user to do something userPrivacySettingRuleAllowContacts = UserPrivacySettingRule; //@description A rule to allow certain specified users to do something @user_ids The user identifiers, total number of users in all rules must not exceed 1000 @@ -4435,7 +4766,7 @@ userPrivacySettingRuleAllowChatMembers chat_ids:vector = UserPrivacySetti //@description A rule to restrict all users from doing something userPrivacySettingRuleRestrictAll = UserPrivacySettingRule; -//@description A rule to restrict all contacts of a user from doing something +//@description A rule to restrict all contacts of the user from doing something userPrivacySettingRuleRestrictContacts = UserPrivacySettingRule; //@description A rule to restrict all specified users from doing something @user_ids The user identifiers, total number of users in all rules must not exceed 1000 @@ -4447,6 +4778,7 @@ userPrivacySettingRuleRestrictChatMembers chat_ids:vector = UserPrivacySe //@description A list of privacy rules. Rules are matched in the specified order. The first matched rule defines the privacy setting for a given user. If no rule matches, the action is not allowed @rules A list of rules userPrivacySettingRules rules:vector = UserPrivacySettingRules; + //@class UserPrivacySetting @description Describes available user privacy settings //@description A privacy setting for managing whether the user's online status is visible @@ -4461,6 +4793,9 @@ userPrivacySettingShowLinkInForwardedMessages = UserPrivacySetting; //@description A privacy setting for managing whether the user's phone number is visible userPrivacySettingShowPhoneNumber = UserPrivacySetting; +//@description A privacy setting for managing whether the user's bio is visible +userPrivacySettingShowBio = UserPrivacySetting; + //@description A privacy setting for managing whether the user can be invited to chats userPrivacySettingAllowChatInvites = UserPrivacySetting; @@ -4543,6 +4878,7 @@ sessionTypeXbox = SessionType; //@id Session identifier //@is_current True, if this session is the current session //@is_password_pending True, if a 2-step verification password is needed to complete authorization of the session +//@is_unconfirmed True, if the session wasn't confirmed from another session //@can_accept_secret_chats True, if incoming secret chats can be accepted by the session //@can_accept_calls True, if incoming calls can be accepted by the session //@type Session type based on the system and application version, which can be used to display a corresponding icon @@ -4555,14 +4891,20 @@ sessionTypeXbox = SessionType; //@system_version Version of the operating system the application has been run or is running on, as provided by the application //@log_in_date Point in time (Unix timestamp) when the user has logged in //@last_active_date Point in time (Unix timestamp) when the session was last used -//@ip IP address from which the session was created, in human-readable format -//@country A two-letter country code for the country from which the session was created, based on the IP address -//@region Region code from which the session was created, based on the IP address -session id:int64 is_current:Bool is_password_pending:Bool can_accept_secret_chats:Bool can_accept_calls:Bool type:SessionType api_id:int32 application_name:string application_version:string is_official_application:Bool device_model:string platform:string system_version:string log_in_date:int32 last_active_date:int32 ip:string country:string region:string = Session; +//@ip_address IP address from which the session was created, in human-readable format +//@location A human-readable description of the location from which the session was created, based on the IP address +session id:int64 is_current:Bool is_password_pending:Bool is_unconfirmed:Bool can_accept_secret_chats:Bool can_accept_calls:Bool type:SessionType api_id:int32 application_name:string application_version:string is_official_application:Bool device_model:string platform:string system_version:string log_in_date:int32 last_active_date:int32 ip_address:string location:string = Session; //@description Contains a list of sessions @sessions List of sessions @inactive_session_ttl_days Number of days of inactivity before sessions will automatically be terminated; 1-366 days sessions sessions:vector inactive_session_ttl_days:int32 = Sessions; +//@description Contains information about an unconfirmed session +//@id Session identifier +//@log_in_date Point in time (Unix timestamp) when the user has logged in +//@device_model Model of the device that was used for the session creation, as provided by the application +//@location A human-readable description of the location from which the session was created, based on the IP address +unconfirmedSession id:int64 log_in_date:int32 device_model:string location:string = UnconfirmedSession; + //@description Contains information about one website the current user is logged in with Telegram //@id Website identifier @@ -4572,45 +4914,45 @@ sessions sessions:vector inactive_session_ttl_days:int32 = Sessions; //@platform Operating system the browser is running on //@log_in_date Point in time (Unix timestamp) when the user was logged in //@last_active_date Point in time (Unix timestamp) when obtained authorization was last used -//@ip IP address from which the user was logged in, in human-readable format +//@ip_address IP address from which the user was logged in, in human-readable format //@location Human-readable description of a country and a region from which the user was logged in, based on the IP address -connectedWebsite id:int64 domain_name:string bot_user_id:int53 browser:string platform:string log_in_date:int32 last_active_date:int32 ip:string location:string = ConnectedWebsite; +connectedWebsite id:int64 domain_name:string bot_user_id:int53 browser:string platform:string log_in_date:int32 last_active_date:int32 ip_address:string location:string = ConnectedWebsite; //@description Contains a list of websites the current user is logged in with Telegram @websites List of connected websites connectedWebsites websites:vector = ConnectedWebsites; -//@class ChatReportReason @description Describes the reason why a chat is reported +//@class ReportReason @description Describes the reason why a chat is reported //@description The chat contains spam messages -chatReportReasonSpam = ChatReportReason; +reportReasonSpam = ReportReason; //@description The chat promotes violence -chatReportReasonViolence = ChatReportReason; +reportReasonViolence = ReportReason; //@description The chat contains pornographic messages -chatReportReasonPornography = ChatReportReason; +reportReasonPornography = ReportReason; //@description The chat has child abuse related content -chatReportReasonChildAbuse = ChatReportReason; +reportReasonChildAbuse = ReportReason; //@description The chat contains copyrighted content -chatReportReasonCopyright = ChatReportReason; +reportReasonCopyright = ReportReason; //@description The location-based chat is unrelated to its stated location -chatReportReasonUnrelatedLocation = ChatReportReason; +reportReasonUnrelatedLocation = ReportReason; //@description The chat represents a fake account -chatReportReasonFake = ChatReportReason; +reportReasonFake = ReportReason; //@description The chat has illegal drugs related content -chatReportReasonIllegalDrugs = ChatReportReason; +reportReasonIllegalDrugs = ReportReason; //@description The chat contains messages with personal details -chatReportReasonPersonalDetails = ChatReportReason; +reportReasonPersonalDetails = ReportReason; //@description A custom reason provided by the user -chatReportReasonCustom = ChatReportReason; +reportReasonCustom = ReportReason; //@class TargetChat @description Describes the target chat to be opened @@ -4636,7 +4978,8 @@ internalLinkTypeActiveSessions = InternalLinkType; //@description The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat. //-Then, call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then, use getAttachmentMenuBot to receive information about the bot. -//-If the bot isn't added to attachment menu, then user needs to confirm adding the bot to attachment menu. If user confirms adding, then use toggleBotIsAddedToAttachmentMenu to add it. +//-If the bot isn't added to attachment menu, then show a disclaimer about Mini Apps being a third-party apps, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu. +//-If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. //-If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL //@target_chat Target chat to be opened //@bot_username Username of the bot @@ -4738,7 +5081,7 @@ internalLinkTypePremiumFeatures referrer:string = InternalLinkType; internalLinkTypePrivacyAndSecuritySettings = InternalLinkType; //@description The link is a link to a proxy. Call addProxy with the given parameters to process the link and add the proxy -//@server Proxy server IP address +//@server Proxy server domain or IP address //@port Proxy server port //@type Type of the proxy internalLinkTypeProxy server:string port:int32 type:ProxyType = InternalLinkType; @@ -4756,11 +5099,24 @@ internalLinkTypeRestorePurchases = InternalLinkType; //@description The link is a link to application settings internalLinkTypeSettings = InternalLinkType; +//@description The link is a link to a bot, which can be installed to the side menu. Call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. +//-Then, use getAttachmentMenuBot to receive information about the bot. If the bot isn't added to side menu, then show a disclaimer about Mini Apps being a third-party apps, +//-ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. +//-If the bot is added to side menu, then use getWebAppUrl with the given URL +//@bot_username Username of the bot +//@url URL to be passed to getWebAppUrl +internalLinkTypeSideMenuBot bot_username:string url:string = InternalLinkType; + //@description The link is a link to a sticker set. Call searchStickerSet with the given sticker set name to process the link and show the sticker set //@sticker_set_name Name of the sticker set //@expect_custom_emoji True, if the sticker set is expected to contain custom emoji internalLinkTypeStickerSet sticker_set_name:string expect_custom_emoji:Bool = InternalLinkType; +//@description The link is a link to a story. Call searchPublicChat with the given sender username, then call getStory with the received chat identifier and the given story identifier +//@story_sender_username Username of the sender of the story +//@story_id Story identifier +internalLinkTypeStory story_sender_username:string story_id:int32 = InternalLinkType; + //@description The link is a link to a theme. TDLib has no theme support yet @theme_name Name of the theme internalLinkTypeTheme theme_name:string = InternalLinkType; @@ -4786,7 +5142,9 @@ internalLinkTypeUserToken token:string = InternalLinkType; internalLinkTypeVideoChat chat_username:string invite_hash:string is_live_stream:Bool = InternalLinkType; //@description The link is a link to a Web App. Call searchPublicChat with the given bot username, check that the user is a bot, then call searchWebApp with the received bot and the given web_app_short_name. -//-Process received foundWebApp by showing a confirmation dialog if needed, then calling getWebAppLinkUrl and opening the returned URL +//-Process received foundWebApp by showing a confirmation dialog if needed. If the bot can be added to attachment or side menu, but isn't added yet, then show a disclaimer about Mini Apps being a third-party apps +//-instead of the dialog and ask the user to accept their Terms of service. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. +//-Then call getWebAppLinkUrl and open the returned URL as a Web App //@bot_username Username of the bot that owns the Web App //@web_app_short_name Short name of the Web App //@start_parameter Start parameter to be passed to getWebAppLinkUrl @@ -4806,6 +5164,15 @@ messageLink link:string is_public:Bool = MessageLink; messageLinkInfo is_public:Bool chat_id:int53 message_thread_id:int53 message:message media_timestamp:int32 for_album:Bool = MessageLinkInfo; +//@class BlockList @description Describes a type of a block list + +//@description The main block list that disallows writing messages to the current user, receiving their status and photo, viewing of stories, and some other actions +blockListMain = BlockList; + +//@description The block list that disallows viewing of stories of the current user +blockListStories = BlockList; + + //@description Contains a part of a file @data File bytes filePart data:bytes = FilePart; @@ -4830,6 +5197,9 @@ fileTypeNotificationSound = FileType; //@description The file is a photo fileTypePhoto = FileType; +//@description The file is a photo published as a story +fileTypePhotoStory = FileType; + //@description The file is a profile photo fileTypeProfilePhoto = FileType; @@ -4857,6 +5227,9 @@ fileTypeVideo = FileType; //@description The file is a video note fileTypeVideoNote = FileType; +//@description The file is a video published as a story +fileTypeVideoStory = FileType; + //@description The file is a voice note fileTypeVoiceNote = FileType; @@ -4942,8 +5315,9 @@ networkStatistics since_date:int32 entries:vector = Netw //@video_upload_bitrate The maximum suggested bitrate for uploaded videos, in kbit/s //@preload_large_videos True, if the beginning of video files needs to be preloaded for instant playback //@preload_next_audio True, if the next audio track needs to be preloaded while the user is listening to an audio file +//@preload_stories True, if stories needs to be preloaded //@use_less_data_for_calls True, if "use less data for calls" option needs to be enabled -autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int53 max_other_file_size:int53 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings; +autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int53 max_other_file_size:int53 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool preload_stories:Bool use_less_data_for_calls:Bool = AutoDownloadSettings; //@description Contains auto-download settings presets for the current user //@low Preset with lowest settings; supposed to be used by default when roaming @@ -5028,6 +5402,10 @@ topChatCategoryCalls = TopChatCategory; topChatCategoryForwardChats = TopChatCategory; +//@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; + + //@class TMeUrlType @description Describes the type of a URL linking to an internal Telegram entity //@description A URL linking to a user @user_id Identifier of the user @@ -5051,7 +5429,7 @@ tMeUrls urls:vector = TMeUrls; //@class SuggestedAction @description Describes an action suggested to the current user -//@description Suggests the user to enable "archive_and_mute_new_chats_from_unknown_users" option +//@description Suggests the user to enable archive_and_mute_new_chats_from_unknown_users setting in archiveChatListSettings suggestedActionEnableArchiveAndMuteNewChats = SuggestedAction; //@description Suggests the user to check whether they still remember their 2-step verification password @@ -5073,6 +5451,9 @@ suggestedActionSetPassword authorization_delay:int32 = SuggestedAction; //@description Suggests the user to upgrade the Premium subscription from monthly payments to annual payments suggestedActionUpgradePremium = SuggestedAction; +//@description Suggests the user to restore a recently expired Premium subscription +suggestedActionRestorePremium = SuggestedAction; + //@description Suggests the user to subscribe to the Premium subscription with annual payments suggestedActionSubscribeToAnnualPremium = SuggestedAction; @@ -5118,7 +5499,7 @@ proxyTypeMtproto secret:string = ProxyType; //@description Contains information about a proxy server //@id Unique identifier of the proxy -//@server Proxy server IP address +//@server Proxy server domain or IP address //@port Proxy server port //@last_used_date Point in time (Unix timestamp) when the proxy was last used; 0 if never //@is_enabled True, if the proxy is enabled now @@ -5407,8 +5788,8 @@ updateChatIsTranslatable chat_id:int53 is_translatable:Bool = Update; //@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update; -//@description A chat was blocked or unblocked @chat_id Chat identifier @is_blocked New value of is_blocked -updateChatIsBlocked chat_id:int53 is_blocked:Bool = Update; +//@description A chat was blocked or unblocked @chat_id Chat identifier @block_list Block list to which the chat is added; may be null if none +updateChatBlockList chat_id:int53 block_list:BlockList = Update; //@description A chat's has_scheduled_messages field has changed @chat_id Chat identifier @has_scheduled_messages New value of has_scheduled_messages updateChatHasScheduledMessages chat_id:int53 has_scheduled_messages:Bool = Update; @@ -5438,8 +5819,8 @@ updateNotification notification_group_id:int32 notification:notification = Updat //@notification_settings_chat_id Chat identifier, which notification settings must be applied to the added notifications //@notification_sound_id Identifier of the notification sound to be played; 0 if sound is disabled //@total_count Total number of unread notifications in the group, can be bigger than number of active notifications -//@added_notifications List of added group notifications, sorted by notification ID -//@removed_notification_ids Identifiers of removed group notifications, sorted by notification ID +//@added_notifications List of added group notifications, sorted by notification identifier +//@removed_notification_ids Identifiers of removed group notifications, sorted by notification identifier updateNotificationGroup notification_group_id:int32 type:NotificationGroupType chat_id:int53 notification_settings_chat_id:int53 notification_sound_id:int64 total_count:int32 added_notifications:vector removed_notification_ids:vector = Update; //@description Contains active notifications that was shown on previous application launches. This update is sent only if the message database is used. In that case it comes once before any updateNotification and updateNotificationGroup update @groups Lists of active notification groups @@ -5557,6 +5938,34 @@ updateUnreadMessageCount chat_list:ChatList unread_count:int32 unread_unmuted_co //@marked_as_unread_unmuted_count Total number of unmuted chats marked as unread updateUnreadChatCount chat_list:ChatList total_count:int32 unread_count:int32 unread_unmuted_count:int32 marked_as_unread_count:int32 marked_as_unread_unmuted_count:int32 = Update; +//@description A story was changed @story The new information about the story +updateStory story:story = Update; + +//@description A story became inaccessible @story_sender_chat_id Identifier of the chat that posted the story @story_id Story identifier +updateStoryDeleted story_sender_chat_id:int53 story_id:int32 = Update; + +//@description A story has been successfully sent @story The sent story @old_story_id The previous temporary story identifier +updateStorySendSucceeded story:story old_story_id:int32 = Update; + +//@description A story failed to send. If the story sending is canceled, then updateStoryDeleted will be received instead of this update +//@story The failed to send story +//@error The cause of the failure; may be null if unknown +//@error_code An error code +//@error_message Error message +updateStorySendFailed story:story error:CanSendStoryResult error_code:int32 error_message:string = Update; + +//@description The list of active stories posted by a specific chat has changed +//@active_stories The new list of active stories +updateChatActiveStories active_stories:chatActiveStories = Update; + +//@description Number of chats in a story list has changed @story_list The story list @chat_count Approximate total number of chats with active stories in the list +updateStoryListChatCount story_list:StoryList chat_count:int32 = Update; + +//@description Story stealth mode settings have changed +//@active_until_date Point in time (Unix timestamp) until stealth mode is active; 0 if it is disabled +//@cooldown_until_date Point in time (Unix timestamp) when stealth mode can be enabled again; 0 if there is no active cooldown +updateStoryStealthMode active_until_date:int32 cooldown_until_date:int32 = Update; + //@description An option changed its value @name The option name @value The new option value updateOption name:string value:OptionValue = Update; @@ -5599,7 +6008,10 @@ updateTermsOfService terms_of_service_id:string terms_of_service:termsOfService //@description The list of users nearby has changed. The update is guaranteed to be sent only 60 seconds after a successful searchChatsNearby request @users_nearby The new list of users nearby updateUsersNearby users_nearby:vector = Update; -//@description The list of bots added to attachment menu has changed @bots The new list of bots added to attachment menu. The bots must not be shown on scheduled messages screen +//@description The first unconfirmed session has changed @session The unconfirmed session; may be null if none +updateUnconfirmedSession session:unconfirmedSession = Update; + +//@description The list of bots added to attachment or side menu has changed @bots The new list of bots. The bots must not be shown on scheduled messages screen updateAttachmentMenuBots bots:vector = Update; //@description A message was sent by an opened Web App, so the Web App needs to be closed @web_app_launch_id Identifier of Web App launch @@ -5692,8 +6104,11 @@ updateNewCustomQuery id:int64 data:string timeout:int32 = Update; //@description A poll was updated; for bots only @poll New data about the poll updatePoll poll:poll = Update; -//@description A user changed the answer to a poll; for bots only @poll_id Unique poll identifier @user_id The user, who changed the answer to the poll @option_ids 0-based identifiers of answer options, chosen by the user -updatePollAnswer poll_id:int64 user_id:int53 option_ids:vector = Update; +//@description A user changed the answer to a poll; for bots only +//@poll_id Unique poll identifier +//@voter_id Identifier of the message sender that changed the answer to the poll +//@option_ids 0-based identifiers of answer options, chosen by the user +updatePollAnswer poll_id:int64 voter_id:MessageSender option_ids:vector = Update; //@description User rights changed in a chat; for bots only //@chat_id Chat identifier @@ -5947,7 +6362,7 @@ getSupergroupFullInfo supergroup_id:int53 = SupergroupFullInfo; //@description Returns information about a secret chat by its identifier. This is an offline request @secret_chat_id Secret chat identifier getSecretChat secret_chat_id:int32 = SecretChat; -//@description Returns information about a chat by its identifier, this is an offline request if the current user is not a bot @chat_id Chat identifier +//@description Returns information about a chat by its identifier; this is an offline request if the current user is not a bot @chat_id Chat identifier getChat chat_id:int53 = Chat; //@description Returns information about a message @chat_id Identifier of the chat the message belongs to @message_id Identifier of the message to get @@ -5982,7 +6397,7 @@ getMessageViewers chat_id:int53 message_id:int53 = MessageViewers; //@description Returns information about a file; this is an offline request @file_id Identifier of the file to get getFile file_id:int32 = File; -//@description Returns information about a file by its remote ID; this is an offline request. Can be used to register a URL as a file for further uploading, or sending as a message. Even the request succeeds, the file can be used only if it is still accessible to the user. +//@description Returns information about a file by its remote identifier; this is an offline request. Can be used to register a URL as a file for further uploading, or sending as a message. Even the request succeeds, the file can be used only if it is still accessible to the user. //-For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application //@remote_file_id Remote identifier of the file to get //@file_type File type; pass null if unknown @@ -6006,7 +6421,7 @@ searchPublicChat username:string = Chat; //@query Query to search for searchPublicChats query:string = Chats; -//@description Searches for the specified query in the title and username of already known chats, this is an offline request. Returns chats in the order seen in the main chat list +//@description Searches for the specified query in the title and username of already known chats; this is an offline request. Returns chats in the order seen in the main chat list //@query Query to search for. If the query is empty, returns up to 50 recently found chats //@limit The maximum number of chats to be returned searchChats query:string limit:int32 = Chats; @@ -6019,12 +6434,17 @@ searchChatsOnServer query:string limit:int32 = Chats; //@location Current user location searchChatsNearby location:location = ChatsNearby; -//@description Returns a list of frequently used chats. Supported only if the chat info database is enabled @category Category of chats to be returned @limit The maximum number of chats to be returned; up to 30 +//@description Returns a list of frequently used chats @category Category of chats to be returned @limit The maximum number of chats to be returned; up to 30 getTopChats category:TopChatCategory limit:int32 = Chats; //@description Removes a chat from the list of frequently used chats. Supported only if the chat info database is enabled @category Category of frequently used chats @chat_id Chat identifier removeTopChat category:TopChatCategory chat_id:int53 = Ok; +//@description Searches for the specified query in the title and username of up to 50 recently found chats; this is an offline request +//@query Query to search for +//@limit The maximum number of chats to be returned +searchRecentlyFoundChats query:string limit:int32 = Chats; + //@description Adds a chat to the list of recently found chats. The chat is added to the beginning of the list. If the chat is already in the list, it will be removed from the list first @chat_id Identifier of the chat to add addRecentlyFoundChat chat_id:int53 = Ok; @@ -6034,10 +6454,10 @@ removeRecentlyFoundChat chat_id:int53 = Ok; //@description Clears the list of recently found chats clearRecentlyFoundChats = Ok; -//@description Returns recently opened chats, this is an offline request. Returns chats in the order of last opening @limit The maximum number of chats to be returned +//@description Returns recently opened chats; this is an offline request. Returns chats in the order of last opening @limit The maximum number of chats to be returned getRecentlyOpenedChats limit:int32 = Chats; -//@description Checks whether a username can be set for a chat @chat_id Chat identifier; must be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if the chat is being created @username Username to be checked +//@description Checks whether a username can be set for a chat @chat_id Chat identifier; must be identifier of a supergroup chat, or a channel chat, or a private chat with self, or 0 if the chat is being created @username Username to be checked checkChatUsername chat_id:int53 username:string = CheckChatUsernameResult; //@description Returns a list of public chats of the specified type, owned by the user @type Type of the public chats to return @@ -6188,6 +6608,11 @@ getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int3 //@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; +//@description Informs TDLib that the user opened the sponsored chat via the button, the name, the photo, or a mention in the sponsored message +//@chat_id Chat identifier of the sponsored message +//@message_id Identifier of the sponsored message +clickChatSponsoredMessage chat_id:int53 message_id:int53 = Ok; + //@description Removes an active notification from notification list. Needs to be called only if the notification is removed by the current user @notification_group_id Identifier of notification group to which the notification belongs @notification_id Identifier of removed notification removeNotification notification_group_id:int32 notification_id:int32 = Ok; @@ -6251,20 +6676,20 @@ 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_message_id Identifier of the replied message; 0 if none +//@reply_to Identifier of the replied message or story; 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_message_id:int53 options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; +sendMessage chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo 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_message_id Identifier of a replied message; 0 if none +//@reply_to Identifier of the replied message or story; 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_message_id:int53 options:messageSendOptions input_message_contents:vector only_preview:Bool = Messages; +sendMessageAlbum chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo options:messageSendOptions input_message_contents:vector only_preview:Bool = 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 @@ -6275,12 +6700,12 @@ 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_message_id Identifier of a replied message; 0 if none +//@reply_to Identifier of the replied message or story; 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 result +//@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_message_id:int53 options:messageSendOptions query_id:int64 result_id:string hide_via_bot:Bool = Message; +sendInlineQueryResultMessage chat_id:int53 message_thread_id:int53 reply_to:MessageReplyTo 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 @@ -6299,16 +6724,13 @@ forwardMessages chat_id:int53 message_thread_id:int53 from_chat_id:int53 message //@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; -//@description Sends a notification about a screenshot taken in a chat. Supported only in private and secret chats @chat_id Chat identifier -sendChatScreenshotTakenNotification chat_id:int53 = Ok; - //@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_message_id Identifier of the replied message; 0 if none +//@reply_to Identifier of the replied message or story; 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_message_id:int53 disable_notification:Bool input_message_content:InputMessageContent = Message; +addLocalMessage chat_id:int53 sender_id:MessageSender reply_to:MessageReplyTo 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; @@ -6549,13 +6971,13 @@ getThemeParametersJsonString theme:themeParameters = Text; //@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers setPollAnswer chat_id:int53 message_id:int53 option_ids:vector = Ok; -//@description Returns users voted for the specified option in a non-anonymous polls. For optimal performance, the number of returned users is chosen by TDLib +//@description Returns message senders voted for the specified option in a non-anonymous polls. For optimal performance, the number of returned users is chosen by TDLib //@chat_id Identifier of the chat to which the poll belongs //@message_id Identifier of the message containing the poll //@option_id 0-based identifier of the answer option -//@offset Number of users to skip in the result; must be non-negative -//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned users is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached -getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users; +//@offset Number of voters to skip in the result; must be non-negative +//@limit The maximum number of voters to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned voters is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached +getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = MessageSenders; //@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set //@chat_id Identifier of the chat to which the poll belongs @@ -6607,7 +7029,7 @@ shareChatWithBot chat_id:int53 message_id:int53 button_id:int32 shared_chat_id:i //@chat_id Identifier of the chat where the query was sent //@user_location Location of the user; pass null if unknown or the bot doesn't need user's location //@query Text of the query -//@offset Offset of the first entry to return +//@offset Offset of the first entry to return; use empty string to get the first chunk of results getInlineQueryResults bot_user_id:int53 chat_id:int53 user_location:location query:string offset:string = InlineQueryResults; //@description Sets the result of an inline query; for bots only @@ -6635,9 +7057,9 @@ searchWebApp bot_user_id:int53 web_app_short_name:string = FoundWebApp; //@allow_write_access Pass true if the current user allowed the bot to send them messages getWebAppLinkUrl chat_id:int53 bot_user_id:int53 web_app_short_name:string start_parameter:string theme:themeParameters application_name:string allow_write_access:Bool = HttpUrl; -//@description Returns an HTTPS URL of a Web App to open after keyboardButtonTypeWebApp or inlineQueryResultsButtonTypeWebApp button is pressed +//@description Returns an HTTPS URL of a Web App to open from the side menu, a keyboardButtonTypeWebApp button, an inlineQueryResultsButtonTypeWebApp button, or an internalLinkTypeSideMenuBot link //@bot_user_id Identifier of the target bot -//@url The URL from the keyboardButtonTypeWebApp or inlineQueryResultsButtonTypeWebApp button +//@url The URL from a keyboardButtonTypeWebApp button, inlineQueryResultsButtonTypeWebApp button, an internalLinkTypeSideMenuBot link, or an empty when the bot is opened from the side menu //@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 getWebAppUrl bot_user_id:int53 url:string theme:themeParameters application_name:string = HttpUrl; @@ -6648,16 +7070,16 @@ getWebAppUrl bot_user_id:int53 url:string theme:themeParameters application_name //@data The data sendWebAppData bot_user_id:int53 button_text:string data:string = Ok; -//@description Informs TDLib that a Web App is being opened from attachment menu, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an inlineKeyboardButtonTypeWebApp button. +//@description Informs TDLib that a Web App is being opened from the attachment menu, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an inlineKeyboardButtonTypeWebApp button. //-For each bot, a confirmation alert about data sent to the bot must be shown once //@chat_id Identifier of the chat in which the Web App is opened. The Web App can't be opened in secret chats //@bot_user_id Identifier of the bot, providing the Web App -//@url The URL from an inlineKeyboardButtonTypeWebApp button, a botMenuButton button, or an internalLinkTypeAttachmentMenuBot link, or an empty string otherwise +//@url The URL from an inlineKeyboardButtonTypeWebApp button, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an empty string otherwise //@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_message_id Identifier of the replied message for the message sent by the Web App; 0 if none -openWebApp chat_id:int53 bot_user_id:int53 url:string theme:themeParameters application_name:string message_thread_id:int53 reply_to_message_id:int53 = WebAppInfo; +//@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; //@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; @@ -6833,6 +7255,9 @@ deleteChatFolder chat_folder_id:int32 leave_chat_ids:vector = Ok; //@description Returns identifiers of pinned or always included chats from a chat folder, which are suggested to be left when the chat folder is deleted @chat_folder_id Chat folder identifier getChatFolderChatsToLeave chat_folder_id:int32 = Chats; +//@description Returns approximate number of chats in a being created chat folder. Main and archive chat lists must be fully preloaded for this function to work correctly @folder The new chat folder +getChatFolderChatCount folder:chatFolder = Count; + //@description Changes the order of chat folders @chat_folder_ids Identifiers of chat folders in the new correct order @main_chat_list_position Position of the main chat list among chat folders, 0-based. Can be non-zero only for Premium users reorderChatFolders chat_folder_ids:vector main_chat_list_position:int32 = Ok; @@ -6878,6 +7303,12 @@ getChatFolderNewChats chat_folder_id:int32 = Chats; //@description Process new chats added to a shareable chat folder by its owner @chat_folder_id Chat folder identifier @added_chat_ids Identifiers of the new chats, which are added to the chat folder. The chats are automatically joined if they aren't joined yet processChatFolderNewChats chat_folder_id:int32 added_chat_ids:vector = Ok; +//@description Returns settings for automatic moving of chats to and from the Archive chat lists +getArchiveChatListSettings = ArchiveChatListSettings; + +//@description Changes settings for automatic moving of chats to and from the Archive chat lists @settings New settings +setArchiveChatListSettings settings:archiveChatListSettings = Ok; + //@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires can_change_info administrator right //@chat_id Chat identifier @@ -7042,7 +7473,7 @@ addSavedNotificationSound sound:InputFile = NotificationSound; removeSavedNotificationSound notification_sound_id:int64 = Ok; -//@description Returns list of chats with non-default notification settings +//@description Returns list of chats with non-default notification settings for new messages //@scope If specified, only chats from the scope will be returned; pass null to return chats from all scopes //@compare_sound Pass true to include in the response chats with only non-default sound getChatNotificationSettingsExceptions scope:NotificationSettingsScope compare_sound:Bool = Chats; @@ -7070,10 +7501,115 @@ setPinnedChats chat_list:ChatList chat_ids:vector = Ok; readChatList chat_list:ChatList = Ok; -//@description Returns information about a bot that can be added to attachment menu @bot_user_id Bot's user identifier +//@description Returns a story +//@story_sender_chat_id Identifier of the chat that posted the story +//@story_id Story identifier +//@only_local Pass true to get only locally available information without sending network requests +getStory story_sender_chat_id:int53 story_id:int32 only_local:Bool = Story; + +//@description Checks whether the current user can send a story +canSendStory = CanSendStoryResult; + +//@description Sends a new story. Returns a temporary story +//@content Content of the story +//@areas Clickable rectangle areas to be shown on the story media; pass null if none +//@caption Story caption; pass null to use an empty caption; 0-getOption("story_caption_length_max") characters +//@privacy_settings The privacy settings for the story +//@active_period Period after which the story is moved to archive, in seconds; must be one of 6 * 3600, 12 * 3600, 86400, or 2 * 86400 for Telegram Premium users, and 86400 otherwise +//@is_pinned Pass true to keep the story accessible after expiration +//@protect_content Pass true if the content of the story must be protected from forwarding and screenshotting +sendStory content:InputStoryContent areas:inputStoryAreas caption:formattedText privacy_settings:StoryPrivacySettings active_period:int32 is_pinned:Bool protect_content:Bool = Story; + +//@description Changes content and caption of a previously sent story +//@story_id Identifier of the story to edit +//@content New content of the story; pass null to keep the current content +//@areas New clickable rectangle areas to be shown on the story media; pass null to keep the current areas. Areas can't be edited if story content isn't changed +//@caption New story caption; pass null to keep the current caption +editStory story_id:int32 content:InputStoryContent areas:inputStoryAreas caption:formattedText = Ok; + +//@description Changes privacy settings of a previously sent story @story_id Identifier of the story @privacy_settings The new privacy settigs for the story +setStoryPrivacySettings story_id:int32 privacy_settings:StoryPrivacySettings = Ok; + +//@description Toggles whether a story is accessible after expiration @story_id Identifier of the story @is_pinned Pass true to make the story accessible after expiration; pass false to make it private +toggleStoryIsPinned story_id:int32 is_pinned:Bool = Ok; + +//@description Deletes a previously sent story @story_id Identifier of the story to delete +deleteStory story_id:int32 = Ok; + +//@description Returns list of chats with non-default notification settings for stories +getStoryNotificationSettingsExceptions = Chats; + +//@description Loads more active stories from a story list. The loaded stories will be sent through updates. Active stories are sorted by +//-the pair (active_stories.order, active_stories.story_sender_chat_id) in descending order. Returns a 404 error if all active stories have been loaded +//@story_list The story list in which to load active stories +loadActiveStories story_list:StoryList = Ok; + +//@description Changes story list in which stories from the chat are shown @chat_id Identifier of the chat that posted stories @story_list New list for active stories posted by the chat +setChatActiveStoriesList chat_id:int53 story_list:StoryList = Ok; + +//@description Returns the list of active stories posted by the given chat @chat_id Chat identifier +getChatActiveStories chat_id:int53 = ChatActiveStories; + +//@description Returns the list of pinned stories posted by the given chat. The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). +//-For optimal performance, the number of returned stories is chosen by TDLib +//@chat_id Chat identifier +//@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from the last story +//@limit The maximum number of stories to be returned +//-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +getChatPinnedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; + +//@description Returns the list of all stories of the current user. The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). +//-For optimal performance, the number of returned stories is chosen by TDLib +//@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from the last story +//@limit The maximum number of stories to be returned +//-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit +getArchivedStories from_story_id:int32 limit:int32 = Stories; + +//@description Informs TDLib that a story is opened and is being viewed by the user +//@story_sender_chat_id The identifier of the sender of the opened story +//@story_id The identifier of the story +openStory story_sender_chat_id:int53 story_id:int32 = Ok; + +//@description Informs TDLib that a story is closed by the user +//@story_sender_chat_id The identifier of the sender of the story to close +//@story_id The identifier of the story +closeStory story_sender_chat_id:int53 story_id:int32 = Ok; + +//@description Returns reactions, which can be chosen for a story @row_size Number of reaction per row, 5-25 +getStoryAvailableReactions row_size:int32 = AvailableReactions; + +//@description Changes chosen reaction on a story +//@story_sender_chat_id The identifier of the sender of the story +//@story_id The identifier of the story +//@reaction_type Type of the reaction to set; pass null to remove the reaction. `reactionTypeCustomEmoji` reactions can be used only by Telegram Premium users +//@update_recent_reactions Pass true if the reaction needs to be added to recent reactions +setStoryReaction story_sender_chat_id:int53 story_id:int32 reaction_type:ReactionType update_recent_reactions:Bool = Ok; + +//@description Returns viewers of a story. The method can be called if story.can_get_viewers == true +//@story_id Story identifier +//@query Query to search for in names and usernames of the viewers; may be empty to get all relevant viewers +//@only_contacts Pass true to get only contacts; pass false to get all relevant viewers +//@prefer_with_reaction Pass true to get viewers with reaction first; pass false to get viewers sorted just by view_date +//@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 story viewers to return +getStoryViewers story_id:int32 query:string only_contacts:Bool prefer_with_reaction:Bool offset:string limit:int32 = StoryViewers; + +//@description Reports a story to the Telegram moderators +//@story_sender_chat_id The identifier of the sender of the story to report +//@story_id The identifier of the story to report +//@reason The reason for reporting the story +//@text Additional report details; 0-1024 characters +reportStory story_sender_chat_id:int53 story_id:int32 reason:ReportReason text:string = Ok; + +//@description Activates stealth mode for stories, which hides all views of stories from the current user in the last "story_stealth_mode_past_period" seconds +//-and for the next "story_stealth_mode_future_period" seconds; for Telegram Premium users only +activateStoryStealthMode = Ok; + + +//@description Returns information about a bot that can be added to attachment or side menu @bot_user_id Bot's user identifier getAttachmentMenuBot bot_user_id:int53 = AttachmentMenuBot; -//@description Adds or removes a bot to attachment menu. Bot can be added to attachment menu, only if userTypeBot.can_be_added_to_attachment_menu == true +//@description Adds or removes a bot to attachment and side menu. Bot can be added to the menu, only if userTypeBot.can_be_added_to_attachment_menu == true //@bot_user_id Bot's user identifier //@is_added Pass true to add the bot to attachment menu; pass false to remove the bot from attachment menu //@allow_write_access Pass true if the current user allowed the bot to send them messages. Ignored if is_added is false @@ -7405,7 +7941,7 @@ setGroupCallParticipantIsSpeaking group_call_id:int32 audio_source:int32 is_spea //@description Toggles whether a participant of an active group call is muted, unmuted, or allowed to unmute themselves //@group_call_id Group call identifier //@participant_id Participant identifier -//@is_muted Pass true to mute the user; pass false to unmute the them +//@is_muted Pass true to mute the user; pass false to unmute them toggleGroupCallParticipantIsMuted group_call_id:int32 participant_id:MessageSender is_muted:Bool = Ok; //@description Changes volume level of a participant of an active group call. If the current user can manage the group call, then the participant's volume level will be changed for all users with the default volume level @@ -7443,8 +7979,10 @@ getGroupCallStreams group_call_id:int32 = GroupCallStreams; getGroupCallStreamSegment group_call_id:int32 time_offset:int53 scale:int32 channel_id:int32 video_quality:GroupCallVideoQuality = FilePart; -//@description Changes the block state of a message sender. Currently, only users and supergroup chats can be blocked @sender_id Identifier of a message sender to block/unblock @is_blocked New value of is_blocked -toggleMessageSenderIsBlocked sender_id:MessageSender is_blocked:Bool = Ok; +//@description Changes the block list of a message sender. Currently, only users and supergroup chats can be blocked +//@sender_id Identifier of a message sender to block/unblock +//@block_list New block list for the message sender; pass null to unblock the message sender +setMessageSenderBlockList sender_id:MessageSender block_list:BlockList = Ok; //@description Blocks an original sender of a message in the Replies chat //@message_id The identifier of an incoming message in the Replies chat @@ -7453,8 +7991,11 @@ toggleMessageSenderIsBlocked sender_id:MessageSender is_blocked:Bool = Ok; //@report_spam Pass true to report the sender to the Telegram moderators blockMessageSenderFromReplies message_id:int53 delete_message:Bool delete_all_messages:Bool report_spam:Bool = Ok; -//@description Returns users and chats that were blocked by the current user @offset Number of users and chats to skip in the result; must be non-negative @limit The maximum number of users and chats to return; up to 100 -getBlockedMessageSenders offset:int32 limit:int32 = MessageSenders; +//@description Returns users and chats that were blocked by the current user +//@block_list Block list from which to return users +//@offset Number of users and chats to skip in the result; must be non-negative +//@limit The maximum number of users and chats to return; up to 100 +getBlockedMessageSenders block_list:BlockList offset:int32 limit:int32 = MessageSenders; //@description Adds a user to the contact list or edits an existing contact by their user identifier @@ -7466,7 +8007,7 @@ addContact contact:contact share_phone_number:Bool = Ok; //@description Adds new contacts or edits existing contacts by their phone numbers; contacts' user identifiers are ignored @contacts The list of contacts to import or edit; contacts' vCard are ignored and are not imported importContacts contacts:vector = ImportedContacts; -//@description Returns all user contacts +//@description Returns all contacts of the user getContacts = Users; //@description Searches for the specified query in the first names, last names and usernames of the known user contacts @@ -7488,6 +8029,12 @@ changeImportedContacts contacts:vector = ImportedContacts; //@description Clears all imported contacts, contact list remains unchanged clearImportedContacts = Ok; +//@description Changes the list of close friends of the current user @user_ids User identifiers of close friends; the users must be contacts of the current user +setCloseFriends user_ids:vector = Ok; + +//@description Returns all close friends of the current user +getCloseFriends = Users; + //@description Changes a personal profile photo of a contact user @user_id User identifier @photo Profile photo to set; pass null to delete the photo; inputChatPhotoPrevious isn't supported in this function setUserPersonalProfilePhoto user_id:int53 photo:InputChatPhoto = Ok; @@ -7513,6 +8060,13 @@ getUserProfilePhotos user_id:int53 offset:int32 limit:int32 = ChatPhotos; //@chat_id Chat identifier for which to return stickers. Available custom emoji stickers may be different for different chats getStickers sticker_type:StickerType query:string limit:int32 chat_id:int53 = Stickers; +//@description Returns unique emoji that correspond to stickers to be found by the getStickers(sticker_type, query, 1000000, chat_id) +//@sticker_type Type of the stickers to search for +//@query Search query +//@chat_id Chat identifier for which to find stickers +//@return_only_main_emoji Pass true if only main emoji for each found sticker must be included in the result +getAllStickerEmojis sticker_type:StickerType query:string chat_id:int53 return_only_main_emoji:Bool = Emojis; + //@description Searches for stickers from public sticker sets that correspond to any of the given emoji //@sticker_type Type of the stickers to return //@emojis Space-separated list of emoji to search for; must be non-empty @@ -7667,10 +8221,8 @@ toggleUsernameIsActive username:string is_active:Bool = Ok; //@description Changes order of active usernames of the current user @usernames The new order of active usernames. All currently active usernames must be specified reorderActiveUsernames usernames:vector = Ok; -//@description Changes the emoji status of the current user; for Telegram Premium users only -//@emoji_status New emoji status; pass null to switch to the default badge -//@duration Duration of the status, in seconds; pass 0 to keep the status active until it will be changed manually -setEmojiStatus emoji_status:emojiStatus duration:int32 = Ok; +//@description Changes the emoji status of the current user; for Telegram Premium users only @emoji_status New emoji status; pass null to switch to the default badge +setEmojiStatus emoji_status:emojiStatus = Ok; //@description Changes the location of the current user. Needs to be called if getOption("is_location_visible") is true and location changes for more than 1 kilometer @location The new location of the user setLocation location:location = Ok; @@ -7718,13 +8270,26 @@ setMenuButton user_id:int53 menu_button:botMenuButton = Ok; //@description Returns menu button set by the bot for the given user; for bots only @user_id Identifier of the user or 0 to get the default menu button getMenuButton user_id:int53 = BotMenuButton; -//@description Sets default administrator rights for adding the bot to basic group and supergroup chats; for bots only @default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null +//@description Sets default administrator rights for adding the bot to basic group and supergroup chats; for bots only @default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; pass null to remove default rights setDefaultGroupAdministratorRights default_group_administrator_rights:chatAdministratorRights = Ok; -//@description Sets default administrator rights for adding the bot to channel chats; for bots only @default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null +//@description Sets default administrator rights for adding the bot to channel chats; for bots only @default_channel_administrator_rights Default administrator rights for adding the bot to channels; pass null to remove default rights setDefaultChannelAdministratorRights default_channel_administrator_rights:chatAdministratorRights = Ok; +//@description Checks whether the specified bot can send messages to the user. Returns a 404 error if can't and the access can be granted by call to allowBotToSendMessages @bot_user_id Identifier of the target bot +canBotSendMessages bot_user_id:int53 = Ok; + +//@description Allows the specified bot to send messages to the user @bot_user_id Identifier of the target bot +allowBotToSendMessages bot_user_id:int53 = Ok; + +//@description Sends a custom request from a Web App +//@bot_user_id Identifier of the bot +//@method The method name +//@parameters JSON-serialized method parameters +sendWebAppCustomRequest bot_user_id:int53 method:string parameters:string = CustomRequestResult; + + //@description Sets the name of a bot. Can be called only if userTypeBot.can_be_edited == true //@bot_user_id Identifier of the target bot //@language_code A two-letter ISO 639-1 language code. If empty, the name will be shown to all users for whose languages there is no dedicated name @@ -7746,7 +8311,7 @@ setBotProfilePhoto bot_user_id:int53 photo:InputChatPhoto = Ok; toggleBotUsernameIsActive bot_user_id:int53 username:string is_active:Bool = Ok; //@description Changes order of active usernames of a bot. Can be called only if userTypeBot.can_be_edited == true @bot_user_id Identifier of the target bot @usernames The new order of active usernames. All currently active usernames must be specified -reorderActiveBotUsernames bot_user_id:int53 usernames:vector = Ok; +reorderBotActiveUsernames bot_user_id:int53 usernames:vector = Ok; //@description Sets the text shown in the chat with a bot if the chat is empty. Can be called only if userTypeBot.can_be_edited == true //@bot_user_id Identifier of the target bot @@ -7780,6 +8345,9 @@ terminateSession session_id:int64 = Ok; //@description Terminates all other sessions of the current user terminateAllOtherSessions = Ok; +//@description Confirms an unconfirmed session of the current user from another device @session_id Session identifier +confirmSession session_id:int64 = Ok; + //@description Toggles whether a session can accept incoming calls @session_id Session identifier @can_accept_calls Pass true to allow accepting incoming calls by the session; pass false otherwise toggleSessionCanAcceptCalls session_id:int64 can_accept_calls:Bool = Ok; @@ -7953,14 +8521,16 @@ getLanguagePackInfo language_pack_id:string = LanguagePackInfo; //@keys Language pack keys of the strings to be returned; leave empty to request all available strings getLanguagePackStrings language_pack_id:string keys:vector = LanguagePackStrings; -//@description Fetches the latest versions of all strings from a language pack in the current localization target from the server. This method doesn't need to be called explicitly for the current used/base language packs. Can be called before authorization @language_pack_id Language pack identifier +//@description Fetches the latest versions of all strings from a language pack in the current localization target from the server. +//-This method doesn't need to be called explicitly for the current used/base language packs. Can be called before authorization +//@language_pack_id Language pack identifier synchronizeLanguagePack language_pack_id:string = Ok; //@description Adds a custom server language pack to the list of installed language packs in current localization target. Can be called before authorization @language_pack_id Identifier of a language pack to be added addCustomServerLanguagePack language_pack_id:string = Ok; //@description Adds or changes a custom local language pack to the current localization target -//@info Information about the language pack. Language pack ID must start with 'X', consist only of English letters, digits and hyphens, and must not exceed 64 characters. Can be called before authorization +//@info Information about the language pack. Language pack identifier must start with 'X', consist only of English letters, digits and hyphens, and must not exceed 64 characters. Can be called before authorization //@strings Strings of the new language pack setCustomLanguagePack info:languagePackInfo strings:vector = Ok; @@ -8036,14 +8606,14 @@ removeChatActionBar chat_id:int53 = Ok; //@message_ids Identifiers of reported messages; may be empty to report the whole chat //@reason The reason for reporting the chat //@text Additional report details; 0-1024 characters -reportChat chat_id:int53 message_ids:vector reason:ChatReportReason text:string = Ok; +reportChat chat_id:int53 message_ids:vector reason:ReportReason text:string = Ok; //@description Reports a chat photo to the Telegram moderators. A chat photo can be reported only if chat.can_be_reported //@chat_id Chat identifier //@file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported //@reason The reason for reporting the chat photo //@text Additional report details; 0-1024 characters -reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok; +reportChatPhoto chat_id:int53 file_id:int32 reason:ReportReason text:string = Ok; //@description Reports reactions set on a message to the Telegram moderators. Reactions on a message can be reported only if message.can_report_reactions //@chat_id Chat identifier @@ -8316,6 +8886,14 @@ assignGooglePlayTransaction package_name:string store_product_id:string purchase acceptTermsOfService terms_of_service_id:string = Ok; +//@description Searches specified query by word prefixes in the provided strings. Returns 0-based positions of strings that matched. Can be called synchronously +//@strings The strings to search in for the query +//@query Query to search for +//@limit The maximum number of objects to return +//@return_none_for_empty_query Pass true to receive no results for an empty query +searchStringsByPrefix strings:vector query:string limit:int32 return_none_for_empty_query:Bool = FoundPositions; + + //@description Sends a custom request; for bots only @method The method name @parameters JSON-serialized method parameters sendCustomRequest method:string parameters:string = CustomRequestResult; @@ -8349,7 +8927,7 @@ getDeepLinkInfo link:string = DeepLinkInfo; //@description Returns application config, provided by the server. Can be called before authorization getApplicationConfig = JsonValue; -//@description Adds server-provided application changelog as messages to the chat 777000 (Telegram); for official applications only. Returns a 404 error if nothing changed @previous_application_version The previous application version +//@description Adds server-provided application changelog as messages to the chat 777000 (Telegram) or as a stories; for official applications only. Returns a 404 error if nothing changed @previous_application_version The previous application version addApplicationChangelog previous_application_version:string = Ok; //@description Saves application log event on the server. Can be called before authorization @type Event type @chat_id Optional chat identifier, associated with the event @data The log event data @@ -8360,7 +8938,7 @@ getApplicationDownloadLink = HttpUrl; //@description Adds a proxy server for network requests. Can be called before authorization -//@server Proxy server IP address +//@server Proxy server domain or IP address //@port Proxy server port //@enable Pass true to immediately enable the proxy //@type Proxy type @@ -8368,7 +8946,7 @@ addProxy server:string port:int32 enable:Bool type:ProxyType = Proxy; //@description Edits an existing proxy server for network requests. Can be called before authorization //@proxy_id Proxy identifier -//@server Proxy server IP address +//@server Proxy server domain or IP address //@port Proxy server port //@enable Pass true to immediately enable the proxy //@type Proxy type @@ -8462,7 +9040,7 @@ testSquareInt x:int32 = TestInt; testNetwork = Ok; //@description Sends a simple network request to the Telegram servers via proxy; for testing only. Can be called before authorization -//@server Proxy server IP address +//@server Proxy server domain or IP address //@port Proxy server port //@type Proxy type //@dc_id Identifier of a datacenter with which to test connection diff --git a/td/generate/scheme/telegram_api.tl b/td/generate/scheme/telegram_api.tl index 00b46b7213d3..feed2ddb4cf9 100644 --- a/td/generate/scheme/telegram_api.tl +++ b/td/generate/scheme/telegram_api.tl @@ -63,6 +63,7 @@ inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0 inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; 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#9a86b58f user_id:InputUser id:int = 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; @@ -101,7 +102,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#8f97c628 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 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 = 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; 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; @@ -141,7 +142,7 @@ messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_ 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#9cb070d7 flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document ttl_seconds:flags.2?int = 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; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -149,6 +150,7 @@ messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true tes messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; +messageMediaStory#cbb20d88 flags:# via_mention:flags.1?true user_id:long id:int story:flags.0?StoryItem = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -169,7 +171,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_ messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; -messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction; +messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true from_request:flags.3?true domain:flags.0?string app:flags.2?BotApp = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; @@ -220,9 +222,9 @@ inputNotifyChats#4a95e84e = InputNotifyPeer; inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer; inputNotifyForumTopic#5c467992 peer:InputPeer top_msg_id:int = InputNotifyPeer; -inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings; +inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_sound:flags.8?NotificationSound = InputPeerNotifySettings; -peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings; +peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings; peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings; @@ -240,7 +242,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#93eadb53 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper = UserFull; +userFull#4fe1cc86 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?UserStories = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -298,6 +300,7 @@ updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update; +updateNewAuthorization#8951abef flags:# unconfirmed:flags.0?true hash:long date:flags.0?int device:flags.0?string location:flags.0?string = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -312,7 +315,7 @@ 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; updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update; updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update; -updateReadMessagesContents#68c13933 messages:Vector pts:int pts_count:int = Update; +updateReadMessagesContents#f8227181 flags:# messages:Vector pts:int pts_count:int date:flags.0?int = Update; updateChannelTooLong#108d941f flags:# channel_id:long pts:flags.0?int = Update; updateChannel#635b4c09 channel_id:long = Update; updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update; @@ -361,7 +364,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector = Update; updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; -updateMessagePollVote#106395c9 poll_id:long user_id:long options:Vector qts:int = Update; +updateMessagePollVote#24f40e77 poll_id:long peer:Peer options:Vector qts:int = Update; updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; @@ -369,7 +372,7 @@ updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updateChannelMessageForwards#d29a27f4 channel_id:long id:int forwards:int = Update; updateReadChannelDiscussionInbox#d6b19546 flags:# channel_id:long top_msg_id:int read_max_id:int broadcast_id:flags.0?long broadcast_post:flags.0?int = Update; updateReadChannelDiscussionOutbox#695c9e7c channel_id:long top_msg_id:int read_max_id:int = Update; -updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; +updatePeerBlocked#ebe07752 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true peer_id:Peer = Update; updateChannelUserTyping#8c88c923 flags:# channel_id:long top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#5bb98608 flags:# pinned:flags.0?true channel_id:long messages:Vector pts:int pts_count:int = Update; @@ -401,6 +404,11 @@ updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector< updateUser#20529438 user_id:long = Update; updateAutoSaveSettings#ec05b097 = Update; updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; +updateStory#205a4133 user_id:long story:StoryItem = Update; +updateReadStories#feb5345a user_id:long max_id:int = Update; +updateStoryID#1bf335b9 id:int random_id:long = Update; +updateStoriesStealthMode#2c084dc1 stealth_mode:StoriesStealthMode = Update; +updateSentStoryReaction#e3a73d20 user_id:long story_id:int reaction:Reaction = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -505,6 +513,7 @@ inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey; inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey; inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey; inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey; +inputPrivacyKeyAbout#3823cc40 = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; @@ -515,6 +524,7 @@ privacyKeyProfilePhoto#96151fed = PrivacyKey; privacyKeyPhoneNumber#d19ae46d = PrivacyKey; privacyKeyAddedByPhone#42ffd42b = PrivacyKey; privacyKeyVoiceMessages#697f414 = PrivacyKey; +privacyKeyAbout#a486b761 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -524,6 +534,7 @@ inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule; inputPrivacyValueDisallowUsers#90110467 users:Vector = InputPrivacyRule; inputPrivacyValueAllowChatParticipants#840649cf chats:Vector = InputPrivacyRule; inputPrivacyValueDisallowChatParticipants#e94f0f86 chats:Vector = InputPrivacyRule; +inputPrivacyValueAllowCloseFriends#2f453e49 = InputPrivacyRule; privacyValueAllowContacts#fffe1bac = PrivacyRule; privacyValueAllowAll#65427b82 = PrivacyRule; @@ -533,6 +544,7 @@ privacyValueDisallowAll#8b73e763 = PrivacyRule; privacyValueDisallowUsers#e4621141 users:Vector = PrivacyRule; privacyValueAllowChatParticipants#6b134e8e chats:Vector = PrivacyRule; privacyValueDisallowChatParticipants#41c87565 chats:Vector = PrivacyRule; +privacyValueAllowCloseFriends#f7e8d89b = PrivacyRule; account.privacyRules#50a04e45 rules:Vector chats:Vector users:Vector = account.PrivacyRules; @@ -541,7 +553,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute; documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute; -documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute; +documentAttributeVideo#d38ff1c2 flags:# round_message:flags.0?true supports_streaming:flags.1?true nosound:flags.3?true duration:double w:int h:int preload_prefix_size:flags.2?int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; @@ -562,7 +574,7 @@ 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; 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 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; +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; account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; @@ -580,7 +592,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 title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = 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; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -1126,7 +1138,7 @@ poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true mul pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; -pollResults#dcb82ea3 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; +pollResults#7adf2420 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; chatOnlines#f041e250 onlines:int = ChatOnlines; @@ -1147,7 +1159,7 @@ codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags. wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; -autoDownloadSettings#8efab953 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int = AutoDownloadSettings; +autoDownloadSettings#baa57628 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true stories_preload:flags.4?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int small_queue_active_operations_max:int large_queue_active_operations_max:int = AutoDownloadSettings; account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings; @@ -1207,12 +1219,9 @@ inputThemeSettings#8fde504f flags:# message_colors_animated:flags.2?true base_th themeSettings#fa58b6d4 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector wallpaper:flags.1?WallPaper = ThemeSettings; webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; +webPageAttributeStory#939a4671 flags:# user_id:long id:int story:flags.0?StoryItem = WebPageAttribute; -messageUserVote#34d247b4 user_id:long option:bytes date:int = MessageUserVote; -messageUserVoteInputOption#3ca5b0ec user_id:long date:int = MessageUserVote; -messageUserVoteMultiple#8a65e557 user_id:long options:Vector date:int = MessageUserVote; - -messages.votesList#823f649 flags:# count:int votes:Vector users:Vector next_offset:flags.0?string = messages.VotesList; +messages.votesList#4899484e flags:# count:int votes:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.VotesList; bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; @@ -1253,7 +1262,7 @@ statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInvite stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; -globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; +globalPrivacySettings#734c4ccb flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true = GlobalPrivacySettings; help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; @@ -1269,6 +1278,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; +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; @@ -1337,7 +1347,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#fc25b828 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; +sponsoredMessage#daafff6b flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string webpage:flags.9?SponsoredWebPage message:string entities:flags.1?Vector sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; @@ -1369,7 +1379,7 @@ availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true re messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; -messagePeerReaction#8c79b63c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer date:int reaction:Reaction = MessagePeerReaction; +messagePeerReaction#8c79b63c flags:# big:flags.0?true unread:flags.1?true my:flags.2?true peer_id:Peer date:int reaction:Reaction = MessagePeerReaction; groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel; @@ -1381,7 +1391,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; +attachMenuBot#d90d8dfe flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true show_in_attach_menu:flags.3?true show_in_side_menu:flags.4?true side_menu_disclaimer_needed:flags.5?true bot_id:long short_name:string peer_types:flags.3?Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1511,7 +1521,7 @@ inputBotAppShortName#908c0407 bot_id:InputUser short_name:string = InputBotApp; botAppNotModified#5da674b7 = BotApp; botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document hash:long = BotApp; -messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true app:BotApp = messages.BotApp; +messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true has_settings:flags.2?true app:BotApp = messages.BotApp; appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; @@ -1534,6 +1544,46 @@ chatlists.chatlistUpdates#93bd878d missing_peers:Vector chats:Vector bots.botInfo#e8a775b0 name:string about:string description:string = bots.BotInfo; +messagePeerVote#b6cc2d5c peer:Peer option:bytes date:int = MessagePeerVote; +messagePeerVoteInputOption#74cda504 peer:Peer date:int = MessagePeerVote; +messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector date:int = MessagePeerVote; + +sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage; + +storyViews#c64c0b97 flags:# has_viewers:flags.1?true views_count:int reactions_count:int recent_viewers:flags.0?Vector = StoryViews; + +storyItemDeleted#51e6ee4f id:int = StoryItem; +storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; +storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; + +userStories#8611a200 flags:# user_id:long max_read_id:flags.0?int stories:Vector = UserStories; + +stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; +stories.allStories#519d899e flags:# has_more:flags.0?true count:int state:string user_stories:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; + +stories.stories#4fe57df1 count:int stories:Vector users:Vector = stories.Stories; + +stories.userStories#37a6ff5f stories:UserStories users:Vector = stories.UserStories; + +storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int reaction:flags.2?Reaction = StoryView; + +stories.storyViewsList#46e9b9ec flags:# count:int reactions_count:int views:Vector users:Vector next_offset:flags.0?string = stories.StoryViewsList; + +stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; + +inputReplyToMessage#9c5386e4 flags:# reply_to_msg_id:int top_msg_id:flags.0?int = InputReplyTo; +inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; + +exportedStoryLink#3fc9053b link:string = ExportedStoryLink; + +storiesStealthMode#712e27fd flags:# active_until_date:flags.0?int cooldown_until_date:flags.1?int = StoriesStealthMode; + +mediaAreaCoordinates#3d1ea4e x:double y:double w:double h:double rotation:double = MediaAreaCoordinates; + +mediaAreaVenue#be82db9c coordinates:MediaAreaCoordinates geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MediaArea; +inputMediaAreaVenue#b282217f coordinates:MediaAreaCoordinates query_id:long result_id:string = MediaArea; +mediaAreaGeoPoint#df8b3b22 coordinates:MediaAreaCoordinates geo:GeoPoint = MediaArea; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1614,7 +1664,7 @@ account.resendPasswordEmail#7a7f2a15 = Bool; account.cancelPasswordEmail#c1cbd5b6 = Bool; account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; -account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; +account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true compare_stories:flags.2?true peer:flags.0?InputNotifyPeer = Updates; account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; account.uploadWallPaper#e39a8f03 flags:# for_chat:flags.0?true file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool; @@ -1639,7 +1689,7 @@ account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; account.getChatThemes#d638de89 hash:long = account.Themes; account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool; -account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; +account.changeAuthorizationSettings#40f48462 flags:# confirmed:flags.3?true hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones; account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone; account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document; @@ -1654,10 +1704,12 @@ account.getDefaultGroupPhotoEmojis#915860ae hash:long = EmojiList; 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; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; +users.getStoriesMaxIDs#ca1cb9ab id:Vector = Vector; contacts.getContactIDs#7adc669d hash:long = Vector; contacts.getStatuses#c4a353ee = Vector; @@ -1665,9 +1717,9 @@ contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; -contacts.block#68cc1411 id:InputPeer = Bool; -contacts.unblock#bea65d50 id:InputPeer = Bool; -contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; +contacts.block#2e2e8734 flags:# my_stories_from:flags.0?true id:InputPeer = Bool; +contacts.unblock#b550d328 flags:# my_stories_from:flags.0?true id:InputPeer = Bool; +contacts.getBlocked#9a868f80 flags:# my_stories_from:flags.0?true offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers; @@ -1682,6 +1734,9 @@ contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_hi contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer; contacts.exportContactToken#f8654027 = ExportedContactToken; contacts.importContactToken#13005788 token:string = User; +contacts.editCloseFriends#ba6705f0 id:Vector = Bool; +contacts.toggleStoriesHidden#753fb865 id:InputUser hidden:Bool = Bool; +contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector limit:int = Bool; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; @@ -1692,8 +1747,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#1cc20387 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.sendMedia#7547c966 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMessage#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.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; @@ -1737,7 +1792,7 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; 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#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +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; @@ -1759,7 +1814,6 @@ 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.getAllChats#875f74be except_ids:Vector = messages.Chats; messages.getWebPage#32ca8f91 url:string hash:int = 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; @@ -1767,13 +1821,13 @@ messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs; messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector = Bool; messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool; messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia; -messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates; +messages.sendScreenshotNotification#a1405817 peer:InputPeer reply_to:InputReplyTo random_id:long = Updates; messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers; 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#b6f11a1c flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int 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 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; @@ -1845,9 +1899,9 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; -messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult; -messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#299bec8e flags:# from_switch_webview:flags.1?true bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.requestWebView#269dc2c1 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = WebViewResult; +messages.prolongWebView#b0d81a83 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to:flags.0?InputReplyTo send_as:flags.13?InputPeer = Bool; +messages.requestSimpleWebView#1a46500a flags:# from_switch_webview:flags.1?true from_side_menu:flags.2?true bot:InputUser url:flags.3?string start_param:flags.4?string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -1873,7 +1927,7 @@ messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:Inpu messages.setChatWallPaper#8ffacae1 flags:# peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; updates.getState#edd4882a = updates.State; -updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; +updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; photos.updateProfilePhoto#9e82039 flags:# fallback:flags.0?true bot:flags.1?InputUser id:InputPhoto = photos.Photo; @@ -1970,6 +2024,7 @@ channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:In 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; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1984,6 +2039,9 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo; bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector = Bool; bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; +bots.canSendMessage#1359f4e6 bot:InputUser = Bool; +bots.allowSendMessage#f132e3ef bot:InputUser = Updates; +bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -2065,3 +2123,24 @@ chatlists.joinChatlistUpdates#e089f8f5 chatlist:InputChatlist peers:Vector; chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; + +stories.canSendStory#b100d45d = Bool; +stories.sendStory#d455fcec flags:# pinned:flags.2?true noforwards:flags.4?true media:InputMedia media_areas:flags.5?Vector caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int = Updates; +stories.editStory#a9b91ae4 flags:# id:int media:flags.0?InputMedia media_areas:flags.3?Vector caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; +stories.deleteStories#b5d501d7 id:Vector = Vector; +stories.togglePinned#51602944 id:Vector pinned:Bool = Vector; +stories.getAllStories#eeb0d625 flags:# next:flags.1?true hidden:flags.2?true state:flags.0?string = stories.AllStories; +stories.getUserStories#96d528e0 user_id:InputUser = stories.UserStories; +stories.getPinnedStories#b471137 user_id:InputUser offset_id:int limit:int = stories.Stories; +stories.getStoriesArchive#1f5bc5d2 offset_id:int limit:int = stories.Stories; +stories.getStoriesByID#6a15cf46 user_id:InputUser id:Vector = stories.Stories; +stories.toggleAllStoriesHidden#7c2557c4 hidden:Bool = Bool; +stories.getAllReadUserStories#729c562c = Updates; +stories.readStories#edc5105b user_id:InputUser max_id:int = Vector; +stories.incrementStoryViews#22126127 user_id:InputUser id:Vector = Bool; +stories.getStoryViewsList#f95f61a4 flags:# just_contacts:flags.0?true reactions_first:flags.2?true q:flags.1?string id:int offset:string limit:int = stories.StoryViewsList; +stories.getStoriesViews#9a75d6a6 id:Vector = stories.StoryViews; +stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink; +stories.report#c95be06a user_id:InputUser id:Vector reason:ReportReason message:string = Bool; +stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?true = Updates; +stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates; diff --git a/td/generate/tl_json_converter.cpp b/td/generate/tl_json_converter.cpp index 8512cb84434a..34f0234beb12 100644 --- a/td/generate/tl_json_converter.cpp +++ b/td/generate/tl_json_converter.cpp @@ -103,8 +103,8 @@ void gen_from_json_constructor(StringBuilder &sb, const T *constructor, bool is_ sb << " {\n"; for (auto &arg : constructor->args) { sb << " TRY_STATUS(from_json" << (arg.type->type == tl::simple::Type::Bytes ? "_bytes" : "") << "(to." - << tl::simple::gen_cpp_field_name(arg.name) << ", get_json_object_field_force(from, \"" - << tl::simple::gen_cpp_name(arg.name) << "\")));\n"; + << tl::simple::gen_cpp_field_name(arg.name) << ", from.extract_field(\"" << tl::simple::gen_cpp_name(arg.name) + << "\")));\n"; } sb << " return Status::OK();\n"; sb << "}\n\n"; @@ -187,7 +187,6 @@ void gen_tl_constructor_from_string(StringBuilder &sb, const tl::simple::Schema void gen_json_converter_file(const tl::simple::Schema &schema, const std::string &file_name_base, bool is_header, Mode mode) { auto file_name = is_header ? file_name_base + ".h" : file_name_base + ".cpp"; - file_name = "auto/" + file_name; auto old_file_content = [&] { auto r_content = read_file(file_name); if (r_content.is_error()) { diff --git a/td/generate/tl_writer_c.h b/td/generate/tl_writer_c.h index 41bb38b5d529..94d88029d248 100644 --- a/td/generate/tl_writer_c.h +++ b/td/generate/tl_writer_c.h @@ -17,11 +17,10 @@ namespace td { class TlWriterCCommon final : public tl::TL_writer { - public: int is_header_; - std::string prefix_; - TlWriterCCommon(const std::string &name, int is_header, const std::string &prefix = "") - : TL_writer(name), is_header_(is_header), prefix_(prefix) { + + public: + TlWriterCCommon(const std::string &name, int is_header) : TL_writer(name), is_header_(is_header) { } int get_max_arity() const final { @@ -223,13 +222,26 @@ class TlWriterCCommon final : public tl::TL_writer { std::string gen_type_name(const tl::tl_tree_type *tree_type) const final { return gen_type_name(tree_type, false); } - std::string gen_output_begin() const final { + std::string gen_output_begin(const std::string &additional_imports) const final { if (is_header_ == 1) { - return "#pragma once\n" + return "#pragma once\n\n" + additional_imports + "#ifdef __cplusplus\n" "extern \"C\" {\n" - "#endif\n" - "struct TdBytes {\n" + "#endif\n"; + } + if (is_header_ == -1) { + return "#pragma once\n\n" + gen_import_declaration("td/telegram/td_tdc_api.h", false) + + gen_import_declaration("td/telegram/td_api.h", false) + "\n" + additional_imports; + } + return gen_import_declaration("td/telegram/td_tdc_api_inner.h", false) + "\n" + + gen_import_declaration("td/utils/format.h", false) + gen_import_declaration("td/utils/logging.h", false) + + gen_import_declaration("td/utils/misc.h", false) + gen_import_declaration("td/utils/Slice.h", false) + "\n" + + additional_imports; + } + + std::string gen_output_begin_once() const final { + if (is_header_ == 1) { + return "struct TdBytes {\n" " unsigned char *data;\n" " int len;\n" "};\n" @@ -266,18 +278,9 @@ class TlWriterCCommon final : public tl::TL_writer { " int (*is_nil)(void);\n" "};\n"; } - if (is_header_ == -1) { - return "#pragma once\n" - "#include \"td/telegram/td_tdc_api.h\"\n" - "#include \"td/telegram/td_api.h\"\n"; - } - return "#include \"td/telegram/td_tdc_api_inner.h\"\n\n" - "#include \"td/utils/format.h\"\n" - "#include \"td/utils/logging.h\"\n" - "#include \"td/utils/misc.h\"\n" - "#include \"td/utils/Slice.h\"\n" - "\n"; + return std::string(); } + std::string gen_output_end() const final { if (is_header_ == 1) { return "#ifdef __cplusplus\n" @@ -289,17 +292,29 @@ class TlWriterCCommon final : public tl::TL_writer { return ""; } + std::string gen_import_declaration(const std::string &name, bool is_system) const final { + if (is_system) { + return "#include <" + name + ">\n"; + } else { + return "#include \"" + name + "\"\n"; + } + } + + std::string gen_package_suffix() const final { + return ".h"; + } + std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final { if (is_header_ != 1 || class_name == "") { return ""; } return "struct Td" + class_name + ";\n" - "TDC_VECTOR(" + - class_name + ", struct Td" + class_name + - " *);\n" - "TDC_VECTOR(Vector" + - class_name + ", struct TdVector" + class_name + " *);\n"; + "struct TdVector" + + class_name + + ";\n" + "struct TdVectorVector" + + class_name + ";\n"; } std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy, @@ -312,7 +327,13 @@ class TlWriterCCommon final : public tl::TL_writer { if (class_name == "Function" || class_name == "Object") { tail = "};\n"; } - return "struct Td" + class_name + " {\n" + " int ID;\n int refcnt;\n" + tail; + return "TDC_VECTOR(" + class_name + ", struct Td" + class_name + + " *);\n" + "TDC_VECTOR(Vector" + + class_name + ", struct TdVector" + class_name + + " *);\n" + "struct Td" + + class_name + " {\n" + " int ID;\n int refcnt;\n" + tail; } std::string gen_class_end() const final { return ""; @@ -1353,4 +1374,5 @@ class TlWriterCCommon final : public tl::TL_writer { return 2; } }; + } // namespace td diff --git a/td/generate/tl_writer_cpp.cpp b/td/generate/tl_writer_cpp.cpp index ac5e8b76fb5a..05a9b727d143 100644 --- a/td/generate/tl_writer_cpp.cpp +++ b/td/generate/tl_writer_cpp.cpp @@ -10,7 +10,7 @@ namespace td { -std::string TD_TL_writer_cpp::gen_output_begin() const { +std::string TD_TL_writer_cpp::gen_output_begin(const std::string &additional_imports) const { std::string ext_include_str; for (auto &it : ext_include) { ext_include_str += "#include " + it + "\n"; @@ -26,12 +26,15 @@ std::string TD_TL_writer_cpp::gen_output_begin() const { "#include \"td/utils/SliceBuilder.h\"\n" "#include \"td/utils/tl_parsers.h\"\n" "#include \"td/utils/tl_storers.h\"\n" - "#include \"td/utils/TlStorerToString.h\"\n\n" + "#include \"td/utils/TlStorerToString.h\"\n\n" + + additional_imports + "namespace td {\n" "namespace " + - tl_name + - " {\n\n" - "std::string to_string(const BaseObject &value) {\n" + tl_name + " {\n\n"; +} + +std::string TD_TL_writer_cpp::gen_output_begin_once() const { + return "std::string to_string(const BaseObject &value) {\n" " TlStorerToString storer;\n" " value.store(storer, \"\");\n" " return storer.move_as_string();\n" @@ -538,9 +541,7 @@ std::string TD_TL_writer_cpp::gen_fetch_function_begin(const std::string &parser result += "p);\n" "}\n\n" + - class_name + "::" + class_name + "(" + parser_name + - " &p)\n" - "#define FAIL(error) p.set_error(error)\n"; + class_name + "::" + class_name + "(" + parser_name + " &p)\n"; } return result; } @@ -562,8 +563,7 @@ std::string TD_TL_writer_cpp::gen_fetch_function_end(bool has_parent, int field_ if (field_count == 0) { return "}\n"; } - return "#undef FAIL\n" - "{}\n"; + return "{}\n"; } if (parser_type == -1) { diff --git a/td/generate/tl_writer_cpp.h b/td/generate/tl_writer_cpp.h index 8d983ca1bbae..071040a3f716 100644 --- a/td/generate/tl_writer_cpp.h +++ b/td/generate/tl_writer_cpp.h @@ -41,7 +41,8 @@ class TD_TL_writer_cpp : public TD_TL_writer { : TD_TL_writer(tl_name, string_type, bytes_type), ext_include(ext_include) { } - std::string gen_output_begin() const override; + std::string gen_output_begin(const std::string &additional_imports) const override; + std::string gen_output_begin_once() const override; std::string gen_output_end() const override; std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override; diff --git a/td/generate/tl_writer_dotnet.h b/td/generate/tl_writer_dotnet.h index 7cee87723541..c0226c4c3078 100644 --- a/td/generate/tl_writer_dotnet.h +++ b/td/generate/tl_writer_dotnet.h @@ -18,12 +18,14 @@ namespace td { namespace tl { class TlWriterDotNet final : public TL_writer { - public: bool is_header_; std::string prefix_; - TlWriterDotNet(const std::string &name, bool is_header, const std::string &prefix = "") + + public: + TlWriterDotNet(const std::string &name, bool is_header, const std::string &prefix) : TL_writer(name), is_header_(is_header), prefix_(prefix) { } + int get_max_arity(void) const final { return 0; } @@ -178,19 +180,36 @@ class TlWriterDotNet final : public TL_writer { return gen_main_class_name(t) + "^"; } - std::string gen_output_begin(void) const final { - return prefix_ + - "#include \"td/tl/tl_dotnet_object.h\"\n\n" + + std::string gen_output_begin(const std::string &additional_imports) const final { + return prefix_ + "#include \"td/tl/tl_dotnet_object.h\"\n\n" + additional_imports + "namespace Telegram {\n" "namespace Td {\n" "namespace Api {\n"; } + + std::string gen_output_begin_once(void) const final { + return std::string(); + } + std::string gen_output_end() const final { return "}\n" "}\n" "}\n"; } + std::string gen_import_declaration(const std::string &name, bool is_system) const final { + if (is_system) { + return "#include <" + name + ">\n"; + } else { + return "#include \"" + name + "\"\n"; + } + } + + std::string gen_package_suffix() const final { + return ".h"; + } + std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final { if (!is_header_) { return ""; diff --git a/td/generate/tl_writer_h.cpp b/td/generate/tl_writer_h.cpp index 19585d69ed23..1270a6b86330 100644 --- a/td/generate/tl_writer_h.cpp +++ b/td/generate/tl_writer_h.cpp @@ -28,7 +28,14 @@ std::string TD_TL_writer_h::forward_declaration(std::string type) { return ""; } -std::string TD_TL_writer_h::gen_output_begin() const { +std::string TD_TL_writer_h::gen_output_begin(const std::string &additional_imports) const { + if (!additional_imports.empty()) { + return "#pragma once\n\n" + additional_imports + + "namespace td {\n" + "namespace " + + tl_name + " {\n\n"; + } + std::string ext_include_str; for (auto &it : ext_include) { ext_include_str += "#include " + it + "\n"; @@ -53,10 +60,11 @@ std::string TD_TL_writer_h::gen_output_begin() const { "#include \n" "#include \n\n" "namespace td {\n" + - ext_forward_declaration + "namespace " + tl_name + - " {\n\n" + ext_forward_declaration + "namespace " + tl_name + " {\n\n"; +} - "using int32 = std::int32_t;\n" +std::string TD_TL_writer_h::gen_output_begin_once() const { + return "using int32 = std::int32_t;\n" "using int53 = std::int64_t;\n" "using int64 = std::int64_t;\n\n" diff --git a/td/generate/tl_writer_h.h b/td/generate/tl_writer_h.h index 273087e9cc68..df90bef27124 100644 --- a/td/generate/tl_writer_h.h +++ b/td/generate/tl_writer_h.h @@ -28,7 +28,8 @@ class TD_TL_writer_h : public TD_TL_writer { : TD_TL_writer(tl_name, string_type, bytes_type), ext_include(ext_include) { } - std::string gen_output_begin() const override; + std::string gen_output_begin(const std::string &additional_imports) const override; + std::string gen_output_begin_once() const override; std::string gen_output_end() const override; std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override; diff --git a/td/generate/tl_writer_hpp.cpp b/td/generate/tl_writer_hpp.cpp index 6f14a6a7e780..747244c72439 100644 --- a/td/generate/tl_writer_hpp.cpp +++ b/td/generate/tl_writer_hpp.cpp @@ -38,7 +38,7 @@ std::string TD_TL_writer_hpp::gen_base_tl_class_name() const { return "BaseObject"; } -std::string TD_TL_writer_hpp::gen_output_begin() const { +std::string TD_TL_writer_hpp::gen_output_begin(const std::string &additional_imports) const { return "#pragma once\n" "\n" #ifndef DISABLE_HPP_DOCUMENTATION @@ -57,6 +57,10 @@ std::string TD_TL_writer_hpp::gen_output_begin() const { tl_name + " {\n\n"; } +std::string TD_TL_writer_hpp::gen_output_begin_once() const { + return std::string(); +} + std::string TD_TL_writer_hpp::gen_output_end() const { return "} // namespace " + tl_name + "\n" diff --git a/td/generate/tl_writer_hpp.h b/td/generate/tl_writer_hpp.h index 4c38187b24a4..9e1feade720f 100644 --- a/td/generate/tl_writer_hpp.h +++ b/td/generate/tl_writer_hpp.h @@ -28,7 +28,8 @@ class TD_TL_writer_hpp final : public TD_TL_writer { std::string gen_base_type_class_name(int arity) const final; std::string gen_base_tl_class_name() const final; - std::string gen_output_begin() const final; + std::string gen_output_begin(const std::string &additional_imports) const final; + std::string gen_output_begin_once() const final; std::string gen_output_end() const final; std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final; diff --git a/td/generate/tl_writer_java.cpp b/td/generate/tl_writer_java.cpp index 917fb433c1a2..bbd57a5b4f7b 100644 --- a/td/generate/tl_writer_java.cpp +++ b/td/generate/tl_writer_java.cpp @@ -190,17 +190,19 @@ std::string TD_TL_writer_java::gen_int_const(const tl::tl_tree *tree_c, return std::string(); } -std::string TD_TL_writer_java::gen_output_begin() const { +std::string TD_TL_writer_java::gen_output_begin(const std::string &additional_imports) const { return "package " + package_name + ";\n\n" "public class " + - tl_name + - " {\n" - " static {\n" + tl_name + " {\n"; +} + +std::string TD_TL_writer_java::gen_output_begin_once() const { + return " static {\n" " try {\n" " System.loadLibrary(\"tdjni\");\n" " } catch (UnsatisfiedLinkError e) {\n" - " e.printStackTrace();\n" + + " e.printStackTrace();\n" " }\n" " }\n\n" " private " + @@ -213,6 +215,14 @@ std::string TD_TL_writer_java::gen_output_end() const { return "}\n"; } +std::string TD_TL_writer_java::gen_import_declaration(const std::string &name, bool is_system) const { + return "import " + name + ";\n"; +} + +std::string TD_TL_writer_java::gen_package_suffix() const { + return ""; +} + std::string TD_TL_writer_java::gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const { return ""; } diff --git a/td/generate/tl_writer_java.h b/td/generate/tl_writer_java.h index 69f2671734b9..67b90048693d 100644 --- a/td/generate/tl_writer_java.h +++ b/td/generate/tl_writer_java.h @@ -53,9 +53,14 @@ class TD_TL_writer_java final : public tl::TL_writer { std::string gen_int_const(const tl::tl_tree *tree_c, const std::vector &vars) const final; - std::string gen_output_begin() const final; + std::string gen_output_begin(const std::string &additional_imports) const final; + std::string gen_output_begin_once() const final; std::string gen_output_end() const final; + std::string gen_import_declaration(const std::string &name, bool is_system) const final; + + std::string gen_package_suffix() const final; + std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final; std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy, diff --git a/td/generate/tl_writer_jni_cpp.cpp b/td/generate/tl_writer_jni_cpp.cpp index cc531f54de77..ad23c3e912e6 100644 --- a/td/generate/tl_writer_jni_cpp.cpp +++ b/td/generate/tl_writer_jni_cpp.cpp @@ -11,11 +11,11 @@ namespace td { -std::string TD_TL_writer_jni_cpp::gen_output_begin() const { - return TD_TL_writer_cpp::gen_output_begin() + - "\nstatic const char *package_name = \"Call set_package_name\";\n\n" - "void set_package_name(const char *new_package_name) {\n" - " package_name = new_package_name;\n" +std::string TD_TL_writer_jni_cpp::gen_output_begin_once() const { + return TD_TL_writer_cpp::gen_output_begin_once() + + "\nconst char *&get_package_name_ref() {\n" + " static const char *package_name = \"Package name must be initialized first\";\n" + " return package_name;\n" "}\n"; } @@ -437,7 +437,7 @@ std::string TD_TL_writer_jni_cpp::gen_fetch_function_result_begin(const std::str const tl::tl_tree *result) const { return "\n" + class_name + "::ReturnType " + class_name + "::fetch_result(" + parser_name + " &p) {\n" - " if (p == nullptr) return ReturnType();\n" + + " if (p == nullptr) return ReturnType();\n" " return "; } @@ -538,7 +538,7 @@ std::string TD_TL_writer_jni_cpp::gen_basic_java_class_name(std::string name) co } std::string TD_TL_writer_jni_cpp::gen_java_class_name(std::string name) const { - return "(PSLICE() << package_name << \"/TdApi$" + gen_basic_java_class_name(name) + "\").c_str()"; + return "(PSLICE() << get_package_name_ref() << \"/TdApi$" + gen_basic_java_class_name(name) + "\").c_str()"; } std::string TD_TL_writer_jni_cpp::gen_type_signature(const tl::tl_tree_type *tree_type) const { @@ -600,7 +600,7 @@ std::string TD_TL_writer_jni_cpp::gen_additional_function(const std::string &fun std::string new_type_signature = "(PSLICE()"; std::size_t pos = type_signature.find("%PACKAGE_NAME%"); while (pos != std::string::npos) { - new_type_signature += " << \"" + type_signature.substr(0, pos) + "\" << package_name"; + new_type_signature += " << \"" + type_signature.substr(0, pos) + "\" << get_package_name_ref()"; type_signature = type_signature.substr(pos + 14); pos = type_signature.find("%PACKAGE_NAME%"); } diff --git a/td/generate/tl_writer_jni_cpp.h b/td/generate/tl_writer_jni_cpp.h index 7ceda54b9c3c..853af8cd4a39 100644 --- a/td/generate/tl_writer_jni_cpp.h +++ b/td/generate/tl_writer_jni_cpp.h @@ -16,7 +16,7 @@ namespace td { class TD_TL_writer_jni_cpp final : public TD_TL_writer_cpp { - std::string gen_output_begin() const final; + std::string gen_output_begin_once() const final; std::string gen_vector_fetch(std::string field_name, const tl::tl_tree_type *t, const std::vector &vars, int parser_type) const; diff --git a/td/generate/tl_writer_jni_h.cpp b/td/generate/tl_writer_jni_h.cpp index 095cfed523f4..3bfc6c5c7390 100644 --- a/td/generate/tl_writer_jni_h.cpp +++ b/td/generate/tl_writer_jni_h.cpp @@ -58,7 +58,7 @@ std::string TD_TL_writer_jni_h::gen_base_tl_class_name() const { return "Object"; } -std::string TD_TL_writer_jni_h::gen_output_begin() const { +std::string TD_TL_writer_jni_h::gen_output_begin(const std::string &additional_imports) const { std::string ext_include_str; for (auto &it : ext_include) { ext_include_str += "#include " + it + "\n"; @@ -69,62 +69,25 @@ std::string TD_TL_writer_jni_h::gen_output_begin() const { "#include \n" "#include \n\n" "#include \n\n" + - ext_include_str + - "\n" + ext_include_str + "\n" + additional_imports + - "namespace td {\n" + - forward_declaration("TlStorerToString") + + "namespace td {\n" + forward_declaration("TlStorerToString") + "\n" "namespace " + - tl_name + - " {\n\n" - - "using int32 = std::int32_t;\n" - "using int53 = std::int64_t;\n" - "using int64 = std::int64_t;\n\n" - - "using string = " + - string_type + - ";\n\n" - - "using bytes = " + - bytes_type + - ";\n\n" - - "template \n" - "using array = std::vector;\n\n" - - "class " + - gen_base_tl_class_name() + - ";\n" - "using BaseObject = " + - gen_base_tl_class_name() + - ";\n\n" - - "template \n" - "using object_ptr = ::td::tl_object_ptr;\n\n" - "template \n" - "object_ptr make_object(Args &&... args) {\n" - " return object_ptr(new Type(std::forward(args)...));\n" - "}\n\n" - - "template \n" - "object_ptr move_object_as(FromType &&from) {\n" - " return object_ptr(static_cast(from.release()));\n" - "}\n\n" - - "std::string to_string(const BaseObject &value);\n\n" - - "template \n" - "std::string to_string(const object_ptr &value) {\n" - " if (value == nullptr) {\n" - " return \"null\";\n" - " }\n" - "\n" - " return to_string(*value);\n" - "}\n\n" + tl_name + " {\n\n"; +} - "void set_package_name(const char *new_package_name);\n\n"; +std::string TD_TL_writer_jni_h::gen_output_begin_once() const { + std::string result = TD_TL_writer_h::gen_output_begin_once(); + std::string old_base_object = "using BaseObject = ::td::TlObject"; + std::size_t pos = result.find(old_base_object); + assert(pos != std::string::npos); + result.replace(pos, old_base_object.size(), + "class " + gen_base_tl_class_name() + + ";\n" + "using BaseObject = " + + gen_base_tl_class_name()); + return result + "const char *&get_package_name_ref();\n\n"; } std::string TD_TL_writer_jni_h::gen_class_begin(const std::string &class_name, const std::string &base_class_name, @@ -136,7 +99,7 @@ std::string TD_TL_writer_jni_h::gen_class_begin(const std::string &class_name, c " virtual ~" + class_name + "() {\n" - " }\n\n" + + " }\n\n" " virtual void store(JNIEnv *env, jobject &s) const {\n" " }\n\n" " virtual void store(TlStorerToString &s, const char *field_name) const = 0;\n\n" diff --git a/td/generate/tl_writer_jni_h.h b/td/generate/tl_writer_jni_h.h index 78479756a694..2cf0502e5afd 100644 --- a/td/generate/tl_writer_jni_h.h +++ b/td/generate/tl_writer_jni_h.h @@ -32,7 +32,8 @@ class TD_TL_writer_jni_h final : public TD_TL_writer_h { std::string gen_base_type_class_name(int arity) const final; std::string gen_base_tl_class_name() const final; - std::string gen_output_begin() const final; + std::string gen_output_begin(const std::string &additional_imports) const final; + std::string gen_output_begin_once() const final; std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy, const tl::tl_tree *result) const final; diff --git a/td/generate/tl_writer_td.cpp b/td/generate/tl_writer_td.cpp index 537eaa452f1a..ff99fa1de133 100644 --- a/td/generate/tl_writer_td.cpp +++ b/td/generate/tl_writer_td.cpp @@ -63,7 +63,8 @@ bool TD_TL_writer::is_full_constructor_generated(const tl::tl_combinator *t, boo t->name == "encryptedChatWaiting" || t->name == "encryptedChatRequested" || t->name == "encryptedChat" || t->name == "langPackString" || t->name == "langPackStringPluralized" || t->name == "langPackStringDeleted" || t->name == "peerUser" || t->name == "peerChat" || t->name == "updateServiceNotification" || - t->name == "updateNewMessage" || t->name == "updateChannelTooLong" || t->name == "messages.stickerSet"; + t->name == "updateNewMessage" || t->name == "updateChannelTooLong" || t->name == "messages.stickerSet" || + t->name == "updates.differenceSlice"; } int TD_TL_writer::get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const { @@ -116,6 +117,18 @@ std::vector TD_TL_writer::get_storers() const { return storers; } +std::string TD_TL_writer::gen_import_declaration(const std::string &name, bool is_system) const { + if (is_system) { + return "#include <" + name + ">\n"; + } else { + return "#include \"" + name + "\"\n"; + } +} + +std::string TD_TL_writer::gen_package_suffix() const { + return ".h"; +} + std::string TD_TL_writer::gen_base_tl_class_name() const { return base_tl_class_name; } diff --git a/td/generate/tl_writer_td.h b/td/generate/tl_writer_td.h index 7cdbdcb5ab15..bdf74710465a 100644 --- a/td/generate/tl_writer_td.h +++ b/td/generate/tl_writer_td.h @@ -45,6 +45,8 @@ class TD_TL_writer : public tl::TL_writer { std::vector get_parsers() const override; std::vector get_storers() const override; + std::string gen_import_declaration(const std::string &package_name, bool is_system) const override; + std::string gen_package_suffix() const override; std::string gen_base_tl_class_name() const override; std::string gen_base_type_class_name(int arity) const override; std::string gen_base_function_class_name() const override; diff --git a/td/mtproto/AuthData.h b/td/mtproto/AuthData.h index fcf9d2abd1c0..bc2403508dd0 100644 --- a/td/mtproto/AuthData.h +++ b/td/mtproto/AuthData.h @@ -84,23 +84,19 @@ class AuthData { tmp_auth_key_ = std::move(auth_key); } const AuthKey &get_tmp_auth_key() const { - // CHECK(has_tmp_auth_key()); return tmp_auth_key_; } bool was_tmp_auth_key() const { return use_pfs() && !tmp_auth_key_.empty(); } - bool need_tmp_auth_key(double now) const { + bool need_tmp_auth_key(double now, double refresh_margin) const { if (!use_pfs()) { return false; } if (tmp_auth_key_.empty()) { return true; } - if (now > tmp_auth_key_.expires_at() - 60 * 60 * 2 /*2 hours*/) { - return true; - } - if (!has_tmp_auth_key(now)) { + if (now > tmp_auth_key_.expires_at() - refresh_margin) { return true; } return false; @@ -118,7 +114,7 @@ class AuthData { if (tmp_auth_key_.empty()) { return false; } - if (now > tmp_auth_key_.expires_at() - 60 * 60 /*1 hour*/) { + if (now > tmp_auth_key_.expires_at()) { return false; } return true; diff --git a/td/mtproto/CryptoStorer.h b/td/mtproto/CryptoStorer.h index 21514771ae50..1034cd85b2a4 100644 --- a/td/mtproto/CryptoStorer.h +++ b/td/mtproto/CryptoStorer.h @@ -206,12 +206,11 @@ class CryptoImpl { CryptoImpl(const vector &to_send, Slice header, vector &&to_ack, int64 ping_id, int ping_timeout, int max_delay, int max_after, int max_wait, int future_salt_n, vector get_info, vector resend, const vector &cancel, bool destroy_key, AuthData *auth_data, - uint64 *container_id, uint64 *get_info_id, uint64 *resend_id, uint64 *ping_message_id, + uint64 *container_id, uint64 *get_info_message_id, uint64 *resend_message_id, uint64 *ping_message_id, uint64 *parent_message_id) : query_storer_(to_send, header) , ack_empty_(to_ack.empty()) , ack_storer_(!ack_empty_, mtproto_api::msgs_ack(std::move(to_ack)), auth_data) - , ping_storer_(ping_id != 0, mtproto_api::ping_delay_disconnect(ping_id, ping_timeout), auth_data) , http_wait_storer_(max_delay >= 0, mtproto_api::http_wait(max_delay, max_after, max_wait), auth_data) , get_future_salts_storer_(future_salt_n > 0, mtproto_api::get_future_salts(future_salt_n), auth_data) , get_info_not_empty_(!get_info.empty()) @@ -222,6 +221,7 @@ class CryptoImpl { , cancel_cnt_(static_cast(cancel.size())) , cancel_storer_(cancel_not_empty_, cancel, auth_data, true) , destroy_key_storer_(destroy_key, mtproto_api::destroy_auth_key(), auth_data, true) + , ping_storer_(ping_id != 0, mtproto_api::ping_delay_disconnect(ping_id, ping_timeout), auth_data) , tmp_storer_(query_storer_, ack_storer_) , tmp2_storer_(tmp_storer_, http_wait_storer_) , tmp3_storer_(tmp2_storer_, get_future_salts_storer_) @@ -235,11 +235,11 @@ class CryptoImpl { resend_storer_.not_empty() + cancel_cnt_ + destroy_key_storer_.not_empty()) , container_storer_(cnt_, concat_storer_) { CHECK(cnt_ != 0); - if (get_info_storer_.not_empty() && get_info_id) { - *get_info_id = get_info_storer_.get_message_id(); + if (get_info_storer_.not_empty() && get_info_message_id) { + *get_info_message_id = get_info_storer_.get_message_id(); } - if (resend_storer_.not_empty() && resend_id) { - *resend_id = resend_storer_.get_message_id(); + if (resend_storer_.not_empty() && resend_message_id) { + *resend_message_id = resend_storer_.get_message_id(); } if (ping_storer_.not_empty() && ping_message_id) { *ping_message_id = ping_storer_.get_message_id(); @@ -328,7 +328,6 @@ class CryptoImpl { PacketStorer query_storer_; bool ack_empty_; PacketStorer ack_storer_; - PacketStorer ping_storer_; PacketStorer http_wait_storer_; PacketStorer get_future_salts_storer_; bool get_info_not_empty_; @@ -339,6 +338,7 @@ class CryptoImpl { int32 cancel_cnt_; PacketStorer cancel_storer_; PacketStorer destroy_key_storer_; + PacketStorer ping_storer_; ConcatStorer tmp_storer_; ConcatStorer tmp2_storer_; ConcatStorer tmp3_storer_; diff --git a/td/mtproto/HttpTransport.h b/td/mtproto/HttpTransport.h index bbcf6e9b02ec..6be834ad012c 100644 --- a/td/mtproto/HttpTransport.h +++ b/td/mtproto/HttpTransport.h @@ -14,6 +14,7 @@ #include "td/net/HttpReader.h" #include "td/utils/buffer.h" +#include "td/utils/common.h" #include "td/utils/port/detail/PollableFd.h" #include "td/utils/Status.h" diff --git a/td/mtproto/PacketInfo.h b/td/mtproto/PacketInfo.h index c49df9fb5e9a..68bb4142055d 100644 --- a/td/mtproto/PacketInfo.h +++ b/td/mtproto/PacketInfo.h @@ -7,16 +7,13 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/UInt.h" namespace td { namespace mtproto { struct PacketInfo { enum { Common, EndToEnd } type = Common; - uint64 auth_key_id{0}; uint32 message_ack{0}; - UInt128 message_key; uint64 salt{0}; uint64 session_id{0}; @@ -28,7 +25,6 @@ struct PacketInfo { bool is_creator{false}; bool check_mod4{true}; bool use_random_padding{false}; - uint32 size{0}; }; } // namespace mtproto diff --git a/td/mtproto/PingConnection.cpp b/td/mtproto/PingConnection.cpp index a08a1d61c885..3e801144dfd0 100644 --- a/td/mtproto/PingConnection.cpp +++ b/td/mtproto/PingConnection.cpp @@ -122,7 +122,7 @@ class PingConnectionPingPong final void on_server_time_difference_updated(bool force) final { } - void on_session_created(uint64 unique_id, uint64 first_id) final { + void on_new_session_created(uint64 unique_id, uint64 first_message_id) final { } void on_session_failed(Status status) final { } @@ -153,7 +153,7 @@ class PingConnectionPingPong final } void on_message_failed(uint64 id, Status status) final { } - void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) final { + void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size, int32 source) final { } Status on_destroy_auth_key() final { diff --git a/td/mtproto/RawConnection.cpp b/td/mtproto/RawConnection.cpp index fcb3e22a98b2..14e62295e6e4 100644 --- a/td/mtproto/RawConnection.cpp +++ b/td/mtproto/RawConnection.cpp @@ -32,6 +32,10 @@ namespace td { namespace mtproto { +RawConnection::~RawConnection() { + LOG(DEBUG) << "Destroy raw connection " << this; +} + class RawConnectionDefault final : public RawConnection { public: RawConnectionDefault(BufferedFd buffered_socket_fd, TransportType transport_type, @@ -39,6 +43,7 @@ class RawConnectionDefault final : public RawConnection { : socket_fd_(std::move(buffered_socket_fd)) , transport_(create_transport(std::move(transport_type))) , stats_callback_(std::move(stats_callback)) { + LOG(DEBUG) << "Create raw connection " << this; transport_->init(&socket_fd_.input_buffer(), &socket_fd_.output_buffer()); } @@ -56,25 +61,23 @@ class RawConnectionDefault final : public RawConnection { size_t send_crypto(const Storer &storer, uint64 session_id, int64 salt, const AuthKey &auth_key, uint64 quick_ack_token) final { - PacketInfo info; - info.version = 2; - info.no_crypto_flag = false; - info.salt = salt; - info.session_id = session_id; - info.use_random_padding = transport_->use_random_padding(); - - auto packet = BufferWriter{Transport::write(storer, auth_key, &info), transport_->max_prepend_size(), - transport_->max_append_size()}; - Transport::write(storer, auth_key, &info, packet.as_mutable_slice()); + PacketInfo packet_info; + packet_info.version = 2; + packet_info.no_crypto_flag = false; + packet_info.salt = salt; + packet_info.session_id = session_id; + packet_info.use_random_padding = transport_->use_random_padding(); + auto packet = + Transport::write(storer, auth_key, &packet_info, transport_->max_prepend_size(), transport_->max_append_size()); bool use_quick_ack = false; if (quick_ack_token != 0 && transport_->support_quick_ack()) { - CHECK(info.message_ack & (1u << 31)); - auto tmp = quick_ack_to_token_.emplace(info.message_ack, quick_ack_token); + CHECK(packet_info.message_ack & (1u << 31)); + auto tmp = quick_ack_to_token_.emplace(packet_info.message_ack, quick_ack_token); if (tmp.second) { use_quick_ack = true; } else { - LOG(ERROR) << "Quick ack " << info.message_ack << " collision"; + LOG(ERROR) << "Quick ack " << packet_info.message_ack << " collision"; } } @@ -84,15 +87,14 @@ class RawConnectionDefault final : public RawConnection { } uint64 send_no_crypto(const Storer &storer) final { - PacketInfo info; + PacketInfo packet_info; + packet_info.no_crypto_flag = true; + auto packet = Transport::write(storer, AuthKey(), &packet_info, transport_->max_prepend_size(), + transport_->max_append_size()); - info.no_crypto_flag = true; - auto packet = BufferWriter{Transport::write(storer, AuthKey(), &info), transport_->max_prepend_size(), - transport_->max_append_size()}; - Transport::write(storer, AuthKey(), &info, packet.as_mutable_slice()); LOG(INFO) << "Send handshake packet: " << format::as_hex_dump<4>(packet.as_slice()); transport_->write(std::move(packet), false); - return info.message_id; + return packet_info.message_id; } PollableFdInfo &get_poll_info() final { @@ -120,6 +122,7 @@ class RawConnectionDefault final : public RawConnection { } void close() final { + LOG(DEBUG) << "Close raw connection " << this; transport_.reset(); socket_fd_.close(); } @@ -184,10 +187,10 @@ class RawConnectionDefault final : public RawConnection { << old_pointer << ' ' << packet.as_slice().ubegin() << ' ' << BufferSlice(0).as_slice().ubegin() << ' ' << packet.size() << ' ' << wait_size << ' ' << quick_ack; - PacketInfo info; - info.version = 2; + PacketInfo packet_info; + packet_info.version = 2; - TRY_RESULT(read_result, Transport::read(packet.as_mutable_slice(), auth_key, &info)); + TRY_RESULT(read_result, Transport::read(packet.as_mutable_slice(), auth_key, &packet_info)); switch (read_result.type()) { case Transport::ReadResult::Quickack: TRY_STATUS(on_quick_ack(read_result.quick_ack(), callback)); @@ -203,7 +206,7 @@ class RawConnectionDefault final : public RawConnection { } } - TRY_STATUS(callback.on_raw_packet(info, packet.from_slice(read_result.packet()))); + TRY_STATUS(callback.on_raw_packet(packet_info, packet.from_slice(read_result.packet()))); break; case Transport::ReadResult::Nop: break; @@ -280,6 +283,7 @@ class RawConnectionHttp final : public RawConnection { public: RawConnectionHttp(IPAddress ip_address, unique_ptr stats_callback) : ip_address_(std::move(ip_address)), stats_callback_(std::move(stats_callback)) { + LOG(DEBUG) << "Create raw connection " << this; answers_ = std::make_shared>>(); answers_->init(); } @@ -298,15 +302,13 @@ class RawConnectionHttp final : public RawConnection { size_t send_crypto(const Storer &storer, uint64 session_id, int64 salt, const AuthKey &auth_key, uint64 quick_ack_token) final { - PacketInfo info; - info.version = 2; - info.no_crypto_flag = false; - info.salt = salt; - info.session_id = session_id; - info.use_random_padding = false; - - auto packet = BufferWriter{Transport::write(storer, auth_key, &info), 0, 0}; - Transport::write(storer, auth_key, &info, packet.as_mutable_slice()); + PacketInfo packet_info; + packet_info.version = 2; + packet_info.no_crypto_flag = false; + packet_info.salt = salt; + packet_info.session_id = session_id; + packet_info.use_random_padding = false; + auto packet = Transport::write(storer, auth_key, &packet_info); auto packet_size = packet.size(); send_packet(packet.as_buffer_slice()); @@ -314,14 +316,13 @@ class RawConnectionHttp final : public RawConnection { } uint64 send_no_crypto(const Storer &storer) final { - PacketInfo info; + PacketInfo packet_info; + packet_info.no_crypto_flag = true; + auto packet = Transport::write(storer, AuthKey(), &packet_info); - info.no_crypto_flag = true; - auto packet = BufferWriter{Transport::write(storer, AuthKey(), &info), 0, 0}; - Transport::write(storer, AuthKey(), &info, packet.as_mutable_slice()); LOG(INFO) << "Send handshake packet: " << format::as_hex_dump<4>(packet.as_slice()); send_packet(packet.as_buffer_slice()); - return info.message_id; + return packet_info.message_id; } PollableFdInfo &get_poll_info() final { @@ -349,6 +350,7 @@ class RawConnectionHttp final : public RawConnection { } void close() final { + LOG(DEBUG) << "Close raw connection " << this; } PublicFields &extra() final { @@ -401,10 +403,10 @@ class RawConnectionHttp final : public RawConnection { CHECK(mode_ == Receive); mode_ = Send; - PacketInfo info; - info.version = 2; + PacketInfo packet_info; + packet_info.version = 2; - TRY_RESULT(read_result, Transport::read(packet.as_mutable_slice(), auth_key, &info)); + TRY_RESULT(read_result, Transport::read(packet.as_mutable_slice(), auth_key, &packet_info)); switch (read_result.type()) { case Transport::ReadResult::Quickack: { break; @@ -421,7 +423,7 @@ class RawConnectionHttp final : public RawConnection { } } - TRY_STATUS(callback.on_raw_packet(info, packet.from_slice(read_result.packet()))); + TRY_STATUS(callback.on_raw_packet(packet_info, packet.from_slice(read_result.packet()))); break; } case Transport::ReadResult::Nop: diff --git a/td/mtproto/RawConnection.h b/td/mtproto/RawConnection.h index a2cafff1faa4..5ac928ee2d62 100644 --- a/td/mtproto/RawConnection.h +++ b/td/mtproto/RawConnection.h @@ -39,7 +39,7 @@ class RawConnection { RawConnection() = default; RawConnection(const RawConnection &) = delete; RawConnection &operator=(const RawConnection &) = delete; - virtual ~RawConnection() = default; + virtual ~RawConnection(); static unique_ptr create(IPAddress ip_address, BufferedFd buffered_socket_fd, TransportType transport_type, unique_ptr stats_callback); @@ -61,7 +61,7 @@ class RawConnection { Callback(const Callback &) = delete; Callback &operator=(const Callback &) = delete; virtual ~Callback() = default; - virtual Status on_raw_packet(const PacketInfo &info, BufferSlice packet) = 0; + virtual Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) = 0; virtual Status on_quick_ack(uint64 quick_ack_token) { return Status::Error("Quick acknowledgements are unsupported by the callback"); } diff --git a/td/mtproto/SessionConnection.cpp b/td/mtproto/SessionConnection.cpp index cfe796072166..37d0a75b878c 100644 --- a/td/mtproto/SessionConnection.cpp +++ b/td/mtproto/SessionConnection.cpp @@ -171,6 +171,10 @@ namespace mtproto { * */ +inline StringBuilder &operator<<(StringBuilder &string_builder, const SessionConnection::MsgInfo &info) { + return string_builder << "[msg_id:" << format::as_hex(info.message_id) << "][seq_no:" << info.seq_no << ']'; +} + unique_ptr SessionConnection::move_as_raw_connection() { was_moved_ = true; return std::move(raw_connection_); @@ -219,6 +223,7 @@ Status SessionConnection::on_packet_container(const MsgInfo &info, Slice packet) if (parser.get_error()) { return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_container: " << parser.get_error()); } + VLOG(mtproto) << "Receive container " << format::as_hex(container_id_) << " of size " << size; for (int i = 0; i < size; i++) { TRY_STATUS(parse_packet(parser)); } @@ -238,9 +243,10 @@ Status SessionConnection::on_packet_rpc_result(const MsgInfo &info, Slice packet return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_result: " << parser.get_error()); } if (req_msg_id == 0) { - LOG(ERROR) << "Receive an update in rpc_result: message_id = " << info.message_id << ", seq_no = " << info.seq_no; + LOG(ERROR) << "Receive an update in rpc_result " << info; return Status::Error("Receive an update in rpc_result"); } + VLOG(mtproto) << "Receive result for request " << format::as_hex(req_msg_id) << " with " << info; if (info.message_id < req_msg_id - (static_cast(15) << 32)) { reset_server_time_difference(info.message_id); @@ -252,8 +258,6 @@ Status SessionConnection::on_packet_rpc_result(const MsgInfo &info, Slice packet if (parser.get_error()) { return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_error: " << parser.get_error()); } - VLOG(mtproto) << "ERROR " << tag("code", rpc_error.error_code_) << tag("message", rpc_error.error_message_) - << tag("req_msg_id", req_msg_id); callback_->on_message_result_error(req_msg_id, rpc_error.error_code_, rpc_error.error_message_.str()); return Status::OK(); } @@ -280,33 +284,37 @@ Status SessionConnection::on_packet(const MsgInfo &info, const T &packet) { } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_ok &destroy_auth_key) { + VLOG(mtproto) << "Receive destroy_auth_key_ok with " << info; return on_destroy_auth_key(destroy_auth_key); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_none &destroy_auth_key) { + VLOG(mtproto) << "Receive destroy_auth_key_none with " << info; return on_destroy_auth_key(destroy_auth_key); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_fail &destroy_auth_key) { + VLOG(mtproto) << "Receive destroy_auth_key_fail with " << info; return on_destroy_auth_key(destroy_auth_key); } Status SessionConnection::on_destroy_auth_key(const mtproto_api::DestroyAuthKeyRes &destroy_auth_key) { LOG_CHECK(need_destroy_auth_key_) << static_cast(mode_); - LOG(INFO) << to_string(destroy_auth_key); return callback_->on_destroy_auth_key(); } -Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::rpc_error &rpc_error) { - LOG(ERROR) << "Receive rpc_error as update: [" << rpc_error.error_code_ << "][" << rpc_error.error_message_ << "]"; - return Status::OK(); -} - Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::new_session_created &new_session_created) { - VLOG(mtproto) << "NEW_SESSION_CREATED: [first_msg_id:" << format::as_hex(new_session_created.first_msg_id_) - << "] [unique_id:" << format::as_hex(new_session_created.unique_id_) - << "] [server_salt:" << format::as_hex(new_session_created.server_salt_) << "]"; - callback_->on_session_created(new_session_created.unique_id_, new_session_created.first_msg_id_); + auto first_message_id = new_session_created.first_msg_id_; + VLOG(mtproto) << "Receive new_session_created with " << info << ": [first_msg_id:" << format::as_hex(first_message_id) + << "] [unique_id:" << format::as_hex(new_session_created.unique_id_) << ']'; + + auto it = service_queries_.find(first_message_id); + if (it != service_queries_.end()) { + first_message_id = it->second.container_message_id; + LOG(INFO) << "Update first_message_id to container's " << format::as_hex(first_message_id); + } + + callback_->on_new_session_created(new_session_created.unique_id_, first_message_id); return Status::OK(); } @@ -330,68 +338,53 @@ Status SessionConnection::on_packet(const MsgInfo &info, }; Slice common = ". BUG! CALL FOR A DEVELOPER! Session will be closed"; switch (bad_msg_notification.error_code_) { - case MsgIdTooLow: { + case MsgIdTooLow: LOG(WARNING) << bad_info << ": MessageId is too low. Message will be re-sent"; // time will be updated automagically on_message_failed(bad_info.message_id, Status::Error("MessageId is too low")); break; - } - case MsgIdTooHigh: { + case MsgIdTooHigh: LOG(WARNING) << bad_info << ": MessageId is too high. Session will be closed"; // All this queries will be re-sent by parent to_send_.clear(); reset_server_time_difference(info.message_id); callback_->on_session_failed(Status::Error("MessageId is too high")); return Status::Error("MessageId is too high"); - } - case MsgIdMod4: { + case MsgIdMod4: LOG(ERROR) << bad_info << ": MessageId is not divisible by 4" << common; return Status::Error("MessageId is not divisible by 4"); - } - case MsgIdCollision: { + case MsgIdCollision: LOG(ERROR) << bad_info << ": Container and older message MessageId collision" << common; return Status::Error("Container and older message MessageId collision"); - } - - case MsgIdTooOld: { + case MsgIdTooOld: LOG(WARNING) << bad_info << ": MessageId is too old. Message will be re-sent"; on_message_failed(bad_info.message_id, Status::Error("MessageId is too old")); break; - } - - case SeqNoTooLow: { + case SeqNoTooLow: LOG(ERROR) << bad_info << ": SeqNo is too low" << common; return Status::Error("SeqNo is too low"); - } - case SeqNoTooHigh: { + case SeqNoTooHigh: LOG(ERROR) << bad_info << ": SeqNo is too high" << common; return Status::Error("SeqNo is too high"); - } - case SeqNoNotEven: { + case SeqNoNotEven: LOG(ERROR) << bad_info << ": SeqNo is not even for an irrelevant message" << common; return Status::Error("SeqNo is not even for an irrelevant message"); - } - case SeqNoNotOdd: { + case SeqNoNotOdd: LOG(ERROR) << bad_info << ": SeqNo is not odd for a relevant message" << common; return Status::Error("SeqNo is not odd for a relevant message"); - } - - case InvalidContainer: { + case InvalidContainer: LOG(ERROR) << bad_info << ": Invalid Container" << common; return Status::Error("Invalid Container"); - } - - default: { + default: LOG(ERROR) << bad_info << ": Unknown error [code:" << bad_msg_notification.error_code_ << "]" << common; return Status::Error("Unknown error code"); - } } return Status::OK(); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::bad_server_salt &bad_server_salt) { MsgInfo bad_info{static_cast(bad_server_salt.bad_msg_id_), bad_server_salt.bad_msg_seqno_, 0}; - VLOG(mtproto) << "BAD_SERVER_SALT: " << bad_info; + VLOG(mtproto) << "Receive bad_server_salt with " << info << ": " << bad_info; auth_data_->set_server_salt(bad_server_salt.new_server_salt_, Time::now_cached()); callback_->on_server_salt_updated(); @@ -400,6 +393,7 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::bad_ } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msgs_ack &msgs_ack) { + VLOG(mtproto) << "Receive msgs_ack with " << info << ": " << msgs_ack.msg_ids_; for (auto id : msgs_ack.msg_ids_) { callback_->on_message_ack(id); } @@ -413,7 +407,7 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::gzip } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::pong &pong) { - VLOG(mtproto) << "PONG"; + VLOG(mtproto) << "Receive pong with " << info; if (info.message_id < static_cast(pong.msg_id_) - (static_cast(15) << 32)) { reset_server_time_difference(info.message_id); } @@ -423,7 +417,6 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::pong } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::future_salts &salts) { - VLOG(mtproto) << "FUTURE_SALTS"; vector new_salts; for (auto &it : salts.salts_) { new_salts.push_back( @@ -431,7 +424,7 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::futu } auto now = Time::now_cached(); auth_data_->set_future_salts(new_salts, now); - VLOG(mtproto) << "Have server salts: is_valid = " << auth_data_->is_server_salt_valid(now) + VLOG(mtproto) << "Receive future_salts with " << info << ": is_valid = " << auth_data_->is_server_salt_valid(now) << ", has_salt = " << auth_data_->has_salt(now) << ", need_future_salts = " << auth_data_->need_future_salts(now); callback_->on_server_salt_updated(); @@ -446,7 +439,7 @@ Status SessionConnection::on_msgs_state_info(const vector &message_ids, S } size_t i = 0; for (auto message_id : message_ids) { - callback_->on_message_info(static_cast(message_id), info[i], 0, 0); + callback_->on_message_info(static_cast(message_id), info[i], 0, 0, 1); i++; } return Status::OK(); @@ -463,22 +456,26 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msgs if (query.type != ServiceQuery::GetStateInfo) { return Status::Error("Receive msgs_state_info in response not to GetStateInfo"); } + VLOG(mtproto) << "Receive msgs_state_info with " << info; return on_msgs_state_info(query.message_ids, msgs_state_info.info_); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msgs_all_info &msgs_all_info) { + VLOG(mtproto) << "Receive msgs_all_info with " << info; return on_msgs_state_info(msgs_all_info.msg_ids_, msgs_all_info.info_); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msg_detailed_info &msg_detailed_info) { + VLOG(mtproto) << "Receive msg_detailed_info with " << info; callback_->on_message_info(msg_detailed_info.msg_id_, msg_detailed_info.status_, msg_detailed_info.answer_msg_id_, - msg_detailed_info.bytes_); + msg_detailed_info.bytes_, 2); return Status::OK(); } Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msg_new_detailed_info &msg_new_detailed_info) { - callback_->on_message_info(0, 0, msg_new_detailed_info.answer_msg_id_, msg_new_detailed_info.bytes_); + VLOG(mtproto) << "Receive msg_new_detailed_info with " << info; + callback_->on_message_info(0, 0, msg_new_detailed_info.answer_msg_id_, msg_new_detailed_info.bytes_, 0); return Status::OK(); } @@ -518,10 +515,10 @@ Status SessionConnection::on_slice_packet(const MsgInfo &info, Slice packet) { } auto get_update_description = [&] { - return PSTRING() << "update from " << get_name() << " active for " << (Time::now() - created_at_) - << " seconds in container " << container_id_ << " from session " << auth_data_->get_session_id() - << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_ - << ", seq_no = " << info.seq_no << " and original size = " << info.size; + return PSTRING() << "update from " << get_name() << " with auth key " << auth_data_->get_auth_key().id() + << " active for " << (Time::now() - created_at_) << " seconds in container " << container_id_ + << " from session " << auth_data_->get_session_id() << " with " << info + << ", main_message_id = " << main_message_id_ << " and original size = " << info.size; }; // It is an update... I hope. @@ -551,7 +548,7 @@ Status SessionConnection::parse_packet(TlParser &parser) { return on_slice_packet(info, packet); } -Status SessionConnection::on_main_packet(const PacketInfo &info, Slice packet) { +Status SessionConnection::on_main_packet(const PacketInfo &packet_info, Slice packet) { // Update pong here too. Real pong can be delayed by many big packets last_pong_at_ = Time::now_cached(); real_last_pong_at_ = last_pong_at_; @@ -561,9 +558,11 @@ Status SessionConnection::on_main_packet(const PacketInfo &info, Slice packet) { callback_->on_connected(); } - VLOG(raw_mtproto) << "Receive packet of size " << packet.size() << " from session " << format::as_hex(info.session_id) - << ":" << format::as_hex_dump<4>(packet); - if (info.no_crypto_flag) { + VLOG(raw_mtproto) << "Receive packet of size " << packet.size() << ':' << format::as_hex_dump<4>(packet); + VLOG(mtproto) << "Receive packet with seq_no " << packet_info.seq_no << " and msg_id " + << format::as_hex(packet_info.message_id) << " of size " << packet.size(); + + if (packet_info.no_crypto_flag) { return Status::Error("Unencrypted packet"); } @@ -608,18 +607,16 @@ void SessionConnection::on_message_failed_inner(uint64 id) { service_queries_.erase(it); switch (query.type) { - case ServiceQuery::ResendAnswer: { + case ServiceQuery::ResendAnswer: for (auto message_id : query.message_ids) { resend_answer(message_id); } break; - } - case ServiceQuery::GetStateInfo: { + case ServiceQuery::GetStateInfo: for (auto message_id : query.message_ids) { get_state_info(message_id); } break; - } default: UNREACHABLE(); } @@ -691,27 +688,27 @@ Status SessionConnection::before_write() { return Status::OK(); } -Status SessionConnection::on_raw_packet(const PacketInfo &info, BufferSlice packet) { +Status SessionConnection::on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) { auto old_main_message_id = main_message_id_; - main_message_id_ = info.message_id; + main_message_id_ = packet_info.message_id; SCOPE_EXIT { main_message_id_ = old_main_message_id; }; - if (info.no_crypto_flag) { + if (packet_info.no_crypto_flag) { return Status::Error("Unexpected unencrypted packet"); } bool time_difference_was_updated = false; - auto status = - auth_data_->check_packet(info.session_id, info.message_id, Time::now_cached(), time_difference_was_updated); + auto status = auth_data_->check_packet(packet_info.session_id, packet_info.message_id, Time::now_cached(), + time_difference_was_updated); if (time_difference_was_updated) { callback_->on_server_time_difference_updated(false); } if (status.is_error()) { if (status.code() == 1) { - LOG(INFO) << "Packet ignored: " << status; - send_ack(info.message_id); + LOG(INFO) << "Packet is ignored: " << status; + send_ack(packet_info.message_id); return Status::OK(); } else if (status.code() == 2) { LOG(WARNING) << "Receive too old packet: " << status; @@ -723,7 +720,7 @@ Status SessionConnection::on_raw_packet(const PacketInfo &info, BufferSlice pack } auto guard = set_buffer_slice(&packet); - TRY_STATUS(on_main_packet(info, packet.as_slice())); + TRY_STATUS(on_main_packet(packet_info, packet.as_slice())); return Status::OK(); } @@ -804,8 +801,9 @@ Result SessionConnection::send_query(BufferSlice buffer, bool gzip_flag, } to_send_.push_back( MtprotoQuery{message_id, seq_no, std::move(buffer), gzip_flag, std::move(invoke_after_ids), use_quick_ack}); - VLOG(mtproto) << "Invoke query " << message_id << " of size " << to_send_.back().packet.size() << " with seq_no " - << seq_no << " after " << invoke_after_ids << (use_quick_ack ? " with quick ack" : ""); + VLOG(mtproto) << "Invoke query with msg_id " << format::as_hex(message_id) << " and seq_no " << seq_no << " of size " + << to_send_.back().packet.size() << " after " << invoke_after_ids + << (use_quick_ack ? " with quick ack" : ""); return message_id; } @@ -849,15 +847,13 @@ std::pair SessionConnection::encrypted_bind(int64 perm_key, auth_data_->next_message_id(Time::now_cached()), 0, object_packet.as_buffer_slice(), false, {}, false}; PacketStorer query_storer(query, Slice()); - PacketInfo info; - info.version = 1; - info.no_crypto_flag = false; - info.salt = Random::secure_int64(); - info.session_id = Random::secure_int64(); - const AuthKey &main_auth_key = auth_data_->get_main_auth_key(); - auto packet = BufferWriter{Transport::write(query_storer, main_auth_key, &info), 0, 0}; - Transport::write(query_storer, main_auth_key, &info, packet.as_mutable_slice()); + PacketInfo packet_info; + packet_info.version = 1; + packet_info.no_crypto_flag = false; + packet_info.salt = Random::secure_int64(); + packet_info.session_id = Random::secure_int64(); + auto packet = Transport::write(query_storer, main_auth_key, &packet_info); return std::make_pair(query.message_id, packet.as_buffer_slice()); } @@ -958,11 +954,11 @@ void SessionConnection::flush_packet() { sent_destroy_auth_key_ |= destroy_auth_key; - VLOG(mtproto) << "Sent packet: " << tag("query_count", queries.size()) << tag("ack_cnt", to_ack_.size()) + VLOG(mtproto) << "Sent packet: " << tag("query_count", queries.size()) << tag("ack_count", to_ack_.size()) << tag("ping", ping_id != 0) << tag("http_wait", max_delay >= 0) << tag("future_salt", future_salt_n > 0) << tag("get_info", to_get_state_info_.size()) << tag("resend", to_resend_answer_.size()) << tag("cancel", to_cancel_answer_.size()) - << tag("destroy_key", destroy_auth_key) << tag("auth_id", auth_data_->get_auth_key().id()); + << tag("destroy_key", destroy_auth_key) << tag("auth_key_id", auth_data_->get_auth_key().id()); auto cut_tail = [](vector &v, size_t size, Slice name) { if (size >= v.size()) { @@ -978,11 +974,11 @@ void SessionConnection::flush_packet() { // no more than 8192 message identifiers per container.. auto to_resend_answer = cut_tail(to_resend_answer_, 8192, "resend_answer"); - uint64 resend_answer_id = 0; + uint64 resend_answer_message_id = 0; CHECK(queries.size() <= 1020); auto to_cancel_answer = cut_tail(to_cancel_answer_, 1020 - queries.size(), "cancel_answer"); auto to_get_state_info = cut_tail(to_get_state_info_, 8192, "get_state_info"); - uint64 get_state_info_id = 0; + uint64 get_state_info_message_id = 0; auto to_ack = cut_tail(to_ack_, 8192, "ack"); uint64 ping_message_id = 0; @@ -992,21 +988,23 @@ void SessionConnection::flush_packet() { { // LOG(ERROR) << (auth_data_->get_header().empty() ? '-' : '+'); uint64 parent_message_id = 0; - auto storer = PacketStorer(queries, auth_data_->get_header(), std::move(to_ack), ping_id, - static_cast(ping_disconnect_delay() + 2.0), max_delay, max_after, - max_wait, future_salt_n, to_get_state_info, to_resend_answer, - to_cancel_answer, destroy_auth_key, auth_data_, &container_id, - &get_state_info_id, &resend_answer_id, &ping_message_id, &parent_message_id); + auto storer = PacketStorer( + queries, auth_data_->get_header(), std::move(to_ack), ping_id, static_cast(ping_disconnect_delay() + 2.0), + max_delay, max_after, max_wait, future_salt_n, to_get_state_info, to_resend_answer, to_cancel_answer, + destroy_auth_key, auth_data_, &container_id, &get_state_info_message_id, &resend_answer_message_id, + &ping_message_id, &parent_message_id); auto quick_ack_token = use_quick_ack ? parent_message_id : 0; send_crypto(storer, quick_ack_token); } - if (resend_answer_id) { - service_queries_.emplace(resend_answer_id, ServiceQuery{ServiceQuery::ResendAnswer, std::move(to_resend_answer)}); + if (resend_answer_message_id) { + service_queries_.emplace(resend_answer_message_id, + ServiceQuery{ServiceQuery::ResendAnswer, container_id, std::move(to_resend_answer)}); } - if (get_state_info_id) { - service_queries_.emplace(get_state_info_id, ServiceQuery{ServiceQuery::GetStateInfo, std::move(to_get_state_info)}); + if (get_state_info_message_id) { + service_queries_.emplace(get_state_info_message_id, + ServiceQuery{ServiceQuery::GetStateInfo, container_id, std::move(to_get_state_info)}); } if (ping_id != 0) { last_ping_container_id_ = container_id; @@ -1023,11 +1021,11 @@ void SessionConnection::flush_packet() { // So I will re-ask salt if have no answer in 60 second. callback_->on_container_sent(container_id, std::move(message_ids)); - if (resend_answer_id) { - container_to_service_msg_[container_id].push_back(resend_answer_id); + if (resend_answer_message_id) { + container_to_service_msg_[container_id].push_back(resend_answer_message_id); } - if (get_state_info_id) { - container_to_service_msg_[container_id].push_back(get_state_info_id); + if (get_state_info_message_id) { + container_to_service_msg_[container_id].push_back(get_state_info_message_id); } } @@ -1062,8 +1060,8 @@ Status SessionConnection::do_flush() { auto result = raw_connection_->flush(auth_data_->get_auth_key(), *this); auto elapsed_time = Time::now() - start_time; if (elapsed_time >= 0.1) { - LOG(ERROR) << "RawConnection::flush took " << elapsed_time << " seconds, written " << last_write_size_ - << " bytes, read " << last_read_size_ << " bytes and returned " << result; + LOG(WARNING) << "RawConnection::flush took " << elapsed_time << " seconds, written " << last_write_size_ + << " bytes, read " << last_read_size_ << " bytes and returned " << result; } if (result.is_error()) { return result; diff --git a/td/mtproto/SessionConnection.h b/td/mtproto/SessionConnection.h index 0452b86e38d2..1e9c00cde1db 100644 --- a/td/mtproto/SessionConnection.h +++ b/td/mtproto/SessionConnection.h @@ -11,8 +11,8 @@ #include "td/mtproto/RawConnection.h" #include "td/utils/buffer.h" +#include "td/utils/common.h" #include "td/utils/FlatHashMap.h" -#include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/Named.h" #include "td/utils/port/detail/PollableFd.h" @@ -30,7 +30,6 @@ namespace td { extern int VERBOSITY_NAME(mtproto); namespace mtproto_api { -class rpc_error; class new_session_created; class bad_msg_notification; class bad_server_salt; @@ -52,17 +51,6 @@ namespace mtproto { class AuthData; -struct MsgInfo { - uint64 message_id; - int32 seq_no; - size_t size; -}; - -inline StringBuilder &operator<<(StringBuilder &string_builder, const MsgInfo &info) { - return string_builder << "[msg_id:" << format::as_hex(info.message_id) << "] [seq_no:" << format::as_hex(info.seq_no) - << "]"; -} - class SessionConnection final : public Named , private RawConnection::Callback { @@ -106,7 +94,7 @@ class SessionConnection final virtual void on_server_salt_updated() = 0; virtual void on_server_time_difference_updated(bool force) = 0; - virtual void on_session_created(uint64 unique_id, uint64 first_id) = 0; + virtual void on_new_session_created(uint64 unique_id, uint64 first_message_id) = 0; virtual void on_session_failed(Status status) = 0; virtual void on_container_sent(uint64 container_id, vector msgs_id) = 0; @@ -118,7 +106,7 @@ class SessionConnection final virtual Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) = 0; virtual void on_message_result_error(uint64 id, int code, string message) = 0; virtual void on_message_failed(uint64 id, Status status) = 0; - virtual void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) = 0; + virtual void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size, int32 source) = 0; virtual Status on_destroy_auth_key() = 0; }; @@ -133,6 +121,14 @@ class SessionConnection final static constexpr double QUERY_DELAY = 0.001; // 0.001s static constexpr double RESEND_ANSWER_DELAY = 0.001; // 0.001s + struct MsgInfo { + uint64 message_id; + int32 seq_no; + size_t size; + }; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MsgInfo &info); + bool online_flag_ = false; bool is_main_ = false; bool was_moved_ = false; @@ -169,6 +165,7 @@ class SessionConnection final struct ServiceQuery { enum Type { GetStateInfo, ResendAnswer } type; + uint64 container_message_id; vector message_ids; }; vector to_resend_answer_; @@ -231,7 +228,6 @@ class SessionConnection final template Status on_packet(const MsgInfo &info, const T &packet) TD_WARN_UNUSED_RESULT; - Status on_packet(const MsgInfo &info, const mtproto_api::rpc_error &rpc_error) TD_WARN_UNUSED_RESULT; Status on_packet(const MsgInfo &info, const mtproto_api::new_session_created &new_session_created) TD_WARN_UNUSED_RESULT; Status on_packet(const MsgInfo &info, @@ -256,7 +252,7 @@ class SessionConnection final Status on_destroy_auth_key(const mtproto_api::DestroyAuthKeyRes &destroy_auth_key); Status on_slice_packet(const MsgInfo &info, Slice packet) TD_WARN_UNUSED_RESULT; - Status on_main_packet(const PacketInfo &info, Slice packet) TD_WARN_UNUSED_RESULT; + Status on_main_packet(const PacketInfo &packet_info, Slice packet) TD_WARN_UNUSED_RESULT; void on_message_failed(uint64 id, Status status); void on_message_failed_inner(uint64 id); @@ -274,7 +270,7 @@ class SessionConnection final Status do_flush() TD_WARN_UNUSED_RESULT; Status before_write() final TD_WARN_UNUSED_RESULT; - Status on_raw_packet(const PacketInfo &info, BufferSlice packet) final; + Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) final; Status on_quick_ack(uint64 quick_ack_token) final; void on_read(size_t size) final; }; diff --git a/td/mtproto/TlsInit.h b/td/mtproto/TlsInit.h index f5183cf01d64..149788574b73 100644 --- a/td/mtproto/TlsInit.h +++ b/td/mtproto/TlsInit.h @@ -10,6 +10,7 @@ #include "td/actor/actor.h" +#include "td/utils/common.h" #include "td/utils/port/IPAddress.h" #include "td/utils/port/SocketFd.h" #include "td/utils/Slice.h" diff --git a/td/mtproto/Transport.cpp b/td/mtproto/Transport.cpp index ef0440ef6fe5..c3ef707e9254 100644 --- a/td/mtproto/Transport.cpp +++ b/td/mtproto/Transport.cpp @@ -13,7 +13,6 @@ #include "td/utils/crypto.h" #include "td/utils/format.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" @@ -186,26 +185,21 @@ size_t do_calc_crypto_size2_rand(size_t data_size, size_t enc_size, size_t raw_s } // namespace template -size_t Transport::calc_crypto_size2(size_t data_size, PacketInfo *info) { - if (info->size != 0) { - return info->size; - } - +size_t Transport::calc_crypto_size2(size_t data_size, PacketInfo *packet_info) { size_t enc_size = HeaderT::encrypted_header_size(); size_t raw_size = sizeof(HeaderT) - enc_size; - if (info->use_random_padding) { - info->size = narrow_cast(do_calc_crypto_size2_rand(data_size, enc_size, raw_size)); + if (packet_info->use_random_padding) { + return do_calc_crypto_size2_rand(data_size, enc_size, raw_size); } else { - info->size = narrow_cast(do_calc_crypto_size2_basic(data_size, enc_size, raw_size)); + return do_calc_crypto_size2_basic(data_size, enc_size, raw_size); } - return info->size; } size_t Transport::calc_no_crypto_size(size_t data_size) { return sizeof(NoCryptoHeader) + data_size; } -Status Transport::read_no_crypto(MutableSlice message, PacketInfo *info, MutableSlice *data) { +Status Transport::read_no_crypto(MutableSlice message, PacketInfo *packet_info, MutableSlice *data) { if (message.size() < sizeof(NoCryptoHeader)) { return Status::Error(PSLICE() << "Invalid MTProto message: too small [message.size() = " << message.size() << "] < [sizeof(NoCryptoHeader) = " << sizeof(NoCryptoHeader) << "]"); @@ -218,7 +212,7 @@ Status Transport::read_no_crypto(MutableSlice message, PacketInfo *info, Mutable template Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &auth_key, HeaderT **header_ptr, - PrefixT **prefix_ptr, MutableSlice *data, PacketInfo *info) { + PrefixT **prefix_ptr, MutableSlice *data, PacketInfo *packet_info) { if (message.size() < sizeof(HeaderT)) { return Status::Error(PSLICE() << "Invalid MTProto message: too small [message.size() = " << message.size() << "] < [sizeof(HeaderT) = " << sizeof(HeaderT) << "]"); @@ -237,7 +231,7 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a UInt256 aes_key; UInt256 aes_iv; - if (info->version == 1) { + if (packet_info->version == 1) { KDF(auth_key.key(), header->message_key, X, &aes_key, &aes_iv); } else { KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv); @@ -256,14 +250,14 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a bool is_length_bad = false; UInt128 real_message_key; - if (info->version == 1) { - is_length_bad |= info->check_mod4 && prefix->message_data_length % 4 != 0; + if (packet_info->version == 1) { + is_length_bad |= packet_info->check_mod4 && prefix->message_data_length % 4 != 0; auto expected_size = calc_crypto_size(data_size); is_length_bad |= expected_size != message.size(); auto check_size = data_size * (1 - is_length_bad) + tail_size * is_length_bad; - std::tie(info->message_ack, real_message_key) = calc_message_ack_and_key(*header, check_size); + std::tie(packet_info->message_ack, real_message_key) = calc_message_ack_and_key(*header, check_size); } else { - std::tie(info->message_ack, real_message_key) = calc_message_key2(auth_key, X, to_decrypt); + std::tie(packet_info->message_ack, real_message_key) = calc_message_key2(auth_key, X, to_decrypt); } int is_key_bad = false; @@ -276,8 +270,8 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a << "] [expected = " << format::as_hex_dump(real_message_key) << "]"); } - if (info->version == 2) { - if (info->check_mod4 && prefix->message_data_length % 4 != 0) { + if (packet_info->version == 2) { + if (packet_info->check_mod4 && prefix->message_data_length % 4 != 0) { return Status::Error(PSLICE() << "Invalid MTProto message: invalid length (not divisible by four)" << tag("total_size", message.size()) << tag("message_data_length", prefix->message_data_length)); @@ -304,125 +298,113 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a return Status::OK(); } -Status Transport::read_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data) { +Status Transport::read_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *packet_info, + MutableSlice *data) { CryptoHeader *header = nullptr; CryptoPrefix *prefix = nullptr; - TRY_STATUS(read_crypto_impl(8, message, auth_key, &header, &prefix, data, info)); + TRY_STATUS(read_crypto_impl(8, message, auth_key, &header, &prefix, data, packet_info)); CHECK(header != nullptr); CHECK(prefix != nullptr); - CHECK(info != nullptr); - info->type = PacketInfo::Common; - info->salt = header->salt; - info->session_id = header->session_id; - info->message_id = prefix->message_id; - info->seq_no = prefix->seq_no; + CHECK(packet_info != nullptr); + packet_info->type = PacketInfo::Common; + packet_info->salt = header->salt; + packet_info->session_id = header->session_id; + packet_info->message_id = prefix->message_id; + packet_info->seq_no = prefix->seq_no; return Status::OK(); } -Status Transport::read_e2e_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data) { +Status Transport::read_e2e_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *packet_info, + MutableSlice *data) { EndToEndHeader *header = nullptr; EndToEndPrefix *prefix = nullptr; - TRY_STATUS(read_crypto_impl(info->is_creator && info->version != 1 ? 8 : 0, message, auth_key, &header, &prefix, data, - info)); + TRY_STATUS(read_crypto_impl(packet_info->is_creator && packet_info->version != 1 ? 8 : 0, message, auth_key, &header, + &prefix, data, packet_info)); CHECK(header != nullptr); CHECK(prefix != nullptr); - CHECK(info != nullptr); - info->type = PacketInfo::EndToEnd; + CHECK(packet_info != nullptr); + packet_info->type = PacketInfo::EndToEnd; return Status::OK(); } -size_t Transport::write_no_crypto(const Storer &storer, PacketInfo *info, MutableSlice dest) { +BufferWriter Transport::write_no_crypto(const Storer &storer, PacketInfo *packet_info, size_t prepend_size, + size_t append_size) { size_t size = calc_no_crypto_size(storer.size()); - if (size > dest.size()) { - return size; - } + auto packet = BufferWriter{size, prepend_size, append_size}; + // NoCryptoHeader - as(dest.begin()) = 0; - auto real_size = storer.store(dest.ubegin() + sizeof(uint64)); + auto *begin = packet.as_mutable_slice().ubegin(); + as(begin) = 0; + auto real_size = storer.store(begin + sizeof(uint64)); CHECK(real_size == storer.size()); - return size; + return packet; } template -void Transport::write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *info, - HeaderT *header, size_t data_size) { +void Transport::write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + HeaderT *header, size_t data_size, size_t padded_size) { auto real_data_size = storer.store(header->data); CHECK(real_data_size == data_size); - VLOG(raw_mtproto) << "Send packet of size " << data_size << " to session " << format::as_hex(info->session_id) << ":" + VLOG(raw_mtproto) << "Send packet of size " << data_size << ':' << format::as_hex_dump<4>(Slice(header->data, data_size)); - - size_t size = 0; - if (info->version == 1) { - size = calc_crypto_size(data_size); - } else { - size = calc_crypto_size2(data_size, info); - } - - size_t pad_size = size - (sizeof(HeaderT) + data_size); + size_t pad_size = padded_size - (sizeof(HeaderT) + data_size); MutableSlice pad(header->data + data_size, pad_size); Random::secure_bytes(pad.ubegin(), pad.size()); MutableSlice to_encrypt = MutableSlice(header->encrypt_begin(), pad.uend()); - if (info->version == 1) { - std::tie(info->message_ack, info->message_key) = calc_message_ack_and_key(*header, data_size); - } else { - std::tie(info->message_ack, info->message_key) = calc_message_key2(auth_key, X, to_encrypt); - } - - header->message_key = info->message_key; - UInt256 aes_key; UInt256 aes_iv; - if (info->version == 1) { + if (packet_info->version == 1) { + std::tie(packet_info->message_ack, header->message_key) = calc_message_ack_and_key(*header, data_size); KDF(auth_key.key(), header->message_key, X, &aes_key, &aes_iv); } else { + std::tie(packet_info->message_ack, header->message_key) = calc_message_key2(auth_key, X, to_encrypt); KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv); } aes_ige_encrypt(as_slice(aes_key), as_mutable_slice(aes_iv), to_encrypt, to_encrypt); } -size_t Transport::write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) { +BufferWriter Transport::write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size, size_t append_size) { size_t data_size = storer.size(); - size_t size; - if (info->version == 1) { - size = calc_crypto_size(data_size); + size_t padded_size; + if (packet_info->version == 1) { + padded_size = calc_crypto_size(data_size); } else { - size = calc_crypto_size2(data_size, info); - } - if (size > dest.size()) { - return size; + padded_size = calc_crypto_size2(data_size, packet_info); } + auto packet = BufferWriter{padded_size, prepend_size, append_size}; //FIXME: rewrite without reinterpret cast - auto &header = *reinterpret_cast(dest.begin()); + auto &header = *reinterpret_cast(packet.as_mutable_slice().begin()); header.auth_key_id = auth_key.id(); - header.salt = info->salt; - header.session_id = info->session_id; + header.salt = packet_info->salt; + header.session_id = packet_info->session_id; - write_crypto_impl(0, storer, auth_key, info, &header, data_size); + write_crypto_impl(0, storer, auth_key, packet_info, &header, data_size, padded_size); - return size; + return packet; } -size_t Transport::write_e2e_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) { +BufferWriter Transport::write_e2e_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size, size_t append_size) { size_t data_size = storer.size(); - size_t size; - if (info->version == 1) { - size = calc_crypto_size(data_size); + size_t padded_size; + if (packet_info->version == 1) { + padded_size = calc_crypto_size(data_size); } else { - size = calc_crypto_size2(data_size, info); - } - if (size > dest.size()) { - return size; + padded_size = calc_crypto_size2(data_size, packet_info); } + auto packet = BufferWriter{padded_size, prepend_size, append_size}; //FIXME: rewrite without reinterpret cast - auto &header = *reinterpret_cast(dest.begin()); + auto &header = *reinterpret_cast(packet.as_mutable_slice().begin()); header.auth_key_id = auth_key.id(); - write_crypto_impl(info->is_creator || info->version == 1 ? 0 : 8, storer, auth_key, info, &header, data_size); + write_crypto_impl(packet_info->is_creator || packet_info->version == 1 ? 0 : 8, storer, auth_key, packet_info, + &header, data_size, padded_size); - return size; + return packet; } Result Transport::read_auth_key_id(Slice message) { @@ -432,7 +414,7 @@ Result Transport::read_auth_key_id(Slice message) { return as(message.begin()); } -Result Transport::read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info) { +Result Transport::read(MutableSlice message, const AuthKey &auth_key, PacketInfo *packet_info) { if (message.size() < 12) { if (message.size() < 4) { return Status::Error(PSLICE() << "Invalid MTProto message: smaller than 4 bytes [size = " << message.size() @@ -449,31 +431,31 @@ Result Transport::read(MutableSlice message, const AuthKe } } - info->auth_key_id = as(message.begin()); - info->no_crypto_flag = info->auth_key_id == 0; + packet_info->no_crypto_flag = as(message.begin()) == 0; MutableSlice data; - if (info->type == PacketInfo::EndToEnd) { - TRY_STATUS(read_e2e_crypto(message, auth_key, info, &data)); - } else if (info->no_crypto_flag) { - TRY_STATUS(read_no_crypto(message, info, &data)); + if (packet_info->type == PacketInfo::EndToEnd) { + TRY_STATUS(read_e2e_crypto(message, auth_key, packet_info, &data)); + } else if (packet_info->no_crypto_flag) { + TRY_STATUS(read_no_crypto(message, packet_info, &data)); } else { if (auth_key.empty()) { return Status::Error("Failed to decrypt MTProto message: auth key is empty"); } - TRY_STATUS(read_crypto(message, auth_key, info, &data)); + TRY_STATUS(read_crypto(message, auth_key, packet_info, &data)); } return ReadResult::make_packet(data); } -size_t Transport::write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) { - if (info->type == PacketInfo::EndToEnd) { - return write_e2e_crypto(storer, auth_key, info, dest); +BufferWriter Transport::write(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size, size_t append_size) { + if (packet_info->type == PacketInfo::EndToEnd) { + return write_e2e_crypto(storer, auth_key, packet_info, prepend_size, append_size); } - if (info->no_crypto_flag) { - return write_no_crypto(storer, info, dest); + if (packet_info->no_crypto_flag) { + return write_no_crypto(storer, packet_info, prepend_size, append_size); } else { CHECK(!auth_key.empty()); - return write_crypto(storer, auth_key, info, dest); + return write_crypto(storer, auth_key, packet_info, prepend_size, append_size); } } diff --git a/td/mtproto/Transport.h b/td/mtproto/Transport.h index c309ad5ea281..45716990c7ee 100644 --- a/td/mtproto/Transport.h +++ b/td/mtproto/Transport.h @@ -8,6 +8,7 @@ #include "td/mtproto/PacketInfo.h" +#include "td/utils/buffer.h" #include "td/utils/common.h" #include "td/utils/logging.h" #include "td/utils/Slice.h" @@ -86,11 +87,13 @@ class Transport { // Returns size of MTProto packet. // If dest.size() >= size, the packet is also written into [dest]. // If auth_key is nonempty, encryption will be used. - static Result read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info) TD_WARN_UNUSED_RESULT; + static Result read(MutableSlice message, const AuthKey &auth_key, + PacketInfo *packet_info) TD_WARN_UNUSED_RESULT; - static size_t write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, - MutableSlice dest = MutableSlice()); + static BufferWriter write(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size = 0, size_t append_size = 0); + // public for testing purposes static std::pair calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt); private: @@ -101,27 +104,35 @@ class Transport { static size_t calc_crypto_size(size_t data_size); template - static size_t calc_crypto_size2(size_t data_size, PacketInfo *info); + static size_t calc_crypto_size2(size_t data_size, PacketInfo *packet_info); static size_t calc_no_crypto_size(size_t data_size); - static Status read_no_crypto(MutableSlice message, PacketInfo *info, MutableSlice *data) TD_WARN_UNUSED_RESULT; + static Status read_no_crypto(MutableSlice message, PacketInfo *packet_info, MutableSlice *data) TD_WARN_UNUSED_RESULT; - static Status read_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, + static Status read_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *packet_info, MutableSlice *data) TD_WARN_UNUSED_RESULT; - static Status read_e2e_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, + + static Status read_e2e_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *packet_info, MutableSlice *data) TD_WARN_UNUSED_RESULT; + template static Status read_crypto_impl(int X, MutableSlice message, const AuthKey &auth_key, HeaderT **header_ptr, - PrefixT **prefix_ptr, MutableSlice *data, PacketInfo *info) TD_WARN_UNUSED_RESULT; + PrefixT **prefix_ptr, MutableSlice *data, + PacketInfo *packet_info) TD_WARN_UNUSED_RESULT; + + static BufferWriter write_no_crypto(const Storer &storer, PacketInfo *packet_info, size_t prepend_size, + size_t append_size); + + static BufferWriter write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size, size_t append_size); - static size_t write_no_crypto(const Storer &storer, PacketInfo *info, MutableSlice dest); + static BufferWriter write_e2e_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + size_t prepend_size, size_t append_size); - static size_t write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest); - static size_t write_e2e_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest); template - static void write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *info, HeaderT *header, - size_t data_size); + static void write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *packet_info, + HeaderT *header, size_t data_size, size_t padded_size); }; } // namespace mtproto diff --git a/td/telegram/Account.h b/td/telegram/Account.h deleted file mode 100644 index 4b43db7cce10..000000000000 --- a/td/telegram/Account.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// 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/td_api.h" - -#include "td/utils/common.h" -#include "td/utils/Promise.h" - -namespace td { - -class Td; - -void set_default_message_ttl(Td *td, int32 message_ttl, Promise &&promise); - -void get_default_message_ttl(Td *td, Promise &&promise); - -void set_account_ttl(Td *td, int32 account_ttl, Promise &&promise); - -void get_account_ttl(Td *td, Promise &&promise); - -void confirm_qr_code_authentication(Td *td, const string &link, Promise> &&promise); - -void get_active_sessions(Td *td, Promise> &&promise); - -void terminate_session(Td *td, int64 session_id, Promise &&promise); - -void terminate_all_other_sessions(Td *td, Promise &&promise); - -void toggle_session_can_accept_calls(Td *td, int64 session_id, bool can_accept_calls, Promise &&promise); - -void toggle_session_can_accept_secret_chats(Td *td, int64 session_id, bool can_accept_secret_chats, - Promise &&promise); - -void set_inactive_session_ttl_days(Td *td, int32 authorization_ttl_days, Promise &&promise); - -void get_connected_websites(Td *td, Promise> &&promise); - -void disconnect_website(Td *td, int64 website_id, Promise &&promise); - -void disconnect_all_websites(Td *td, Promise &&promise); - -void export_contact_token(Td *td, Promise> &&promise); - -void import_contact_token(Td *td, const string &token, Promise> &&promise); - -} // namespace td diff --git a/td/telegram/Account.cpp b/td/telegram/AccountManager.cpp similarity index 58% rename from td/telegram/Account.cpp rename to td/telegram/AccountManager.cpp index f7f9c4bd6b63..cbd8ad2cb796 100644 --- a/td/telegram/Account.cpp +++ b/td/telegram/AccountManager.cpp @@ -4,18 +4,20 @@ // 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/Account.h" +#include "td/telegram/AccountManager.h" +#include "td/telegram/AuthManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/DeviceTokenManager.h" #include "td/telegram/Global.h" +#include "td/telegram/LinkManager.h" +#include "td/telegram/logevent/LogEvent.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" -#include "td/actor/actor.h" - #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/buffer.h" @@ -23,6 +25,7 @@ #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include "td/utils/tl_helpers.h" #include @@ -101,12 +104,12 @@ static td_api::object_ptr convert_authorization_object( tl_object_ptr &&authorization) { CHECK(authorization != nullptr); return td_api::make_object( - authorization->hash_, authorization->current_, authorization->password_pending_, + authorization->hash_, authorization->current_, authorization->password_pending_, authorization->unconfirmed_, !authorization->encrypted_requests_disabled_, !authorization->call_requests_disabled_, get_session_type_object(authorization), authorization->api_id_, authorization->app_name_, authorization->app_version_, authorization->official_app_, authorization->device_model_, authorization->platform_, authorization->system_version_, authorization->date_created_, authorization->date_active_, authorization->ip_, - authorization->country_, authorization->region_); + authorization->country_); } class SetDefaultHistoryTtlQuery final : public Td::ResultHandler { @@ -290,8 +293,16 @@ class GetAuthorizationsQuery final : public Td::ResultHandler { if (lhs->is_password_pending_ != rhs->is_password_pending_) { return lhs->is_password_pending_; } + if (lhs->is_unconfirmed_ != rhs->is_unconfirmed_) { + return lhs->is_unconfirmed_; + } return lhs->last_active_date_ > rhs->last_active_date_; }); + for (auto &session : results->sessions_) { + if (!session->is_current_ && !session->is_unconfirmed_) { + td_->account_manager_->on_confirm_authorization(session->id_); + } + } promise_.set_value(std::move(results)); } @@ -364,7 +375,7 @@ class ChangeAuthorizationSettingsQuery final : public Td::ResultHandler { } void send(int64 hash, bool set_encrypted_requests_disabled, bool encrypted_requests_disabled, - bool set_call_requests_disabled, bool call_requests_disabled) { + bool set_call_requests_disabled, bool call_requests_disabled, bool confirm) { int32 flags = 0; if (set_encrypted_requests_disabled) { flags |= telegram_api::account_changeAuthorizationSettings::ENCRYPTED_REQUESTS_DISABLED_MASK; @@ -372,9 +383,13 @@ class ChangeAuthorizationSettingsQuery final : public Td::ResultHandler { if (set_call_requests_disabled) { flags |= telegram_api::account_changeAuthorizationSettings::CALL_REQUESTS_DISABLED_MASK; } - send_query(G()->net_query_creator().create(telegram_api::account_changeAuthorizationSettings( - flags, hash, encrypted_requests_disabled, call_requests_disabled), - {{"me"}})); + if (confirm) { + flags |= telegram_api::account_changeAuthorizationSettings::CONFIRMED_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::account_changeAuthorizationSettings(flags, false /*ignored*/, hash, encrypted_requests_disabled, + call_requests_disabled), + {{"me"}})); } void on_result(BufferSlice packet) final { @@ -582,24 +597,201 @@ class ImportContactTokenQuery final : public Td::ResultHandler { } }; -void set_default_message_ttl(Td *td, int32 message_ttl, Promise &&promise) { - td->create_handler(std::move(promise))->send(message_ttl); +class InvalidateSignInCodesQuery final : public Td::ResultHandler { + public: + void send(vector &&codes) { + send_query(G()->net_query_creator().create(telegram_api::account_invalidateSignInCodes(std::move(codes)))); + } + + 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 InvalidateSignInCodesQuery: " << result_ptr.ok(); + } + + void on_error(Status status) final { + LOG(DEBUG) << "Receive error for InvalidateSignInCodesQuery: " << status; + } +}; + +class AccountManager::UnconfirmedAuthorization { + int64 hash_ = 0; + int32 date_ = 0; + string device_; + string location_; + + public: + UnconfirmedAuthorization() = default; + UnconfirmedAuthorization(int64 hash, int32 date, string &&device, string &&location) + : hash_(hash), date_(date), device_(std::move(device)), location_(std::move(location)) { + } + + int64 get_hash() const { + return hash_; + } + + int32 get_date() const { + return date_; + } + + td_api::object_ptr get_unconfirmed_session_object() const { + return td_api::make_object(hash_, date_, device_, location_); + } + + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + td::store(hash_, storer); + td::store(date_, storer); + td::store(device_, storer); + td::store(location_, storer); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + td::parse(hash_, parser); + td::parse(date_, parser); + td::parse(device_, parser); + td::parse(location_, parser); + } +}; + +class AccountManager::UnconfirmedAuthorizations { + vector authorizations_; + + static int32 get_authorization_autoconfirm_period() { + return narrow_cast(G()->get_option_integer("authorization_autoconfirm_period", 604800)); + } + + public: + bool is_empty() const { + return authorizations_.empty(); + } + + bool add_authorization(UnconfirmedAuthorization &&unconfirmed_authorization, bool &is_first_changed) { + if (unconfirmed_authorization.get_hash() == 0) { + LOG(ERROR) << "Receive empty unconfirmed authorization"; + return false; + } + for (const auto &authorization : authorizations_) { + if (authorization.get_hash() == unconfirmed_authorization.get_hash()) { + return false; + } + } + auto it = authorizations_.begin(); + while (it != authorizations_.end() && it->get_date() <= unconfirmed_authorization.get_date()) { + ++it; + } + is_first_changed = it == authorizations_.begin(); + authorizations_.insert(it, std::move(unconfirmed_authorization)); + return true; + } + + bool delete_authorization(int64 hash, bool &is_first_changed) { + auto it = authorizations_.begin(); + while (it != authorizations_.end() && it->get_hash() != hash) { + ++it; + } + if (it == authorizations_.end()) { + return false; + } + is_first_changed = it == authorizations_.begin(); + authorizations_.erase(it); + return true; + } + + bool delete_expired_authorizations() { + auto up_to_date = G()->unix_time() - get_authorization_autoconfirm_period(); + auto it = authorizations_.begin(); + while (it != authorizations_.end() && it->get_date() <= up_to_date) { + ++it; + } + if (it == authorizations_.begin()) { + return false; + } + authorizations_.erase(authorizations_.begin(), it); + return true; + } + + int32 get_next_authorization_expire_date() const { + CHECK(!authorizations_.empty()); + return authorizations_[0].get_date() + get_authorization_autoconfirm_period(); + } + + td_api::object_ptr get_first_unconfirmed_session_object() const { + CHECK(!authorizations_.empty()); + return authorizations_[0].get_unconfirmed_session_object(); + } + + template + void store(StorerT &storer) const { + CHECK(!authorizations_.empty()); + td::store(authorizations_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(authorizations_, parser); + } +}; + +AccountManager::AccountManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +AccountManager::~AccountManager() = default; + +void AccountManager::start_up() { + auto unconfirmed_authorizations_log_event_string = + G()->td_db()->get_binlog_pmc()->get(get_unconfirmed_authorizations_key()); + if (!unconfirmed_authorizations_log_event_string.empty()) { + log_event_parse(unconfirmed_authorizations_, unconfirmed_authorizations_log_event_string).ensure(); + CHECK(unconfirmed_authorizations_ != nullptr); + if (delete_expired_unconfirmed_authorizations()) { + save_unconfirmed_authorizations(); + } + if (unconfirmed_authorizations_ != nullptr) { + update_unconfirmed_authorization_timeout(false); + send_update_unconfirmed_session(); + get_active_sessions(Auto()); + } + } +} + +void AccountManager::timeout_expired() { + update_unconfirmed_authorization_timeout(true); + if (unconfirmed_authorizations_ != nullptr) { + get_active_sessions(Auto()); + } +} + +void AccountManager::tear_down() { + parent_.reset(); +} + +void AccountManager::set_default_message_ttl(int32 message_ttl, Promise &&promise) { + td_->create_handler(std::move(promise))->send(message_ttl); } -void get_default_message_ttl(Td *td, Promise &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::get_default_message_ttl(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); } -void set_account_ttl(Td *td, int32 account_ttl, Promise &&promise) { - td->create_handler(std::move(promise))->send(account_ttl); +void AccountManager::set_account_ttl(int32 account_ttl, Promise &&promise) { + td_->create_handler(std::move(promise))->send(account_ttl); } -void get_account_ttl(Td *td, Promise &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::get_account_ttl(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); } -void confirm_qr_code_authentication(Td *td, const string &link, - Promise> &&promise) { +void AccountManager::confirm_qr_code_authentication(const string &link, + Promise> &&promise) { Slice prefix("tg://login?token="); if (!begins_with(to_lower(link), prefix)) { return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID")); @@ -608,54 +800,186 @@ void confirm_qr_code_authentication(Td *td, const string &link, if (r_token.is_error()) { return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID")); } - td->create_handler(std::move(promise))->send(r_token.ok()); + td_->create_handler(std::move(promise))->send(r_token.ok()); } -void get_active_sessions(Td *td, Promise> &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::get_active_sessions(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); } -void terminate_session(Td *td, int64 session_id, Promise &&promise) { - td->create_handler(std::move(promise))->send(session_id); +void AccountManager::terminate_session(int64 session_id, Promise &&promise) { + on_confirm_authorization(session_id); + td_->create_handler(std::move(promise))->send(session_id); } -void terminate_all_other_sessions(Td *td, Promise &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::terminate_all_other_sessions(Promise &&promise) { + if (unconfirmed_authorizations_ != nullptr) { + unconfirmed_authorizations_ = nullptr; + update_unconfirmed_authorization_timeout(false); + send_update_unconfirmed_session(); + save_unconfirmed_authorizations(); + } + td_->create_handler(std::move(promise))->send(); } -void toggle_session_can_accept_calls(Td *td, int64 session_id, bool can_accept_calls, Promise &&promise) { - td->create_handler(std::move(promise)) - ->send(session_id, false, false, true, !can_accept_calls); +void AccountManager::confirm_session(int64 session_id, Promise &&promise) { + if (!on_confirm_authorization(session_id)) { + // the authorization can be from the list of active authorizations, but the update could have been lost + // return promise.set_value(Unit()); + } + td_->create_handler(std::move(promise)) + ->send(session_id, false, false, false, false, true); } -void toggle_session_can_accept_secret_chats(Td *td, int64 session_id, bool can_accept_secret_chats, - Promise &&promise) { - td->create_handler(std::move(promise)) - ->send(session_id, true, !can_accept_secret_chats, false, false); +void AccountManager::toggle_session_can_accept_calls(int64 session_id, bool can_accept_calls, Promise &&promise) { + td_->create_handler(std::move(promise)) + ->send(session_id, false, false, true, !can_accept_calls, false); } -void set_inactive_session_ttl_days(Td *td, int32 authorization_ttl_days, Promise &&promise) { - td->create_handler(std::move(promise))->send(authorization_ttl_days); +void AccountManager::toggle_session_can_accept_secret_chats(int64 session_id, bool can_accept_secret_chats, + Promise &&promise) { + td_->create_handler(std::move(promise)) + ->send(session_id, true, !can_accept_secret_chats, false, false, false); } -void get_connected_websites(Td *td, Promise> &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::set_inactive_session_ttl_days(int32 authorization_ttl_days, Promise &&promise) { + td_->create_handler(std::move(promise))->send(authorization_ttl_days); } -void disconnect_website(Td *td, int64 website_id, Promise &&promise) { - td->create_handler(std::move(promise))->send(website_id); +void AccountManager::get_connected_websites(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); } -void disconnect_all_websites(Td *td, Promise &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::disconnect_website(int64 website_id, Promise &&promise) { + td_->create_handler(std::move(promise))->send(website_id); } -void export_contact_token(Td *td, Promise> &&promise) { - td->create_handler(std::move(promise))->send(); +void AccountManager::disconnect_all_websites(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); } -void import_contact_token(Td *td, const string &token, Promise> &&promise) { - td->create_handler(std::move(promise))->send(token); +void AccountManager::get_user_link(Promise> &&promise) { + td_->contacts_manager_->get_me( + PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &AccountManager::get_user_link_impl, std::move(promise)); + } + })); +} + +void AccountManager::get_user_link_impl(Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + auto username = td_->contacts_manager_->get_user_first_username(td_->contacts_manager_->get_my_id()); + if (!username.empty()) { + return promise.set_value( + td_api::make_object(LinkManager::get_public_dialog_link(username, true), 0)); + } + td_->create_handler(std::move(promise))->send(); +} + +void AccountManager::import_contact_token(const string &token, Promise> &&promise) { + td_->create_handler(std::move(promise))->send(token); +} + +void AccountManager::invalidate_authentication_codes(vector &&authentication_codes) { + td_->create_handler()->send(std::move(authentication_codes)); +} + +void AccountManager::on_new_unconfirmed_authorization(int64 hash, int32 date, string &&device, string &&location) { + if (td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive unconfirmed session by a bot"; + return; + } + auto unix_time = G()->unix_time(); + if (date > unix_time + 1) { + LOG(ERROR) << "Receive new session at " << date << ", but the current time is " << unix_time; + date = unix_time + 1; + } + if (unconfirmed_authorizations_ == nullptr) { + unconfirmed_authorizations_ = make_unique(); + } + bool is_first_changed = false; + if (unconfirmed_authorizations_->add_authorization({hash, date, std::move(device), std::move(location)}, + is_first_changed)) { + CHECK(!unconfirmed_authorizations_->is_empty()); + if (is_first_changed) { + update_unconfirmed_authorization_timeout(false); + send_update_unconfirmed_session(); + } + save_unconfirmed_authorizations(); + } +} + +bool AccountManager::on_confirm_authorization(int64 hash) { + bool is_first_changed = false; + if (unconfirmed_authorizations_ != nullptr && + unconfirmed_authorizations_->delete_authorization(hash, is_first_changed)) { + if (unconfirmed_authorizations_->is_empty()) { + unconfirmed_authorizations_ = nullptr; + } + if (is_first_changed) { + update_unconfirmed_authorization_timeout(false); + send_update_unconfirmed_session(); + } + save_unconfirmed_authorizations(); + return true; + } + return false; +} + +string AccountManager::get_unconfirmed_authorizations_key() { + return "new_authorizations"; +} + +void AccountManager::save_unconfirmed_authorizations() const { + if (unconfirmed_authorizations_ == nullptr) { + G()->td_db()->get_binlog_pmc()->erase(get_unconfirmed_authorizations_key()); + } else { + G()->td_db()->get_binlog_pmc()->set(get_unconfirmed_authorizations_key(), + log_event_store(unconfirmed_authorizations_).as_slice().str()); + } +} + +bool AccountManager::delete_expired_unconfirmed_authorizations() { + if (unconfirmed_authorizations_ != nullptr && unconfirmed_authorizations_->delete_expired_authorizations()) { + if (unconfirmed_authorizations_->is_empty()) { + unconfirmed_authorizations_ = nullptr; + } + return true; + } + return false; +} + +void AccountManager::update_unconfirmed_authorization_timeout(bool is_external) { + if (delete_expired_unconfirmed_authorizations() && is_external) { + send_update_unconfirmed_session(); + save_unconfirmed_authorizations(); + } + if (unconfirmed_authorizations_ == nullptr) { + cancel_timeout(); + } else { + set_timeout_in(min(unconfirmed_authorizations_->get_next_authorization_expire_date() - G()->unix_time() + 1, 3600)); + } +} + +td_api::object_ptr AccountManager::get_update_unconfirmed_session() const { + if (unconfirmed_authorizations_ == nullptr) { + return td_api::make_object(nullptr); + } + return td_api::make_object( + unconfirmed_authorizations_->get_first_unconfirmed_session_object()); +} + +void AccountManager::send_update_unconfirmed_session() const { + send_closure(G()->td(), &Td::send_update, get_update_unconfirmed_session()); +} + +void AccountManager::get_current_state(vector> &updates) const { + if (unconfirmed_authorizations_ != nullptr) { + updates.push_back(get_update_unconfirmed_session()); + } } } // namespace td diff --git a/td/telegram/AccountManager.h b/td/telegram/AccountManager.h new file mode 100644 index 000000000000..6aa152eff47f --- /dev/null +++ b/td/telegram/AccountManager.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/td_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" + +namespace td { + +class Td; + +class AccountManager final : public Actor { + public: + AccountManager(Td *td, ActorShared<> parent); + AccountManager(const AccountManager &) = delete; + AccountManager &operator=(const AccountManager &) = delete; + AccountManager(AccountManager &&) = delete; + AccountManager &operator=(AccountManager &&) = delete; + ~AccountManager() final; + + void set_default_message_ttl(int32 message_ttl, Promise &&promise); + + void get_default_message_ttl(Promise &&promise); + + void set_account_ttl(int32 account_ttl, Promise &&promise); + + void get_account_ttl(Promise &&promise); + + void confirm_qr_code_authentication(const string &link, Promise> &&promise); + + void get_active_sessions(Promise> &&promise); + + void terminate_session(int64 session_id, Promise &&promise); + + void terminate_all_other_sessions(Promise &&promise); + + void confirm_session(int64 session_id, Promise &&promise); + + void toggle_session_can_accept_calls(int64 session_id, bool can_accept_calls, Promise &&promise); + + void toggle_session_can_accept_secret_chats(int64 session_id, bool can_accept_secret_chats, Promise &&promise); + + void set_inactive_session_ttl_days(int32 authorization_ttl_days, Promise &&promise); + + void get_connected_websites(Promise> &&promise); + + void disconnect_website(int64 website_id, Promise &&promise); + + void disconnect_all_websites(Promise &&promise); + + void get_user_link(Promise> &&promise); + + void import_contact_token(const string &token, Promise> &&promise); + + void invalidate_authentication_codes(vector &&authentication_codes); + + void update_unconfirmed_authorization_timeout(bool is_external); + + void on_new_unconfirmed_authorization(int64 hash, int32 date, string &&device, string &&location); + + bool on_confirm_authorization(int64 hash); + + void get_current_state(vector> &updates) const; + + private: + class UnconfirmedAuthorization; + class UnconfirmedAuthorizations; + + void start_up() final; + + void timeout_expired() final; + + void tear_down() final; + + void get_user_link_impl(Promise> &&promise); + + static string get_unconfirmed_authorizations_key(); + + void save_unconfirmed_authorizations() const; + + bool delete_expired_unconfirmed_authorizations(); + + td_api::object_ptr get_update_unconfirmed_session() const; + + void send_update_unconfirmed_session() const; + + Td *td_; + ActorShared<> parent_; + + unique_ptr unconfirmed_authorizations_; +}; + +} // namespace td diff --git a/td/telegram/AnimationsManager.cpp b/td/telegram/AnimationsManager.cpp index 500f556a5e35..a9623c1e4125 100644 --- a/td/telegram/AnimationsManager.cpp +++ b/td/telegram/AnimationsManager.cpp @@ -339,8 +339,8 @@ tl_object_ptr AnimationsManager::get_input_media( string mime_type = animation->mime_type; if (mime_type == "video/mp4") { attributes.push_back(make_tl_object( - 0, false /*ignored*/, false /*ignored*/, animation->duration, animation->dimensions.width, - animation->dimensions.height)); + 0, false /*ignored*/, false /*ignored*/, false /*ignored*/, animation->duration, animation->dimensions.width, + animation->dimensions.height, 0)); } else if (animation->dimensions.width != 0 && animation->dimensions.height != 0) { if (!begins_with(mime_type, "image/")) { mime_type = "image/gif"; diff --git a/td/telegram/Application.cpp b/td/telegram/Application.cpp index dfbf71bbe48c..0b29e35254b2 100644 --- a/td/telegram/Application.cpp +++ b/td/telegram/Application.cpp @@ -11,6 +11,7 @@ #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/db/binlog/BinlogEvent.h" diff --git a/td/telegram/AttachMenuManager.cpp b/td/telegram/AttachMenuManager.cpp index 851877712db9..7b18d8487786 100644 --- a/td/telegram/AttachMenuManager.cpp +++ b/td/telegram/AttachMenuManager.cpp @@ -21,6 +21,7 @@ #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/WebApp.h" @@ -119,7 +120,7 @@ class RequestWebViewQuery final : public Td::ResultHandler { DialogId dialog_id_; UserId bot_user_id_; MessageId top_thread_message_id_; - MessageId reply_to_message_id_; + MessageInputReplyTo input_reply_to_; DialogId as_dialog_id_; bool from_attach_menu_ = false; @@ -130,11 +131,11 @@ class RequestWebViewQuery final : public Td::ResultHandler { void send(DialogId dialog_id, UserId bot_user_id, tl_object_ptr &&input_user, string &&url, td_api::object_ptr &&theme, string &&platform, MessageId top_thread_message_id, - MessageId reply_to_message_id, bool silent, DialogId as_dialog_id) { + MessageInputReplyTo input_reply_to, bool silent, DialogId as_dialog_id) { dialog_id_ = dialog_id; bot_user_id_ = bot_user_id; top_thread_message_id_ = top_thread_message_id; - reply_to_message_id_ = reply_to_message_id; + input_reply_to_ = input_reply_to; as_dialog_id_ = as_dialog_id; int32 flags = 0; @@ -167,12 +168,9 @@ class RequestWebViewQuery final : public Td::ResultHandler { flags |= telegram_api::messages_requestWebView::THEME_PARAMS_MASK; } - if (top_thread_message_id.is_valid()) { - flags |= telegram_api::messages_requestWebView::TOP_MSG_ID_MASK; - } - - if (reply_to_message_id.is_valid()) { - flags |= telegram_api::messages_requestWebView::REPLY_TO_MSG_ID_MASK; + auto reply_to = input_reply_to_.get_input_reply_to(td_, top_thread_message_id); + if (reply_to != nullptr) { + flags |= telegram_api::messages_requestWebView::REPLY_TO_MASK; } if (silent) { @@ -189,8 +187,7 @@ class RequestWebViewQuery final : public Td::ResultHandler { send_query(G()->net_query_creator().create(telegram_api::messages_requestWebView( flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(input_user), url, start_parameter, - std::move(theme_parameters), platform, reply_to_message_id.get_server_message_id().get(), - top_thread_message_id.get_server_message_id().get(), std::move(as_input_peer)))); + std::move(theme_parameters), platform, std::move(reply_to), std::move(as_input_peer)))); } void on_result(BufferSlice packet) final { @@ -201,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_, - reply_to_message_id_, as_dialog_id_); + input_reply_to_, as_dialog_id_); promise_.set_value(td_api::make_object(ptr->query_id_, ptr->url_)); } @@ -220,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, - MessageId reply_to_message_id, bool silent, DialogId as_dialog_id) { + 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); @@ -230,14 +227,10 @@ class ProlongWebViewQuery final : public Td::ResultHandler { } int32 flags = 0; - if (reply_to_message_id.is_valid()) { - flags |= telegram_api::messages_prolongWebView::REPLY_TO_MSG_ID_MASK; - } - - if (top_thread_message_id.is_valid()) { - flags |= telegram_api::messages_prolongWebView::TOP_MSG_ID_MASK; + auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); + if (reply_to != nullptr) { + flags |= telegram_api::messages_prolongWebView::REPLY_TO_MASK; } - if (silent) { flags |= telegram_api::messages_prolongWebView::SILENT_MASK; } @@ -251,8 +244,7 @@ class ProlongWebViewQuery final : public Td::ResultHandler { } send_query(G()->net_query_creator().create(telegram_api::messages_prolongWebView( - flags, false /*ignored*/, std::move(input_peer), r_input_user.move_as_ok(), query_id, - reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(), + flags, false /*ignored*/, std::move(input_peer), r_input_user.move_as_ok(), query_id, std::move(reply_to), std::move(as_input_peer)))); } @@ -273,6 +265,38 @@ class ProlongWebViewQuery final : public Td::ResultHandler { } }; +class InvokeWebViewCustomMethodQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit InvokeWebViewCustomMethodQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(UserId bot_user_id, const string &method, const string ¶meters) { + auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create(telegram_api::bots_invokeWebViewCustomMethod( + r_input_user.move_as_ok(), method, make_tl_object(parameters)))); + } + + 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(); + promise_.set_value(td_api::make_object(result->data_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetAttachMenuBotsQuery final : public Td::ResultHandler { Promise> promise_; @@ -391,11 +415,16 @@ bool operator==(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuMan 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.name_ == rhs.name_ && lhs.default_icon_file_id_ == rhs.default_icon_file_id_ && + 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_ && lhs.ios_static_icon_file_id_ == rhs.ios_static_icon_file_id_ && lhs.ios_animated_icon_file_id_ == rhs.ios_animated_icon_file_id_ && lhs.android_icon_file_id_ == rhs.android_icon_file_id_ && lhs.macos_icon_file_id_ == rhs.macos_icon_file_id_ && - lhs.is_added_ == rhs.is_added_ && lhs.name_color_ == rhs.name_color_ && lhs.icon_color_ == rhs.icon_color_ && + lhs.android_side_menu_icon_file_id_ == rhs.android_side_menu_icon_file_id_ && + lhs.ios_side_menu_icon_file_id_ == rhs.ios_side_menu_icon_file_id_ && + lhs.macos_side_menu_icon_file_id_ == rhs.macos_side_menu_icon_file_id_ && lhs.is_added_ == rhs.is_added_ && + lhs.name_color_ == rhs.name_color_ && lhs.icon_color_ == rhs.icon_color_ && lhs.placeholder_file_id_ == rhs.placeholder_file_id_; } @@ -414,6 +443,9 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { bool has_support_flags = true; bool has_placeholder_file_id = placeholder_file_id_.is_valid(); bool has_cache_version = cache_version_ != 0; + bool has_android_side_menu_icon_file_id = android_side_menu_icon_file_id_.is_valid(); + bool has_ios_side_menu_icon_file_id = ios_side_menu_icon_file_id_.is_valid(); + bool has_macos_side_menu_icon_file_id = macos_side_menu_icon_file_id_.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_ios_static_icon_file_id); STORE_FLAG(has_ios_animated_icon_file_id); @@ -432,6 +464,12 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { STORE_FLAG(has_placeholder_file_id); STORE_FLAG(has_cache_version); STORE_FLAG(request_write_access_); + STORE_FLAG(show_in_attach_menu_); + STORE_FLAG(show_in_side_menu_); + STORE_FLAG(side_menu_disclaimer_needed_); + STORE_FLAG(has_android_side_menu_icon_file_id); + STORE_FLAG(has_ios_side_menu_icon_file_id); + STORE_FLAG(has_macos_side_menu_icon_file_id); END_STORE_FLAGS(); td::store(user_id_, storer); td::store(name_, storer); @@ -460,6 +498,15 @@ void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const { if (has_cache_version) { td::store(cache_version_, storer); } + if (has_android_side_menu_icon_file_id) { + td::store(android_side_menu_icon_file_id_, storer); + } + if (has_ios_side_menu_icon_file_id) { + td::store(ios_side_menu_icon_file_id_, storer); + } + if (has_macos_side_menu_icon_file_id) { + td::store(macos_side_menu_icon_file_id_, storer); + } } template @@ -473,6 +520,9 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { bool has_support_flags; bool has_placeholder_file_id; bool has_cache_version; + bool has_android_side_menu_icon_file_id; + bool has_ios_side_menu_icon_file_id; + bool has_macos_side_menu_icon_file_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_ios_static_icon_file_id); PARSE_FLAG(has_ios_animated_icon_file_id); @@ -491,6 +541,12 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { PARSE_FLAG(has_placeholder_file_id); PARSE_FLAG(has_cache_version); PARSE_FLAG(request_write_access_); + PARSE_FLAG(show_in_attach_menu_); + PARSE_FLAG(show_in_side_menu_); + PARSE_FLAG(side_menu_disclaimer_needed_); + PARSE_FLAG(has_android_side_menu_icon_file_id); + PARSE_FLAG(has_ios_side_menu_icon_file_id); + PARSE_FLAG(has_macos_side_menu_icon_file_id); END_PARSE_FLAGS(); td::parse(user_id_, parser); td::parse(name_, parser); @@ -519,12 +575,25 @@ void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) { if (has_cache_version) { td::parse(cache_version_, parser); } + if (has_android_side_menu_icon_file_id) { + td::parse(android_side_menu_icon_file_id_, parser); + } + if (has_ios_side_menu_icon_file_id) { + td::parse(ios_side_menu_icon_file_id_, parser); + } + if (has_macos_side_menu_icon_file_id) { + td::parse(macos_side_menu_icon_file_id_, parser); + } if (!has_support_flags) { supports_self_dialog_ = true; supports_user_dialogs_ = true; supports_bot_dialogs_ = true; } + if (is_added_ && !show_in_attach_menu_ && !show_in_side_menu_ && !has_android_side_menu_icon_file_id && + !has_ios_side_menu_icon_file_id && !has_macos_side_menu_icon_file_id) { + show_in_attach_menu_ = true; + } } class AttachMenuManager::AttachMenuBotsLogEvent { @@ -614,6 +683,9 @@ void AttachMenuManager::init() { register_file_source(attach_menu_bot.android_icon_file_id_); register_file_source(attach_menu_bot.macos_icon_file_id_); register_file_source(attach_menu_bot.placeholder_file_id_); + register_file_source(attach_menu_bot.android_side_menu_icon_file_id_); + register_file_source(attach_menu_bot.ios_side_menu_icon_file_id_); + register_file_source(attach_menu_bot.macos_side_menu_icon_file_id_); } } else { LOG(ERROR) << "Ignore invalid attachment menu bots log event"; @@ -682,7 +754,7 @@ void AttachMenuManager::ping_web_view() { bool silent = td_->messages_manager_->get_dialog_silent_send_message(opened_web_view.dialog_id_); td_->create_handler()->send( opened_web_view.dialog_id_, opened_web_view.bot_user_id_, it.first, opened_web_view.top_thread_message_id_, - opened_web_view.reply_to_message_id_, silent, opened_web_view.as_dialog_id_); + opened_web_view.input_reply_to_, silent, opened_web_view.as_dialog_id_); } schedule_ping_web_view(); @@ -729,7 +801,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_), + promise.set_value(td_api::make_object(web_app.get_web_app_object(td_), bot_app->has_settings_, bot_app->request_write_access_, !bot_app->inactive_)); } @@ -762,7 +834,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, - MessageId reply_to_message_id, 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)); @@ -790,26 +862,24 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, return promise.set_error(Status::Error(400, "Have no write access to the chat")); } - if (!reply_to_message_id.is_valid() || !reply_to_message_id.is_server() || - !td_->messages_manager_->have_message_force({dialog_id, reply_to_message_id}, "request_web_view")) { - reply_to_message_id = MessageId(); - } if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server() || dialog_id.get_type() != DialogType::Channel || !td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id())) { top_thread_message_id = MessageId(); } + auto input_reply_to = + td_->messages_manager_->get_message_input_reply_to(dialog_id, top_thread_message_id, std::move(reply_to), false); bool silent = td_->messages_manager_->get_dialog_silent_send_message(dialog_id); DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id); td_->create_handler(std::move(promise)) ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), std::move(platform), - top_thread_message_id, reply_to_message_id, silent, as_dialog_id); + top_thread_message_id, 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, MessageId reply_to_message_id, + 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"; @@ -823,7 +893,7 @@ void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId opened_web_view.dialog_id_ = dialog_id; opened_web_view.bot_user_id_ = bot_user_id; opened_web_view.top_thread_message_id_ = top_thread_message_id; - opened_web_view.reply_to_message_id_ = reply_to_message_id; + opened_web_view.input_reply_to_ = std::move(input_reply_to); opened_web_view.as_dialog_id_ = as_dialog_id; opened_web_views_.emplace(query_id, std::move(opened_web_view)); } @@ -836,6 +906,12 @@ void AttachMenuManager::close_web_view(int64 query_id, Promise &&promise) promise.set_value(Unit()); } +void AttachMenuManager::invoke_web_view_custom_method( + UserId bot_user_id, const string &method, const string ¶meters, + Promise> &&promise) { + td_->create_handler(std::move(promise))->send(bot_user_id, method, parameters); +} + Result AttachMenuManager::get_attach_menu_bot( tl_object_ptr &&bot) { UserId user_id(bot->bot_id_); @@ -858,7 +934,8 @@ Result AttachMenuManager::get_attach_menu_bot( CHECK(document_id == telegram_api::document::ID); if (name != "default_static" && name != "ios_static" && name != "ios_animated" && name != "android_animated" && - name != "macos_animated" && name != "placeholder_static") { + name != "macos_animated" && name != "placeholder_static" && name != "ios_side_menu_static" && + name != "android_side_menu_static" && name != "macos_side_menu_static") { LOG(ERROR) << "Have icon for " << user_id << " with name " << name; continue; } @@ -882,11 +959,21 @@ Result AttachMenuManager::get_attach_menu_bot( attach_menu_bot.ios_animated_icon_file_id_ = parsed_document.file_id; break; case 'i': - attach_menu_bot.android_icon_file_id_ = parsed_document.file_id; - expect_colors = true; + if (name[8] == 's') { + attach_menu_bot.android_side_menu_icon_file_id_ = parsed_document.file_id; + } else if (name[8] == '_') { + attach_menu_bot.ios_side_menu_icon_file_id_ = parsed_document.file_id; + } else { + attach_menu_bot.android_icon_file_id_ = parsed_document.file_id; + expect_colors = true; + } break; case '_': - attach_menu_bot.macos_icon_file_id_ = parsed_document.file_id; + if (name[6] == 's') { + attach_menu_bot.macos_side_menu_icon_file_id_ = parsed_document.file_id; + } else { + attach_menu_bot.macos_icon_file_id_ = parsed_document.file_id; + } break; case 'h': attach_menu_bot.placeholder_file_id_ = parsed_document.file_id; @@ -966,6 +1053,9 @@ Result AttachMenuManager::get_attach_menu_bot( } 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_; + attach_menu_bot.side_menu_disclaimer_needed_ = bot->side_menu_disclaimer_needed_; if (!attach_menu_bot.default_icon_file_id_.is_valid()) { return Status::Error(PSLICE() << "Have no default icon for " << user_id); } @@ -1023,11 +1113,6 @@ void AttachMenuManager::on_reload_attach_menu_bots( new_hash = 0; continue; } - if (!r_attach_menu_bot.ok().is_added_) { - LOG(ERROR) << "Receive non-added attachment menu bot " << r_attach_menu_bot.ok().user_id_; - new_hash = 0; - continue; - } new_attach_menu_bots.push_back(r_attach_menu_bot.move_as_ok()); } @@ -1123,19 +1208,26 @@ void AttachMenuManager::on_get_attach_menu_bot( for (auto &old_bot : attach_menu_bots_) { if (old_bot.user_id_ == user_id) { is_found = true; + if (old_bot != attach_menu_bot) { + LOG(INFO) << "Update attachment menu bot " << user_id; + + old_bot = attach_menu_bot; + + send_update_attach_menu_bots(); + save_attach_menu_bots(); + } break; } } if (!is_found) { LOG(INFO) << "Add missing attachment menu bot " << user_id; - } - hash_ = 0; - attach_menu_bots_.insert(attach_menu_bots_.begin(), attach_menu_bot); - send_update_attach_menu_bots(); - save_attach_menu_bots(); - } else { - remove_bot_from_attach_menu(user_id); + hash_ = 0; + attach_menu_bots_.insert(attach_menu_bots_.begin(), attach_menu_bot); + + send_update_attach_menu_bots(); + save_attach_menu_bots(); + } } promise.set_value(get_attachment_menu_bot_object(attach_menu_bot)); } @@ -1172,17 +1264,6 @@ void AttachMenuManager::toggle_bot_is_added_to_attach_menu(UserId user_id, bool TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); - bool is_found = false; - for (auto &bot : attach_menu_bots_) { - if (bot.user_id_ == user_id) { - is_found = true; - break; - } - } - if (is_added == is_found) { - return promise.set_value(Unit()); - } - if (is_added) { TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(user_id)); if (!bot_data.can_be_added_to_attach_menu) { @@ -1224,11 +1305,14 @@ 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.name_, + 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_, 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.android_icon_file_id_), get_file(bot.macos_icon_file_id_), - get_attach_menu_bot_color_object(bot.icon_color_), get_file(bot.placeholder_file_id_)); + get_file(bot.ios_side_menu_icon_file_id_), get_file(bot.android_icon_file_id_), + get_file(bot.android_side_menu_icon_file_id_), get_file(bot.macos_icon_file_id_), + get_file(bot.macos_side_menu_icon_file_id_), get_attach_menu_bot_color_object(bot.icon_color_), + get_file(bot.placeholder_file_id_)); } td_api::object_ptr AttachMenuManager::get_update_attachment_menu_bots_object() const { diff --git a/td/telegram/AttachMenuManager.h b/td/telegram/AttachMenuManager.h index 3983bfb9cee8..94d94a6822bf 100644 --- a/td/telegram/AttachMenuManager.h +++ b/td/telegram/AttachMenuManager.h @@ -10,6 +10,7 @@ #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -42,15 +43,18 @@ 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, - MessageId reply_to_message_id, 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, - MessageId reply_to_message_id, DialogId as_dialog_id); + MessageInputReplyTo input_reply_to, DialogId as_dialog_id); void close_web_view(int64 query_id, Promise &&promise); + void invoke_web_view_custom_method(UserId bot_user_id, const string &method, const string ¶meters, + Promise> &&promise); + void reload_attach_menu_bots(Promise &&promise); void get_attach_menu_bot(UserId user_id, Promise> &&promise); @@ -102,6 +106,9 @@ class AttachMenuManager final : public Actor { 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; + bool side_menu_disclaimer_needed_ = false; string name_; AttachMenuBotColor name_color_; FileId default_icon_file_id_; @@ -109,10 +116,13 @@ class AttachMenuManager final : public Actor { FileId ios_animated_icon_file_id_; FileId android_icon_file_id_; FileId macos_icon_file_id_; + FileId android_side_menu_icon_file_id_; + FileId ios_side_menu_icon_file_id_; + FileId macos_side_menu_icon_file_id_; AttachMenuBotColor icon_color_; FileId placeholder_file_id_; - static constexpr uint32 CACHE_VERSION = 2; + static constexpr uint32 CACHE_VERSION = 3; uint32 cache_version_ = 0; template @@ -175,7 +185,7 @@ class AttachMenuManager final : public Actor { DialogId dialog_id_; UserId bot_user_id_; MessageId top_thread_message_id_; - MessageId reply_to_message_id_; + MessageInputReplyTo input_reply_to_; DialogId as_dialog_id_; }; FlatHashMap opened_web_views_; diff --git a/td/telegram/AudiosManager.cpp b/td/telegram/AudiosManager.cpp index ddceb1f71381..c2526e8f8a73 100644 --- a/td/telegram/AudiosManager.cpp +++ b/td/telegram/AudiosManager.cpp @@ -14,6 +14,7 @@ #include "td/telegram/PhotoFormat.h" #include "td/telegram/secret_api.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/actor/actor.h" diff --git a/td/telegram/AuthManager.cpp b/td/telegram/AuthManager.cpp index aaf91b03573f..9d7a23a569d7 100644 --- a/td/telegram/AuthManager.cpp +++ b/td/telegram/AuthManager.cpp @@ -13,6 +13,7 @@ #include "td/telegram/DialogFilterManager.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" @@ -21,25 +22,263 @@ #include "td/telegram/NotificationManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PasswordManager.h" +#include "td/telegram/ReactionManager.h" +#include "td/telegram/SendCodeHelper.hpp" #include "td/telegram/StateManager.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/Version.h" #include "td/utils/base64.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Promise.h" -#include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" namespace td { +struct AuthManager::DbState { + State state_; + int32 api_id_; + string api_hash_; + double expires_at_; + + // WaitEmailAddress and WaitEmailCode + bool allow_apple_id_ = false; + bool allow_google_id_ = false; + + // WaitEmailCode + string email_address_; + SentEmailCode email_code_info_; + int32 reset_available_period_ = -1; + int32 reset_pending_date_ = -1; + + // WaitEmailAddress, WaitEmailCode, WaitCode and WaitRegistration + SendCodeHelper send_code_helper_; + + // WaitQrCodeConfirmation + vector other_user_ids_; + string login_token_; + double login_token_expires_at_ = 0; + + // WaitPassword + WaitPasswordState wait_password_state_; + + // WaitRegistration + TermsOfService terms_of_service_; + + DbState() = default; + + static DbState wait_email_address(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, + SendCodeHelper send_code_helper) { + DbState state(State::WaitEmailAddress, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + state.allow_apple_id_ = allow_apple_id; + state.allow_google_id_ = allow_google_id; + return state; + } + + static DbState wait_email_code(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, + string email_address, SentEmailCode email_code_info, int32 reset_available_period, + int32 reset_pending_date, SendCodeHelper send_code_helper) { + DbState state(State::WaitEmailCode, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + state.allow_apple_id_ = allow_apple_id; + state.allow_google_id_ = allow_google_id; + state.email_address_ = std::move(email_address); + state.email_code_info_ = std::move(email_code_info); + state.reset_available_period_ = reset_available_period; + state.reset_pending_date_ = reset_pending_date; + return state; + } + + static DbState wait_code(int32 api_id, string api_hash, SendCodeHelper send_code_helper) { + DbState state(State::WaitCode, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + return state; + } + + static DbState wait_qr_code_confirmation(int32 api_id, string api_hash, vector other_user_ids, + string login_token, double login_token_expires_at) { + DbState state(State::WaitQrCodeConfirmation, api_id, std::move(api_hash)); + state.other_user_ids_ = std::move(other_user_ids); + state.login_token_ = std::move(login_token); + state.login_token_expires_at_ = login_token_expires_at; + return state; + } + + static DbState wait_password(int32 api_id, string api_hash, WaitPasswordState wait_password_state) { + DbState state(State::WaitPassword, api_id, std::move(api_hash)); + state.wait_password_state_ = std::move(wait_password_state); + return state; + } + + static DbState wait_registration(int32 api_id, string api_hash, SendCodeHelper send_code_helper, + TermsOfService terms_of_service) { + DbState state(State::WaitRegistration, api_id, std::move(api_hash)); + state.send_code_helper_ = std::move(send_code_helper); + state.terms_of_service_ = std::move(terms_of_service); + return state; + } + + template + void store(StorerT &storer) const; + template + void parse(ParserT &parser); + + private: + DbState(State state, int32 api_id, string &&api_hash) + : state_(state), api_id_(api_id), api_hash_(std::move(api_hash)) { + auto state_timeout = [state] { + switch (state) { + case State::WaitPassword: + case State::WaitRegistration: + return 86400; + case State::WaitEmailAddress: + case State::WaitEmailCode: + case State::WaitCode: + case State::WaitQrCodeConfirmation: + return 5 * 60; + default: + UNREACHABLE(); + return 0; + } + }(); + expires_at_ = Time::now() + state_timeout; + } +}; + +template +void AuthManager::DbState::store(StorerT &storer) const { + using td::store; + bool has_terms_of_service = !terms_of_service_.get_id().empty(); + bool is_pbkdf2_supported = true; + bool is_srp_supported = true; + bool is_wait_registration_supported = true; + bool is_wait_registration_stores_phone_number = true; + bool is_wait_qr_code_confirmation_supported = true; + bool is_time_store_supported = true; + bool is_reset_email_address_supported = true; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_terms_of_service); + STORE_FLAG(is_pbkdf2_supported); + STORE_FLAG(is_srp_supported); + STORE_FLAG(is_wait_registration_supported); + STORE_FLAG(is_wait_registration_stores_phone_number); + STORE_FLAG(is_wait_qr_code_confirmation_supported); + STORE_FLAG(allow_apple_id_); + STORE_FLAG(allow_google_id_); + STORE_FLAG(is_time_store_supported); + STORE_FLAG(is_reset_email_address_supported); + END_STORE_FLAGS(); + store(state_, storer); + store(api_id_, storer); + store(api_hash_, storer); + store_time(expires_at_, storer); + + if (has_terms_of_service) { + store(terms_of_service_, storer); + } + + if (state_ == State::WaitEmailAddress) { + store(send_code_helper_, storer); + } else if (state_ == State::WaitEmailCode) { + store(send_code_helper_, storer); + store(email_address_, storer); + store(email_code_info_, storer); + store(reset_available_period_, storer); + store(reset_pending_date_, storer); + } else if (state_ == State::WaitCode) { + store(send_code_helper_, storer); + } else if (state_ == State::WaitQrCodeConfirmation) { + store(other_user_ids_, storer); + store(login_token_, storer); + store_time(login_token_expires_at_, storer); + } else if (state_ == State::WaitPassword) { + store(wait_password_state_, storer); + } else if (state_ == State::WaitRegistration) { + store(send_code_helper_, storer); + } else { + UNREACHABLE(); + } +} + +template +void AuthManager::DbState::parse(ParserT &parser) { + using td::parse; + bool has_terms_of_service = false; + bool is_pbkdf2_supported = false; + bool is_srp_supported = false; + bool is_wait_registration_supported = false; + bool is_wait_registration_stores_phone_number = false; + bool is_wait_qr_code_confirmation_supported = false; + bool is_time_store_supported = false; + bool is_reset_email_address_supported = false; + if (parser.version() >= static_cast(Version::AddTermsOfService)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_terms_of_service); + PARSE_FLAG(is_pbkdf2_supported); + PARSE_FLAG(is_srp_supported); + PARSE_FLAG(is_wait_registration_supported); + PARSE_FLAG(is_wait_registration_stores_phone_number); + PARSE_FLAG(is_wait_qr_code_confirmation_supported); + PARSE_FLAG(allow_apple_id_); + PARSE_FLAG(allow_google_id_); + PARSE_FLAG(is_time_store_supported); + PARSE_FLAG(is_reset_email_address_supported); + END_PARSE_FLAGS(); + } + if (!is_reset_email_address_supported) { + return parser.set_error("Have no reset email address support"); + } + CHECK(is_pbkdf2_supported); + CHECK(is_srp_supported); + CHECK(is_wait_registration_supported); + CHECK(is_wait_registration_stores_phone_number); + CHECK(is_wait_qr_code_confirmation_supported); + CHECK(is_time_store_supported); + + parse(state_, parser); + parse(api_id_, parser); + parse(api_hash_, parser); + parse_time(expires_at_, parser); + + if (has_terms_of_service) { + parse(terms_of_service_, parser); + } + + if (state_ == State::WaitEmailAddress) { + parse(send_code_helper_, parser); + } else if (state_ == State::WaitEmailCode) { + parse(send_code_helper_, parser); + parse(email_address_, parser); + parse(email_code_info_, parser); + parse(reset_available_period_, parser); + parse(reset_pending_date_, parser); + } else if (state_ == State::WaitCode) { + parse(send_code_helper_, parser); + } else if (state_ == State::WaitQrCodeConfirmation) { + parse(other_user_ids_, parser); + parse(login_token_, parser); + parse_time(login_token_expires_at_, parser); + } else if (state_ == State::WaitPassword) { + parse(wait_password_state_, parser); + } else if (state_ == State::WaitRegistration) { + parse(send_code_helper_, parser); + } else { + parser.set_error(PSTRING() << "Unexpected " << tag("state", static_cast(state_))); + } +} + AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> parent) : parent_(std::move(parent)), api_id_(api_id), api_hash_(api_hash) { string auth_str = G()->td_db()->get_binlog_pmc()->get("auth"); @@ -410,7 +649,7 @@ void AuthManager::register_user(uint64 query_id, string first_name, string last_ on_new_query(query_id); first_name = clean_name(first_name, MAX_NAME_LENGTH); if (first_name.empty()) { - return on_query_error(Status::Error(400, "First name must be non-empty")); + return on_current_query_error(Status::Error(400, "First name must be non-empty")); } last_name = clean_name(last_name, MAX_NAME_LENGTH); @@ -488,7 +727,7 @@ void AuthManager::log_out(uint64 query_id) { // TODO: send auth.cancelCode if state_ == State::WaitCode LOG(WARNING) << "Destroying auth keys by user request"; destroy_auth_keys(); - on_query_ok(); + on_current_query_ok(); } else { LOG(WARNING) << "Logging out by user request"; G()->td_db()->get_binlog_pmc()->set("auth", "logout"); @@ -548,7 +787,7 @@ void AuthManager::on_closing(bool destroy_flag) { void AuthManager::on_new_query(uint64 query_id) { if (query_id_ != 0) { - on_query_error(Status::Error(400, "Another authorization query has started")); + on_current_query_error(Status::Error(400, "Another authorization query has started")); } checking_password_ = false; net_query_id_ = 0; @@ -557,8 +796,10 @@ void AuthManager::on_new_query(uint64 query_id) { // TODO: cancel older net_query } -void AuthManager::on_query_error(Status status) { - CHECK(query_id_ != 0); +void AuthManager::on_current_query_error(Status status) { + if (query_id_ == 0) { + return; + } auto id = query_id_; query_id_ = 0; net_query_id_ = 0; @@ -571,8 +812,10 @@ void AuthManager::on_query_error(uint64 query_id, Status status) { send_closure(G()->td(), &Td::send_error, query_id, std::move(status)); } -void AuthManager::on_query_ok() { - CHECK(query_id_ != 0); +void AuthManager::on_current_query_ok() { + if (query_id_ == 0) { + return; + } auto id = query_id_; net_query_id_ = 0; net_query_type_ = NetQueryType::None; @@ -632,21 +875,21 @@ void AuthManager::on_sent_code(telegram_api::object_ptr(result->ok()); +void AuthManager::on_send_code_result(NetQueryPtr &&net_query) { + auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { - return on_query_error(r_sent_code.move_as_error()); + return on_current_query_error(r_sent_code.move_as_error()); } on_sent_code(r_sent_code.move_as_ok()); } -void AuthManager::on_send_email_code_result(NetQueryPtr &result) { - auto r_sent_code = fetch_result(result->ok()); +void AuthManager::on_send_email_code_result(NetQueryPtr &&net_query) { + auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { - return on_query_error(r_sent_code.move_as_error()); + return on_current_query_error(r_sent_code.move_as_error()); } auto sent_code = r_sent_code.move_as_ok(); @@ -654,23 +897,23 @@ void AuthManager::on_send_email_code_result(NetQueryPtr &result) { email_code_info_ = SentEmailCode(std::move(sent_code)); if (email_code_info_.is_empty()) { - return on_query_error(Status::Error(500, "Receive invalid response")); + return on_current_query_error(Status::Error(500, "Receive invalid response")); } update_state(State::WaitEmailCode, true); - on_query_ok(); + on_current_query_ok(); } -void AuthManager::on_verify_email_address_result(NetQueryPtr &result) { - auto r_email_verified = fetch_result(result->ok()); +void AuthManager::on_verify_email_address_result(NetQueryPtr &&net_query) { + auto r_email_verified = fetch_result(std::move(net_query)); if (r_email_verified.is_error()) { - return on_query_error(r_email_verified.move_as_error()); + return on_current_query_error(r_email_verified.move_as_error()); } auto email_verified = r_email_verified.move_as_ok(); LOG(INFO) << "Receive " << to_string(email_verified); if (email_verified->get_id() != telegram_api::account_emailVerifiedLogin::ID) { - return on_query_error(Status::Error(500, "Receive invalid response")); + return on_current_query_error(Status::Error(500, "Receive invalid response")); } reset_available_period_ = -1; reset_pending_date_ = -1; @@ -679,8 +922,8 @@ void AuthManager::on_verify_email_address_result(NetQueryPtr &result) { on_sent_code(std::move(verified_login->sent_code_)); } -void AuthManager::on_reset_email_address_result(NetQueryPtr &result) { - auto r_sent_code = fetch_result(result->ok()); +void AuthManager::on_reset_email_address_result(NetQueryPtr &&net_query) { + auto r_sent_code = fetch_result(std::move(net_query)); if (r_sent_code.is_error()) { if (reset_available_period_ > 0 && reset_pending_date_ == -1 && r_sent_code.error().message() == "TASK_ALREADY_EXISTS") { @@ -688,40 +931,33 @@ void AuthManager::on_reset_email_address_result(NetQueryPtr &result) { reset_available_period_ = -1; update_state(State::WaitEmailCode, true); } - return on_query_error(r_sent_code.move_as_error()); + return on_current_query_error(r_sent_code.move_as_error()); } on_sent_code(r_sent_code.move_as_ok()); } -void AuthManager::on_request_qr_code_result(NetQueryPtr &result, bool is_import) { - Status status; - if (result->is_ok()) { - auto r_login_token = fetch_result(result->ok()); - if (r_login_token.is_ok()) { - auto login_token = r_login_token.move_as_ok(); - - if (is_import) { - CHECK(DcId::is_valid(imported_dc_id_)); - G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_); - imported_dc_id_ = -1; - } +void AuthManager::on_request_qr_code_result(NetQueryPtr &&net_query, bool is_import) { + auto r_login_token = fetch_result(std::move(net_query)); + if (r_login_token.is_ok()) { + auto login_token = r_login_token.move_as_ok(); - on_get_login_token(std::move(login_token)); - return; + if (is_import) { + CHECK(DcId::is_valid(imported_dc_id_)); + G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_); + imported_dc_id_ = -1; } - status = r_login_token.move_as_error(); - } else { - status = std::move(result->error()); + on_get_login_token(std::move(login_token)); + return; } - CHECK(status.is_error()); + auto status = r_login_token.move_as_error(); LOG(INFO) << "Receive " << status << " for login token " << (is_import ? "import" : "export"); if (is_import) { imported_dc_id_ = -1; } if (query_id_ != 0) { - on_query_error(std::move(status)); + on_current_query_error(std::move(status)); } else { login_code_retry_delay_ = clamp(2 * login_code_retry_delay_, 1, 60); set_login_token_expires_at(Time::now() + login_code_retry_delay_); @@ -740,9 +976,7 @@ void AuthManager::on_get_login_token(tl_object_ptrtoken_.as_slice().str(); set_login_token_expires_at(Time::now() + td::max(token->expires_ - G()->server_time(), 1.0)); update_state(State::WaitQrCodeConfirmation, true); - if (query_id_ != 0) { - on_query_ok(); - } + on_current_query_ok(); break; } case telegram_api::auth_loginTokenMigrateTo::ID: { @@ -751,9 +985,7 @@ void AuthManager::on_get_login_token(tl_object_ptrdc_id_; return; } - if (query_id_ != 0) { - on_query_ok(); - } + on_current_query_ok(); imported_dc_id_ = token->dc_id_; start_net_query(NetQueryType::ImportQrCode, G()->net_query_creator().create_unauth( @@ -771,15 +1003,10 @@ void AuthManager::on_get_login_token(tl_object_ptr> r_password; - if (result->is_error()) { - r_password = std::move(result->error()); - } else { - r_password = fetch_result(result->ok()); - } +void AuthManager::on_get_password_result(NetQueryPtr &&net_query) { + auto r_password = fetch_result(std::move(net_query)); if (r_password.is_error() && query_id_ != 0) { - return on_query_error(r_password.move_as_error()); + return on_current_query_error(r_password.move_as_error()); } auto password = r_password.is_ok() ? r_password.move_as_ok() : nullptr; LOG(INFO) << "Receive password info: " << to_string(password); @@ -789,7 +1016,7 @@ void AuthManager::on_get_password_result(NetQueryPtr &result) { if (password != nullptr && password->current_algo_ != nullptr) { switch (password->current_algo_->get_id()) { case telegram_api::passwordKdfAlgoUnknown::ID: - return on_query_error(Status::Error(400, "Application update is needed to log in")); + return on_current_query_error(Status::Error(400, "Application update is needed to log in")); case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: { auto algo = move_tl_object_as( password->current_algo_); @@ -828,13 +1055,13 @@ void AuthManager::on_get_password_result(NetQueryPtr &result) { if (state_ == State::WaitPassword && checking_password_) { if (!new_password_.empty()) { if (r_new_password_state.is_error()) { - return on_query_error(r_new_password_state.move_as_error()); + return on_current_query_error(r_new_password_state.move_as_error()); } auto r_new_settings = PasswordManager::get_password_input_settings(std::move(new_password_), std::move(new_hint_), r_new_password_state.ok()); if (r_new_settings.is_error()) { - return on_query_error(r_new_settings.move_as_error()); + return on_current_query_error(r_new_settings.move_as_error()); } int32 flags = telegram_api::auth_recoverPassword::NEW_SETTINGS_MASK; @@ -853,76 +1080,66 @@ void AuthManager::on_get_password_result(NetQueryPtr &result) { G()->net_query_creator().create_unauth(telegram_api::auth_checkPassword(std::move(hash)))); } else { update_state(State::WaitPassword); - if (query_id_ != 0) { - on_query_ok(); - } + on_current_query_ok(); } } -void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) { - auto r_email_address_pattern = fetch_result(result->ok()); +void AuthManager::on_request_password_recovery_result(NetQueryPtr &&net_query) { + auto r_email_address_pattern = fetch_result(std::move(net_query)); if (r_email_address_pattern.is_error()) { - return on_query_error(r_email_address_pattern.move_as_error()); + return on_current_query_error(r_email_address_pattern.move_as_error()); } auto email_address_pattern = r_email_address_pattern.move_as_ok(); CHECK(email_address_pattern->get_id() == telegram_api::auth_passwordRecovery::ID); wait_password_state_.email_address_pattern_ = std::move(email_address_pattern->email_pattern_); update_state(State::WaitPassword, true); - on_query_ok(); + on_current_query_ok(); } -void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &result) { - auto r_success = fetch_result(result->ok()); +void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &&net_query) { + auto r_success = fetch_result(std::move(net_query)); if (r_success.is_error()) { - return on_query_error(r_success.move_as_error()); + return on_current_query_error(r_success.move_as_error()); } if (!r_success.ok()) { - return on_query_error(Status::Error(400, "Invalid recovery code")); + return on_current_query_error(Status::Error(400, "Invalid recovery code")); } - on_query_ok(); + on_current_query_ok(); } -void AuthManager::on_request_firebase_sms_result(NetQueryPtr &result) { - auto r_bool = fetch_result(result->ok()); +void AuthManager::on_request_firebase_sms_result(NetQueryPtr &&net_query) { + auto r_bool = fetch_result(std::move(net_query)); if (r_bool.is_error()) { - return on_query_error(r_bool.move_as_error()); + return on_current_query_error(r_bool.move_as_error()); } - on_query_ok(); + on_current_query_ok(); } -void AuthManager::on_authentication_result(NetQueryPtr &result, bool is_from_current_query) { - auto r_sign_in = fetch_result(result->ok()); +void AuthManager::on_authentication_result(NetQueryPtr &&net_query, bool is_from_current_query) { + auto r_sign_in = fetch_result(std::move(net_query)); if (r_sign_in.is_error()) { - if (is_from_current_query && query_id_ != 0) { - return on_query_error(r_sign_in.move_as_error()); + if (is_from_current_query) { + return on_current_query_error(r_sign_in.move_as_error()); } return; } on_get_authorization(r_sign_in.move_as_ok()); } -void AuthManager::on_log_out_result(NetQueryPtr &result) { - Status status; - if (result->is_ok()) { - auto r_log_out = fetch_result(result->ok()); - if (r_log_out.is_ok()) { - auto logged_out = r_log_out.move_as_ok(); - if (!logged_out->future_auth_token_.empty()) { - td_->option_manager_->set_option_string("authentication_token", - base64url_encode(logged_out->future_auth_token_.as_slice())); - } - } else { - status = r_log_out.move_as_error(); +void AuthManager::on_log_out_result(NetQueryPtr &&net_query) { + auto r_log_out = fetch_result(std::move(net_query)); + if (r_log_out.is_ok()) { + auto logged_out = r_log_out.move_as_ok(); + if (!logged_out->future_auth_token_.empty()) { + td_->option_manager_->set_option_string("authentication_token", + base64url_encode(logged_out->future_auth_token_.as_slice())); } - } else { - status = std::move(result->error()); + } else if (r_log_out.error().code() != 401) { + LOG(ERROR) << "Receive error for auth.logOut: " << r_log_out.error(); } - LOG_IF(ERROR, status.is_error() && status.code() != 401) << "Receive error for auth.logOut: " << status; // state_ will stay LoggingOut, so no queries will work. destroy_auth_keys(); - if (query_id_ != 0) { - on_query_ok(); - } + on_current_query_ok(); } void AuthManager::on_authorization_lost(string source) { if (state_ == State::LoggingOut && net_query_type_ == NetQueryType::LogOut) { @@ -955,51 +1172,36 @@ void AuthManager::destroy_auth_keys() { G()->td_db()->get_binlog_pmc()->force_sync(std::move(promise)); } -void AuthManager::on_delete_account_result(NetQueryPtr &result) { - Status status; - if (result->is_ok()) { - auto r_delete_account = fetch_result(result->ok()); - if (r_delete_account.is_ok()) { - if (!r_delete_account.ok()) { - // status = Status::Error(500, "Receive false as result of the request"); - } - } else { - status = r_delete_account.move_as_error(); +void AuthManager::on_delete_account_result(NetQueryPtr &&net_query) { + auto r_delete_account = fetch_result(std::move(net_query)); + if (r_delete_account.is_ok()) { + if (!r_delete_account.ok()) { + // status = Status::Error(500, "Receive false as result of the request"); } } else { - status = std::move(result->error()); - } - if (status.is_error() && status.message() != "USER_DEACTIVATED") { - LOG(WARNING) << "Request account.deleteAccount failed: " << status; - // TODO handle some errors - if (query_id_ != 0) { - on_query_error(std::move(status)); - } - } else { - destroy_auth_keys(); - if (query_id_ != 0) { - on_query_ok(); + auto status = r_delete_account.move_as_error(); + if (status.message() != "USER_DEACTIVATED") { + LOG(WARNING) << "Request account.deleteAccount failed: " << status; + // TODO handle some errors + return on_current_query_error(std::move(status)); } } + + destroy_auth_keys(); + on_current_query_ok(); } void AuthManager::on_get_authorization(tl_object_ptr auth_ptr) { if (state_ == State::Ok) { LOG(WARNING) << "Ignore duplicate auth.Authorization"; - if (query_id_ != 0) { - on_query_ok(); - } - return; + return on_current_query_ok(); } CHECK(auth_ptr != nullptr); if (auth_ptr->get_id() == telegram_api::auth_authorizationSignUpRequired::ID) { auto sign_up_required = telegram_api::move_object_as(auth_ptr); terms_of_service_ = TermsOfService(std::move(sign_up_required->terms_of_service_)); update_state(State::WaitRegistration); - if (query_id_ != 0) { - on_query_ok(); - } - return; + return on_current_query_ok(); } auto auth = telegram_api::move_object_as(auth_ptr); @@ -1015,13 +1217,20 @@ void AuthManager::on_get_authorization(tl_object_ptrcontacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization", true); + if (auth->user_->get_id() == telegram_api::user::ID) { + auto *user = static_cast(auth->user_.get()); + int32 mask = 1 << 10; + if ((user->flags_ & mask) == 0) { + LOG(ERROR) << "Receive invalid authorization for " << to_string(auth->user_); + user->flags_ |= mask; + user->self_ = true; + } + } + td_->contacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization"); update_state(State::Ok, true); if (!td_->contacts_manager_->get_my_id().is_valid()) { - LOG(ERROR) << "Server doesn't send proper authorization"; - if (query_id_ != 0) { - on_query_error(Status::Error(500, "Server doesn't send proper authorization")); - } + LOG(ERROR) << "Server didsn't send proper authorization"; + on_current_query_error(Status::Error(500, "Server didn't send proper authorization")); log_out(0); return; } @@ -1040,6 +1249,7 @@ void AuthManager::on_get_authorization(tl_object_ptrdialog_filter_manager_->on_authorization_success(); // must be after MessagesManager::on_authorization_success() // to have folders created td_->notification_manager_->init(); + td_->reaction_manager_->init(); td_->stickers_manager_->init(); td_->theme_manager_->init(); td_->top_dialog_manager_->init(); @@ -1053,40 +1263,36 @@ void AuthManager::on_get_authorization(tl_object_ptrset_is_bot_online(true); } send_closure(G()->config_manager(), &ConfigManager::request_config, false); - if (query_id_ != 0) { - on_query_ok(); - } + on_current_query_ok(); } -void AuthManager::on_result(NetQueryPtr result) { - SCOPE_EXIT { - result->clear(); - }; +void AuthManager::on_result(NetQueryPtr net_query) { NetQueryType type = NetQueryType::None; - LOG(INFO) << "Receive result of query " << result->id() << ", expecting " << net_query_id_ << " with type " + LOG(INFO) << "Receive result of query " << net_query->id() << ", expecting " << net_query_id_ << " with type " << static_cast(net_query_type_); - if (result->id() == net_query_id_) { + if (net_query->id() == net_query_id_) { net_query_id_ = 0; type = net_query_type_; net_query_type_ = NetQueryType::None; - if (result->is_error()) { + if (net_query->is_error()) { if ((type == NetQueryType::SendCode || type == NetQueryType::SendEmailCode || type == NetQueryType::VerifyEmailAddress || type == NetQueryType::SignIn || type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) && - result->error().code() == 401 && result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) { + net_query->error().code() == 401 && net_query->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) { auto dc_id = DcId::main(); if (type == NetQueryType::ImportQrCode) { CHECK(DcId::is_valid(imported_dc_id_)); dc_id = DcId::internal(imported_dc_id_); } + net_query->clear(); start_net_query(NetQueryType::GetPassword, G()->net_query_creator().create_unauth(telegram_api::account_getPassword(), dc_id)); return; } - if (result->error().message() == CSlice("PHONE_NUMBER_BANNED")) { - LOG(PLAIN) - << "Your phone number was banned for suspicious activity. If you think that this is a mistake, please " - "write to recover@telegram.org your phone number and other details to recover the account."; + if (net_query->error().message() == CSlice("PHONE_NUMBER_BANNED")) { + LOG(ERROR) << "Your phone number was banned for suspicious activity. If you think that this is a mistake, " + "please try to log in from an official mobile app and send a email to recover the account by " + "following instructions provided by the app."; } if (type != NetQueryType::LogOut && type != NetQueryType::DeleteAccount) { if (query_id_ != 0) { @@ -1097,69 +1303,71 @@ void AuthManager::on_result(NetQueryPtr result) { was_qr_code_request_ = false; was_check_bot_token_ = false; } - on_query_error(std::move(result->error())); + on_current_query_error(net_query->move_as_error()); return; } if (type != NetQueryType::RequestQrCode && type != NetQueryType::ImportQrCode && type != NetQueryType::GetPassword) { - LOG(INFO) << "Ignore error for net query of type " << static_cast(net_query_type_); - return; + LOG(INFO) << "Ignore error for net query of type " << static_cast(type); + type = NetQueryType::None; } } } - } else if (result->is_ok() && result->ok_tl_constructor() == telegram_api::auth_authorization::ID) { + } else if (net_query->is_ok() && net_query->ok_tl_constructor() == telegram_api::auth_authorization::ID) { type = NetQueryType::Authentication; } switch (type) { case NetQueryType::None: - result->ignore(); + net_query->clear(); break; case NetQueryType::SignIn: case NetQueryType::SignUp: case NetQueryType::BotAuthentication: case NetQueryType::CheckPassword: case NetQueryType::RecoverPassword: - on_authentication_result(result, true); + on_authentication_result(std::move(net_query), true); break; case NetQueryType::Authentication: - on_authentication_result(result, false); + on_authentication_result(std::move(net_query), false); break; case NetQueryType::SendCode: - on_send_code_result(result); + on_send_code_result(std::move(net_query)); break; case NetQueryType::SendEmailCode: - on_send_email_code_result(result); + on_send_email_code_result(std::move(net_query)); break; case NetQueryType::VerifyEmailAddress: - on_verify_email_address_result(result); + on_verify_email_address_result(std::move(net_query)); break; case NetQueryType::ResetEmailAddress: - on_reset_email_address_result(result); + on_reset_email_address_result(std::move(net_query)); break; case NetQueryType::RequestQrCode: - on_request_qr_code_result(result, false); + on_request_qr_code_result(std::move(net_query), false); break; case NetQueryType::ImportQrCode: - on_request_qr_code_result(result, true); + on_request_qr_code_result(std::move(net_query), true); break; case NetQueryType::GetPassword: - on_get_password_result(result); + on_get_password_result(std::move(net_query)); break; case NetQueryType::RequestPasswordRecovery: - on_request_password_recovery_result(result); + on_request_password_recovery_result(std::move(net_query)); break; case NetQueryType::CheckPasswordRecoveryCode: - on_check_password_recovery_code_result(result); + on_check_password_recovery_code_result(std::move(net_query)); break; case NetQueryType::RequestFirebaseSms: - on_request_firebase_sms_result(result); + on_request_firebase_sms_result(std::move(net_query)); break; case NetQueryType::LogOut: - on_log_out_result(result); + on_log_out_result(std::move(net_query)); break; case NetQueryType::DeleteAccount: - on_delete_account_result(result); + on_delete_account_result(std::move(net_query)); break; + default: + UNREACHABLE(); } } diff --git a/td/telegram/AuthManager.h b/td/telegram/AuthManager.h index a3bc17ac6996..d525cc4a11d7 100644 --- a/td/telegram/AuthManager.h +++ b/td/telegram/AuthManager.h @@ -21,7 +21,6 @@ #include "td/utils/common.h" #include "td/utils/Status.h" -#include "td/utils/Time.h" namespace td { @@ -78,6 +77,7 @@ class AuthManager final : public NetActor { DestroyingKeys, Closing } state_ = State::None; + enum class NetQueryType : int32 { None, SignIn, @@ -118,116 +118,7 @@ class AuthManager final : public NetActor { void parse(ParserT &parser); }; - struct DbState { - State state_; - int32 api_id_; - string api_hash_; - double expires_at_; - - // WaitEmailAddress and WaitEmailCode - bool allow_apple_id_ = false; - bool allow_google_id_ = false; - - // WaitEmailCode - string email_address_; - SentEmailCode email_code_info_; - int32 reset_available_period_ = -1; - int32 reset_pending_date_ = -1; - - // WaitEmailAddress, WaitEmailCode, WaitCode and WaitRegistration - SendCodeHelper send_code_helper_; - - // WaitQrCodeConfirmation - vector other_user_ids_; - string login_token_; - double login_token_expires_at_ = 0; - - // WaitPassword - WaitPasswordState wait_password_state_; - - // WaitRegistration - TermsOfService terms_of_service_; - - DbState() = default; - - static DbState wait_email_address(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, - SendCodeHelper send_code_helper) { - DbState state(State::WaitEmailAddress, api_id, std::move(api_hash)); - state.send_code_helper_ = std::move(send_code_helper); - state.allow_apple_id_ = allow_apple_id; - state.allow_google_id_ = allow_google_id; - return state; - } - - static DbState wait_email_code(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id, - string email_address, SentEmailCode email_code_info, int32 reset_available_period, - int32 reset_pending_date, SendCodeHelper send_code_helper) { - DbState state(State::WaitEmailCode, api_id, std::move(api_hash)); - state.send_code_helper_ = std::move(send_code_helper); - state.allow_apple_id_ = allow_apple_id; - state.allow_google_id_ = allow_google_id; - state.email_address_ = std::move(email_address); - state.email_code_info_ = std::move(email_code_info); - state.reset_available_period_ = reset_available_period; - state.reset_pending_date_ = reset_pending_date; - return state; - } - - static DbState wait_code(int32 api_id, string api_hash, SendCodeHelper send_code_helper) { - DbState state(State::WaitCode, api_id, std::move(api_hash)); - state.send_code_helper_ = std::move(send_code_helper); - return state; - } - - static DbState wait_qr_code_confirmation(int32 api_id, string api_hash, vector other_user_ids, - string login_token, double login_token_expires_at) { - DbState state(State::WaitQrCodeConfirmation, api_id, std::move(api_hash)); - state.other_user_ids_ = std::move(other_user_ids); - state.login_token_ = std::move(login_token); - state.login_token_expires_at_ = login_token_expires_at; - return state; - } - - static DbState wait_password(int32 api_id, string api_hash, WaitPasswordState wait_password_state) { - DbState state(State::WaitPassword, api_id, std::move(api_hash)); - state.wait_password_state_ = std::move(wait_password_state); - return state; - } - - static DbState wait_registration(int32 api_id, string api_hash, SendCodeHelper send_code_helper, - TermsOfService terms_of_service) { - DbState state(State::WaitRegistration, api_id, std::move(api_hash)); - state.send_code_helper_ = std::move(send_code_helper); - state.terms_of_service_ = std::move(terms_of_service); - return state; - } - - template - void store(StorerT &storer) const; - template - void parse(ParserT &parser); - - private: - DbState(State state, int32 api_id, string &&api_hash) - : state_(state), api_id_(api_id), api_hash_(std::move(api_hash)) { - auto state_timeout = [state] { - switch (state) { - case State::WaitPassword: - case State::WaitRegistration: - return 86400; - case State::WaitEmailAddress: - case State::WaitEmailCode: - case State::WaitCode: - case State::WaitQrCodeConfirmation: - return 5 * 60; - default: - UNREACHABLE(); - return 0; - } - }(); - expires_at_ = Time::now() + state_timeout; - } - }; + struct DbState; bool load_state(); void save_state(); @@ -290,8 +181,8 @@ class AuthManager final : public NetActor { vector pending_get_authorization_state_requests_; void on_new_query(uint64 query_id); - void on_query_error(Status status); - void on_query_ok(); + void on_current_query_error(Status status); + void on_current_query_ok(); void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query); static void on_update_login_token_static(void *td); @@ -307,22 +198,22 @@ class AuthManager final : public NetActor { void on_sent_code(telegram_api::object_ptr &&sent_code_ptr); - void on_send_code_result(NetQueryPtr &result); - void on_send_email_code_result(NetQueryPtr &result); - void on_verify_email_address_result(NetQueryPtr &result); - void on_reset_email_address_result(NetQueryPtr &result); - void on_request_qr_code_result(NetQueryPtr &result, bool is_import); - void on_get_password_result(NetQueryPtr &result); - void on_request_password_recovery_result(NetQueryPtr &result); - void on_check_password_recovery_code_result(NetQueryPtr &result); - void on_request_firebase_sms_result(NetQueryPtr &result); - void on_authentication_result(NetQueryPtr &result, bool is_from_current_query); - void on_log_out_result(NetQueryPtr &result); - void on_delete_account_result(NetQueryPtr &result); + void on_send_code_result(NetQueryPtr &&net_query); + void on_send_email_code_result(NetQueryPtr &&net_query); + void on_verify_email_address_result(NetQueryPtr &&net_query); + void on_reset_email_address_result(NetQueryPtr &&net_query); + void on_request_qr_code_result(NetQueryPtr &&net_query, bool is_import); + void on_get_password_result(NetQueryPtr &&net_query); + void on_request_password_recovery_result(NetQueryPtr &&net_query); + void on_check_password_recovery_code_result(NetQueryPtr &&net_query); + void on_request_firebase_sms_result(NetQueryPtr &&net_query); + void on_authentication_result(NetQueryPtr &&net_query, bool is_from_current_query); + void on_log_out_result(NetQueryPtr &&net_query); + void on_delete_account_result(NetQueryPtr &&net_query); void on_get_login_token(tl_object_ptr login_token); void on_get_authorization(tl_object_ptr auth_ptr); - void on_result(NetQueryPtr result) final; + void on_result(NetQueryPtr net_query) final; void update_state(State new_state, bool force = false, bool should_save_state = true); tl_object_ptr get_authorization_state_object(State authorization_state) const; diff --git a/td/telegram/AuthManager.hpp b/td/telegram/AuthManager.hpp index 60cddc8c280d..5b640e1be5ae 100644 --- a/td/telegram/AuthManager.hpp +++ b/td/telegram/AuthManager.hpp @@ -8,12 +8,6 @@ #include "td/telegram/AuthManager.h" -#include "td/telegram/logevent/LogEventHelper.h" -#include "td/telegram/SendCodeHelper.hpp" -#include "td/telegram/Version.h" - -#include "td/utils/format.h" -#include "td/utils/SliceBuilder.h" #include "td/utils/tl_helpers.h" namespace td { @@ -48,126 +42,4 @@ void AuthManager::WaitPasswordState::parse(ParserT &parser) { parse(has_secure_values_, parser); } -template -void AuthManager::DbState::store(StorerT &storer) const { - using td::store; - bool has_terms_of_service = !terms_of_service_.get_id().empty(); - bool is_pbkdf2_supported = true; - bool is_srp_supported = true; - bool is_wait_registration_supported = true; - bool is_wait_registration_stores_phone_number = true; - bool is_wait_qr_code_confirmation_supported = true; - bool is_time_store_supported = true; - bool is_reset_email_address_supported = true; - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_terms_of_service); - STORE_FLAG(is_pbkdf2_supported); - STORE_FLAG(is_srp_supported); - STORE_FLAG(is_wait_registration_supported); - STORE_FLAG(is_wait_registration_stores_phone_number); - STORE_FLAG(is_wait_qr_code_confirmation_supported); - STORE_FLAG(allow_apple_id_); - STORE_FLAG(allow_google_id_); - STORE_FLAG(is_time_store_supported); - STORE_FLAG(is_reset_email_address_supported); - END_STORE_FLAGS(); - store(state_, storer); - store(api_id_, storer); - store(api_hash_, storer); - store_time(expires_at_, storer); - - if (has_terms_of_service) { - store(terms_of_service_, storer); - } - - if (state_ == State::WaitEmailAddress) { - store(send_code_helper_, storer); - } else if (state_ == State::WaitEmailCode) { - store(send_code_helper_, storer); - store(email_address_, storer); - store(email_code_info_, storer); - store(reset_available_period_, storer); - store(reset_pending_date_, storer); - } else if (state_ == State::WaitCode) { - store(send_code_helper_, storer); - } else if (state_ == State::WaitQrCodeConfirmation) { - store(other_user_ids_, storer); - store(login_token_, storer); - store_time(login_token_expires_at_, storer); - } else if (state_ == State::WaitPassword) { - store(wait_password_state_, storer); - } else if (state_ == State::WaitRegistration) { - store(send_code_helper_, storer); - } else { - UNREACHABLE(); - } -} - -template -void AuthManager::DbState::parse(ParserT &parser) { - using td::parse; - bool has_terms_of_service = false; - bool is_pbkdf2_supported = false; - bool is_srp_supported = false; - bool is_wait_registration_supported = false; - bool is_wait_registration_stores_phone_number = false; - bool is_wait_qr_code_confirmation_supported = false; - bool is_time_store_supported = false; - bool is_reset_email_address_supported = false; - if (parser.version() >= static_cast(Version::AddTermsOfService)) { - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_terms_of_service); - PARSE_FLAG(is_pbkdf2_supported); - PARSE_FLAG(is_srp_supported); - PARSE_FLAG(is_wait_registration_supported); - PARSE_FLAG(is_wait_registration_stores_phone_number); - PARSE_FLAG(is_wait_qr_code_confirmation_supported); - PARSE_FLAG(allow_apple_id_); - PARSE_FLAG(allow_google_id_); - PARSE_FLAG(is_time_store_supported); - PARSE_FLAG(is_reset_email_address_supported); - END_PARSE_FLAGS(); - } - if (!is_reset_email_address_supported) { - return parser.set_error("Have no reset email address support"); - } - CHECK(is_pbkdf2_supported); - CHECK(is_srp_supported); - CHECK(is_wait_registration_supported); - CHECK(is_wait_registration_stores_phone_number); - CHECK(is_wait_qr_code_confirmation_supported); - CHECK(is_time_store_supported); - - parse(state_, parser); - parse(api_id_, parser); - parse(api_hash_, parser); - parse_time(expires_at_, parser); - - if (has_terms_of_service) { - parse(terms_of_service_, parser); - } - - if (state_ == State::WaitEmailAddress) { - parse(send_code_helper_, parser); - } else if (state_ == State::WaitEmailCode) { - parse(send_code_helper_, parser); - parse(email_address_, parser); - parse(email_code_info_, parser); - parse(reset_available_period_, parser); - parse(reset_pending_date_, parser); - } else if (state_ == State::WaitCode) { - parse(send_code_helper_, parser); - } else if (state_ == State::WaitQrCodeConfirmation) { - parse(other_user_ids_, parser); - parse(login_token_, parser); - parse_time(login_token_expires_at_, parser); - } else if (state_ == State::WaitPassword) { - parse(wait_password_state_, parser); - } else if (state_ == State::WaitRegistration) { - parse(send_code_helper_, parser); - } else { - parser.set_error(PSTRING() << "Unexpected " << tag("state", static_cast(state_))); - } -} - } // namespace td diff --git a/td/telegram/AutoDownloadSettings.cpp b/td/telegram/AutoDownloadSettings.cpp index bba89065cc3b..e56d3a51f531 100644 --- a/td/telegram/AutoDownloadSettings.cpp +++ b/td/telegram/AutoDownloadSettings.cpp @@ -25,6 +25,7 @@ static td_api::object_ptr convert_auto_download_se auto disabled = (flags & telegram_api::autoDownloadSettings::DISABLED_MASK) != 0; auto video_preload_large = (flags & telegram_api::autoDownloadSettings::VIDEO_PRELOAD_LARGE_MASK) != 0; auto audio_preload_next = (flags & telegram_api::autoDownloadSettings::AUDIO_PRELOAD_NEXT_MASK) != 0; + auto stories_preload = (flags & telegram_api::autoDownloadSettings::STORIES_PRELOAD_MASK) != 0; auto phonecalls_less_data = (flags & telegram_api::autoDownloadSettings::PHONECALLS_LESS_DATA_MASK) != 0; constexpr int32 MAX_PHOTO_SIZE = 10 * (1 << 20) /* 10 MB */; constexpr int64 MAX_DOCUMENT_SIZE = (static_cast(1) << 52); @@ -32,7 +33,7 @@ static td_api::object_ptr convert_auto_download_se !disabled, clamp(settings->photo_size_max_, static_cast(0), MAX_PHOTO_SIZE), clamp(settings->video_size_max_, static_cast(0), MAX_DOCUMENT_SIZE), clamp(settings->file_size_max_, static_cast(0), MAX_DOCUMENT_SIZE), settings->video_upload_maxbitrate_, - video_preload_large, audio_preload_next, phonecalls_less_data); + video_preload_large, audio_preload_next, stories_preload, phonecalls_less_data); } class GetAutoDownloadSettingsQuery final : public Td::ResultHandler { @@ -64,7 +65,7 @@ class GetAutoDownloadSettingsQuery final : public Td::ResultHandler { } }; -telegram_api::object_ptr get_input_auto_download_settings( +static telegram_api::object_ptr get_input_auto_download_settings( const AutoDownloadSettings &settings) { int32 flags = 0; if (!settings.is_enabled) { @@ -76,12 +77,16 @@ telegram_api::object_ptr get_input_auto_down if (settings.preload_next_audio) { flags |= telegram_api::autoDownloadSettings::AUDIO_PRELOAD_NEXT_MASK; } + if (settings.preload_stories) { + flags |= telegram_api::autoDownloadSettings::STORIES_PRELOAD_MASK; + } if (settings.use_less_data_for_calls) { flags |= telegram_api::autoDownloadSettings::PHONECALLS_LESS_DATA_MASK; } return telegram_api::make_object( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, settings.max_photo_file_size, - settings.max_video_file_size, settings.max_other_file_size, settings.video_upload_bitrate); + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + settings.max_photo_file_size, settings.max_video_file_size, settings.max_other_file_size, + settings.video_upload_bitrate, 0, 0); } class SaveAutoDownloadSettingsQuery final : public Td::ResultHandler { @@ -128,6 +133,7 @@ AutoDownloadSettings get_auto_download_settings(const td_api::object_ptris_auto_download_enabled_; result.preload_large_videos = settings->preload_large_videos_; result.preload_next_audio = settings->preload_next_audio_; + result.preload_stories = settings->preload_stories_; result.use_less_data_for_calls = settings->use_less_data_for_calls_; return result; } diff --git a/td/telegram/AutoDownloadSettings.h b/td/telegram/AutoDownloadSettings.h index 58b8e31d61d3..03ab63e0101a 100644 --- a/td/telegram/AutoDownloadSettings.h +++ b/td/telegram/AutoDownloadSettings.h @@ -25,6 +25,7 @@ class AutoDownloadSettings { bool is_enabled = false; bool preload_large_videos = false; bool preload_next_audio = false; + bool preload_stories = false; bool use_less_data_for_calls = false; }; diff --git a/td/telegram/AutosaveManager.cpp b/td/telegram/AutosaveManager.cpp index 3b163ef3ed03..0af476299867 100644 --- a/td/telegram/AutosaveManager.cpp +++ b/td/telegram/AutosaveManager.cpp @@ -14,6 +14,7 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/db/SqliteKeyValueAsync.h" diff --git a/td/telegram/BackgroundManager.cpp b/td/telegram/BackgroundManager.cpp index b33e58b0c764..084142abda9c 100644 --- a/td/telegram/BackgroundManager.cpp +++ b/td/telegram/BackgroundManager.cpp @@ -22,6 +22,7 @@ #include "td/telegram/PhotoFormat.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/db/SqliteKeyValueAsync.h" @@ -224,8 +225,9 @@ class UploadBackgroundQuery final : public Td::ResultHandler { void on_error(Status status) final { CHECK(file_id_.is_valid()); - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - // TODO td_->background_manager_->on_upload_background_file_part_missing(file_id_, to_integer(status.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + // TODO td_->background_manager_->on_upload_background_file_parts_missing(file_id_, std::move(bad_parts)); // return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { diff --git a/td/telegram/BackgroundType.cpp b/td/telegram/BackgroundType.cpp index a8c93f7acc0e..8c89ed4ba8eb 100644 --- a/td/telegram/BackgroundType.cpp +++ b/td/telegram/BackgroundType.cpp @@ -468,8 +468,8 @@ telegram_api::object_ptr BackgroundType::get_in flags |= telegram_api::wallPaperSettings::INTENSITY_MASK; } return telegram_api::make_object( - flags, false /*ignored*/, false /*ignored*/, fill_.top_color_, fill_.bottom_color_, fill_.third_color_, - fill_.fourth_color_, intensity_, fill_.rotation_angle_); + flags, is_blurred_, is_moving_, fill_.top_color_, fill_.bottom_color_, fill_.third_color_, fill_.fourth_color_, + intensity_, fill_.rotation_angle_); } } // namespace td diff --git a/td/telegram/BlockListId.h b/td/telegram/BlockListId.h new file mode 100644 index 000000000000..9610a4f4f83e --- /dev/null +++ b/td/telegram/BlockListId.h @@ -0,0 +1,99 @@ +// +// 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/td_api.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class BlockListId { + enum class Type : int32 { None = -1, Main, Stories }; + Type type_ = Type::None; + + friend struct BlockListIdHash; + + explicit BlockListId(Type type) : type_(type) { + } + + public: + BlockListId() = default; + + BlockListId(bool is_blocked, bool is_blocked_for_stories) + : type_(is_blocked ? Type::Main : (is_blocked_for_stories ? Type::Stories : Type::None)) { + } + + explicit BlockListId(const td_api::object_ptr &block_list) { + if (block_list == nullptr) { + return; + } + switch (block_list->get_id()) { + case td_api::blockListMain::ID: + type_ = Type::Main; + break; + case td_api::blockListStories::ID: + type_ = Type::Stories; + break; + default: + UNREACHABLE(); + } + } + + static BlockListId main() { + return BlockListId(Type::Main); + } + + static BlockListId stories() { + return BlockListId(Type::Stories); + } + + td_api::object_ptr get_block_list_object() const { + switch (type_) { + case Type::None: + return nullptr; + case Type::Main: + return td_api::make_object(); + case Type::Stories: + return td_api::make_object(); + default: + UNREACHABLE(); + } + } + + bool is_valid() const { + return type_ == Type::Main || type_ == Type::Stories; + } + + bool operator==(const BlockListId &other) const { + return type_ == other.type_; + } + + bool operator!=(const BlockListId &other) const { + return type_ != other.type_; + } +}; + +struct BlockListIdHash { + uint32 operator()(BlockListId block_list_id) const { + return Hash()(static_cast(block_list_id.type_)); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, BlockListId block_list_id) { + if (block_list_id == BlockListId::main()) { + return string_builder << "MainBlockList"; + } + if (block_list_id == BlockListId::stories()) { + return string_builder << "StoriesBlockList"; + } + return string_builder << "InvalidBlockList"; +} + +} // namespace td diff --git a/td/telegram/BotCommand.cpp b/td/telegram/BotCommand.cpp index 0711f6ffb1c9..1c5ca30460c3 100644 --- a/td/telegram/BotCommand.cpp +++ b/td/telegram/BotCommand.cpp @@ -11,6 +11,7 @@ #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" diff --git a/td/telegram/BotInfoManager.cpp b/td/telegram/BotInfoManager.cpp index cbbb1d41dffd..1672504798d6 100644 --- a/td/telegram/BotInfoManager.cpp +++ b/td/telegram/BotInfoManager.cpp @@ -13,6 +13,7 @@ #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UpdatesManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -89,6 +90,72 @@ class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler { } }; +class CanBotSendMessageQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit CanBotSendMessageQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId bot_user_id) { + auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query( + G()->net_query_creator().create(telegram_api::bots_canSendMessage(r_input_user.move_as_ok()), {{bot_user_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()); + } + + if (result_ptr.ok()) { + promise_.set_value(Unit()); + } else { + promise_.set_error(Status::Error(404, "Not Found")); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class AllowBotSendMessageQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit AllowBotSendMessageQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId bot_user_id) { + auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create(telegram_api::bots_allowSendMessage(r_input_user.move_as_ok()), + {{bot_user_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 AllowBotSendMessageQuery: " << 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)); + } +}; + static Result> get_bot_input_user(const Td *td, UserId bot_user_id) { if (td->auth_manager_->is_bot()) { if (bot_user_id != UserId() && bot_user_id != td->contacts_manager_->get_my_id()) { @@ -161,7 +228,7 @@ class SetBotInfoQuery final : public Td::ResultHandler { if (set_info_) { invalidate_bot_info(); if (!td_->auth_manager_->is_bot()) { - return td_->contacts_manager_->reload_user_full(bot_user_id_, std::move(promise_)); + return td_->contacts_manager_->reload_user_full(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); } } if (set_name_) { @@ -317,6 +384,14 @@ void BotInfoManager::set_default_channel_administrator_rights(AdministratorRight td_->create_handler(std::move(promise))->send(administrator_rights); } +void BotInfoManager::can_bot_send_messages(UserId bot_user_id, Promise &&promise) { + td_->create_handler(std::move(promise))->send(bot_user_id); +} + +void BotInfoManager::allow_bot_to_send_messages(UserId bot_user_id, Promise &&promise) { + td_->create_handler(std::move(promise))->send(bot_user_id); +} + void BotInfoManager::add_pending_set_query(UserId bot_user_id, const string &language_code, int type, const string &value, Promise &&promise) { pending_set_bot_info_queries_.emplace_back(bot_user_id, language_code, type, value, std::move(promise)); diff --git a/td/telegram/BotInfoManager.h b/td/telegram/BotInfoManager.h index 4c5f99fb9822..808660559cd9 100644 --- a/td/telegram/BotInfoManager.h +++ b/td/telegram/BotInfoManager.h @@ -26,6 +26,10 @@ class BotInfoManager final : public Actor { void set_default_channel_administrator_rights(AdministratorRights administrator_rights, Promise &&promise); + void can_bot_send_messages(UserId bot_user_id, Promise &&promise); + + void allow_bot_to_send_messages(UserId bot_user_id, Promise &&promise); + void set_bot_name(UserId bot_user_id, const string &language_code, const string &name, Promise &&promise); void get_bot_name(UserId bot_user_id, const string &language_code, Promise &&promise); diff --git a/td/telegram/BotMenuButton.cpp b/td/telegram/BotMenuButton.cpp index e46806852975..a5fffec7a613 100644 --- a/td/telegram/BotMenuButton.cpp +++ b/td/telegram/BotMenuButton.cpp @@ -12,6 +12,7 @@ #include "td/telegram/LinkManager.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" diff --git a/td/telegram/CallActor.cpp b/td/telegram/CallActor.cpp index 9e330f54d93f..6476cbc3ae68 100644 --- a/td/telegram/CallActor.cpp +++ b/td/telegram/CallActor.cpp @@ -17,7 +17,7 @@ #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/NotificationManager.h" #include "td/telegram/Td.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/utils/algorithm.h" @@ -458,8 +458,9 @@ void CallActor::on_save_log_query_result(FileId file_id, Promise promise, auto res = fetch_result(std::move(r_net_query)); if (res.is_error()) { auto error = res.move_as_error(); - if (begins_with(error.message(), "FILE_PART_") && ends_with(error.message(), "_MISSING")) { - // TODO on_upload_log_file_part_missing(file_id, to_integer(error.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(error); + if (!bad_parts.empty()) { + // TODO on_upload_log_file_parts_missing(file_id, std::move(bad_parts)); // return; } return promise.set_error(std::move(error)); @@ -475,8 +476,25 @@ void CallActor::on_save_log_query_result(FileId file_id, Promise promise, // Requests void CallActor::update_call(tl_object_ptr call) { LOG(INFO) << "Receive " << to_string(call); - Status status; - downcast_call(*call, [&](auto &call) { status = this->do_update_call(call); }); + auto status = [&] { + switch (call->get_id()) { + case telegram_api::phoneCallEmpty::ID: + return do_update_call(static_cast(*call)); + case telegram_api::phoneCallWaiting::ID: + return do_update_call(static_cast(*call)); + case telegram_api::phoneCallRequested::ID: + return do_update_call(static_cast(*call)); + case telegram_api::phoneCallAccepted::ID: + return do_update_call(static_cast(*call)); + case telegram_api::phoneCall::ID: + return do_update_call(static_cast(*call)); + case telegram_api::phoneCallDiscarded::ID: + return do_update_call(static_cast(*call)); + default: + UNREACHABLE(); + return Status::OK(); + } + }(); if (status.is_error()) { LOG(INFO) << "Receive error " << status << ", while handling update " << to_string(call); on_error(std::move(status)); @@ -490,11 +508,11 @@ void CallActor::update_call_inner(tl_object_ptr c update_call(std::move(call->phone_call_)); } -Status CallActor::do_update_call(telegram_api::phoneCallEmpty &call) { +Status CallActor::do_update_call(const telegram_api::phoneCallEmpty &call) { return Status::Error(400, "Call is finished"); } -Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) { +Status CallActor::do_update_call(const telegram_api::phoneCallWaiting &call) { if (state_ != State::WaitRequestResult && state_ != State::WaitAcceptResult) { return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call)); } @@ -518,9 +536,7 @@ Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) { is_video_ |= call.video_; call_admin_user_id_ = UserId(call.admin_id_); // call_participant_user_id_ = UserId(call.participant_id_); - if (call_id_promise_) { - call_id_promise_.set_value(std::move(call.id_)); - } + on_get_call_id(); if (!call_state_.is_created) { call_state_.is_created = true; @@ -530,7 +546,7 @@ Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) { return Status::OK(); } -Status CallActor::do_update_call(telegram_api::phoneCallRequested &call) { +Status CallActor::do_update_call(const telegram_api::phoneCallRequested &call) { if (state_ != State::Empty) { return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call)); } @@ -541,9 +557,7 @@ Status CallActor::do_update_call(telegram_api::phoneCallRequested &call) { is_video_ |= call.video_; call_admin_user_id_ = UserId(call.admin_id_); // call_participant_user_id_ = UserId(call.participant_id_); - if (call_id_promise_) { - call_id_promise_.set_value(std::move(call.id_)); - } + on_get_call_id(); dh_handshake_.set_g_a_hash(call.g_a_hash_.as_slice()); state_ = State::SendAcceptQuery; @@ -562,7 +576,7 @@ tl_object_ptr CallActor::get_input_phone_call(cons return make_tl_object(call_id_, call_access_hash_); } -Status CallActor::do_update_call(telegram_api::phoneCallAccepted &call) { +Status CallActor::do_update_call(const telegram_api::phoneCallAccepted &call) { if (state_ != State::WaitRequestResult) { return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call)); } @@ -574,9 +588,7 @@ Status CallActor::do_update_call(telegram_api::phoneCallAccepted &call) { is_call_id_inited_ = true; call_admin_user_id_ = UserId(call.admin_id_); // call_participant_user_id_ = UserId(call.participant_id_); - if (call_id_promise_) { - call_id_promise_.set_value(std::move(call.id_)); - } + on_get_call_id(); } is_video_ |= call.video_; dh_handshake_.set_g_a(call.g_b_.as_slice()); @@ -596,7 +608,7 @@ void CallActor::on_begin_exchanging_key() { set_timeout_in(timeout); } -Status CallActor::do_update_call(telegram_api::phoneCall &call) { +Status CallActor::do_update_call(const telegram_api::phoneCall &call) { if (state_ != State::WaitAcceptResult && state_ != State::WaitConfirmResult) { return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call)); } @@ -628,12 +640,20 @@ Status CallActor::do_update_call(telegram_api::phoneCall &call) { return Status::OK(); } -Status CallActor::do_update_call(telegram_api::phoneCallDiscarded &call) { +Status CallActor::do_update_call(const telegram_api::phoneCallDiscarded &call) { LOG(DEBUG) << "Do update call to Discarded"; on_call_discarded(get_call_discard_reason(call.reason_), call.need_rating_, call.need_debug_, call.video_); return Status::OK(); } +void CallActor::on_get_call_id() { + if (call_id_promise_) { + int64 call_id = call_id_; + call_id_promise_.set_value(std::move(call_id)); + call_id_promise_ = {}; + } +} + void CallActor::on_call_discarded(CallDiscardReason reason, bool need_rating, bool need_debug, bool is_video) { state_ = State::Discarded; is_video_ |= is_video; diff --git a/td/telegram/CallActor.h b/td/telegram/CallActor.h index 5114341f1d86..7d15504d95fc 100644 --- a/td/telegram/CallActor.h +++ b/td/telegram/CallActor.h @@ -160,12 +160,14 @@ class CallActor final : public NetQueryCallback { void on_dh_config(Result> r_dh_config, bool dummy); void do_load_dh_config(Promise> promise); - Status do_update_call(telegram_api::phoneCallEmpty &call); - Status do_update_call(telegram_api::phoneCallWaiting &call); - Status do_update_call(telegram_api::phoneCallRequested &call); - Status do_update_call(telegram_api::phoneCallAccepted &call); - Status do_update_call(telegram_api::phoneCall &call); - Status do_update_call(telegram_api::phoneCallDiscarded &call); + Status do_update_call(const telegram_api::phoneCallEmpty &call); + Status do_update_call(const telegram_api::phoneCallWaiting &call); + Status do_update_call(const telegram_api::phoneCallRequested &call); + Status do_update_call(const telegram_api::phoneCallAccepted &call); + Status do_update_call(const telegram_api::phoneCall &call); + Status do_update_call(const telegram_api::phoneCallDiscarded &call); + + void on_get_call_id(); void send_received_query(); void on_received_query_result(Result r_net_query); diff --git a/td/telegram/CallManager.cpp b/td/telegram/CallManager.cpp index aa2c907c7ce1..ddc322ae8d30 100644 --- a/td/telegram/CallManager.cpp +++ b/td/telegram/CallManager.cpp @@ -6,7 +6,7 @@ // #include "td/telegram/CallManager.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/utils/common.h" #include "td/utils/logging.h" @@ -21,8 +21,25 @@ CallManager::CallManager(ActorShared<> parent) : parent_(std::move(parent)) { } void CallManager::update_call(Update call) { - int64 call_id = 0; - downcast_call(*call->phone_call_, [&](auto &update) { call_id = update.id_; }); + auto call_id = [phone_call = call->phone_call_.get()] { + switch (phone_call->get_id()) { + case telegram_api::phoneCallEmpty::ID: + return static_cast(phone_call)->id_; + case telegram_api::phoneCallWaiting::ID: + return static_cast(phone_call)->id_; + case telegram_api::phoneCallRequested::ID: + return static_cast(phone_call)->id_; + case telegram_api::phoneCallAccepted::ID: + return static_cast(phone_call)->id_; + case telegram_api::phoneCall::ID: + return static_cast(phone_call)->id_; + case telegram_api::phoneCallDiscarded::ID: + return static_cast(phone_call)->id_; + default: + UNREACHABLE(); + return static_cast(0); + } + }(); LOG(DEBUG) << "Receive UpdateCall for " << call_id; auto &info = call_info_[call_id]; diff --git a/td/telegram/ChainId.h b/td/telegram/ChainId.h index 16a006000236..749d9c4f2bbe 100644 --- a/td/telegram/ChainId.h +++ b/td/telegram/ChainId.h @@ -13,6 +13,7 @@ #include "td/telegram/FullMessageId.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/PollId.h" +#include "td/telegram/StoryFullId.h" #include "td/telegram/UserId.h" #include "td/utils/common.h" @@ -53,6 +54,10 @@ class ChainId { ChainId(UserId user_id) : ChainId(DialogId(user_id)) { } + ChainId(StoryFullId story_full_id) : ChainId(story_full_id.get_dialog_id()) { + id += static_cast(story_full_id.get_story_id().get()) << 10; + } + uint64 get() const { return id; } diff --git a/td/telegram/ChatReactions.cpp b/td/telegram/ChatReactions.cpp index e0e577b1f937..ac2c6370ebc4 100644 --- a/td/telegram/ChatReactions.cpp +++ b/td/telegram/ChatReactions.cpp @@ -6,8 +6,6 @@ // #include "td/telegram/ChatReactions.h" -#include "td/telegram/MessageReaction.h" - #include "td/utils/algorithm.h" namespace td { @@ -27,10 +25,9 @@ ChatReactions::ChatReactions(telegram_api::object_ptr(chat_reactions_ptr); - reactions_ = - transform(chat_reactions->reactions_, [](const telegram_api::object_ptr &reaction) { - return get_message_reaction_string(reaction); - }); + reaction_types_ = transform( + chat_reactions->reactions_, + [](const telegram_api::object_ptr &reaction) { return ReactionType(reaction); }); break; } default: @@ -50,9 +47,9 @@ ChatReactions::ChatReactions(td_api::object_ptr break; case td_api::chatAvailableReactionsSome::ID: { auto chat_reactions = move_tl_object_as(chat_reactions_ptr); - reactions_ = transform(chat_reactions->reactions_, [](const td_api::object_ptr &reaction) { - return get_message_reaction_string(reaction); - }); + reaction_types_ = + transform(chat_reactions->reactions_, + [](const td_api::object_ptr &reaction) { return ReactionType(reaction); }); break; } default: @@ -60,30 +57,33 @@ ChatReactions::ChatReactions(td_api::object_ptr } } -ChatReactions ChatReactions::get_active_reactions(const FlatHashMap &active_reaction_pos) const { +ChatReactions ChatReactions::get_active_reactions( + const FlatHashMap &active_reaction_pos) const { ChatReactions result = *this; - if (!reactions_.empty()) { + if (!reaction_types_.empty()) { CHECK(!allow_all_); CHECK(!allow_custom_); - td::remove_if(result.reactions_, - [&](const string &reaction) { return !is_active_reaction(reaction, active_reaction_pos); }); + td::remove_if(result.reaction_types_, [&](const ReactionType &reaction_type) { + return !reaction_type.is_active_reaction(active_reaction_pos); + }); } return result; } -bool ChatReactions::is_allowed_reaction(const string &reaction) const { +bool ChatReactions::is_allowed_reaction_type(const ReactionType &reaction_type) const { CHECK(!allow_all_); - if (allow_custom_ && is_custom_reaction(reaction)) { + if (allow_custom_ && reaction_type.is_custom_reaction()) { return true; } - return td::contains(reactions_, reaction); + return td::contains(reaction_types_, reaction_type); } td_api::object_ptr ChatReactions::get_chat_available_reactions_object() const { if (allow_all_) { return td_api::make_object(); } - return td_api::make_object(transform(reactions_, get_reaction_type_object)); + return td_api::make_object(transform( + reaction_types_, [](const ReactionType &reaction_type) { return reaction_type.get_reaction_type_object(); })); } telegram_api::object_ptr ChatReactions::get_input_chat_reactions() const { @@ -92,17 +92,18 @@ telegram_api::object_ptr ChatReactions::get_input_c if (allow_custom_) { flags |= telegram_api::chatReactionsAll::ALLOW_CUSTOM_MASK; } - return telegram_api::make_object(flags, false /*ignored*/); + return telegram_api::make_object(flags, allow_custom_); } - if (!reactions_.empty()) { - return telegram_api::make_object(transform(reactions_, get_input_reaction)); + if (!reaction_types_.empty()) { + return telegram_api::make_object(transform( + reaction_types_, [](const ReactionType &reaction_type) { return reaction_type.get_input_reaction(); })); } return telegram_api::make_object(); } bool operator==(const ChatReactions &lhs, const ChatReactions &rhs) { // don't compare allow_custom_ - return lhs.reactions_ == rhs.reactions_ && lhs.allow_all_ == rhs.allow_all_; + return lhs.reaction_types_ == rhs.reaction_types_ && lhs.allow_all_ == rhs.allow_all_; } StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions) { @@ -112,7 +113,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &re } return string_builder << "AllRegularReactions"; } - return string_builder << '[' << reactions.reactions_ << ']'; + return string_builder << '[' << reactions.reaction_types_ << ']'; } } // namespace td diff --git a/td/telegram/ChatReactions.h b/td/telegram/ChatReactions.h index bf92d37c00c3..d0ebe7703246 100644 --- a/td/telegram/ChatReactions.h +++ b/td/telegram/ChatReactions.h @@ -6,24 +6,24 @@ // #pragma once +#include "td/telegram/ReactionType.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/StringBuilder.h" -#include "td/utils/tl_helpers.h" namespace td { struct ChatReactions { - vector reactions_; - bool allow_all_ = false; // implies empty reactions - bool allow_custom_ = false; // implies allow_all + vector reaction_types_; + bool allow_all_ = false; // implies empty reaction_types_ + bool allow_custom_ = false; // implies allow_all_ ChatReactions() = default; - explicit ChatReactions(vector &&reactions) : reactions_(std::move(reactions)) { + explicit ChatReactions(vector &&reactions) : reaction_types_(std::move(reactions)) { } explicit ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr); @@ -33,43 +33,24 @@ struct ChatReactions { ChatReactions(bool allow_all, bool allow_custom) : allow_all_(allow_all), allow_custom_(allow_custom) { } - ChatReactions get_active_reactions(const FlatHashMap &active_reaction_pos) const; + ChatReactions get_active_reactions( + const FlatHashMap &active_reaction_pos) const; - bool is_allowed_reaction(const string &reaction) const; + bool is_allowed_reaction_type(const ReactionType &reaction) const; telegram_api::object_ptr get_input_chat_reactions() const; td_api::object_ptr get_chat_available_reactions_object() const; bool empty() const { - return reactions_.empty() && !allow_all_; + return reaction_types_.empty() && !allow_all_; } template - void store(StorerT &storer) const { - bool has_reactions = !reactions_.empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(allow_all_); - STORE_FLAG(allow_custom_); - STORE_FLAG(has_reactions); - END_STORE_FLAGS(); - if (has_reactions) { - td::store(reactions_, storer); - } - } + void store(StorerT &storer) const; template - void parse(ParserT &parser) { - bool has_reactions; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(allow_all_); - PARSE_FLAG(allow_custom_); - PARSE_FLAG(has_reactions); - END_PARSE_FLAGS(); - if (has_reactions) { - td::parse(reactions_, parser); - } - } + void parse(ParserT &parser); }; bool operator==(const ChatReactions &lhs, const ChatReactions &rhs); diff --git a/td/telegram/ChatReactions.hpp b/td/telegram/ChatReactions.hpp new file mode 100644 index 000000000000..8bae2e4178c0 --- /dev/null +++ b/td/telegram/ChatReactions.hpp @@ -0,0 +1,43 @@ +// +// 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/ChatReactions.h" +#include "td/telegram/ReactionType.hpp" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void ChatReactions::store(StorerT &storer) const { + bool has_reactions = !reaction_types_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(allow_all_); + STORE_FLAG(allow_custom_); + STORE_FLAG(has_reactions); + END_STORE_FLAGS(); + if (has_reactions) { + td::store(reaction_types_, storer); + } +} + +template +void ChatReactions::parse(ParserT &parser) { + bool has_reactions; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(allow_all_); + PARSE_FLAG(allow_custom_); + PARSE_FLAG(has_reactions); + END_PARSE_FLAGS(); + if (has_reactions) { + td::parse(reaction_types_, parser); + } +} + +} // namespace td diff --git a/td/telegram/ClientActor.cpp b/td/telegram/ClientActor.cpp index d1d8e6d98970..7a8e2bdb5222 100644 --- a/td/telegram/ClientActor.cpp +++ b/td/telegram/ClientActor.cpp @@ -23,7 +23,7 @@ void ClientActor::start_up() { } void ClientActor::request(uint64 id, td_api::object_ptr request) { - send_closure(td_, &Td::request, id, std::move(request)); + send_closure_later(td_, &Td::request, id, std::move(request)); } ClientActor::~ClientActor() = default; diff --git a/td/telegram/ClientJson.cpp b/td/telegram/ClientJson.cpp index f817f70d80de..dffcba6eaf28 100644 --- a/td/telegram/ClientJson.cpp +++ b/td/telegram/ClientJson.cpp @@ -40,9 +40,8 @@ static std::pair, string> to_request(Slice } string extra; - if (has_json_object_field(json_value.get_object(), "@extra")) { - extra = json_encode( - get_json_object_field(json_value.get_object(), "@extra", JsonValue::Type::Null).move_as_ok()); + if (json_value.get_object().has_field("@extra")) { + extra = json_encode(json_value.get_object().extract_field("@extra")); } td_api::object_ptr func; diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index 47d530dc44bf..7eb3e3d40389 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -8,11 +8,11 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/ConnectionState.h" +#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/JsonValue.h" #include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" -#include "td/telegram/MessageReaction.h" #include "td/telegram/misc.h" #include "td/telegram/net/AuthDataShared.h" #include "td/telegram/net/ConnectionCreator.h" @@ -24,6 +24,7 @@ #include "td/telegram/net/PublicRsaKeyShared.h" #include "td/telegram/net/Session.h" #include "td/telegram/Premium.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" @@ -269,7 +270,7 @@ static ActorOwn<> get_simple_config_dns(Slice address, Slice host, Promise get_simple_config_dns(Slice address, Slice host, Promise get_simple_config_firebase_remote_config(Promise return Status::Error("Expected JSON object"); } auto &entries_object = json.get_object(); - TRY_RESULT(config, get_json_object_string_field(entries_object, "ipconfigv3", false)); + TRY_RESULT(config, entries_object.get_required_string_field("ipconfigv3")); return std::move(config); }; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firebaseremoteconfig.googleapis.com", @@ -381,8 +382,10 @@ ActorOwn<> get_simple_config_firebase_firestore(Promise prom if (json.type() != JsonValue::Type::Object) { return Status::Error("Expected JSON object"); } - TRY_RESULT(data, get_json_object_field(json.get_object(), "data", JsonValue::Type::Object, false)); - TRY_RESULT(config, get_json_object_string_field(data.get_object(), "stringValue", false)); + auto &json_object = json.get_object(); + TRY_RESULT(data, json_object.extract_required_field("data", JsonValue::Type::Object)); + auto &data_object = data.get_object(); + TRY_RESULT(config, data_object.get_required_string_field("stringValue")); return std::move(config); }; return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firestore.googleapis.com", {}, @@ -522,10 +525,10 @@ static ActorOwn<> get_full_config(DcOption option, Promiseis_test_dc()) { int_dc_id += 10000; } - session_ = create_actor("ConfigSession", std::move(session_callback), std::move(auth_data), raw_dc_id, - int_dc_id, false /*is_primary*/, false /*is_main*/, true /*use_pfs*/, - false /*is_cdn*/, false /*need_destroy_auth_key*/, mtproto::AuthKey(), - std::vector()); + session_ = create_actor( + "ConfigSession", std::move(session_callback), std::move(auth_data), raw_dc_id, int_dc_id, + false /*is_primary*/, false /*is_main*/, true /*use_pfs*/, false /*persist_tmp_auth_key*/, false /*is_cdn*/, + false /*need_destroy_auth_key*/, mtproto::AuthKey(), std::vector()); auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), DcId::empty()); query->total_timeout_limit_ = 60 * 60 * 24; query->set_callback(actor_shared(this)); @@ -938,7 +941,8 @@ void ConfigManager::start_up() { send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, load_dc_options_update()); auto expire_time = load_config_expire_time(); - if (expire_time.is_in_past() || true) { + bool reload_config_on_restart = true; + if (expire_time.is_in_past() || reload_config_on_restart) { request_config(false); } else { expire_time_ = expire_time; @@ -1081,41 +1085,6 @@ void ConfigManager::set_content_settings(bool ignore_sensitive_content_restricti } } -void ConfigManager::get_global_privacy_settings(Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get(); - if (auth_manager == nullptr || !auth_manager->is_authorized() || auth_manager->is_bot()) { - return promise.set_value(Unit()); - } - - get_global_privacy_settings_queries_.push_back(std::move(promise)); - if (get_global_privacy_settings_queries_.size() == 1) { - G()->net_query_dispatcher().dispatch_with_callback( - G()->net_query_creator().create(telegram_api::account_getGlobalPrivacySettings()), actor_shared(this, 5)); - } -} - -void ConfigManager::set_archive_and_mute(bool archive_and_mute, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (archive_and_mute) { - remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}); - } - - last_set_archive_and_mute_ = archive_and_mute; - auto &queries = set_archive_and_mute_queries_[archive_and_mute]; - queries.push_back(std::move(promise)); - if (!is_set_archive_and_mute_request_sent_) { - is_set_archive_and_mute_request_sent_ = true; - int32 flags = telegram_api::globalPrivacySettings::ARCHIVE_AND_MUTE_NEW_NONCONTACT_PEERS_MASK; - auto settings = make_tl_object(flags, archive_and_mute); - G()->net_query_dispatcher().dispatch_with_callback( - G()->net_query_creator().create(telegram_api::account_setGlobalPrivacySettings(std::move(settings))), - actor_shared(this, 6 + static_cast(archive_and_mute))); - } -} - void ConfigManager::on_dc_options_update(DcOptions dc_options) { save_dc_options_update(dc_options); if (!dc_options.dc_options.empty()) { @@ -1143,13 +1112,6 @@ void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sen } } -void ConfigManager::do_set_archive_and_mute(bool archive_and_mute) { - if (archive_and_mute) { - remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}); - } - G()->set_option_boolean("archive_and_mute_new_chats_from_unknown_users", archive_and_mute); -} - void ConfigManager::hide_suggested_action(SuggestedAction suggested_action) { remove_suggested_action(suggested_actions_, suggested_action); } @@ -1176,7 +1138,7 @@ void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, P } } -void ConfigManager::on_result(NetQueryPtr res) { +void ConfigManager::on_result(NetQueryPtr net_query) { auto token = get_link_token(); if (token >= 100 && token <= 200) { auto type = static_cast(token - 100); @@ -1187,7 +1149,7 @@ void ConfigManager::on_result(NetQueryPtr res) { CHECK(dismiss_suggested_action_request_count_ >= promises.size()); dismiss_suggested_action_request_count_ -= promises.size(); - auto result_ptr = fetch_result(std::move(res)); + auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(promises, result_ptr.move_as_error()); return; @@ -1198,50 +1160,10 @@ void ConfigManager::on_result(NetQueryPtr res) { set_promises(promises); return; } - if (token == 6 || token == 7) { - is_set_archive_and_mute_request_sent_ = false; - bool archive_and_mute = (token == 7); - auto result_ptr = fetch_result(std::move(res)); - if (result_ptr.is_error()) { - fail_promises(set_archive_and_mute_queries_[archive_and_mute], result_ptr.move_as_error()); - } else { - if (last_set_archive_and_mute_ == archive_and_mute) { - do_set_archive_and_mute(archive_and_mute); - } - - set_promises(set_archive_and_mute_queries_[archive_and_mute]); - } - - if (!set_archive_and_mute_queries_[!archive_and_mute].empty()) { - if (archive_and_mute == last_set_archive_and_mute_) { - set_promises(set_archive_and_mute_queries_[!archive_and_mute]); - } else { - set_archive_and_mute(!archive_and_mute, Auto()); - } - } - return; - } - if (token == 5) { - auto result_ptr = fetch_result(std::move(res)); - if (result_ptr.is_error()) { - fail_promises(get_global_privacy_settings_queries_, result_ptr.move_as_error()); - return; - } - - auto result = result_ptr.move_as_ok(); - if ((result->flags_ & telegram_api::globalPrivacySettings::ARCHIVE_AND_MUTE_NEW_NONCONTACT_PEERS_MASK) != 0) { - do_set_archive_and_mute(result->archive_and_mute_new_noncontact_peers_); - } else { - LOG(ERROR) << "Receive wrong response: " << to_string(result); - } - - set_promises(get_global_privacy_settings_queries_); - return; - } if (token == 3 || token == 4) { is_set_content_settings_request_sent_ = false; bool ignore_sensitive_content_restrictions = (token == 4); - auto result_ptr = fetch_result(std::move(res)); + auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(set_content_settings_queries_[ignore_sensitive_content_restrictions], result_ptr.move_as_error()); } else { @@ -1263,7 +1185,7 @@ void ConfigManager::on_result(NetQueryPtr res) { return; } if (token == 2) { - auto result_ptr = fetch_result(std::move(res)); + auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(get_content_settings_queries_, result_ptr.move_as_error()); return; @@ -1282,7 +1204,7 @@ void ConfigManager::on_result(NetQueryPtr res) { auto unit_promises = std::move(reget_app_config_queries_); reget_app_config_queries_.clear(); CHECK(!promises.empty() || !unit_promises.empty()); - auto result_ptr = fetch_result(std::move(res)); + auto result_ptr = fetch_result(std::move(net_query)); if (result_ptr.is_error()) { fail_promises(promises, result_ptr.error().clone()); fail_promises(unit_promises, result_ptr.move_as_error()); @@ -1318,7 +1240,7 @@ void ConfigManager::on_result(NetQueryPtr res) { CHECK(token == 8 || token == 9); CHECK(config_sent_cnt_ > 0); config_sent_cnt_--; - auto r_config = fetch_result(std::move(res)); + auto r_config = fetch_result(std::move(net_query)); if (r_config.is_error()) { if (!G()->close_flag()) { LOG(WARNING) << "Failed to get config: " << r_config.error(); @@ -1458,10 +1380,12 @@ void ConfigManager::process_config(tl_object_ptr config) { } else { options.set_option_string("animation_search_bot_username", config->gif_search_username_); } - if (config->venue_search_username_.empty()) { - options.set_option_empty("venue_search_bot_username"); - } else { - options.set_option_string("venue_search_bot_username", config->venue_search_username_); + if (!options.have_option("venue_search_bot_username")) { + if (config->venue_search_username_.empty()) { + options.set_option_empty("venue_search_bot_username"); + } else { + options.set_option_string("venue_search_bot_username", config->venue_search_username_); + } } if (config->img_search_username_.empty()) { options.set_option_empty("photo_search_bot_username"); @@ -1480,9 +1404,9 @@ void ConfigManager::process_config(tl_object_ptr config) { options.set_option_integer("notification_default_delay_ms", fix_timeout_ms(config->notify_default_delay_ms_)); if (is_from_main_dc && !options.have_option("default_reaction_need_sync")) { - auto reaction_str = get_message_reaction_string(config->reactions_default_); - if (!reaction_str.empty()) { - options.set_option_string("default_reaction", reaction_str); + ReactionType reaction_type(config->reactions_default_); + if (!reaction_type.is_empty()) { + options.set_option_string("default_reaction", reaction_type.get_string()); } } @@ -1517,9 +1441,6 @@ void ConfigManager::process_config(tl_object_ptr config) { !options.have_option("ignore_sensitive_content_restrictions")) { get_content_settings(Auto()); } - if (!options.have_option("archive_and_mute_new_chats_from_unknown_users")) { - get_global_privacy_settings(Auto()); - } } } @@ -1559,15 +1480,19 @@ void ConfigManager::process_app_config(tl_object_ptr &c bool premium_gift_attach_menu_icon = false; bool premium_gift_text_field_icon = false; int32 dialog_filter_update_period = 300; + // bool archive_all_stories = false; + int32 story_viewers_expire_period = 86400; + int64 stories_changelog_user_id = ContactsManager::get_service_notifications_user_id().get(); if (config->get_id() == telegram_api::jsonObject::ID) { for (auto &key_value : static_cast(config.get())->value_) { Slice key = key_value->key_; telegram_api::JSONValue *value = key_value->value_.get(); if (key == "default_emoji_statuses_stickerset_id" || key == "forum_upgrade_participants_min" || key == "getfile_experimental_params" || key == "message_animated_emoji_max" || - key == "reactions_in_chat_max" || key == "stickers_emoji_cache_time" || key == "test" || - key == "upload_max_fileparts_default" || key == "upload_max_fileparts_premium" || - key == "wallet_blockchain_name" || key == "wallet_config" || key == "wallet_enabled") { + key == "reactions_in_chat_max" || key == "stickers_emoji_cache_time" || + key == "stories_export_nopublic_link" || key == "test" || key == "upload_max_fileparts_default" || + key == "upload_max_fileparts_premium" || key == "wallet_blockchain_name" || key == "wallet_config" || + key == "wallet_enabled") { continue; } if (key == "ignore_restriction_reasons") { @@ -1728,17 +1653,13 @@ void ConfigManager::process_app_config(tl_object_ptr &c if (key == "pending_suggestions") { if (value->get_id() == telegram_api::jsonArray::ID) { auto actions = std::move(static_cast(value)->value_); - const bool archive_and_mute = G()->get_option_boolean("archive_and_mute_new_chats_from_unknown_users"); auto otherwise_relogin_days = G()->get_option_integer("otherwise_relogin_days"); for (auto &action : actions) { auto action_str = get_json_value_string(std::move(action), key); SuggestedAction suggested_action(action_str); if (!suggested_action.is_empty()) { - if (archive_and_mute && - suggested_action == SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}) { - LOG(INFO) << "Skip EnableArchiveAndMuteNewChats suggested action"; - } else if (otherwise_relogin_days > 0 && - suggested_action == SuggestedAction{SuggestedAction::Type::SetPassword}) { + if (otherwise_relogin_days > 0 && + suggested_action == SuggestedAction{SuggestedAction::Type::SetPassword}) { LOG(INFO) << "Skip SetPassword suggested action"; } else { suggested_actions.push_back(suggested_action); @@ -1955,6 +1876,47 @@ void ConfigManager::process_app_config(tl_object_ptr &c dialog_filter_update_period = get_json_value_int(std::move(key_value->value_), key); continue; } + if (key == "stories_all_hidden") { + // archive_all_stories = get_json_value_bool(std::move(key_value->value_), key); + continue; + } + if (key == "story_viewers_expire_period") { + story_viewers_expire_period = get_json_value_int(std::move(key_value->value_), key); + continue; + } + if (key == "stories_changelog_user_id") { + stories_changelog_user_id = get_json_value_long(std::move(key_value->value_), key); + continue; + } + if (key == "stories_venue_search_username") { + G()->set_option_string("venue_search_bot_username", get_json_value_string(std::move(key_value->value_), key)); + continue; + } + if (key == "stories_stealth_past_period") { + G()->set_option_integer("story_stealth_mode_past_period", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stories_stealth_future_period") { + G()->set_option_integer("story_stealth_mode_future_period", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stories_stealth_cooldown_period") { + G()->set_option_integer("story_stealth_mode_cooldown_period", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stories_entities") { + G()->set_option_boolean("need_premium_for_story_caption_entities", + get_json_value_string(std::move(key_value->value_), key) == "premium"); + continue; + } + if (key == "authorization_autoconfirm_period") { + G()->set_option_integer("authorization_autoconfirm_period", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } new_values.push_back(std::move(key_value)); } @@ -2055,6 +2017,14 @@ void ConfigManager::process_app_config(tl_object_ptr &c options.set_option_integer("chat_folder_count_max", options.get_option_integer("dialog_filters_limit_premium", 20)); options.set_option_integer("chat_folder_chosen_chat_count_max", options.get_option_integer("dialog_filters_chats_limit_premium", 200)); + options.set_option_integer("active_story_count_max", + options.get_option_integer("story_expiring_limit_premium", 100)); + options.set_option_integer("weekly_sent_story_count_max", + options.get_option_integer("stories_sent_weekly_limit_premium", 700)); + options.set_option_integer("monthly_sent_story_count_max", + options.get_option_integer("stories_sent_monthly_limit_premium", 3000)); + options.set_option_integer("story_caption_length_max", + options.get_option_integer("story_caption_length_limit_premium", 2048)); options.set_option_integer("bio_length_max", options.get_option_integer("about_length_limit_premium", 140)); options.set_option_integer("saved_animations_limit", options.get_option_integer("saved_gifs_limit_premium", 400)); options.set_option_integer("favorite_stickers_limit", @@ -2066,11 +2036,18 @@ void ConfigManager::process_app_config(tl_object_ptr &c options.set_option_integer("chat_folder_invite_link_count_max", options.get_option_integer("chatlist_invites_limit_premium", 20)); options.set_option_integer("added_shareable_chat_folder_count_max", - options.get_option_integer("chatlist_invites_limit_premium", 20)); + options.get_option_integer("chatlists_joined_limit_premium", 20)); } else { options.set_option_integer("chat_folder_count_max", options.get_option_integer("dialog_filters_limit_default", 10)); options.set_option_integer("chat_folder_chosen_chat_count_max", options.get_option_integer("dialog_filters_chats_limit_default", 100)); + options.set_option_integer("active_story_count_max", options.get_option_integer("story_expiring_limit_default", 3)); + options.set_option_integer("weekly_sent_story_count_max", + options.get_option_integer("stories_sent_weekly_limit_default", 7)); + options.set_option_integer("monthly_sent_story_count_max", + options.get_option_integer("stories_sent_monthly_limit_default", 30)); + options.set_option_integer("story_caption_length_max", + options.get_option_integer("story_caption_length_limit_default", 200)); options.set_option_integer("bio_length_max", options.get_option_integer("about_length_limit_default", 70)); options.set_option_integer("saved_animations_limit", options.get_option_integer("saved_gifs_limit_default", 200)); options.set_option_integer("favorite_stickers_limit", @@ -2082,7 +2059,7 @@ void ConfigManager::process_app_config(tl_object_ptr &c options.set_option_integer("chat_folder_invite_link_count_max", options.get_option_integer("chatlist_invites_limit_default", 3)); options.set_option_integer("added_shareable_chat_folder_count_max", - options.get_option_integer("chatlist_invites_limit_default", 2)); + options.get_option_integer("chatlists_joined_limit_default", 2)); } if (!is_premium_available) { @@ -2120,12 +2097,27 @@ void ConfigManager::process_app_config(tl_object_ptr &c } else { options.set_option_empty("gift_premium_from_input_field"); } + if (stories_changelog_user_id != ContactsManager::get_service_notifications_user_id().get()) { + options.set_option_integer("stories_changelog_user_id", stories_changelog_user_id); + } else { + options.set_option_empty("stories_changelog_user_id"); + } + + if (story_viewers_expire_period >= 0) { + options.set_option_integer("story_viewers_expiration_delay", story_viewers_expire_period); + } + + if (!options.get_option_boolean("need_synchronize_archive_all_stories")) { + // options.set_option_boolean("archive_all_stories", archive_all_stories); + } + options.set_option_empty("archive_all_stories"); options.set_option_integer("stickers_premium_by_emoji_num", stickers_premium_by_emoji_num); options.set_option_integer("stickers_normal_by_emoji_per_premium_num", stickers_normal_by_emoji_per_premium_num); options.set_option_empty("default_ton_blockchain_config"); options.set_option_empty("default_ton_blockchain_name"); + options.set_option_empty("story_viewers_expire_period"); // do not update suggested actions while changing content settings or dismissing an action if (!is_set_content_settings_request_sent_ && dismiss_suggested_action_request_count_ == 0) { diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 2455d610478d..c00e0ab7799e 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -92,10 +92,6 @@ class ConfigManager final : public NetQueryCallback { void set_content_settings(bool ignore_sensitive_content_restrictions, Promise &&promise); - void get_global_privacy_settings(Promise &&promise); - - void set_archive_and_mute(bool archive_and_mute, Promise &&promise); - void hide_suggested_action(SuggestedAction suggested_action); void dismiss_suggested_action(SuggestedAction suggested_action, Promise &&promise); @@ -106,7 +102,7 @@ class ConfigManager final : public NetQueryCallback { private: struct AppConfig { - static constexpr int32 CURRENT_VERSION = 2; + static constexpr int32 CURRENT_VERSION = 13; int32 version_ = 0; int32 hash_ = 0; telegram_api::object_ptr config_; @@ -137,11 +133,6 @@ class ConfigManager final : public NetQueryCallback { bool is_set_content_settings_request_sent_ = false; bool last_set_content_settings_ = false; - vector> get_global_privacy_settings_queries_; - vector> set_archive_and_mute_queries_[2]; - bool is_set_archive_and_mute_request_sent_ = false; - bool last_set_archive_and_mute_ = false; - AppConfig app_config_; vector suggested_actions_; @@ -156,7 +147,7 @@ class ConfigManager final : public NetQueryCallback { void loop() final; void try_stop(); - void on_result(NetQueryPtr res) final; + void on_result(NetQueryPtr net_query) final; void request_config_from_dc_impl(DcId dc_id, bool reopen_sessions); void process_config(tl_object_ptr config); @@ -167,8 +158,6 @@ class ConfigManager final : public NetQueryCallback { void do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions); - void do_set_archive_and_mute(bool archive_and_mute); - static Timestamp load_config_expire_time(); static void save_config_expire(Timestamp timestamp); static void save_dc_options_update(const DcOptions &dc_options); diff --git a/td/telegram/Contact.cpp b/td/telegram/Contact.cpp index 4792bc230df8..ea0dd6266316 100644 --- a/td/telegram/Contact.cpp +++ b/td/telegram/Contact.cpp @@ -8,8 +8,6 @@ #include "td/telegram/misc.h" #include "td/telegram/secret_api.h" -#include "td/telegram/td_api.h" -#include "td/telegram/telegram_api.h" #include "td/utils/common.h" diff --git a/td/telegram/Contact.h b/td/telegram/Contact.h index 227643c2939e..196c6cecd628 100644 --- a/td/telegram/Contact.h +++ b/td/telegram/Contact.h @@ -136,8 +136,8 @@ struct ContactEqual { struct ContactHash { uint32 operator()(const Contact &contact) const { - return (Hash()(contact.phone_number_) * 2023654985u + Hash()(contact.first_name_)) * 2023654985u + - Hash()(contact.last_name_); + return combine_hashes(combine_hashes(Hash()(contact.phone_number_), Hash()(contact.first_name_)), + Hash()(contact.last_name_)); } }; diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 3369f71de946..2ef1b65f7dfd 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -6,9 +6,9 @@ // #include "td/telegram/ContactsManager.h" -#include "td/telegram/Account.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/BlockListId.h" #include "td/telegram/BotMenuButton.h" #include "td/telegram/ChannelParticipantFilter.h" #include "td/telegram/ConfigManager.h" @@ -41,14 +41,16 @@ #include "td/telegram/Photo.hpp" #include "td/telegram/PhotoSize.h" #include "td/telegram/PremiumGiftOption.hpp" +#include "td/telegram/ReactionManager.h" #include "td/telegram/SecretChatLayer.h" #include "td/telegram/SecretChatsManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StickerPhotoSize.h" #include "td/telegram/StickersManager.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" @@ -65,6 +67,7 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" +#include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" @@ -175,7 +178,8 @@ class AddContactQuery final : public Td::ResultHandler { } send_query(G()->net_query_creator().create( telegram_api::contacts_addContact(flags, false /*ignored*/, std::move(input_user), contact.get_first_name(), - contact.get_last_name(), contact.get_phone_number()))); + contact.get_last_name(), contact.get_phone_number()), + {{DialogId(user_id)}})); } void on_result(BufferSlice packet) final { @@ -196,6 +200,34 @@ class AddContactQuery final : public Td::ResultHandler { } }; +class EditCloseFriendsQuery final : public Td::ResultHandler { + Promise promise_; + vector user_ids_; + + public: + explicit EditCloseFriendsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector user_ids) { + user_ids_ = std::move(user_ids); + send_query(G()->net_query_creator().create( + telegram_api::contacts_editCloseFriends(UserId::get_input_user_ids(user_ids_)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->contacts_manager_->on_set_close_friends(user_ids_, std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class ResolvePhoneQuery final : public Td::ResultHandler { Promise promise_; string phone_number_; @@ -3761,6 +3793,29 @@ class GetSupportUserQuery final : public Td::ResultHandler { } }; +class GetStoriesMaxIdsQuery final : public Td::ResultHandler { + vector user_ids_; + + public: + void send(vector user_ids, vector> &&input_users) { + user_ids_ = std::move(user_ids); + send_query(G()->net_query_creator().create(telegram_api::users_getStoriesMaxIDs(std::move(input_users)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->contacts_manager_->on_get_user_max_active_story_ids(user_ids_, result_ptr.move_as_ok()); + } + + void on_error(Status status) final { + td_->contacts_manager_->on_get_user_max_active_story_ids(user_ids_, Auto()); + } +}; + tl_object_ptr ContactsManager::convert_date_range( const tl_object_ptr &obj) { return make_tl_object(obj->min_date_, obj->max_date_); @@ -4398,38 +4453,52 @@ void ContactsManager::User::store(StorerT &storer) const { bool has_restriction_reasons = !restriction_reasons.empty(); bool has_emoji_status = !emoji_status.is_empty(); bool has_usernames = !usernames.is_empty(); + bool has_flags2 = true; + 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(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_received); STORE_FLAG(is_verified); STORE_FLAG(is_deleted); STORE_FLAG(is_bot); STORE_FLAG(can_join_groups); - STORE_FLAG(can_read_all_group_messages); // 5 + STORE_FLAG(can_read_all_group_messages); STORE_FLAG(is_inline_bot); STORE_FLAG(need_location_bot); STORE_FLAG(has_last_name); STORE_FLAG(legacy_has_username); - STORE_FLAG(has_photo); // 10 - STORE_FLAG(false); // legacy is_restricted + STORE_FLAG(has_photo); + STORE_FLAG(false); // legacy is_restricted STORE_FLAG(has_language_code); STORE_FLAG(have_access_hash); STORE_FLAG(is_support); - STORE_FLAG(is_min_access_hash); // 15 + STORE_FLAG(is_min_access_hash); STORE_FLAG(is_scam); STORE_FLAG(has_cache_version); STORE_FLAG(has_is_contact); STORE_FLAG(is_contact); - STORE_FLAG(is_mutual_contact); // 20 + STORE_FLAG(is_mutual_contact); STORE_FLAG(has_restriction_reasons); STORE_FLAG(need_apply_min_photo); STORE_FLAG(is_fake); STORE_FLAG(can_be_added_to_attach_menu); - STORE_FLAG(is_premium); // 25 + STORE_FLAG(is_premium); STORE_FLAG(attach_menu_enabled); STORE_FLAG(has_emoji_status); STORE_FLAG(has_usernames); STORE_FLAG(can_be_edited_bot); END_STORE_FLAGS(); + if (has_flags2) { + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_close_friend); + STORE_FLAG(stories_hidden); + STORE_FLAG(false); + STORE_FLAG(has_max_active_story_id); + STORE_FLAG(has_max_read_story_id); + STORE_FLAG(has_max_active_story_id_next_reload_time); + END_STORE_FLAGS(); + } store(first_name, storer); if (has_last_name) { store(last_name, storer); @@ -4463,6 +4532,15 @@ void ContactsManager::User::store(StorerT &storer) const { if (has_usernames) { store(usernames, storer); } + if (has_max_active_story_id) { + store(max_active_story_id, storer); + } + if (has_max_read_story_id) { + store(max_read_story_id, storer); + } + if (has_max_active_story_id_next_reload_time) { + store_time(max_active_story_id_next_reload_time, storer); + } } template @@ -4479,6 +4557,11 @@ void ContactsManager::User::parse(ParserT &parser) { bool has_restriction_reasons; bool has_emoji_status; bool has_usernames; + bool has_flags2 = parser.version() >= static_cast(Version::AddUserFlags2); + bool legacy_has_stories = false; + bool has_max_active_story_id = false; + bool has_max_read_story_id = false; + bool has_max_active_story_id_next_reload_time = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_received); PARSE_FLAG(is_verified); @@ -4511,6 +4594,16 @@ void ContactsManager::User::parse(ParserT &parser) { PARSE_FLAG(has_usernames); PARSE_FLAG(can_be_edited_bot); END_PARSE_FLAGS(); + if (has_flags2) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_close_friend); + PARSE_FLAG(stories_hidden); + PARSE_FLAG(legacy_has_stories); + PARSE_FLAG(has_max_active_story_id); + PARSE_FLAG(has_max_read_story_id); + PARSE_FLAG(has_max_active_story_id_next_reload_time); + END_PARSE_FLAGS(); + } parse(first_name, parser); if (has_last_name) { parse(last_name, parser); @@ -4543,6 +4636,7 @@ void ContactsManager::User::parse(ParserT &parser) { is_contact = link_state_outbound == 3; is_mutual_contact = is_contact && link_state_inbound == 3; + is_close_friend = false; } parse(was_online, parser); if (legacy_is_restricted) { @@ -4571,6 +4665,15 @@ void ContactsManager::User::parse(ParserT &parser) { CHECK(!legacy_has_username); parse(usernames, parser); } + if (has_max_active_story_id) { + parse(max_active_story_id, parser); + } + if (has_max_read_story_id) { + parse(max_read_story_id, parser); + } + if (has_max_active_story_id_next_reload_time) { + parse_time(max_active_story_id_next_reload_time, parser); + } if (!check_utf8(first_name)) { LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; @@ -4592,6 +4695,11 @@ void ContactsManager::User::parse(ParserT &parser) { is_mutual_contact = false; cache_version = 0; } + if (!is_contact && is_close_friend) { + LOG(ERROR) << "Have invalid flag is_close_friend"; + is_close_friend = false; + cache_version = 0; + } } template @@ -4631,6 +4739,8 @@ void ContactsManager::UserFull::store(StorerT &storer) const { STORE_FLAG(voice_messages_forbidden); STORE_FLAG(has_personal_photo); STORE_FLAG(has_fallback_photo); + STORE_FLAG(has_pinned_stories); + STORE_FLAG(is_blocked_for_stories); END_STORE_FLAGS(); if (has_about) { store(about, storer); @@ -4713,6 +4823,8 @@ void ContactsManager::UserFull::parse(ParserT &parser) { PARSE_FLAG(voice_messages_forbidden); PARSE_FLAG(has_personal_photo); PARSE_FLAG(has_fallback_photo); + PARSE_FLAG(has_pinned_stories); + PARSE_FLAG(is_blocked_for_stories); END_PARSE_FLAGS(); if (has_about) { parse(about, parser); @@ -5855,6 +5967,14 @@ bool ContactsManager::get_channel_has_protected_content(ChannelId channel_id) co return c->noforwards; } +bool ContactsManager::get_user_stories_hidden(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return false; + } + return u->stories_hidden; +} + string ContactsManager::get_user_private_forward_name(UserId user_id) { auto user_full = get_user_full_force(user_id); if (user_full != nullptr) { @@ -6058,7 +6178,7 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo } auto my_id = get_my_id(); - User *u = get_user_force(my_id); + User *u = get_user_force(my_id, "set_my_online_status"); if (u != nullptr) { int32 new_online; int32 now = G()->unix_time(); @@ -6118,7 +6238,7 @@ UserId ContactsManager::get_service_notifications_user_id() { UserId ContactsManager::add_service_notifications_user() { auto user_id = get_service_notifications_user_id(); - if (!have_user_force(user_id)) { + if (!have_user_force(user_id, "add_service_notifications_user")) { LOG(FATAL) << "Failed to load service notification user"; } return user_id; @@ -6142,7 +6262,7 @@ UserId ContactsManager::get_anti_spam_bot_user_id() { UserId ContactsManager::add_anonymous_bot_user() { auto user_id = get_anonymous_bot_user_id(); - if (!have_user_force(user_id)) { + if (!have_user_force(user_id, "add_anonymous_bot_user")) { LOG(FATAL) << "Failed to load anonymous bot user"; } return user_id; @@ -6150,12 +6270,16 @@ UserId ContactsManager::add_anonymous_bot_user() { UserId ContactsManager::add_channel_bot_user() { auto user_id = get_channel_bot_user_id(); - if (!have_user_force(user_id)) { + if (!have_user_force(user_id, "add_channel_bot_user")) { LOG(FATAL) << "Failed to load channel bot user"; } return user_id; } +ChatId ContactsManager::get_unsupported_chat_id() { + return ChatId(static_cast(G()->is_test_dc() ? 10304875 : 1535424647)); +} + void ContactsManager::check_dialog_username(DialogId dialog_id, const string &username, Promise &&promise) { if (dialog_id != DialogId() && !dialog_id.is_valid()) { @@ -6334,7 +6458,7 @@ int64 ContactsManager::get_contacts_hash() { vector user_ids = contacts_hints_.search_empty(100000).second; CHECK(std::is_sorted(user_ids.begin(), user_ids.end())); auto my_id = get_my_id(); - const User *u = get_user_force(my_id); + const User *u = get_user_force(my_id, "get_contacts_hash"); if (u != nullptr && u->is_contact) { user_ids.insert(std::upper_bound(user_ids.begin(), user_ids.end(), my_id.get()), my_id.get()); } @@ -6764,13 +6888,14 @@ void ContactsManager::clear_imported_contacts(Promise &&promise) { void ContactsManager::on_update_contacts_reset() { /* UserId my_id = get_my_id(); - users_.foreach([&](const UserId &user_id, unique_ptr &u) { + users_.foreach([&](const UserId &user_id, unique_ptr &user) { + User *u = user.get(); if (u->is_contact) { LOG(INFO) << "Drop contact with " << user_id; if (user_id != my_id) { CHECK(contacts_hints_.has_key(user_id.get())); } - on_update_user_is_contact(u, user_id, false, false); + on_update_user_is_contact(u, user_id, false, false, false); CHECK(u->is_is_contact_changed); u->cache_version = 0; u->is_repaired = false; @@ -6840,6 +6965,54 @@ std::pair> ContactsManager::search_contacts(const string & return {narrow_cast(result.first), std::move(user_ids)}; } +vector ContactsManager::get_close_friends(Promise &&promise) { + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return {}; + } + reload_contacts(false); + + auto result = contacts_hints_.search_empty(10000); + + vector user_ids; + for (auto key : result.second) { + UserId user_id(key); + const User *u = get_user(user_id); + if (u != nullptr && u->is_close_friend) { + user_ids.push_back(user_id); + } + } + + promise.set_value(Unit()); + return user_ids; +} + +void ContactsManager::set_close_friends(vector user_ids, Promise &&promise) { + for (auto &user_id : user_ids) { + if (!have_user(user_id)) { + return promise.set_error(Status::Error(400, "User not found")); + } + } + + td_->create_handler(std::move(promise))->send(std::move(user_ids)); +} + +void ContactsManager::on_set_close_friends(const vector &user_ids, Promise &&promise) { + FlatHashSet close_friend_user_ids; + for (auto &user_id : user_ids) { + CHECK(user_id.is_valid()); + close_friend_user_ids.insert(user_id); + } + users_.foreach([&](const UserId &user_id, unique_ptr &user) { + User *u = user.get(); + if (u->is_contact && u->is_close_friend != (close_friend_user_ids.count(user_id) > 0)) { + on_update_user_is_contact(u, user_id, u->is_contact, u->is_mutual_contact, !u->is_close_friend); + update_user(u, user_id); + } + }); + promise.set_value(Unit()); +} + UserId ContactsManager::search_user_by_phone_number(string phone_number, Promise &&promise) { clean_phone_number(phone_number); if (phone_number.empty()) { @@ -7318,7 +7491,7 @@ void ContactsManager::on_update_bot_menu_button(UserId bot_user_id, LOG(ERROR) << "Receive updateBotCOmmands about invalid " << bot_user_id; return; } - if (!have_user_force(bot_user_id) || !is_user_bot(bot_user_id)) { + if (!have_user_force(bot_user_id, "on_update_bot_menu_button") || !is_user_bot(bot_user_id)) { return; } if (td_->auth_manager_->is_bot()) { @@ -7496,7 +7669,7 @@ void ContactsManager::delete_profile_photo(int64 profile_photo_id, bool is_recur } send_closure(actor_id, &ContactsManager::delete_profile_photo, profile_photo_id, true, std::move(promise)); }); - reload_user_full(get_my_id(), std::move(reload_promise)); + reload_user_full(get_my_id(), std::move(reload_promise), "delete_profile_photo"); return; } if (user_full->photo.id.get() == profile_photo_id || user_full->fallback_photo.id.get() == profile_photo_id) { @@ -7741,13 +7914,6 @@ void ContactsManager::set_channel_username(ChannelId channel_id, const string &u return promise.set_error(Status::Error(400, "Username is invalid")); } - if (!username.empty() && !c->usernames.has_editable_username()) { - auto channel_full = get_channel_full(channel_id, false, "set_channel_username"); - if (channel_full != nullptr && !channel_full->can_set_username) { - return promise.set_error(Status::Error(400, "Can't set supergroup username")); - } - } - td_->create_handler(std::move(promise))->send(channel_id, username); } @@ -8866,7 +9032,7 @@ void ContactsManager::transfer_dialog_ownership(DialogId dialog_id, UserId user_ if (!td_->messages_manager_->have_dialog_force(dialog_id, "transfer_dialog_ownership")) { return promise.set_error(Status::Error(400, "Chat not found")); } - if (!have_user_force(user_id)) { + if (!have_user_force(user_id, "transfer_dialog_ownership")) { return promise.set_error(Status::Error(400, "User not found")); } if (is_user_bot(user_id)) { @@ -9651,9 +9817,6 @@ void ContactsManager::remove_inactive_channel(ChannelId channel_id) { } void ContactsManager::register_message_users(FullMessageId full_message_id, vector user_ids) { - if (td_->auth_manager_->is_bot()) { - return; - } for (auto user_id : user_ids) { CHECK(user_id.is_valid()); const User *u = get_user(user_id); @@ -9669,9 +9832,6 @@ void ContactsManager::register_message_users(FullMessageId full_message_id, vect } void ContactsManager::register_message_channels(FullMessageId full_message_id, vector channel_ids) { - if (td_->auth_manager_->is_bot()) { - return; - } for (auto channel_id : channel_ids) { CHECK(channel_id.is_valid()); const Channel *c = get_channel(channel_id); @@ -9847,7 +10007,7 @@ void ContactsManager::on_deleted_contacts(const vector &deleted_contact_ } LOG(INFO) << "Drop contact with " << user_id; - on_update_user_is_contact(u, user_id, false, false); + on_update_user_is_contact(u, user_id, false, false, false); CHECK(u->is_is_contact_changed); u->cache_version = 0; u->is_repaired = false; @@ -9903,7 +10063,7 @@ void ContactsManager::on_get_contacts(tl_object_ptris_is_contact_changed); u->cache_version = 0; u->is_repaired = false; @@ -9967,6 +10127,12 @@ void ContactsManager::on_load_contacts_from_database(string value) { return; } + if (log_event_get_version(value) < static_cast(Version::AddUserFlags2)) { + next_contacts_sync_date_ = 0; + save_next_contacts_sync_date(); + reload_contacts(true); + } + LOG(INFO) << "Successfully loaded " << user_ids.size() << " contacts from database"; load_contact_users_multipromise_.add_promise(PromiseCreator::lambda( @@ -10079,7 +10245,7 @@ DialogId ContactsManager::get_dialog_id(const tl_object_ptr return DialogId(get_chat_id(chat)); } -void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, const char *source, bool is_me) { +void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, const char *source) { LOG(DEBUG) << "Receive from " << source << ' ' << to_string(user_ptr); int32 constructor_id = user_ptr->get_id(); if (constructor_id == telegram_api::userEmpty::ID) { @@ -10091,7 +10257,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, } LOG(INFO) << "Receive empty " << user_id << " from " << source; - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, source); if (u == nullptr && Slice(source) != Slice("GetUsersQuery")) { // userEmpty should be received only through getUsers for nonexistent users LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty from " << source; @@ -10106,14 +10272,12 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, LOG(ERROR) << "Receive invalid " << user_id; return; } + int32 flags = user->flags_; int32 flags2 = user->flags2_; LOG(INFO) << "Receive " << user_id << " with flags " << flags << ' ' << flags2 << " from " << source; - if (is_me && (flags & USER_FLAG_IS_ME) == 0) { - LOG(ERROR) << user_id << " doesn't have flag IS_ME, but must have it when received from " << source; - flags |= USER_FLAG_IS_ME; - } + // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used bool is_bot = (flags & USER_FLAG_IS_BOT) != 0; if (flags & USER_FLAG_IS_ME) { set_my_id(user_id); @@ -10126,23 +10290,30 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, bool is_received = (flags & USER_FLAG_IS_INACCESSIBLE) == 0; bool is_contact = (flags & USER_FLAG_IS_CONTACT) != 0; - if (!have_min_user(user_id)) { + User *u = get_user(user_id); + if (u == nullptr) { if (!is_received) { // we must preload received inaccessible users from database in order to not save // the min-user to the database and to not override access_hash and another info - if (!have_user_force(user_id)) { + u = get_user_force(user_id, "on_get_user 2"); + if (u == nullptr) { LOG(INFO) << "Receive inaccessible " << user_id; + u = add_user(user_id); } } else if (is_contact && !are_contacts_loaded_) { // preload contact users from database to know that is_contact didn't changed // and the list of contacts doesn't need to be saved to the database - if (!have_user_force(user_id)) { + u = get_user_force(user_id, "on_get_user 3"); + if (u == nullptr) { LOG(INFO) << "Receive contact " << user_id << " for the first time"; + u = add_user(user_id); } + } else { + u = add_user(user_id); } + CHECK(u != nullptr); } - User *u = add_user(user_id, "on_get_user"); if (have_access_hash) { // access_hash must be updated before photo auto access_hash = user->access_hash_; bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty()); @@ -10154,17 +10325,19 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->need_save_to_database = true; } } - if (is_received || !user->phone_.empty()) { + bool is_me_regular_user = !td_->auth_manager_->is_bot(); + if (is_me_regular_user && (is_received || !user->phone_.empty())) { on_update_user_phone_number(u, user_id, std::move(user->phone_)); } if (is_received || u->need_apply_min_photo || !u->is_received) { on_update_user_photo(u, user_id, std::move(user->photo_), source); } - if (is_received) { + if (is_me_regular_user && is_received) { on_update_user_online(u, user_id, std::move(user->status_)); auto is_mutual_contact = (flags & USER_FLAG_IS_MUTUAL_CONTACT) != 0; - on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact); + auto is_close_friend = (flags2 & USER_FLAG_IS_CLOSE_FRIEND) != 0; + on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact, is_close_friend); } if (is_received || !u->is_received) { @@ -10190,6 +10363,9 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0; bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0; bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0; + bool stories_available = user->stories_max_id_ > 0; + bool stories_unavailable = user->stories_unavailable_; + bool stories_hidden = user->stories_hidden_; LOG_IF(ERROR, !can_join_groups && !is_bot) << "Receive not bot " << user_id << " which can't join groups from " << source; @@ -10255,8 +10431,12 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->attach_menu_enabled = attach_menu_enabled; u->is_changed = true; } + if (is_me_regular_user && is_received) { + on_update_user_stories_hidden(u, user_id, stories_hidden); + } if (is_premium != u->is_premium) { u->is_premium = is_premium; + u->is_is_premium_changed = true; u->is_changed = true; u->is_full_info_changed = true; } @@ -10272,6 +10452,7 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->need_save_to_database = true; } if (is_received && u->need_apply_min_photo != need_apply_min_photo) { + LOG(DEBUG) << "Need apply min photo has changed for " << user_id; u->need_apply_min_photo = need_apply_min_photo; u->need_save_to_database = true; } @@ -10301,6 +10482,11 @@ void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, u->is_changed = true; } + if (is_me_regular_user && (stories_available || stories_unavailable)) { + // update at the end, because it calls need_poll_active_stories + on_update_user_story_ids_impl(u, user_id, StoryId(user->stories_max_id_), StoryId()); + } + if (u->cache_version != User::CACHE_VERSION && u->is_received) { u->cache_version = User::CACHE_VERSION; u->need_save_to_database = true; @@ -10433,7 +10619,8 @@ void ContactsManager::on_save_user_to_database(UserId user_id, bool success) { << u->is_deleted << ' ' << u->is_bot << ' ' << u->need_save_to_database << ' ' << u->is_changed << ' ' << u->is_status_changed << ' ' << u->is_name_changed << ' ' << u->is_username_changed << ' ' << u->is_photo_changed << ' ' - << u->is_is_contact_changed << ' ' << u->is_is_deleted_changed << ' ' << u->log_event_id; + << u->is_is_contact_changed << ' ' << u->is_is_deleted_changed << ' ' + << u->is_stories_hidden_changed << ' ' << u->log_event_id; CHECK(load_user_from_database_queries_.count(user_id) == 0); u->is_being_saved = false; @@ -10503,7 +10690,7 @@ void ContactsManager::on_load_user_from_database(UserId user_id, string value, b User *u = get_user(user_id); if (u == nullptr) { if (!value.empty()) { - u = add_user(user_id, "on_load_user_from_database"); + u = add_user(user_id); if (log_event_parse(*u, value).is_error()) { LOG(ERROR) << "Failed to load " << user_id << " from database"; @@ -10529,12 +10716,12 @@ void ContactsManager::on_load_user_from_database(UserId user_id, string value, b set_promises(promises); } -bool ContactsManager::have_user_force(UserId user_id) { - return get_user_force(user_id) != nullptr; +bool ContactsManager::have_user_force(UserId user_id, const char *source) { + return get_user_force(user_id, source) != nullptr; } -ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { - auto u = get_user_force_impl(user_id); +ContactsManager::User *ContactsManager::get_user_force(UserId user_id, const char *source) { + auto u = get_user_force_impl(user_id, source); if ((u == nullptr || !u->is_received) && (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || @@ -10598,17 +10785,18 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { telegram_api::object_ptr profile_photo; if (!G()->is_test_dc() && profile_photo_id != 0) { - profile_photo = telegram_api::make_object( - 0, false /*ignored*/, false /*ignored*/, profile_photo_id, BufferSlice(), profile_photo_dc_id); + profile_photo = telegram_api::make_object(0, false, false, profile_photo_id, + BufferSlice(), profile_photo_dc_id); } auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, 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>()); + 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); on_get_user(std::move(user), "get_user_force"); u = get_user(user_id); CHECK(u != nullptr && u->is_received); @@ -10618,7 +10806,7 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) { return u; } -ContactsManager::User *ContactsManager::get_user_force_impl(UserId user_id) { +ContactsManager::User *ContactsManager::get_user_force_impl(UserId user_id, const char *source) { if (!user_id.is_valid()) { return nullptr; } @@ -10634,7 +10822,7 @@ ContactsManager::User *ContactsManager::get_user_force_impl(UserId user_id) { return nullptr; } - LOG(INFO) << "Trying to load " << user_id << " from database"; + LOG(INFO) << "Trying to load " << user_id << " from database from " << source; on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)), true); return get_user(user_id); } @@ -11361,7 +11549,7 @@ void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat } // TODO load users asynchronously - if (c != nullptr && !have_user_force(c->user_id)) { + if (c != nullptr && !have_user_force(c->user_id, "on_load_secret_chat_from_database")) { LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id; } @@ -11379,7 +11567,7 @@ ContactsManager::SecretChat *ContactsManager::get_secret_chat_force(SecretChatId SecretChat *c = get_secret_chat(secret_chat_id); if (c != nullptr) { - if (!have_user_force(c->user_id)) { + if (!have_user_force(c->user_id, "get_secret_chat_force")) { LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id; } return c; @@ -11467,12 +11655,12 @@ void ContactsManager::on_load_user_full_from_database(UserId user_id, string val if (is_user_deleted(u)) { drop_user_full(user_id); } else if (user_full->expires_at == 0.0) { - reload_user_full(user_id, Auto()); + reload_user_full(user_id, Auto(), "on_load_user_full_from_database"); } } ContactsManager::UserFull *ContactsManager::get_user_full_force(UserId user_id) { - if (!have_user_force(user_id)) { + if (!have_user_force(user_id, "get_user_full_force")) { return nullptr; } @@ -11565,7 +11753,7 @@ void ContactsManager::on_load_chat_full_from_database(ChatId chat_id, string val if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { chat_full->photo = Photo(); if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto()); + reload_chat_full(chat_id, Auto(), "on_load_chat_full_from_database"); } } @@ -11756,11 +11944,20 @@ void ContactsManager::for_each_secret_chat_with_user(UserId user_id, const std:: void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) { CHECK(u != nullptr); + + if (u->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << user_id; + } + u->is_being_updated = true; + SCOPE_EXIT { + u->is_being_updated = false; + }; + if (user_id == get_my_id()) { if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) { td_->option_manager_->set_option_boolean("is_premium", u->is_premium); send_closure(td_->config_manager_, &ConfigManager::request_config, true); - td_->stickers_manager_->reload_top_reactions(); + td_->reaction_manager_->reload_top_reactions(); td_->messages_manager_->update_is_translatable(u->is_premium); } } @@ -11770,6 +11967,8 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } if (u->is_is_contact_changed) { td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact); + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "is_contact"); if (is_user_contact(u, user_id, false)) { auto user_full = get_user_full(user_id); if (user_full != nullptr && user_full->need_phone_number_privacy_exception) { @@ -11779,6 +11978,12 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } u->is_is_contact_changed = false; } + if (u->is_is_mutual_contact_changed) { + if (!from_database && u->is_update_user_sent) { + send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, DialogId(user_id)); + } + u->is_is_mutual_contact_changed = false; + } if (u->is_is_deleted_changed) { td_->messages_manager_->on_dialog_user_is_deleted_updated(DialogId(user_id), u->is_deleted); if (u->is_deleted) { @@ -11790,6 +11995,11 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } u->is_is_deleted_changed = false; } + if (u->is_is_premium_changed) { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "is_premium"); + u->is_is_premium_changed = false; + } if (u->is_name_changed) { auto messages_manager = td_->messages_manager_.get(); messages_manager->on_dialog_title_updated(DialogId(user_id)); @@ -11823,6 +12033,11 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo user_online_timeout_.cancel_timeout(user_id.get()); } } + if (u->is_stories_hidden_changed) { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "stories_hidden"); + u->is_stories_hidden_changed = false; + } if (!td_->auth_manager_->is_bot()) { if (u->restriction_reasons.empty()) { restricted_user_ids_.erase(user_id); @@ -11832,18 +12047,19 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo } auto unix_time = G()->unix_time(); - auto effective_custom_emoji_id = u->emoji_status.get_effective_custom_emoji_id(u->is_premium, unix_time); - if (effective_custom_emoji_id != u->last_sent_emoji_status) { - u->last_sent_emoji_status = effective_custom_emoji_id; + 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; u->is_changed = true; - } else { + } else if (u->is_emoji_status_changed) { + LOG(DEBUG) << "Emoji status for " << user_id << " has changed"; u->need_save_to_database = true; } - if (u->last_sent_emoji_status.is_valid()) { - auto until_date = u->emoji_status.get_until_date(); + u->is_emoji_status_changed = false; + if (!u->last_sent_emoji_status.is_empty()) { + auto until_date = u->last_sent_emoji_status.get_until_date(); auto left_time = until_date - unix_time; if (left_time >= 0 && left_time < 30 * 86400) { - LOG(DEBUG) << "Set emoji status timeout for " << user_id << " in " << left_time << " seconds"; user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time); } else { user_emoji_status_timeout_.cancel_timeout(user_id.get()); @@ -11855,6 +12071,9 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo if (u->is_deleted) { td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>()); } + if (from_binlog || from_database) { + td_->messages_manager_->on_dialog_usernames_received(DialogId(user_id), u->usernames, true); + } LOG(DEBUG) << "Update " << user_id << ": need_save_to_database = " << u->need_save_to_database << ", is_changed = " << u->is_changed << ", is_status_changed = " << u->is_status_changed @@ -11910,6 +12129,15 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << chat_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + bool need_update_chat_full = false; if (c->is_photo_changed) { td_->messages_manager_->on_dialog_photo_updated(DialogId(chat_id)); @@ -11923,7 +12151,7 @@ void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, boo need_update_chat_full = true; } if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto()); + reload_chat_full(chat_id, Auto(), "update_chat"); } } } @@ -11946,6 +12174,13 @@ void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, boo if (!from_database) { // if the chat is empty, this can add it to a chat list or remove it from a chat list send_closure_later(G()->messages_manager(), &MessagesManager::try_update_dialog_pos, DialogId(chat_id)); + + // reload the chat to repair its status if it is changed back after receiving of outdated data + create_actor("ReloadChatSleepActor", 1.0, + PromiseCreator::lambda([actor_id = actor_id(this), chat_id](Unit) { + send_closure(actor_id, &ContactsManager::reload_chat, chat_id, Promise()); + })) + .release(); } c->is_status_changed = false; } @@ -11990,6 +12225,15 @@ void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, boo void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << channel_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + bool need_update_channel_full = false; if (c->is_photo_changed) { td_->messages_manager_->on_dialog_photo_updated(DialogId(channel_id)); @@ -12038,6 +12282,14 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from if (!c->status.can_manage_invite_links()) { td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(channel_id)); } + if (!from_database) { + // reload the channel to repair its status if it is changed back after receiving of outdated data + create_actor("ReloadChannelSleepActor", 1.0, + PromiseCreator::lambda([actor_id = actor_id(this), channel_id](Unit) { + send_closure(actor_id, &ContactsManager::reload_channel, channel_id, Promise()); + })) + .release(); + } c->is_status_changed = false; } if (c->is_username_changed) { @@ -12077,6 +12329,10 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from } } + if (from_binlog || from_database) { + td_->messages_manager_->on_dialog_usernames_received(DialogId(channel_id), c->usernames, true); + } + if (!is_channel_public(c) && !c->has_linked_channel) { send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, DialogId(channel_id), DialogId(), false); @@ -12132,6 +12388,15 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog, bool from_database) { CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << secret_chat_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + LOG(DEBUG) << "Update " << secret_chat_id << ": need_save_to_database = " << c->need_save_to_database << ", is_changed = " << c->is_changed; c->need_save_to_database |= c->is_changed; @@ -12167,6 +12432,15 @@ void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat void ContactsManager::update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database) { CHECK(user_full != nullptr); + + if (user_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << user_id << " from " << source; + } + user_full->is_being_updated = true; + SCOPE_EXIT { + user_full->is_being_updated = false; + }; + unavailable_user_fulls_.erase(user_id); // don't needed anymore if (user_full->is_common_chat_count_changed) { td_->messages_manager_->drop_common_dialogs_cache(user_id); @@ -12234,6 +12508,15 @@ void ContactsManager::update_user_full(UserFull *user_full, UserId user_id, cons void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database) { CHECK(chat_full != nullptr); + + if (chat_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << chat_id << " from " << source; + } + chat_full->is_being_updated = true; + SCOPE_EXIT { + chat_full->is_being_updated = false; + }; + unavailable_chat_fulls_.erase(chat_id); // don't needed anymore chat_full->need_send_update |= chat_full->is_changed; @@ -12291,6 +12574,15 @@ void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, cons void ContactsManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, bool from_database) { CHECK(channel_full != nullptr); + + if (channel_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << channel_id << " from " << source; + } + channel_full->is_being_updated = true; + SCOPE_EXIT { + channel_full->is_being_updated = false; + }; + unavailable_channel_fulls_.erase(channel_id); // don't needed anymore CHECK(channel_full->participant_count >= channel_full->administrator_count); @@ -12386,14 +12678,18 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u td_->messages_manager_->on_update_dialog_message_ttl(DialogId(user_id), MessageTtl(user->ttl_period_)); - td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_); + td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_, + user->blocked_my_stories_from_); td_->messages_manager_->on_update_dialog_is_translatable(DialogId(user_id), !user->translations_disabled_); + send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_user_stories, DialogId(user_id), + std::move(user->stories_), Promise()); + UserFull *user_full = add_user_full(user_id); user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; - on_update_user_full_is_blocked(user_full, user_id, user->blocked_); + on_update_user_full_is_blocked(user_full, user_id, user->blocked_, user->blocked_my_stories_from_); on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_); on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, user->settings_->need_contacts_exception_); @@ -12406,13 +12702,14 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_)); AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup); AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast); + bool has_pinned_stories = user->stories_pinned_available_; if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || user_full->has_private_calls != has_private_calls || user_full->group_administrator_rights != group_administrator_rights || user_full->broadcast_administrator_rights != broadcast_administrator_rights || user_full->premium_gift_options != premium_gift_options || user_full->voice_messages_forbidden != voice_messages_forbidden || - user_full->can_pin_messages != can_pin_messages) { + user_full->can_pin_messages != can_pin_messages || user_full->has_pinned_stories != has_pinned_stories) { user_full->can_be_called = can_be_called; user_full->supports_video_calls = supports_video_calls; user_full->has_private_calls = has_private_calls; @@ -12421,6 +12718,7 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u user_full->premium_gift_options = std::move(premium_gift_options); user_full->voice_messages_forbidden = voice_messages_forbidden; user_full->can_pin_messages = can_pin_messages; + user_full->has_pinned_stories = has_pinned_stories; user_full->is_changed = true; } @@ -12618,7 +12916,25 @@ void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 lim void ContactsManager::on_get_chat(tl_object_ptr &&chat, const char *source) { LOG(DEBUG) << "Receive from " << source << ' ' << to_string(chat); - downcast_call(*chat, [this, source](auto &c) { this->on_chat_update(c, source); }); + switch (chat->get_id()) { + case telegram_api::chatEmpty::ID: + on_chat_update(static_cast(*chat), source); + break; + case telegram_api::chat::ID: + on_chat_update(static_cast(*chat), source); + break; + case telegram_api::chatForbidden::ID: + on_chat_update(static_cast(*chat), source); + break; + case telegram_api::channel::ID: + on_chat_update(static_cast(*chat), source); + break; + case telegram_api::channelForbidden::ID: + on_chat_update(static_cast(*chat), source); + break; + default: + UNREACHABLE(); + } } void ContactsManager::on_get_chats(vector> &&chats, const char *source) { @@ -12650,7 +12966,7 @@ vector ContactsManager::get_bot_commands(vectoruser_id_); - const User *u = get_user_force(user_id); + const User *u = get_user_force(user_id, "get_bot_commands"); if (u == nullptr) { LOG(ERROR) << "Receive unknown " << user_id; continue; @@ -12742,7 +13058,7 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c } if (chat_full->can_set_username != chat->can_set_username_) { chat_full->can_set_username = chat->can_set_username_; - chat_full->is_changed = true; + chat_full->need_save_to_database = true; } on_get_chat_participants(std::move(chat->participants_), false); @@ -12860,7 +13176,6 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c channel_full->administrator_count != administrator_count || channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count || channel_full->can_get_participants != can_get_participants || - channel_full->can_set_username != can_set_username || channel_full->can_set_sticker_set != can_set_sticker_set || channel_full->can_set_location != can_set_location || channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id || @@ -12874,7 +13189,6 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c channel_full->banned_count = banned_count; channel_full->can_get_participants = can_get_participants; channel_full->has_hidden_participants = has_hidden_participants; - channel_full->can_set_username = can_set_username; channel_full->can_set_sticker_set = can_set_sticker_set; channel_full->can_set_location = can_set_location; channel_full->can_view_statistics = can_view_statistics; @@ -12900,6 +13214,10 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c channel_full->is_can_view_statistics_inited = true; channel_full->need_save_to_database = true; } + if (channel_full->can_set_username != can_set_username) { + channel_full->can_set_username = can_set_username; + channel_full->need_save_to_database = true; + } auto photo = get_photo(td_, std::move(channel->chat_photo_), DialogId(channel_id)); // on_update_channel_photo should be a no-op if server sent consistent data @@ -12912,14 +13230,14 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c MessageId(ServerMessageId(channel->read_outbox_max_id_))); if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) { td_->messages_manager_->on_update_channel_max_unavailable_message_id( - channel_id, MessageId(ServerMessageId(channel->available_min_id_))); + channel_id, MessageId(ServerMessageId(channel->available_min_id_)), "ChannelFull"); } td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)), channel->unread_count_, channel->pts_, "ChannelFull"); on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_)); - td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), channel->blocked_); + td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), channel->blocked_, false); td_->messages_manager_->on_update_dialog_last_pinned_message_id( DialogId(channel_id), MessageId(ServerMessageId(channel->pinned_msg_id_))); @@ -12988,7 +13306,7 @@ void ContactsManager::on_get_chat_full(tl_object_ptr &&c } on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id); - on_update_channel_full_location(channel_full, channel_id, DialogLocation(std::move(channel->location_))); + on_update_channel_full_location(channel_full, channel_id, DialogLocation(td_, std::move(channel->location_))); if (c->is_megagroup) { on_update_channel_full_slow_mode_delay(channel_full, channel_id, channel->slowmode_seconds_, @@ -13085,7 +13403,7 @@ void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, s return; } - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "on_update_user_name"); if (u != nullptr) { on_update_user_name(u, user_id, std::move(first_name), std::move(last_name)); on_update_user_usernames(u, user_id, std::move(usernames)); @@ -13109,8 +13427,8 @@ void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&firs } void ContactsManager::on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames) { - td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); if (u->usernames != usernames) { + td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); if (u->can_be_edited_bot && u->usernames.get_editable_username() != usernames.get_editable_username()) { u->is_full_info_changed = true; } @@ -13118,6 +13436,8 @@ void ContactsManager::on_update_user_usernames(User *u, UserId user_id, Username u->is_username_changed = true; LOG(DEBUG) << "Usernames have changed for " << user_id; u->is_changed = true; + } else { + td_->messages_manager_->on_dialog_usernames_received(DialogId(user_id), usernames, false); } } @@ -13127,7 +13447,7 @@ void ContactsManager::on_update_user_phone_number(UserId user_id, string &&phone return; } - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "on_update_user_phone_number"); if (u != nullptr) { on_update_user_phone_number(u, user_id, std::move(phone_number)); update_user(u, user_id); @@ -13137,6 +13457,10 @@ void ContactsManager::on_update_user_phone_number(UserId user_id, string &&phone } void ContactsManager::on_update_user_phone_number(User *u, UserId user_id, string &&phone_number) { + if (td_->auth_manager_->is_bot()) { + return; + } + clean_phone_number(phone_number); if (u->phone_number != phone_number) { if (!u->phone_number.empty()) { @@ -13284,7 +13608,7 @@ void ContactsManager::on_update_user_emoji_status(UserId user_id, return; } - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "on_update_user_emoji_status"); if (u != nullptr) { on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status))); update_user(u, user_id); @@ -13297,40 +13621,179 @@ void ContactsManager::on_update_user_emoji_status(User *u, UserId user_id, Emoji if (u->emoji_status != emoji_status) { LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status; u->emoji_status = emoji_status; + u->is_emoji_status_changed = true; // effective emoji status might not be changed; checked in update_user // u->is_changed = true; } } -void ContactsManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact) { +void ContactsManager::on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_story_ids"); + if (u != nullptr) { + on_update_user_story_ids_impl(u, user_id, max_active_story_id, max_read_story_id); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user has stories about unknown " << user_id; + } +} + +void ContactsManager::on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, + StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { + LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << user_id; + return; + } + if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { + LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << user_id; + return; + } + + auto has_unread_stories = get_user_has_unread_stories(u); + if (u->max_active_story_id != max_active_story_id) { + LOG(DEBUG) << "Change last active story of " << user_id << " from " << u->max_active_story_id << " to " + << max_active_story_id; + u->max_active_story_id = max_active_story_id; + u->need_save_to_database = true; + } + if (need_poll_active_stories(u, user_id)) { + auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; + if (max_active_story_id_next_reload_time > + u->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { + LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << user_id; + u->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; + u->need_save_to_database = true; + } + } + if (!max_active_story_id.is_valid()) { + CHECK(max_read_story_id == StoryId()); + if (u->max_read_story_id != StoryId()) { + LOG(DEBUG) << "Drop last read " << u->max_read_story_id << " of " << user_id; + u->max_read_story_id = StoryId(); + u->need_save_to_database = true; + } + } else if (max_read_story_id.get() > u->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " + << max_read_story_id; + u->max_read_story_id = max_read_story_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; + u->is_changed = true; + } +} + +void ContactsManager::on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id) { + CHECK(user_id.is_valid()); + + User *u = get_user(user_id); + if (u != nullptr) { + on_update_user_max_read_story_id(u, user_id, max_read_story_id); + update_user(u, user_id); + } +} + +void ContactsManager::on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot() || !u->is_received) { + return; + } + + auto has_unread_stories = get_user_has_unread_stories(u); + if (max_read_story_id.get() > u->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " + << max_read_story_id; + u->max_read_story_id = max_read_story_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; + u->is_changed = true; + } +} + +void ContactsManager::on_update_user_stories_hidden(UserId user_id, bool stories_hidden) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_stories_hidden"); + if (u != nullptr) { + on_update_user_stories_hidden(u, user_id, stories_hidden); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user stories are archived about unknown " << user_id; + } +} + +void ContactsManager::on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (u->stories_hidden != stories_hidden) { + LOG(DEBUG) << "Change stories are archived of " << user_id << " to " << stories_hidden; + u->stories_hidden = stories_hidden; + u->is_stories_hidden_changed = true; + u->need_save_to_database = true; + } +} + +void ContactsManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, + bool is_close_friend) { + if (td_->auth_manager_->is_bot()) { + return; + } + UserId my_id = get_my_id(); if (user_id == my_id) { is_mutual_contact = is_contact; + is_close_friend = false; } - if (!is_contact && is_mutual_contact) { - LOG(ERROR) << "Receive is_mutual_contact == true for non-contact " << user_id; + if (!is_contact && (is_mutual_contact || is_close_friend)) { + LOG(ERROR) << "Receive is_mutual_contact = " << is_mutual_contact << ", and is_close_friend = " << is_close_friend + << " for non-contact " << user_id; is_mutual_contact = false; + is_close_friend = false; } - if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact) { - LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact - << ") to (" << is_contact << ", " << is_mutual_contact << ")"; + if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact || + u->is_close_friend != is_close_friend) { + LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact << ", " + << u->is_close_friend << ") to (" << is_contact << ", " << is_mutual_contact << ", " << is_close_friend + << ")"; if (u->is_contact != is_contact) { + u->is_contact = is_contact; u->is_is_contact_changed = true; } - u->is_contact = is_contact; - u->is_mutual_contact = is_mutual_contact; + if (u->is_mutual_contact != is_mutual_contact) { + u->is_mutual_contact = is_mutual_contact; + u->is_is_mutual_contact_changed = true; + } + u->is_close_friend = is_close_friend; u->is_changed = true; } } void ContactsManager::on_update_user_online(UserId user_id, tl_object_ptr &&status) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "on_update_user_online"); if (u != nullptr) { if (u->is_bot) { LOG(ERROR) << "Receive updateUserStatus about bot " << user_id; @@ -13351,6 +13814,10 @@ void ContactsManager::on_update_user_online(UserId user_id, tl_object_ptr &&status) { + if (td_->auth_manager_->is_bot()) { + return; + } + int32 id = status == nullptr ? telegram_api::userStatusEmpty::ID : status->get_id(); int32 new_online; bool is_offline = false; @@ -13411,8 +13878,11 @@ void ContactsManager::on_update_user_online(User *u, UserId user_id, tl_object_p void ContactsManager::on_update_user_local_was_online(UserId user_id, int32 local_was_online) { CHECK(user_id.is_valid()); + if (td_->auth_manager_->is_bot()) { + return; + } - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "on_update_user_local_was_online"); if (u == nullptr) { return; } @@ -13448,29 +13918,51 @@ void ContactsManager::on_update_user_local_was_online(User *u, UserId user_id, i } } -void ContactsManager::on_update_user_is_blocked(UserId user_id, bool is_blocked) { +void ContactsManager::on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories) { if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; return; } UserFull *user_full = get_user_full_force(user_id); - if (user_full == nullptr || user_full->is_blocked == is_blocked) { + if (user_full == nullptr) { return; } - on_update_user_full_is_blocked(user_full, user_id, is_blocked); + on_update_user_full_is_blocked(user_full, user_id, is_blocked, is_blocked_for_stories); update_user_full(user_full, user_id, "on_update_user_is_blocked"); } -void ContactsManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked) { +void ContactsManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, + bool is_blocked_for_stories) { CHECK(user_full != nullptr); - if (user_full->is_blocked != is_blocked) { - LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked; + if (user_full->is_blocked != is_blocked || user_full->is_blocked_for_stories != is_blocked_for_stories) { + LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked << '/' + << is_blocked_for_stories; user_full->is_blocked = is_blocked; + user_full->is_blocked_for_stories = is_blocked_for_stories; user_full->is_changed = true; } } +void ContactsManager::on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id); + if (user_full == nullptr || user_full->has_pinned_stories == has_pinned_stories) { + return; + } + user_full->has_pinned_stories = has_pinned_stories; + user_full->is_changed = true; + update_user_full(user_full, user_id, "on_update_user_has_pinned_stories"); +} + void ContactsManager::on_update_user_common_chat_count(UserId user_id, int32 common_chat_count) { LOG(INFO) << "Receive " << common_chat_count << " common chat count with " << user_id; if (!user_id.is_valid()) { @@ -13618,7 +14110,7 @@ void ContactsManager::add_set_profile_photo_to_cache(UserId user_id, Photo &&pho // we have subsequence of user photos in user_photos_ // ProfilePhoto in User and Photo in UserFull - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "add_set_profile_photo_to_cache"); if (u == nullptr) { return; } @@ -13692,7 +14184,7 @@ void ContactsManager::add_set_profile_photo_to_cache(UserId user_id, Photo &&pho user_full->need_save_to_database = true; } update_user_full(user_full, user_id, "add_set_profile_photo_to_cache"); - reload_user_full(user_id, Auto()); + reload_user_full(user_id, Auto(), "add_set_profile_photo_to_cache"); } } @@ -13707,7 +14199,7 @@ bool ContactsManager::delete_my_profile_photo_from_cache(int64 profile_photo_id) LOG(INFO) << "Delete profile photo " << profile_photo_id << " from cache"; auto user_id = get_my_id(); - User *u = get_user_force(user_id); + User *u = get_user_force(user_id, "delete_my_profile_photo_from_cache"); bool is_main_photo_deleted = u != nullptr && u->photo.id == profile_photo_id; // update photo list @@ -13777,7 +14269,7 @@ bool ContactsManager::delete_my_profile_photo_from_cache(int64 profile_photo_id) user_full->expires_at = 0.0; user_full->need_save_to_database = true; } - reload_user_full(user_id, Auto()); + reload_user_full(user_id, Auto(), "delete_my_profile_photo_from_cache"); update_user_full(user_full, user_id, "delete_my_profile_photo_from_cache"); } } @@ -13848,7 +14340,8 @@ void ContactsManager::drop_user_full(UserId user_id) { user_full->photo = Photo(); user_full->personal_photo = Photo(); user_full->fallback_photo = Photo(); - user_full->is_blocked = false; + // user_full->is_blocked = false; + // user_full->is_blocked_for_stories = false; user_full->can_be_called = false; user_full->supports_video_calls = false; user_full->has_private_calls = false; @@ -13865,6 +14358,7 @@ void ContactsManager::drop_user_full(UserId user_id) { user_full->broadcast_administrator_rights = {}; user_full->premium_gift_options.clear(); user_full->voice_messages_forbidden = false; + user_full->has_pinned_stories = false; user_full->is_changed = true; update_user_full(user_full, user_id, "drop_user_full"); @@ -14316,6 +14810,14 @@ void ContactsManager::on_get_channel_participants( } } + vector participant_user_ids; + for (const auto &participant : result) { + if (participant.dialog_id_.get_type() == DialogType::User) { + participant_user_ids.push_back(participant.dialog_id_.get_user_id()); + } + } + on_view_user_active_stories(std::move(participant_user_ids)); + promise.set_value(DialogParticipants{total_count, std::move(result)}); } @@ -15036,6 +15538,9 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link, invite_link_info->is_public = is_public; invite_link_info->is_megagroup = is_megagroup; + invite_link_info->is_verified = chat_invite->verified_; + invite_link_info->is_scam = chat_invite->scam_; + invite_link_info->is_fake = chat_invite->fake_; break; } default: @@ -15076,6 +15581,70 @@ void ContactsManager::invalidate_invite_link_info(const string &invite_link) { invite_link_infos_.erase(invite_link); } +bool ContactsManager::need_poll_active_stories(const User *u, UserId user_id) const { + return u != nullptr && user_id != get_my_id() && !is_user_contact(u, user_id, false) && !is_user_bot(u) && + !is_user_support(u) && !is_user_deleted(u) && u->was_online != 0; +} + +void ContactsManager::on_view_user_active_stories(vector user_ids) { + if (user_ids.empty() || td_->auth_manager_->is_bot()) { + return; + } + LOG(DEBUG) << "View active stories of " << user_ids; + + const size_t MAX_SLICE_SIZE = 100; // server side limit + vector input_user_ids; + vector> input_users; + for (auto &user_id : user_ids) { + User *u = get_user(user_id); + if (!need_poll_active_stories(u, user_id) || Time::now() < u->max_active_story_id_next_reload_time || + u->is_max_active_story_id_being_reloaded) { + continue; + } + auto r_input_user = get_input_user(user_id); + if (r_input_user.is_error() || td::contains(input_user_ids, user_id)) { + continue; + } + u->is_max_active_story_id_being_reloaded = true; + input_user_ids.push_back(user_id); + input_users.push_back(r_input_user.move_as_ok()); + if (input_users.size() == MAX_SLICE_SIZE) { + td_->create_handler()->send(std::move(input_user_ids), std::move(input_users)); + input_user_ids.clear(); + input_users.clear(); + } + } + if (!input_users.empty()) { + td_->create_handler()->send(std::move(input_user_ids), std::move(input_users)); + } +} + +void ContactsManager::on_get_user_max_active_story_ids(const vector &user_ids, + const vector &max_story_ids) { + for (auto &user_id : user_ids) { + User *u = get_user(user_id); + CHECK(u != nullptr); + CHECK(u->is_max_active_story_id_being_reloaded); + u->is_max_active_story_id_being_reloaded = false; + } + if (user_ids.size() != max_story_ids.size()) { + if (!max_story_ids.empty()) { + LOG(ERROR) << "Receive " << max_story_ids.size() << " max active story identifiers for users " << user_ids; + } + return; + } + for (size_t i = 0; i < user_ids.size(); i++) { + auto max_story_id = StoryId(max_story_ids[i]); + if (max_story_id == StoryId()) { + on_update_user_story_ids(user_ids[i], StoryId(), StoryId()); + } else if (max_story_id.is_server()) { + on_update_user_story_ids(user_ids[i], max_story_id, StoryId()); + } else { + LOG(ERROR) << "Receive " << max_story_id << " as maximum active story for " << user_ids[i]; + } + } +} + void ContactsManager::repair_chat_participants(ChatId chat_id) { send_get_chat_full_query(chat_id, Auto(), "repair_chat_participants"); } @@ -15481,7 +16050,7 @@ void ContactsManager::on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto chat_full->is_changed = true; } if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto()); + reload_chat_full(chat_id, Auto(), "on_update_chat_photo"); } update_chat_full(chat_full, chat_id, "on_update_chat_photo"); } @@ -15771,8 +16340,8 @@ void ContactsManager::on_update_channel_usernames(ChannelId channel_id, Username } void ContactsManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) { - td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); if (c->usernames != usernames) { + td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); if (c->is_update_supergroup_sent) { on_channel_usernames_changed(c, channel_id, c->usernames, usernames); } @@ -15780,6 +16349,8 @@ void ContactsManager::on_update_channel_usernames(Channel *c, ChannelId channel_ c->usernames = std::move(usernames); c->is_username_changed = true; c->is_changed = true; + } else { + td_->messages_manager_->on_dialog_usernames_received(DialogId(channel_id), usernames, false); } } @@ -15970,7 +16541,7 @@ void ContactsManager::on_update_bot_stopped(UserId user_id, int32 date, bool is_ LOG(ERROR) << "Receive updateBotStopped by non-bot"; return; } - if (date <= 0 || !have_user_force(user_id)) { + if (date <= 0 || !have_user_force(user_id, "on_update_bot_stopped")) { LOG(ERROR) << "Receive invalid updateBotStopped by " << user_id << " at " << date; return; } @@ -16096,8 +16667,8 @@ void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId void ContactsManager::on_update_chat_invite_requester(DialogId dialog_id, UserId user_id, string about, int32 date, DialogInviteLink invite_link) { - if (!td_->auth_manager_->is_bot() || date <= 0 || !have_user_force(user_id) || - !td_->messages_manager_->have_dialog_info_force(dialog_id)) { + if (!td_->auth_manager_->is_bot() || date <= 0 || !have_user_force(user_id, "on_update_chat_invite_requester") || + !td_->messages_manager_->have_dialog_info_force(dialog_id, "on_update_chat_invite_requester")) { LOG(ERROR) << "Receive invalid updateBotChatInviteRequester by " << user_id << " in " << dialog_id << " at " << date; return; @@ -16270,31 +16841,6 @@ void ContactsManager::reload_dialog_info(DialogId dialog_id, Promise &&pro } } -void ContactsManager::get_user_link(Promise> &&promise) { - get_me( - PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &ContactsManager::get_user_link_impl, std::move(promise)); - } - })); -} - -void ContactsManager::get_user_link_impl(Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - const auto *u = get_user(get_my_id()); - if (u != nullptr && u->usernames.has_first_username()) { - return promise.set_value(td_api::make_object( - LinkManager::get_public_dialog_link(u->usernames.get_first_username(), true), 0)); - } - export_contact_token(td_, std::move(promise)); -} - -void ContactsManager::search_user_by_token(string token, Promise> &&promise) { - import_contact_token(td_, token, std::move(promise)); -} - void ContactsManager::send_get_me_query(Td *td, Promise &&promise) { vector> users; users.push_back(make_tl_object()); @@ -16303,7 +16849,7 @@ void ContactsManager::send_get_me_query(Td *td, Promise &&promise) { UserId ContactsManager::get_me(Promise &&promise) { auto my_id = get_my_id(); - if (!have_user_force(my_id)) { + if (!have_user_force(my_id, "get_me")) { send_get_me_query(td_, std::move(promise)); return UserId(); } @@ -16321,7 +16867,7 @@ bool ContactsManager::get_user(UserId user_id, int left_tries, Promise &&p if (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || user_id == get_anti_spam_bot_user_id()) { - get_user_force(user_id); + get_user_force(user_id, "get_user"); } if (td_->auth_manager_->is_bot() ? !have_user(user_id) : !have_min_user(user_id)) { @@ -16348,7 +16894,7 @@ bool ContactsManager::get_user(UserId user_id, int left_tries, Promise &&p return true; } -ContactsManager::User *ContactsManager::add_user(UserId user_id, const char *source) { +ContactsManager::User *ContactsManager::add_user(UserId user_id) { CHECK(user_id.is_valid()); auto &user_ptr = users_[user_id]; if (user_ptr == nullptr) { @@ -16379,7 +16925,7 @@ void ContactsManager::reload_user(UserId user_id, Promise &&promise) { return promise.set_error(Status::Error(400, "Invalid user identifier")); } - have_user_force(user_id); + have_user_force(user_id, "reload_user"); TRY_STATUS_PROMISE(promise, get_input_user(user_id)); @@ -16406,12 +16952,13 @@ void ContactsManager::load_user_full(UserId user_id, bool force, Promise & send_get_user_full_query(user_id, std::move(input_user), Auto(), "load expired user_full"); } + on_view_user_active_stories({user_id}); promise.set_value(Unit()); } -void ContactsManager::reload_user_full(UserId user_id, Promise &&promise) { +void ContactsManager::reload_user_full(UserId user_id, Promise &&promise, const char *source) { TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - send_get_user_full_query(user_id, std::move(input_user), std::move(promise), "reload_user_full"); + send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); } void ContactsManager::send_get_user_full_query(UserId user_id, tl_object_ptr &&input_user, @@ -16584,7 +17131,7 @@ void ContactsManager::on_get_user_profile_photos(UserId user_id, Result && } void ContactsManager::reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise) { - get_user_force(user_id); + get_user_force(user_id, "reload_user_profile_photo"); TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); // this request will be needed only to download the photo, @@ -16720,6 +17267,8 @@ bool ContactsManager::get_chat(ChatId chat_id, int left_tries, Promise &&p } void ContactsManager::reload_chat(ChatId chat_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + if (!chat_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid basic group identifier")); } @@ -16795,11 +17344,19 @@ void ContactsManager::load_chat_full(ChatId chat_id, bool force, Promise & send_get_chat_full_query(chat_id, Auto(), source); } + vector participant_user_ids; + for (const auto &dialog_participant : chat_full->participants) { + if (dialog_participant.dialog_id_.get_type() == DialogType::User) { + participant_user_ids.push_back(dialog_participant.dialog_id_.get_user_id()); + } + } + on_view_user_active_stories(std::move(participant_user_ids)); + promise.set_value(Unit()); } -void ContactsManager::reload_chat_full(ChatId chat_id, Promise &&promise) { - send_get_chat_full_query(chat_id, std::move(promise), "reload_chat_full"); +void ContactsManager::reload_chat_full(ChatId chat_id, Promise &&promise, const char *source) { + send_get_chat_full_query(chat_id, std::move(promise), source); } void ContactsManager::send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source) { @@ -17140,6 +17697,8 @@ bool ContactsManager::get_channel(ChannelId channel_id, int left_tries, Promise< } void ContactsManager::reload_channel(ChannelId channel_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + if (!channel_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid supergroup identifier")); } @@ -17708,13 +18267,19 @@ void ContactsManager::do_search_chat_participants(ChatId chat_id, const string & return promise.set_error(Status::Error(500, "Can't find basic group full info")); } + vector participant_user_ids; vector dialog_ids; for (const auto &participant : chat_full->participants) { if (filter.is_dialog_participant_suitable(td_, participant)) { dialog_ids.push_back(participant.dialog_id_); + if (participant.dialog_id_.get_type() == DialogType::User) { + participant_user_ids.push_back(participant.dialog_id_.get_user_id()); + } } } + on_view_user_active_stories(std::move(participant_user_ids)); + int32 total_count; std::tie(total_count, dialog_ids) = search_among_dialogs(dialog_ids, query, limit); promise.set_value(DialogParticipants{total_count, transform(dialog_ids, [chat_full](DialogId dialog_id) { @@ -18594,6 +19159,10 @@ td_api::object_ptr ContactsManager::get_user_status_object(U } } +bool ContactsManager::get_user_has_unread_stories(const User *u) { + return u->max_active_story_id.get() > u->max_read_story_id.get(); +} + td_api::object_ptr ContactsManager::get_update_user_object(UserId user_id, const User *u) const { if (u == nullptr) { return get_update_unknown_user_object(user_id); @@ -18605,8 +19174,8 @@ td_api::object_ptr ContactsManager::get_update_unknown_user_ 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, have_access, td_api::make_object(), "", - 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 { @@ -18637,14 +19206,16 @@ tl_object_ptr ContactsManager::get_user_object(UserId user_id, con type = make_tl_object(); } - auto emoji_status = u->last_sent_emoji_status.is_valid() ? u->emoji_status.get_emoji_status_object() : nullptr; + 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); - return make_tl_object( + 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_verified, u->is_premium, u->is_support, - get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, have_access, std::move(type), - u->language_code, u->attach_menu_enabled); + 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); } vector ContactsManager::get_user_ids_object(const vector &user_ids, const char *source) const { @@ -18717,12 +19288,13 @@ tl_object_ptr ContactsManager::get_user_full_info_object(U bio_object = get_formatted_text_object(bio, true, 0); } auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false; - return make_tl_object( + auto block_list_id = BlockListId(user_full->is_blocked, user_full->is_blocked_for_stories); + return td_api::make_object( get_chat_photo_object(td_->file_manager_.get(), user_full->personal_photo), get_chat_photo_object(td_->file_manager_.get(), user_full->photo), - get_chat_photo_object(td_->file_manager_.get(), user_full->fallback_photo), user_full->is_blocked, + get_chat_photo_object(td_->file_manager_.get(), user_full->fallback_photo), block_list_id.get_block_list_object(), user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, - !user_full->private_forward_name.empty(), voice_messages_forbidden, + !user_full->private_forward_name.empty(), voice_messages_forbidden, user_full->has_pinned_stories, user_full->need_phone_number_privacy_exception, std::move(bio_object), get_premium_payment_options_object(user_full->premium_gift_options), user_full->common_chat_count, std::move(bot_info)); @@ -18780,8 +19352,8 @@ tl_object_ptr ContactsManager::get_basic_group_full_ auto bot_commands = transform(chat_full->bot_commands, [td = td_](const BotCommands &commands) { return commands.get_bot_commands_object(td); }); - auto members = transform(chat_full->participants, [this](const DialogParticipant &chat_participant) { - return get_chat_member_object(chat_participant); + auto members = transform(chat_full->participants, [this](const DialogParticipant &dialog_participant) { + return get_chat_member_object(dialog_participant); }); return make_tl_object( get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description, @@ -18856,8 +19428,8 @@ tl_object_ptr ContactsManager::get_supergroup_full_i channel_full->participant_count, channel_full->administrator_count, channel_full->restricted_count, channel_full->banned_count, DialogId(channel_full->linked_channel_id).get(), channel_full->slow_mode_delay, slow_mode_delay_expires_in, channel_full->can_get_participants, has_hidden_participants, - can_hide_channel_participants(channel_id, channel_full).is_ok(), channel_full->can_set_username, - channel_full->can_set_sticker_set, channel_full->can_set_location, channel_full->can_view_statistics, + can_hide_channel_participants(channel_id, channel_full).is_ok(), channel_full->can_set_sticker_set, + channel_full->can_set_location, channel_full->can_view_statistics, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full).is_ok(), channel_full->is_all_history_available, channel_full->has_aggressive_anti_spam_enabled, channel_full->sticker_set_id.get(), channel_full->location.get_chat_location_object(), channel_full->invite_link.get_chat_invite_link_object(this), @@ -18914,7 +19486,7 @@ tl_object_ptr ContactsManager::get_secret_chat_object(Secret if (secret_chat == nullptr) { return nullptr; } - get_user_force(secret_chat->user_id); + get_user_force(secret_chat->user_id, "get_secret_chat_object"); return get_secret_chat_object_const(secret_chat_id, secret_chat); } @@ -18936,6 +19508,8 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ CHECK(invite_link_info != nullptr); DialogId dialog_id = invite_link_info->dialog_id; + bool is_chat = false; + bool is_megagroup = false; string title; const DialogPhoto *photo = nullptr; DialogPhoto invite_link_photo; @@ -18945,13 +19519,16 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ bool creates_join_request = false; bool is_public = false; bool is_member = false; - td_api::object_ptr chat_type; + bool is_verified = false; + bool is_scam = false; + bool is_fake = false; if (dialog_id.is_valid()) { switch (dialog_id.get_type()) { case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - const Chat *c = get_chat(chat_id); + const Chat *c = get_chat_force(chat_id); + is_chat = true; if (c != nullptr) { title = c->title; @@ -18961,15 +19538,12 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ } else { LOG(ERROR) << "Have no information about " << chat_id; } - chat_type = td_api::make_object( - get_basic_group_id_object(chat_id, "get_chat_invite_link_info_object")); break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); + const Channel *c = get_channel_force(channel_id); - bool is_megagroup = false; if (c != nullptr) { title = c->title; photo = &c->photo; @@ -18977,11 +19551,12 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ is_megagroup = c->is_megagroup; participant_count = c->participant_count; is_member = c->status.is_member(); + is_verified = c->is_verified; + is_scam = c->is_scam; + is_fake = c->is_fake; } else { LOG(ERROR) << "Have no information about " << channel_id; } - chat_type = td_api::make_object( - get_supergroup_id_object(channel_id, "get_chat_invite_link_info_object"), !is_megagroup); break; } default: @@ -18989,6 +19564,8 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ } description = get_dialog_about(dialog_id); } else { + is_chat = invite_link_info->is_chat; + is_megagroup = invite_link_info->is_megagroup; title = invite_link_info->title; invite_link_photo = as_fake_dialog_photo(invite_link_info->photo, dialog_id, false); photo = &invite_link_photo; @@ -18997,12 +19574,18 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids, "get_chat_invite_link_info_object"); creates_join_request = invite_link_info->creates_join_request; is_public = invite_link_info->is_public; + is_verified = invite_link_info->is_verified; + is_scam = invite_link_info->is_scam; + is_fake = invite_link_info->is_fake; + } - if (invite_link_info->is_chat) { - chat_type = td_api::make_object(0); - } else { - chat_type = td_api::make_object(0, !invite_link_info->is_megagroup); - } + td_api::object_ptr chat_type; + if (is_chat) { + chat_type = td_api::make_object(); + } else if (is_megagroup) { + chat_type = td_api::make_object(); + } else { + chat_type = td_api::make_object(); } if (dialog_id.is_valid()) { @@ -19016,10 +19599,10 @@ tl_object_ptr ContactsManager::get_chat_invite_link_ } } - return make_tl_object( + 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); + 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/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index d6482b10def4..9b1b2115e6b3 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -13,7 +13,6 @@ #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" @@ -36,6 +35,7 @@ #include "td/telegram/RestrictionReason.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/StickerSetId.h" +#include "td/telegram/StoryId.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -127,6 +127,8 @@ class ContactsManager final : public Actor { bool get_chat_has_protected_content(ChatId chat_id) const; bool get_channel_has_protected_content(ChannelId channel_id) const; + bool get_user_stories_hidden(UserId user_id) const; + string get_user_private_forward_name(UserId user_id); bool get_user_voice_messages_forbidden(UserId user_id) const; @@ -159,7 +161,7 @@ class ContactsManager final : public Actor { void reload_contacts(bool force); - void on_get_user(tl_object_ptr &&user, const char *source, bool is_me = false); + void on_get_user(tl_object_ptr &&user, const char *source); void on_get_users(vector> &&users, const char *source); void on_binlog_user_event(BinlogEvent &&event); @@ -184,9 +186,13 @@ class ContactsManager final : public Actor { 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); void on_update_user_emoji_status(UserId user_id, tl_object_ptr &&emoji_status); + void on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); + void on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id); + void on_update_user_stories_hidden(UserId user_id, bool stories_hidden); void on_update_user_online(UserId user_id, tl_object_ptr &&status); void on_update_user_local_was_online(UserId user_id, int32 local_was_online); - void on_update_user_is_blocked(UserId user_id, bool is_blocked); + void on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories); + void on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories); void on_update_user_common_chat_count(UserId user_id, int32 common_chat_count); void on_update_user_need_phone_number_privacy_exception(UserId user_id, bool need_phone_number_privacy_exception); @@ -305,6 +311,8 @@ class ContactsManager final : public Actor { UserId add_channel_bot_user(); + static ChatId get_unsupported_chat_id(); + void on_update_username_is_active(UserId user_id, string &&username, bool is_active, Promise &&promise); void on_update_active_usernames_order(UserId user_id, vector &&usernames, Promise &&promise); @@ -358,6 +366,12 @@ class ContactsManager final : public Actor { void on_update_contacts_reset(); + vector get_close_friends(Promise &&promise); + + void set_close_friends(vector user_ids, Promise &&promise); + + void on_set_close_friends(const vector &user_ids, Promise &&promise); + UserId search_user_by_phone_number(string phone_number, Promise &&promise); void on_resolved_phone_number(const string &phone_number, UserId user_id); @@ -565,23 +579,19 @@ class ContactsManager final : public Actor { bool have_user(UserId user_id) const; bool have_min_user(UserId user_id) const; - bool have_user_force(UserId user_id); + bool have_user_force(UserId user_id, const char *source); bool is_dialog_info_received_from_server(DialogId dialog_id) const; void reload_dialog_info(DialogId dialog_id, Promise &&promise); - void get_user_link(Promise> &&promise); - - void search_user_by_token(string token, Promise> &&promise); - static void send_get_me_query(Td *td, Promise &&promise); UserId get_me(Promise &&promise); bool get_user(UserId user_id, int left_tries, Promise &&promise); void reload_user(UserId user_id, Promise &&promise); void load_user_full(UserId user_id, bool force, Promise &&promise, const char *source); FileSourceId get_user_full_file_source_id(UserId user_id); - void reload_user_full(UserId user_id, Promise &&promise); + void reload_user_full(UserId user_id, Promise &&promise, const char *source); void get_user_profile_photos(UserId user_id, int32 offset, int32 limit, Promise> &&promise); @@ -594,7 +604,7 @@ class ContactsManager final : public Actor { void reload_chat(ChatId chat_id, Promise &&promise); void load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source); FileSourceId get_chat_full_file_source_id(ChatId chat_id); - void reload_chat_full(ChatId chat_id, Promise &&promise); + void reload_chat_full(ChatId chat_id, Promise &&promise, const char *source); int32 get_chat_date(ChatId chat_id) const; int32 get_chat_participant_count(ChatId chat_id) const; @@ -704,6 +714,10 @@ class ContactsManager final : public Actor { void get_support_user(Promise> &&promise); + void on_view_user_active_stories(vector user_ids); + + void on_get_user_max_active_story_ids(const vector &user_ids, const vector &max_story_ids); + void repair_chat_participants(ChatId chat_id); void get_current_state(vector> &updates) const; @@ -735,7 +749,7 @@ class ContactsManager final : public Actor { string phone_number; int64 access_hash = -1; EmojiStatus emoji_status; - CustomEmojiId last_sent_emoji_status; + EmojiStatus last_sent_emoji_status; ProfilePhoto photo; @@ -746,6 +760,10 @@ class ContactsManager final : public Actor { int32 was_online = 0; int32 local_was_online = 0; + double max_active_story_id_next_reload_time = 0.0; + StoryId max_active_story_id; + StoryId max_read_story_id; + string language_code; FlatHashSet photo_ids; @@ -771,21 +789,30 @@ class ContactsManager final : public Actor { bool is_fake = false; bool is_contact = false; bool is_mutual_contact = false; + bool is_close_friend = false; bool need_apply_min_photo = false; bool can_be_added_to_attach_menu = false; bool attach_menu_enabled = false; + bool stories_hidden = false; bool is_photo_inited = false; bool is_repaired = false; // whether cached value is rechecked + bool is_max_active_story_id_being_reloaded = false; + bool is_name_changed = true; bool is_username_changed = true; bool is_photo_changed = true; bool is_phone_number_changed = true; + bool is_emoji_status_changed = true; bool is_is_contact_changed = true; + bool is_is_mutual_contact_changed = true; bool is_is_deleted_changed = true; + bool is_is_premium_changed = true; + bool is_stories_hidden_changed = true; bool is_full_info_changed = false; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_save_to_database = true; // have new changes that need only to be saved to the database bool is_status_changed = true; @@ -831,14 +858,17 @@ class ContactsManager final : public Actor { int32 common_chat_count = 0; bool is_blocked = false; + bool is_blocked_for_stories = false; bool can_be_called = false; bool supports_video_calls = false; bool has_private_calls = false; bool can_pin_messages = true; bool need_phone_number_privacy_exception = false; bool voice_messages_forbidden = false; + bool has_pinned_stories = false; bool is_common_chat_count_changed = true; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_send_update = true; // have new changes that need only to be sent to the client bool need_save_to_database = true; // have new changes that need only to be saved to the database @@ -883,6 +913,7 @@ class ContactsManager final : public Actor { bool is_status_changed = true; bool is_is_active_changed = true; bool is_noforwards_changed = true; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_save_to_database = true; // have new changes that need only to be saved to the database bool is_update_basic_group_sent = false; @@ -921,6 +952,7 @@ class ContactsManager final : public Actor { bool can_set_username = false; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_send_update = true; // have new changes that need only to be sent to the client bool need_save_to_database = true; // have new changes that need only to be saved to the database @@ -974,6 +1006,7 @@ class ContactsManager final : public Actor { bool is_creator_changed = true; bool had_read_access = true; bool was_member = false; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_save_to_database = true; // have new changes that need only to be saved to the database bool is_update_supergroup_sent = false; @@ -1041,6 +1074,7 @@ class ContactsManager final : public Actor { bool can_be_deleted = false; bool is_slow_mode_next_send_date_changed = true; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_send_update = true; // have new changes that need only to be sent to the client bool need_save_to_database = true; // have new changes that need only to be saved to the database @@ -1073,6 +1107,7 @@ class ContactsManager final : public Actor { bool is_ttl_changed = true; bool is_state_changed = true; + bool is_being_updated = false; bool is_changed = true; // have new changes that need to be sent to the client and database bool need_save_to_database = true; // have new changes that need only to be saved to the database @@ -1103,6 +1138,9 @@ class ContactsManager final : public Actor { bool is_channel = false; bool is_public = false; bool is_megagroup = false; + bool is_verified = false; + bool is_scam = false; + bool is_fake = false; }; struct PendingGetPhotoRequest { @@ -1151,8 +1189,10 @@ class ContactsManager final : public Actor { static constexpr size_t MAX_INVITE_LINK_TITLE_LENGTH = 32; // server side limit static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit - static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit + static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit + static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit + // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0; static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1; static constexpr int32 USER_FLAG_HAS_LAST_NAME = 1 << 2; @@ -1184,6 +1224,7 @@ class ContactsManager final : public Actor { static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30; static constexpr int32 USER_FLAG_HAS_USERNAMES = 1 << 0; static constexpr int32 USER_FLAG_CAN_BE_EDITED_BOT = 1 << 1; + static constexpr int32 USER_FLAG_IS_CLOSE_FRIEND = 1 << 2; static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; @@ -1296,10 +1337,10 @@ class ContactsManager final : public Actor { const User *get_user(UserId user_id) const; User *get_user(UserId user_id); - User *get_user_force(UserId user_id); - User *get_user_force_impl(UserId user_id); + User *get_user_force(UserId user_id, const char *source); + User *get_user_force_impl(UserId user_id, const char *source); - User *add_user(UserId user_id, const char *source); + User *add_user(UserId user_id); const UserFull *get_user_full(UserId user_id) const; UserFull *get_user_full(UserId user_id); @@ -1364,8 +1405,6 @@ class ContactsManager final : public Actor { string get_channel_search_text(ChannelId channel_id) const; static string get_channel_search_text(const Channel *c); - void get_user_link_impl(Promise> &&promise); - void set_my_id(UserId my_id); static bool is_allowed_username(const string &username); @@ -1378,7 +1417,11 @@ class ContactsManager final : public Actor { void on_update_user_photo(User *u, UserId user_id, tl_object_ptr &&photo, const char *source); void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status); - void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact); + void on_update_user_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); + void on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden); + void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, + bool is_close_friend); void on_update_user_online(User *u, UserId user_id, tl_object_ptr &&status); void on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online); @@ -1400,7 +1443,8 @@ class ContactsManager final : public Actor { void register_user_photo(User *u, UserId user_id, const Photo &photo); - static void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked); + static void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, + bool is_blocked_for_stories); static void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count); static void on_update_user_full_commands(UserFull *user_full, UserId user_id, vector> &&bot_commands); @@ -1711,6 +1755,10 @@ class ContactsManager final : public Actor { void on_dismiss_suggested_action(SuggestedAction action, Result &&result); + bool need_poll_active_stories(const User *u, UserId user_id) const; + + static bool get_user_has_unread_stories(const User *u); + td_api::object_ptr get_update_user_object(UserId user_id, const User *u) const; td_api::object_ptr get_update_unknown_user_object(UserId user_id) const; @@ -1880,7 +1928,7 @@ class ContactsManager final : public Actor { WaitFreeHashMap, UserIdHash> pending_user_photos_; struct UserIdPhotoIdHash { uint32 operator()(const std::pair &pair) const { - return UserIdHash()(pair.first) * 2023654985u + Hash()(pair.second); + return combine_hashes(UserIdHash()(pair.first), Hash()(pair.second)); } }; WaitFreeHashMap, FileSourceId, UserIdPhotoIdHash> user_profile_photo_file_source_ids_; @@ -1971,7 +2019,7 @@ class ContactsManager final : public Actor { , promise(std::move(promise)) { } }; - FlatHashMap uploaded_profile_photos_; // file_id -> promise + FlatHashMap uploaded_profile_photos_; struct ImportContactsTask { Promise promise_; diff --git a/td/telegram/Dependencies.cpp b/td/telegram/Dependencies.cpp index 91e544179e57..112078f58d01 100644 --- a/td/telegram/Dependencies.cpp +++ b/td/telegram/Dependencies.cpp @@ -8,6 +8,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/WebPagesManager.h" @@ -46,6 +47,13 @@ void Dependencies::add(WebPageId web_page_id) { } } +void Dependencies::add(StoryFullId story_full_id) { + if (story_full_id.is_valid()) { + add_dialog_and_dependencies(story_full_id.get_dialog_id()); + story_full_ids.insert(story_full_id); + } +} + void Dependencies::add_dialog_and_dependencies(DialogId dialog_id) { if (dialog_id.is_valid() && dialog_ids.insert(dialog_id).second) { add_dialog_dependencies(dialog_id); @@ -84,7 +92,7 @@ void Dependencies::add_message_sender_dependencies(DialogId dialog_id) { bool Dependencies::resolve_force(Td *td, const char *source) const { bool success = true; for (auto user_id : user_ids) { - if (!td->contacts_manager_->have_user_force(user_id)) { + if (!td->contacts_manager_->have_user_force(user_id, source)) { LOG(ERROR) << "Can't find " << user_id << " from " << source; success = false; } @@ -114,14 +122,18 @@ bool Dependencies::resolve_force(Td *td, const char *source) const { for (auto dialog_id : dialog_ids) { if (!td->messages_manager_->have_dialog_force(dialog_id, source)) { LOG(ERROR) << "Can't find " << dialog_id << " from " << source; - td->messages_manager_->force_create_dialog(dialog_id, "resolve_dependencies_force", true); + td->messages_manager_->force_create_dialog(dialog_id, source, true); success = false; } } for (auto web_page_id : web_page_ids) { if (!td->web_pages_manager_->have_web_page_force(web_page_id)) { LOG(INFO) << "Can't find " << web_page_id << " from " << source; - success = false; + } + } + for (auto story_full_id : story_full_ids) { + if (!td->story_manager_->have_story_force(story_full_id)) { + LOG(INFO) << "Can't find " << story_full_id << " from " << source; } } return success; diff --git a/td/telegram/Dependencies.h b/td/telegram/Dependencies.h index 4ce8aba4e50b..caff8c56b767 100644 --- a/td/telegram/Dependencies.h +++ b/td/telegram/Dependencies.h @@ -10,6 +10,7 @@ #include "td/telegram/ChatId.h" #include "td/telegram/DialogId.h" #include "td/telegram/SecretChatId.h" +#include "td/telegram/StoryFullId.h" #include "td/telegram/UserId.h" #include "td/telegram/WebPageId.h" @@ -26,6 +27,7 @@ class Dependencies { FlatHashSet secret_chat_ids; FlatHashSet dialog_ids; FlatHashSet web_page_ids; + FlatHashSet story_full_ids; public: void add(UserId user_id); @@ -38,6 +40,8 @@ class Dependencies { void add(WebPageId web_page_id); + void add(StoryFullId story_full_id); + void add_dialog_and_dependencies(DialogId dialog_id); void add_dialog_dependencies(DialogId dialog_id); diff --git a/td/telegram/DeviceTokenManager.cpp b/td/telegram/DeviceTokenManager.cpp index 5b712dc76e1f..4d87cc0bc2ac 100644 --- a/td/telegram/DeviceTokenManager.cpp +++ b/td/telegram/DeviceTokenManager.cpp @@ -9,7 +9,6 @@ #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryDispatcher.h" -#include "td/telegram/td_api.hpp" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" diff --git a/td/telegram/DialogAction.cpp b/td/telegram/DialogAction.cpp index 61973cb53595..a79a19825fba 100644 --- a/td/telegram/DialogAction.cpp +++ b/td/telegram/DialogAction.cpp @@ -8,6 +8,7 @@ #include "td/telegram/misc.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/telegram_api.h" #include "td/utils/emoji.h" #include "td/utils/misc.h" @@ -410,6 +411,8 @@ bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_con case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::Story: + case MessageContentType::WriteAccessAllowedByRequest: return false; default: UNREACHABLE(); diff --git a/td/telegram/DialogDate.h b/td/telegram/DialogDate.h index 881be9f9a31c..cb70f455b02e 100644 --- a/td/telegram/DialogDate.h +++ b/td/telegram/DialogDate.h @@ -62,7 +62,7 @@ const int64 DEFAULT_ORDER = -1; struct DialogDateHash { uint32 operator()(const DialogDate &dialog_date) const { - return Hash()(dialog_date.get_order()) * 2023654985u + DialogIdHash()(dialog_date.get_dialog_id()); + return combine_hashes(Hash()(dialog_date.get_order()), DialogIdHash()(dialog_date.get_dialog_id())); } }; diff --git a/td/telegram/DialogDb.cpp b/td/telegram/DialogDb.cpp index edbb929a7888..ceb9130b138d 100644 --- a/td/telegram/DialogDb.cpp +++ b/td/telegram/DialogDb.cpp @@ -36,7 +36,7 @@ Status init_dialog_db(SqliteDb &db, int32 version, KeyValueSyncInterface &binlog version = 0; } - if (version < static_cast(DbVersion::DialogDbCreated) || version > current_db_version()) { + if (version < static_cast(DbVersion::CreateDialogDb) || version > current_db_version()) { TRY_STATUS(drop_dialog_db(db, version)); version = 0; } @@ -104,7 +104,7 @@ 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::DialogDbCreated)) { + if (version < static_cast(DbVersion::CreateDialogDb)) { if (version != 0) { LOG(WARNING) << "Drop old pmc dialog_db"; } diff --git a/td/telegram/DialogEventLog.cpp b/td/telegram/DialogEventLog.cpp index 2bf2c95076d9..4c048c344048 100644 --- a/td/telegram/DialogEventLog.cpp +++ b/td/telegram/DialogEventLog.cpp @@ -249,8 +249,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionChangeLocation::ID: { auto action = move_tl_object_as(action_ptr); - auto old_location = DialogLocation(std::move(action->prev_value_)); - auto new_location = DialogLocation(std::move(action->new_value_)); + auto old_location = DialogLocation(td, std::move(action->prev_value_)); + auto new_location = DialogLocation(td, std::move(action->new_value_)); return td_api::make_object(old_location.get_chat_location_object(), new_location.get_chat_location_object()); } diff --git a/td/telegram/DialogFilter.cpp b/td/telegram/DialogFilter.cpp index c652c0e0cfb6..a489bd2282ed 100644 --- a/td/telegram/DialogFilter.cpp +++ b/td/telegram/DialogFilter.cpp @@ -16,7 +16,6 @@ #include "td/utils/algorithm.h" #include "td/utils/emoji.h" #include "td/utils/FlatHashSet.h" -#include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -485,7 +484,7 @@ td_api::object_ptr DialogFilter::get_chat_folder_object( td_api::object_ptr DialogFilter::get_chat_folder_info_object() const { return td_api::make_object( dialog_filter_id_.get(), title_, td_api::make_object(get_chosen_or_default_icon_name()), - has_my_invites_); + is_shareable_, has_my_invites_); } void DialogFilter::for_each_dialog(std::function callback) const { diff --git a/td/telegram/DialogFilterManager.cpp b/td/telegram/DialogFilterManager.cpp index 9b73ea786b58..75ecac42f1ca 100644 --- a/td/telegram/DialogFilterManager.cpp +++ b/td/telegram/DialogFilterManager.cpp @@ -19,6 +19,7 @@ #include "td/telegram/OptionManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" @@ -943,7 +944,7 @@ void DialogFilterManager::load_dialog_filter(const DialogFilter *dialog_filter, // TODO load dialogs asynchronously if (!td_->messages_manager_->have_dialog_force(dialog_id, "load_dialog_filter")) { if (dialog_id.get_type() == DialogType::SecretChat) { - if (td_->messages_manager_->have_dialog_info_force(dialog_id)) { + if (td_->messages_manager_->have_dialog_info_force(dialog_id, "load_dialog_filter")) { td_->messages_manager_->force_create_dialog(dialog_id, "load_dialog_filter"); } } else { @@ -1018,6 +1019,7 @@ void DialogFilterManager::delete_dialogs_from_filter(const DialogFilter *dialog_ return; } + bool was_valid = dialog_filter->check_limits().is_ok(); auto new_dialog_filter = td::make_unique(*dialog_filter); for (auto dialog_id : dialog_ids) { new_dialog_filter->remove_dialog_id(dialog_id); @@ -1026,7 +1028,7 @@ void DialogFilterManager::delete_dialogs_from_filter(const DialogFilter *dialog_ delete_dialog_filter(dialog_filter->get_dialog_filter_id(), vector(), Promise()); return; } - CHECK(new_dialog_filter->check_limits().is_ok()); + CHECK(!was_valid || new_dialog_filter->check_limits().is_ok()); if (*new_dialog_filter != *dialog_filter) { LOG(INFO) << "Update " << *dialog_filter << " to " << *new_dialog_filter; @@ -1469,7 +1471,6 @@ td_api::object_ptr DialogFilterManager::get_update_ch void DialogFilterManager::create_dialog_filter(td_api::object_ptr filter, Promise> &&promise) { - CHECK(!td_->auth_manager_->is_bot()); auto max_dialog_filters = clamp(td_->option_manager_->get_option_integer("chat_folder_count_max"), static_cast(0), static_cast(100)); if (dialog_filters_.size() >= narrow_cast(max_dialog_filters)) { @@ -2002,8 +2003,8 @@ void DialogFilterManager::on_get_chatlist_invite( if (icon_name.empty()) { icon_name = "Custom"; } - info = td_api::make_object(0, invite->title_, - td_api::make_object(icon_name), false); + info = td_api::make_object( + 0, invite->title_, td_api::make_object(icon_name), true, false); missing_peers = std::move(invite->peers_); chats = std::move(invite->chats_); users = std::move(invite->users_); diff --git a/td/telegram/DialogId.cpp b/td/telegram/DialogId.cpp index 43c1e392ab3d..12dd6ba92e34 100644 --- a/td/telegram/DialogId.cpp +++ b/td/telegram/DialogId.cpp @@ -6,8 +6,6 @@ // #include "td/telegram/DialogId.h" -#include "td/telegram/telegram_api.h" - #include "td/utils/algorithm.h" #include "td/utils/logging.h" diff --git a/td/telegram/DialogListId.h b/td/telegram/DialogListId.h index 5d4c67a62dee..8ddd5a370fed 100644 --- a/td/telegram/DialogListId.h +++ b/td/telegram/DialogListId.h @@ -27,7 +27,12 @@ class DialogListId { public: DialogListId() = default; - explicit constexpr DialogListId(int64 dialog_list_id) : id(dialog_list_id) { + explicit DialogListId(int64 dialog_list_id) : id(dialog_list_id) { + if (is_folder() && get_folder_id() != FolderId::archive()) { + id = FolderId::main().get(); + } else if (is_filter()) { + CHECK(get_filter_id().is_valid()); + } } template ::value>> DialogListId(T dialog_list_id) = delete; @@ -68,9 +73,6 @@ class DialogListId { if (folder_id == FolderId::archive()) { return td_api::make_object(); } - if (folder_id == FolderId::main()) { - return td_api::make_object(); - } return td_api::make_object(); } if (is_filter()) { @@ -132,7 +134,7 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, DialogListId dia if (dialog_list_id.is_filter()) { return string_builder << "chat list " << dialog_list_id.get_filter_id(); } - return string_builder << "chat list " << dialog_list_id.get(); + return string_builder << "unknown chat list " << dialog_list_id.get(); } } // namespace td diff --git a/td/telegram/DialogLocation.cpp b/td/telegram/DialogLocation.cpp index 4eb5ba440547..b8918785c634 100644 --- a/td/telegram/DialogLocation.cpp +++ b/td/telegram/DialogLocation.cpp @@ -10,10 +10,10 @@ namespace td { -DialogLocation::DialogLocation(telegram_api::object_ptr &&channel_location_ptr) { +DialogLocation::DialogLocation(Td *td, telegram_api::object_ptr &&channel_location_ptr) { if (channel_location_ptr != nullptr && channel_location_ptr->get_id() == telegram_api::channelLocation::ID) { auto channel_location = static_cast(channel_location_ptr.get()); - location_ = Location(channel_location->geo_point_); + location_ = Location(td, channel_location->geo_point_); address_ = std::move(channel_location->address_); } } diff --git a/td/telegram/DialogLocation.h b/td/telegram/DialogLocation.h index cfd80cec5c55..fc6bd0e3e14a 100644 --- a/td/telegram/DialogLocation.h +++ b/td/telegram/DialogLocation.h @@ -16,6 +16,8 @@ namespace td { +class Td; + class DialogLocation { Location location_; string address_; @@ -28,7 +30,7 @@ class DialogLocation { public: DialogLocation() = default; - explicit DialogLocation(telegram_api::object_ptr &&channel_location_ptr); + DialogLocation(Td *td, telegram_api::object_ptr &&channel_location_ptr); explicit DialogLocation(td_api::object_ptr &&chat_location); diff --git a/td/telegram/DialogNotificationSettings.cpp b/td/telegram/DialogNotificationSettings.cpp index 8b57716f0c86..046eed719357 100644 --- a/td/telegram/DialogNotificationSettings.cpp +++ b/td/telegram/DialogNotificationSettings.cpp @@ -12,13 +12,46 @@ namespace td { +telegram_api::object_ptr +DialogNotificationSettings::get_input_peer_notify_settings() const { + int32 flags = 0; + if (!use_default_mute_until) { + flags |= telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK; + } + if (sound != nullptr) { + flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK; + } + if (!use_default_show_preview) { + flags |= telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK; + } + if (!use_default_mute_stories) { + flags |= telegram_api::inputPeerNotifySettings::STORIES_MUTED_MASK; + } + if (story_sound != nullptr) { + flags |= telegram_api::inputPeerNotifySettings::STORIES_SOUND_MASK; + } + if (!use_default_hide_story_sender) { + flags |= telegram_api::inputPeerNotifySettings::STORIES_HIDE_SENDER_MASK; + } + if (silent_send_message) { + flags |= telegram_api::inputPeerNotifySettings::SILENT_MASK; + } + return telegram_api::make_object( + flags, show_preview, silent_send_message, mute_until, get_input_notification_sound(sound), mute_stories, + hide_story_sender, get_input_notification_sound(story_sound)); +} + StringBuilder &operator<<(StringBuilder &string_builder, const DialogNotificationSettings ¬ification_settings) { return string_builder << "[" << notification_settings.mute_until << ", " << notification_settings.sound << ", " - << notification_settings.show_preview << ", " << notification_settings.silent_send_message - << ", " << notification_settings.disable_pinned_message_notifications << ", " + << notification_settings.show_preview << ", " << notification_settings.mute_stories << ", " + << notification_settings.story_sound << ", " << notification_settings.hide_story_sender << ", " + << notification_settings.silent_send_message << ", " + << notification_settings.disable_pinned_message_notifications << ", " << notification_settings.disable_mention_notifications << ", " << notification_settings.use_default_mute_until << ", " << notification_settings.use_default_show_preview << ", " + << notification_settings.use_default_mute_stories << ", " + << notification_settings.use_default_hide_story_sender << ", " << notification_settings.use_default_disable_pinned_message_notifications << ", " << notification_settings.use_default_disable_mention_notifications << ", " << notification_settings.is_synchronized << "]"; @@ -31,7 +64,11 @@ td_api::object_ptr get_chat_notification_setti notification_settings->use_default_mute_until, max(0, notification_settings->mute_until - G()->unix_time()), is_notification_sound_default(notification_settings->sound), get_notification_sound_ringtone_id(notification_settings->sound), notification_settings->use_default_show_preview, - notification_settings->show_preview, notification_settings->use_default_disable_pinned_message_notifications, + notification_settings->show_preview, notification_settings->use_default_mute_stories, + notification_settings->mute_stories, is_notification_sound_default(notification_settings->story_sound), + get_notification_sound_ringtone_id(notification_settings->story_sound), + notification_settings->use_default_hide_story_sender, !notification_settings->hide_story_sender, + notification_settings->use_default_disable_pinned_message_notifications, notification_settings->disable_pinned_message_notifications, notification_settings->use_default_disable_mention_notifications, notification_settings->disable_mention_notifications); @@ -66,13 +103,22 @@ Result get_dialog_notification_settings( if (is_notification_sound_default(old_settings->sound) && is_notification_sound_default(notification_sound)) { notification_sound = dup_notification_sound(old_settings->sound); } - return DialogNotificationSettings(notification_settings->use_default_mute_for_, mute_until, - std::move(notification_sound), notification_settings->use_default_show_preview_, - notification_settings->show_preview_, old_settings->silent_send_message, - notification_settings->use_default_disable_pinned_message_notifications_, - notification_settings->disable_pinned_message_notifications_, - notification_settings->use_default_disable_mention_notifications_, - notification_settings->disable_mention_notifications_); + auto story_notification_sound = + get_notification_sound(notification_settings->use_default_story_sound_, notification_settings->story_sound_id_); + if (is_notification_sound_default(old_settings->story_sound) && + is_notification_sound_default(story_notification_sound)) { + story_notification_sound = dup_notification_sound(old_settings->story_sound); + } + return DialogNotificationSettings( + notification_settings->use_default_mute_for_, mute_until, std::move(notification_sound), + notification_settings->use_default_show_preview_, notification_settings->show_preview_, + notification_settings->use_default_mute_stories_, notification_settings->mute_stories_, + std::move(story_notification_sound), notification_settings->use_default_show_story_sender_, + !notification_settings->show_story_sender_, old_settings->silent_send_message, + notification_settings->use_default_disable_pinned_message_notifications_, + notification_settings->disable_pinned_message_notifications_, + notification_settings->use_default_disable_mention_notifications_, + notification_settings->disable_mention_notifications_); } DialogNotificationSettings get_dialog_notification_settings(tl_object_ptr &&settings, @@ -100,13 +146,21 @@ DialogNotificationSettings get_dialog_notification_settings(tl_object_ptrflags_ & telegram_api::peerNotifySettings::MUTE_UNTIL_MASK) == 0; bool use_default_show_preview = (settings->flags_ & telegram_api::peerNotifySettings::SHOW_PREVIEWS_MASK) == 0; + bool use_default_mute_stories = (settings->flags_ & telegram_api::peerNotifySettings::STORIES_MUTED_MASK) == 0; + bool use_default_hide_story_sender = + (settings->flags_ & telegram_api::peerNotifySettings::STORIES_HIDE_SENDER_MASK) == 0; auto mute_until = use_default_mute_until || settings->mute_until_ <= G()->unix_time() ? 0 : settings->mute_until_; bool silent_send_message = settings->silent_; return {use_default_mute_until, mute_until, - get_notification_sound(settings.get()), + get_notification_sound(settings.get(), false), use_default_show_preview, settings->show_previews_, + use_default_mute_stories, + settings->stories_muted_, + get_notification_sound(settings.get(), true), + use_default_hide_story_sender, + settings->stories_hide_sender_, silent_send_message, old_use_default_disable_pinned_message_notifications, old_disable_pinned_message_notifications, @@ -120,14 +174,25 @@ bool are_default_dialog_notification_settings(const DialogNotificationSettings & settings.use_default_disable_mention_notifications; } +bool are_default_story_notification_settings(const DialogNotificationSettings &settings) { + return settings.use_default_mute_stories && is_notification_sound_default(settings.story_sound) && + settings.use_default_hide_story_sender; +} + NeedUpdateDialogNotificationSettings need_update_dialog_notification_settings( const DialogNotificationSettings *current_settings, const DialogNotificationSettings &new_settings) { NeedUpdateDialogNotificationSettings result; - result.need_update_server = current_settings->mute_until != new_settings.mute_until || - !are_equivalent_notification_sounds(current_settings->sound, new_settings.sound) || - current_settings->show_preview != new_settings.show_preview || - current_settings->use_default_mute_until != new_settings.use_default_mute_until || - current_settings->use_default_show_preview != new_settings.use_default_show_preview; + result.need_update_server = + current_settings->mute_until != new_settings.mute_until || + !are_equivalent_notification_sounds(current_settings->sound, new_settings.sound) || + !are_equivalent_notification_sounds(current_settings->story_sound, new_settings.story_sound) || + current_settings->show_preview != new_settings.show_preview || + current_settings->mute_stories != new_settings.mute_stories || + current_settings->hide_story_sender != new_settings.hide_story_sender || + current_settings->use_default_mute_until != new_settings.use_default_mute_until || + current_settings->use_default_show_preview != new_settings.use_default_show_preview || + current_settings->use_default_mute_stories != new_settings.use_default_mute_stories || + current_settings->use_default_hide_story_sender != new_settings.use_default_hide_story_sender; result.need_update_local = current_settings->use_default_disable_pinned_message_notifications != new_settings.use_default_disable_pinned_message_notifications || @@ -135,10 +200,12 @@ NeedUpdateDialogNotificationSettings need_update_dialog_notification_settings( current_settings->use_default_disable_mention_notifications != new_settings.use_default_disable_mention_notifications || current_settings->disable_mention_notifications != new_settings.disable_mention_notifications; - result.are_changed = result.need_update_server || result.need_update_local || - current_settings->is_synchronized != new_settings.is_synchronized || - current_settings->is_use_default_fixed != new_settings.is_use_default_fixed || - are_different_equivalent_notification_sounds(current_settings->sound, new_settings.sound); + result.are_changed = + result.need_update_server || result.need_update_local || + current_settings->is_synchronized != new_settings.is_synchronized || + current_settings->is_use_default_fixed != new_settings.is_use_default_fixed || + are_different_equivalent_notification_sounds(current_settings->sound, new_settings.sound) || + are_different_equivalent_notification_sounds(current_settings->story_sound, new_settings.story_sound); return result; } diff --git a/td/telegram/DialogNotificationSettings.h b/td/telegram/DialogNotificationSettings.h index 33c041c8fadf..8fe85ec3fa74 100644 --- a/td/telegram/DialogNotificationSettings.h +++ b/td/telegram/DialogNotificationSettings.h @@ -20,10 +20,15 @@ class DialogNotificationSettings { public: int32 mute_until = 0; unique_ptr sound; + unique_ptr story_sound; bool show_preview = true; + bool mute_stories = false; + bool hide_story_sender = false; bool silent_send_message = false; bool use_default_mute_until = true; bool use_default_show_preview = true; + bool use_default_mute_stories = true; + bool use_default_hide_story_sender = true; bool is_use_default_fixed = true; bool is_secret_chat_show_preview_fixed = false; bool is_synchronized = false; @@ -37,22 +42,31 @@ class DialogNotificationSettings { DialogNotificationSettings() = default; DialogNotificationSettings(bool use_default_mute_until, int32 mute_until, unique_ptr &&sound, - bool use_default_show_preview, bool show_preview, bool silent_send_message, + bool use_default_show_preview, bool show_preview, bool use_default_mute_stories, + bool mute_stories, unique_ptr &&story_sound, + bool use_default_hide_story_sender, bool hide_story_sender, bool silent_send_message, bool use_default_disable_pinned_message_notifications, bool disable_pinned_message_notifications, bool use_default_disable_mention_notifications, bool disable_mention_notifications) : mute_until(mute_until) , sound(std::move(sound)) + , story_sound(std::move(story_sound)) , show_preview(show_preview) + , mute_stories(mute_stories) + , hide_story_sender(hide_story_sender) , silent_send_message(silent_send_message) , use_default_mute_until(use_default_mute_until) , use_default_show_preview(use_default_show_preview) + , use_default_mute_stories(use_default_mute_stories) + , use_default_hide_story_sender(use_default_hide_story_sender) , is_synchronized(true) , use_default_disable_pinned_message_notifications(use_default_disable_pinned_message_notifications) , disable_pinned_message_notifications(disable_pinned_message_notifications) , use_default_disable_mention_notifications(use_default_disable_mention_notifications) , disable_mention_notifications(disable_mention_notifications) { } + + telegram_api::object_ptr get_input_peer_notify_settings() const; }; StringBuilder &operator<<(StringBuilder &string_builder, const DialogNotificationSettings ¬ification_settings); @@ -69,6 +83,8 @@ DialogNotificationSettings get_dialog_notification_settings(tl_object_ptr G()->unix_time(); bool has_sound = notification_settings.sound != nullptr; bool has_ringtone_support = true; + bool use_mute_stories = !notification_settings.use_default_mute_stories; + bool has_story_sound = notification_settings.story_sound != nullptr; + bool use_hide_story_sender = !notification_settings.use_default_hide_story_sender; BEGIN_STORE_FLAGS(); STORE_FLAG(is_muted); STORE_FLAG(has_sound); @@ -36,6 +39,11 @@ void store(const DialogNotificationSettings ¬ification_settings, StorerT &sto STORE_FLAG(notification_settings.disable_mention_notifications); STORE_FLAG(notification_settings.is_secret_chat_show_preview_fixed); STORE_FLAG(has_ringtone_support); + STORE_FLAG(notification_settings.mute_stories); + STORE_FLAG(use_mute_stories); + STORE_FLAG(has_story_sound); + STORE_FLAG(notification_settings.hide_story_sender); + STORE_FLAG(use_hide_story_sender); END_STORE_FLAGS(); if (is_muted) { store(notification_settings.mute_until, storer); @@ -43,6 +51,9 @@ void store(const DialogNotificationSettings ¬ification_settings, StorerT &sto if (has_sound) { store(notification_settings.sound, storer); } + if (has_story_sound) { + store(notification_settings.story_sound, storer); + } } template @@ -53,6 +64,9 @@ void parse(DialogNotificationSettings ¬ification_settings, ParserT &parser) { bool use_disable_pinned_message_notifications; bool use_disable_mention_notifications; bool has_ringtone_support; + bool use_mute_stories; + bool has_story_sound; + bool use_hide_story_sender; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_muted); PARSE_FLAG(has_sound); @@ -69,9 +83,16 @@ void parse(DialogNotificationSettings ¬ification_settings, ParserT &parser) { PARSE_FLAG(notification_settings.disable_mention_notifications); PARSE_FLAG(notification_settings.is_secret_chat_show_preview_fixed); PARSE_FLAG(has_ringtone_support); + PARSE_FLAG(notification_settings.mute_stories); + PARSE_FLAG(use_mute_stories); + PARSE_FLAG(has_story_sound); + PARSE_FLAG(notification_settings.hide_story_sender); + PARSE_FLAG(use_hide_story_sender); END_PARSE_FLAGS(); notification_settings.use_default_disable_pinned_message_notifications = !use_disable_pinned_message_notifications; notification_settings.use_default_disable_mention_notifications = !use_disable_mention_notifications; + notification_settings.use_default_mute_stories = !use_mute_stories; + notification_settings.use_default_hide_story_sender = !use_hide_story_sender; if (is_muted) { parse(notification_settings.mute_until, parser); } @@ -84,6 +105,9 @@ void parse(DialogNotificationSettings ¬ification_settings, ParserT &parser) { notification_settings.sound = use_default_sound ? nullptr : get_legacy_notification_sound(sound); } } + if (has_story_sound) { + parse_notification_sound(notification_settings.story_sound, parser); + } } } // namespace td diff --git a/td/telegram/DocumentsManager.cpp b/td/telegram/DocumentsManager.cpp index c1fdc9c78ba7..c5d304ddc76b 100644 --- a/td/telegram/DocumentsManager.cpp +++ b/td/telegram/DocumentsManager.cpp @@ -45,6 +45,7 @@ #include "td/utils/StringBuilder.h" #include "td/utils/utf8.h" +#include #include namespace td { @@ -120,10 +121,18 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo UNREACHABLE(); } } + bool video_is_animation = false; + double video_precise_duration = 0.0; int32 video_duration = 0; + int32 video_preload_prefix_size = 0; string video_waveform; if (video != nullptr) { - video_duration = video->duration_; + video_precise_duration = video->duration_; + video_duration = static_cast(std::ceil(video->duration_)); + if (document_subtype == Subtype::Story) { + video_preload_prefix_size = video->preload_prefix_size_; + } + video_is_animation = video->nosound_; auto video_dimensions = get_dimensions(video->w_, video->h_, "documentAttributeVideo"); if (dimensions.width == 0 || (video_dimensions.width != 0 && video_dimensions != dimensions)) { if (dimensions.width != 0) { @@ -260,6 +269,14 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo file_type = FileType::Ringtone; default_extension = Slice("mp3"); break; + case Subtype::Story: + if (document_type != Document::Type::Video) { + LOG(ERROR) << "Receive story of type " << document_type; + document_type = Document::Type::Video; + } + file_type = FileType::VideoStory; + default_extension = Slice("mp4"); + break; default: break; } @@ -523,10 +540,10 @@ Document DocumentsManager::on_get_document(RemoteDocument remote_document, Dialo std::move(custom_emoji), sticker_format, load_data_multipromise_ptr); break; case Document::Type::Video: - td_->videos_manager_->create_video(file_id, std::move(minithumbnail), std::move(thumbnail), - std::move(animated_thumbnail), has_stickers, vector(), - std::move(file_name), std::move(mime_type), video_duration, dimensions, - supports_streaming, !is_web); + td_->videos_manager_->create_video( + file_id, std::move(minithumbnail), std::move(thumbnail), std::move(animated_thumbnail), has_stickers, + vector(), std::move(file_name), std::move(mime_type), video_duration, video_precise_duration, + dimensions, supports_streaming, video_is_animation, video_preload_prefix_size, !is_web); break; case Document::Type::VideoNote: td_->video_notes_manager_->create_video_note(file_id, std::move(minithumbnail), std::move(thumbnail), diff --git a/td/telegram/DocumentsManager.h b/td/telegram/DocumentsManager.h index 5175c154a307..913c49a7a094 100644 --- a/td/telegram/DocumentsManager.h +++ b/td/telegram/DocumentsManager.h @@ -82,7 +82,7 @@ class DocumentsManager { tl_object_ptr get_document_object(FileId file_id, PhotoFormat thumbnail_format) const; - enum class Subtype : int32 { Background, Pattern, Ringtone, Other }; + enum class Subtype : int32 { Background, Pattern, Ringtone, Story, Other }; Document on_get_document(RemoteDocument remote_document, DialogId owner_dialog_id, MultiPromiseActor *load_data_multipromise_ptr = nullptr, @@ -133,7 +133,7 @@ class DocumentsManager { FileId on_get_document(unique_ptr new_document, bool replace); Td *td_; - WaitFreeHashMap, FileIdHash> documents_; // file_id -> GeneralDocument + WaitFreeHashMap, FileIdHash> documents_; }; } // namespace td diff --git a/td/telegram/DraftMessage.cpp b/td/telegram/DraftMessage.cpp index 96b07e6adc62..313f7f3831b8 100644 --- a/td/telegram/DraftMessage.cpp +++ b/td/telegram/DraftMessage.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/DraftMessage.h" +#include "td/telegram/AccessRights.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Global.h" #include "td/telegram/MessageEntity.h" @@ -13,6 +14,7 @@ #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" #include "td/utils/buffer.h" @@ -77,6 +79,11 @@ class SaveDraftMessageQuery final : public Td::ResultHandler { } void on_error(Status status) final { + if (status.message() == "TOPIC_CLOSED") { + // when the draft is a reply to a message in a closed topic, server will not allow to save it + // with the error "TOPIC_CLOSED", but the draft will be kept locally + return promise_.set_value(Unit()); + } if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDraftMessageQuery")) { LOG(ERROR) << "Receive error for SaveDraftMessageQuery: " << status; } @@ -194,8 +201,12 @@ Result> DraftMessage::get_draft_message( 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_reply_to_message_id(dialog_id, top_thread_message_id, - result->reply_to_message_id_, true); + 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_; auto input_message_content = std::move(draft_message->input_message_text_); if (input_message_content != nullptr) { diff --git a/td/telegram/EmojiStatus.cpp b/td/telegram/EmojiStatus.cpp index 2241c22e203d..db202c224a91 100644 --- a/td/telegram/EmojiStatus.cpp +++ b/td/telegram/EmojiStatus.cpp @@ -11,14 +11,13 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" #include "td/utils/Status.h" -#include - namespace td { struct EmojiStatuses { @@ -26,12 +25,12 @@ struct EmojiStatuses { vector emoji_statuses_; td_api::object_ptr get_emoji_statuses_object() const { - auto emoji_statuses = transform(emoji_statuses_, [](const EmojiStatus &emoji_status) { + auto custom_emoji_ids = transform(emoji_statuses_, [](const EmojiStatus &emoji_status) { CHECK(!emoji_status.is_empty()); - return emoji_status.get_emoji_status_object(); + return emoji_status.get_custom_emoji_id().get(); }); - return td_api::make_object(std::move(emoji_statuses)); + return td_api::make_object(std::move(custom_emoji_ids)); } EmojiStatuses() = default; @@ -201,18 +200,18 @@ class ClearRecentEmojiStatusesQuery final : public Td::ResultHandler { } }; -EmojiStatus::EmojiStatus(const td_api::object_ptr &emoji_status, int32 duration) { +EmojiStatus::EmojiStatus(const td_api::object_ptr &emoji_status) { if (emoji_status == nullptr) { return; } custom_emoji_id_ = CustomEmojiId(emoji_status->custom_emoji_id_); - if (duration != 0) { + if (emoji_status->expiration_date_ != 0) { int32 current_time = G()->unix_time(); - if (duration >= std::numeric_limits::max() - current_time) { - until_date_ = std::numeric_limits::max(); + if (emoji_status->expiration_date_ > current_time) { + until_date_ = emoji_status->expiration_date_; } else { - until_date_ = current_time + duration; + custom_emoji_id_ = {}; } } } @@ -254,17 +253,17 @@ td_api::object_ptr EmojiStatus::get_emoji_status_object() c if (is_empty()) { return nullptr; } - return td_api::make_object(custom_emoji_id_.get()); + return td_api::make_object(custom_emoji_id_.get(), until_date_); } -CustomEmojiId EmojiStatus::get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const { +EmojiStatus EmojiStatus::get_effective_emoji_status(bool is_premium, int32 unix_time) const { if (!is_premium) { - return CustomEmojiId(); + return EmojiStatus(); } if (until_date_ != 0 && until_date_ <= unix_time) { - return CustomEmojiId(); + return EmojiStatus(); } - return custom_emoji_id_; + return *this; } StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &emoji_status) { diff --git a/td/telegram/EmojiStatus.h b/td/telegram/EmojiStatus.h index 3ae979fd8b0f..61be78b8093c 100644 --- a/td/telegram/EmojiStatus.h +++ b/td/telegram/EmojiStatus.h @@ -30,7 +30,7 @@ class EmojiStatus { public: EmojiStatus() = default; - EmojiStatus(const td_api::object_ptr &emoji_status, int32 duration); + explicit EmojiStatus(const td_api::object_ptr &emoji_status); explicit EmojiStatus(tl_object_ptr &&emoji_status); @@ -38,7 +38,7 @@ class EmojiStatus { td_api::object_ptr get_emoji_status_object() const; - CustomEmojiId get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const; + EmojiStatus get_effective_emoji_status(bool is_premium, int32 unix_time) const; bool is_empty() const { return !custom_emoji_id_.is_valid(); diff --git a/td/telegram/FileReferenceManager.cpp b/td/telegram/FileReferenceManager.cpp index bd9d89c9e82c..9c3a692c14a6 100644 --- a/td/telegram/FileReferenceManager.cpp +++ b/td/telegram/FileReferenceManager.cpp @@ -17,6 +17,7 @@ #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/StickerSetId.h" #include "td/telegram/StickersManager.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/WebPageId.h" #include "td/telegram/WebPagesManager.h" @@ -77,6 +78,7 @@ fileSourceSavedRingtones = FileSource; // repa fileSourceUserFull = FileSource; // repaired with users.getFullUser fileSourceAttachmentMenuBot user_id:int53 = FileSource; // repaired with messages.getAttachMenuBot fileSourceWebApp user_id:int53 short_name:string = FileSource; // repaired with messages.getAttachMenuBot +fileSourceStory chat_id:int53 story_id:int32 = FileSource; // repaired with stories.getStoriesByID */ FileSourceId FileReferenceManager::get_current_file_source_id() const { @@ -161,6 +163,11 @@ FileSourceId FileReferenceManager::create_web_app_file_source(UserId user_id, co return add_file_source_id(source, PSLICE() << "Web App " << user_id << '/' << short_name); } +FileSourceId FileReferenceManager::create_story_file_source(StoryFullId story_full_id) { + FileSourceStory source{story_full_id}; + return add_file_source_id(source, PSLICE() << story_full_id); +} + FileReferenceManager::Node &FileReferenceManager::add_node(NodeId node_id) { CHECK(node_id.is_valid()); auto &node = nodes_[node_id]; @@ -288,9 +295,9 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source auto &node = add_node(dest.node_id); node.query->active_queries++; - auto promise = PromiseCreator::lambda([dest, file_source_id, actor_id = actor_id(this), - file_manager_actor_id = G()->file_manager()](Result result) { - auto new_promise = PromiseCreator::lambda([dest, file_source_id, actor_id](Result result) { + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), file_manager_actor_id = G()->file_manager(), dest, + file_source_id](Result result) { + auto new_promise = PromiseCreator::lambda([actor_id, dest, file_source_id](Result result) { Status status; if (result.is_error()) { status = result.move_as_error(); @@ -346,11 +353,11 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source }, [&](const FileSourceChatFull &source) { send_closure_later(G()->contacts_manager(), &ContactsManager::reload_chat_full, source.chat_id, - std::move(promise)); + std::move(promise), "FileSourceChatFull"); }, [&](const FileSourceChannelFull &source) { send_closure_later(G()->contacts_manager(), &ContactsManager::reload_channel_full, source.channel_id, - std::move(promise), "repair file reference"); + std::move(promise), "FileSourceChannelFull"); }, [&](const FileSourceAppConfig &source) { send_closure_later(G()->config_manager(), &ConfigManager::reget_app_config, std::move(promise)); @@ -361,7 +368,7 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source }, [&](const FileSourceUserFull &source) { send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user_full, source.user_id, - std::move(promise)); + std::move(promise), "FileSourceUserFull"); }, [&](const FileSourceAttachMenuBot &source) { send_closure_later(G()->attach_menu_manager(), &AttachMenuManager::reload_attach_menu_bot, source.user_id, @@ -370,6 +377,10 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source [&](const FileSourceWebApp &source) { send_closure_later(G()->attach_menu_manager(), &AttachMenuManager::reload_web_app, source.user_id, source.short_name, std::move(promise)); + }, + [&](const FileSourceStory &source) { + send_closure_later(G()->story_manager(), &StoryManager::reload_story, source.story_full_id, std::move(promise), + "FileSourceStory"); })); } diff --git a/td/telegram/FileReferenceManager.h b/td/telegram/FileReferenceManager.h index 0a23bb8e5b86..01ce1d001816 100644 --- a/td/telegram/FileReferenceManager.h +++ b/td/telegram/FileReferenceManager.h @@ -14,6 +14,7 @@ #include "td/telegram/FullMessageId.h" #include "td/telegram/PhotoSizeSource.h" #include "td/telegram/SetWithPosition.h" +#include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" #include "td/telegram/UserId.h" @@ -64,6 +65,7 @@ class FileReferenceManager final : public Actor { FileSourceId create_user_full_file_source(UserId user_id); FileSourceId create_attach_menu_bot_file_source(UserId user_id); FileSourceId create_web_app_file_source(UserId user_id, const string &short_name); + FileSourceId create_story_file_source(StoryFullId story_full_id); using NodeId = FileId; void repair_file_reference(NodeId node_id, Promise<> promise); @@ -169,13 +171,16 @@ class FileReferenceManager final : public Actor { UserId user_id; string short_name; }; + struct FileSourceStory { + StoryFullId story_full_id; + }; // append only using FileSource = Variant; + FileSourceSavedRingtones, FileSourceUserFull, FileSourceAttachMenuBot, FileSourceWebApp, FileSourceStory>; WaitFreeVector file_sources_; int64 query_generation_{0}; diff --git a/td/telegram/FileReferenceManager.hpp b/td/telegram/FileReferenceManager.hpp index 5df56491f0ea..30839b874905 100644 --- a/td/telegram/FileReferenceManager.hpp +++ b/td/telegram/FileReferenceManager.hpp @@ -18,6 +18,8 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/StickersManager.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/UserId.h" #include "td/telegram/WebPagesManager.h" @@ -58,7 +60,8 @@ void FileReferenceManager::store_file_source(FileSourceId file_source_id, Storer [&](const FileSourceWebApp &source) { td::store(source.user_id, storer); td::store(source.short_name, storer); - })); + }, + [&](const FileSourceStory &source) { td::store(source.story_full_id, storer); })); } template @@ -141,6 +144,11 @@ FileSourceId FileReferenceManager::parse_file_source(Td *td, ParserT &parser) { td::parse(short_name, parser); return td->attach_menu_manager_->get_web_app_file_source_id(user_id, short_name); } + case 17: { + StoryFullId story_full_id; + td::parse(story_full_id, parser); + return td->story_manager_->get_story_file_source_id(story_full_id); + } default: parser.set_error("Invalid type in FileSource"); return FileSourceId(); diff --git a/td/telegram/FolderId.h b/td/telegram/FolderId.h index 0949804b6076..a3fdaf60c954 100644 --- a/td/telegram/FolderId.h +++ b/td/telegram/FolderId.h @@ -21,6 +21,9 @@ class FolderId { FolderId() = default; explicit constexpr FolderId(int32 folder_id) : id(folder_id) { + if (id != 1) { + id = 0; + } } template ::value>> FolderId(T folder_id) = delete; @@ -45,6 +48,9 @@ class FolderId { template void parse(ParserT &parser) { id = parser.fetch_int(); + if (id != 1) { + id = 0; + } } static FolderId main() { diff --git a/td/telegram/ForumTopicInfo.cpp b/td/telegram/ForumTopicInfo.cpp index 924de3dfdc41..5809832d6ed2 100644 --- a/td/telegram/ForumTopicInfo.cpp +++ b/td/telegram/ForumTopicInfo.cpp @@ -29,7 +29,7 @@ ForumTopicInfo::ForumTopicInfo(Td *td, const tl_object_ptrdate_; creator_dialog_id_ = DialogId(forum_topic->from_id_); if (creator_dialog_id_.is_valid() && creator_dialog_id_.get_type() != DialogType::User && - td->messages_manager_->have_dialog_info_force(creator_dialog_id_)) { + td->messages_manager_->have_dialog_info_force(creator_dialog_id_, "ForumTopicInfo")) { td->messages_manager_->force_create_dialog(creator_dialog_id_, "ForumTopicInfo", true); } is_outgoing_ = forum_topic->my_; diff --git a/td/telegram/ForumTopicManager.cpp b/td/telegram/ForumTopicManager.cpp index bed034c9fc24..117d44be7cc4 100644 --- a/td/telegram/ForumTopicManager.cpp +++ b/td/telegram/ForumTopicManager.cpp @@ -414,6 +414,35 @@ class GetForumTopicsQuery final : public Td::ResultHandler { } }; +class ReadForumTopicQuery final : public Td::ResultHandler { + DialogId dialog_id_; + + public: + void send(DialogId dialog_id, MessageId top_thread_message_id, MessageId max_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::messages_readDiscussion(std::move(input_peer), + top_thread_message_id.get_server_message_id().get(), + max_message_id.get_server_message_id().get()), + {{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()); + } + } + + void on_error(Status status) final { + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadForumTopicQuery"); + } +}; + template void ForumTopicManager::Topic::store(StorerT &storer) const { CHECK(info_ != nullptr); @@ -538,6 +567,23 @@ void ForumTopicManager::edit_forum_topic(DialogId dialog_id, MessageId top_threa ->send(channel_id, top_thread_message_id, edit_title, new_title, edit_icon_custom_emoji, icon_custom_emoji_id); } +void ForumTopicManager::read_forum_topic_messages(DialogId dialog_id, MessageId top_thread_message_id, + MessageId last_read_inbox_message_id) { + CHECK(!td_->auth_manager_->is_bot()); + auto topic = get_topic(dialog_id, top_thread_message_id); + if (topic == nullptr || topic->topic_ == nullptr) { + return; + } + + if (topic->topic_->update_last_read_inbox_message_id(last_read_inbox_message_id, -1)) { + // TODO send updates + auto max_message_id = last_read_inbox_message_id.get_prev_server_message_id(); + LOG(INFO) << "Send read topic history request in topic of " << top_thread_message_id << " in " << dialog_id + << " up to " << max_message_id; + td_->create_handler()->send(dialog_id, top_thread_message_id, max_message_id); + } +} + void ForumTopicManager::on_update_forum_topic_unread(DialogId dialog_id, MessageId top_thread_message_id, MessageId last_message_id, MessageId last_read_inbox_message_id, MessageId last_read_outbox_message_id, int32 unread_count) { @@ -603,6 +649,9 @@ void ForumTopicManager::on_update_forum_topic_notify_settings( void ForumTopicManager::on_update_forum_topic_is_pinned(DialogId dialog_id, MessageId top_thread_message_id, bool is_pinned) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "on_update_forum_topic_is_pinned")) { + return; + } if (!can_be_forum(dialog_id)) { LOG(ERROR) << "Receive pinned topics in " << dialog_id; return; @@ -623,6 +672,9 @@ void ForumTopicManager::on_update_forum_topic_is_pinned(DialogId dialog_id, Mess } void ForumTopicManager::on_update_pinned_forum_topics(DialogId dialog_id, vector top_thread_message_ids) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "on_update_pinned_forum_topics")) { + return; + } if (!can_be_forum(dialog_id)) { LOG(ERROR) << "Receive pinned topics in " << dialog_id; return; diff --git a/td/telegram/ForumTopicManager.h b/td/telegram/ForumTopicManager.h index 0eaf529b67af..c414a35f9f62 100644 --- a/td/telegram/ForumTopicManager.h +++ b/td/telegram/ForumTopicManager.h @@ -87,6 +87,9 @@ class ForumTopicManager final : public Actor { void delete_all_dialog_topics(DialogId dialog_id); + void read_forum_topic_messages(DialogId dialog_id, MessageId top_thread_message_id, + MessageId last_read_inbox_message_id); + void on_update_forum_topic_unread(DialogId dialog_id, MessageId top_thread_message_id, MessageId last_message_id, MessageId last_read_inbox_message_id, MessageId last_read_outbox_message_id, int32 unread_count); diff --git a/td/telegram/FullMessageId.h b/td/telegram/FullMessageId.h index 6bb1f5acd077..1b8aac936955 100644 --- a/td/telegram/FullMessageId.h +++ b/td/telegram/FullMessageId.h @@ -11,6 +11,7 @@ #include "td/telegram/telegram_api.h" #include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" #include "td/utils/StringBuilder.h" namespace td { @@ -62,8 +63,8 @@ struct FullMessageId { struct FullMessageIdHash { uint32 operator()(FullMessageId full_message_id) const { - return DialogIdHash()(full_message_id.get_dialog_id()) * 2023654985u + - MessageIdHash()(full_message_id.get_message_id()); + return combine_hashes(DialogIdHash()(full_message_id.get_dialog_id()), + MessageIdHash()(full_message_id.get_message_id())); } }; diff --git a/td/telegram/Game.cpp b/td/telegram/Game.cpp index 92ca27a49a81..5a5acf3e4d2d 100644 --- a/td/telegram/Game.cpp +++ b/td/telegram/Game.cpp @@ -13,6 +13,7 @@ #include "td/telegram/misc.h" #include "td/telegram/Photo.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/common.h" #include "td/utils/logging.h" diff --git a/td/telegram/GameManager.cpp b/td/telegram/GameManager.cpp index 8c3ab03e7424..9fa92460a666 100644 --- a/td/telegram/GameManager.cpp +++ b/td/telegram/GameManager.cpp @@ -18,6 +18,7 @@ #include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/utils/buffer.h" diff --git a/td/telegram/Global.cpp b/td/telegram/Global.cpp index dacaaef86de6..3163fcb7b6b2 100644 --- a/td/telegram/Global.cpp +++ b/td/telegram/Global.cpp @@ -24,7 +24,13 @@ namespace td { -Global::Global() = default; +Global::Global() { + auto current_scheduler_id = Scheduler::instance()->sched_id(); + auto max_scheduler_id = Scheduler::instance()->sched_count() - 1; + database_scheduler_id_ = min(current_scheduler_id + 1, max_scheduler_id); + gc_scheduler_id_ = min(current_scheduler_id + 2, max_scheduler_id); + slow_net_scheduler_id_ = min(current_scheduler_id + 3, max_scheduler_id); +} Global::~Global() = default; @@ -86,9 +92,6 @@ struct ServerTimeDiff { }; Status Global::init(ActorId td, unique_ptr td_db_ptr) { - gc_scheduler_id_ = min(Scheduler::instance()->sched_id() + 2, Scheduler::instance()->sched_count() - 1); - slow_net_scheduler_id_ = min(Scheduler::instance()->sched_id() + 3, Scheduler::instance()->sched_count() - 1); - td_ = td; td_db_ = std::move(td_db_ptr); diff --git a/td/telegram/Global.h b/td/telegram/Global.h index 20319d7d5c4d..6c3f2146374f 100644 --- a/td/telegram/Global.h +++ b/td/telegram/Global.h @@ -30,6 +30,7 @@ namespace td { +class AccountManager; class AnimationsManager; class AttachMenuManager; class AuthManager; @@ -54,11 +55,13 @@ class NotificationManager; class NotificationSettingsManager; class OptionManager; class PasswordManager; +class ReactionManager; class SecretChatsManager; class SponsoredMessageManager; class StateManager; class StickersManager; class StorageManager; +class StoryManager; class Td; class TdDb; class TempAuthKeyWatchdog; @@ -141,7 +144,7 @@ class Global final : public ActorContext { string get_option_string(Slice name, string default_value = "") const; bool is_server_time_reliable() const { - return server_time_difference_was_updated_; + return server_time_difference_was_updated_.load(std::memory_order_relaxed); } double to_server_time(double now) const { return now + get_server_time_difference(); @@ -182,6 +185,13 @@ class Global final : public ActorContext { return td_; } + ActorId account_manager() const { + return account_manager_; + } + void set_account_manager(ActorId account_manager) { + account_manager_ = account_manager; + } + ActorId animations_manager() const { return animations_manager_; } @@ -330,6 +340,13 @@ class Global final : public ActorContext { password_manager_ = password_manager; } + ActorId reaction_manager() const { + return reaction_manager_; + } + void set_reaction_manager(ActorId reaction_manager) { + reaction_manager_ = reaction_manager; + } + ActorId secret_chats_manager() const { return secret_chats_manager_; } @@ -358,6 +375,13 @@ class Global final : public ActorContext { storage_manager_ = storage_manager; } + ActorId story_manager() const { + return story_manager_; + } + void set_story_manager(ActorId story_manager) { + story_manager_ = story_manager; + } + ActorId theme_manager() const { return theme_manager_; } @@ -410,6 +434,10 @@ class Global final : public ActorContext { return use_file_database(); } + int32 get_database_scheduler_id() { + return database_scheduler_id_; + } + int32 get_gc_scheduler_id() const { return gc_scheduler_id_; } @@ -497,6 +525,7 @@ class Global final : public ActorContext { unique_ptr td_db_; ActorId td_; + ActorId account_manager_; ActorId animations_manager_; ActorId attach_menu_manager_; ActorId auth_manager_; @@ -518,10 +547,12 @@ class Global final : public ActorContext { ActorId notification_manager_; ActorId notification_settings_manager_; ActorId password_manager_; + ActorId reaction_manager_; ActorId secret_chats_manager_; ActorId sponsored_message_manager_; ActorId stickers_manager_; ActorId storage_manager_; + ActorId story_manager_; ActorId theme_manager_; ActorId top_dialog_manager_; ActorId updates_manager_; @@ -533,6 +564,7 @@ class Global final : public ActorContext { OptionManager *option_manager_ = nullptr; + int32 database_scheduler_id_ = 0; int32 gc_scheduler_id_ = 0; int32 slow_net_scheduler_id_ = 0; diff --git a/td/telegram/GlobalPrivacySettings.cpp b/td/telegram/GlobalPrivacySettings.cpp new file mode 100644 index 000000000000..85061c6f12c0 --- /dev/null +++ b/td/telegram/GlobalPrivacySettings.cpp @@ -0,0 +1,126 @@ +// +// 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/GlobalPrivacySettings.h" + +#include "td/telegram/ConfigManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/net/NetQueryCreator.h" +#include "td/telegram/SuggestedAction.h" +#include "td/telegram/Td.h" + +#include "td/actor/actor.h" + +#include "td/utils/buffer.h" +#include "td/utils/Status.h" + +namespace td { + +class GetGlobalPrivacySettingsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetGlobalPrivacySettingsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::account_getGlobalPrivacySettings(), {{"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 settings = GlobalPrivacySettings(result_ptr.move_as_ok()); + promise_.set_value(settings.get_archive_chat_list_settings_object()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class SetGlobalPrivacySettingsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit SetGlobalPrivacySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(GlobalPrivacySettings settings) { + send_query(G()->net_query_creator().create( + telegram_api::account_setGlobalPrivacySettings(settings.get_input_global_privacy_settings()), {{"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()); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +GlobalPrivacySettings::GlobalPrivacySettings(telegram_api::object_ptr &&settings) + : archive_and_mute_new_noncontact_peers_(settings->archive_and_mute_new_noncontact_peers_) + , keep_archived_unmuted_(settings->keep_archived_unmuted_) + , keep_archived_folders_(settings->keep_archived_folders_) { +} + +GlobalPrivacySettings::GlobalPrivacySettings(td_api::object_ptr &&settings) { + if (settings != nullptr) { + archive_and_mute_new_noncontact_peers_ = settings->archive_and_mute_new_chats_from_unknown_users_; + keep_archived_unmuted_ = settings->keep_unmuted_chats_archived_; + keep_archived_folders_ = settings->keep_chats_from_folders_archived_; + } +} + +telegram_api::object_ptr GlobalPrivacySettings::get_input_global_privacy_settings() + const { + int32 flags = 0; + if (archive_and_mute_new_noncontact_peers_) { + flags |= telegram_api::globalPrivacySettings::ARCHIVE_AND_MUTE_NEW_NONCONTACT_PEERS_MASK; + } + if (keep_archived_unmuted_) { + flags |= telegram_api::globalPrivacySettings::KEEP_ARCHIVED_UNMUTED_MASK; + } + if (keep_archived_folders_) { + flags |= telegram_api::globalPrivacySettings::KEEP_ARCHIVED_FOLDERS_MASK; + } + return telegram_api::make_object(flags, false /*ignored*/, false /*ignored*/, + false /*ignored*/); +} + +td_api::object_ptr GlobalPrivacySettings::get_archive_chat_list_settings_object() + const { + return td_api::make_object(archive_and_mute_new_noncontact_peers_, + keep_archived_unmuted_, keep_archived_folders_); +} + +void GlobalPrivacySettings::get_global_privacy_settings( + Td *td, Promise> &&promise) { + td->create_handler(std::move(promise))->send(); +} + +void GlobalPrivacySettings::set_global_privacy_settings(Td *td, GlobalPrivacySettings settings, + Promise &&promise) { + if (settings.archive_and_mute_new_noncontact_peers_) { + send_closure(td->config_manager_, &ConfigManager::hide_suggested_action, + SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}); + } + + td->create_handler(std::move(promise))->send(std::move(settings)); +} + +} // namespace td diff --git a/td/telegram/GlobalPrivacySettings.h b/td/telegram/GlobalPrivacySettings.h new file mode 100644 index 000000000000..6ad47e9a75aa --- /dev/null +++ b/td/telegram/GlobalPrivacySettings.h @@ -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) +// +#pragma once + +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" + +namespace td { + +class Td; + +class GlobalPrivacySettings { + bool archive_and_mute_new_noncontact_peers_ = false; + bool keep_archived_unmuted_ = false; + bool keep_archived_folders_ = false; + + public: + explicit GlobalPrivacySettings(telegram_api::object_ptr &&settings); + + explicit GlobalPrivacySettings(td_api::object_ptr &&settings); + + telegram_api::object_ptr get_input_global_privacy_settings() const; + + td_api::object_ptr get_archive_chat_list_settings_object() const; + + static void get_global_privacy_settings(Td *td, + Promise> &&promise); + + static void set_global_privacy_settings(Td *td, GlobalPrivacySettings settings, Promise &&promise); +}; + +} // namespace td diff --git a/td/telegram/GroupCallManager.cpp b/td/telegram/GroupCallManager.cpp index 0e7895894c0d..1a3b40bf7bcb 100644 --- a/td/telegram/GroupCallManager.cpp +++ b/td/telegram/GroupCallManager.cpp @@ -19,6 +19,7 @@ #include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/utils/algorithm.h" @@ -2638,7 +2639,7 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, DialogId as_di if (as_dialog_id != my_dialog_id) { return promise.set_error(Status::Error(400, "Can't join voice chat as another user")); } - if (!td_->contacts_manager_->have_user_force(as_dialog_id.get_user_id())) { + if (!td_->contacts_manager_->have_user_force(as_dialog_id.get_user_id(), "join_group_call")) { have_as_dialog_id = false; } } else { @@ -4442,7 +4443,7 @@ InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptrscheduled_start_date && call.scheduled_start_date_version >= group_call->scheduled_start_date_version) { - LOG_IF(ERROR, group_call->scheduled_start_date == 0) << call.group_call_id << " became scheduled"; + LOG_IF(ERROR, group_call->scheduled_start_date == 0) << input_group_call_id << " became scheduled"; group_call->scheduled_start_date = call.scheduled_start_date; group_call->scheduled_start_date_version = call.scheduled_start_date_version; need_update = true; @@ -4545,7 +4546,7 @@ void GroupCallManager::on_user_speaking_in_group_call(GroupCallId group_call_id, return; } - if (!td_->messages_manager_->have_dialog_info_force(dialog_id) || + if (!td_->messages_manager_->have_dialog_info_force(dialog_id, "on_user_speaking_in_group_call") || (!is_recursive && need_group_call_participants(input_group_call_id, group_call) && get_group_call_participant(input_group_call_id, dialog_id) == nullptr)) { if (is_recursive) { diff --git a/td/telegram/GroupCallParticipant.cpp b/td/telegram/GroupCallParticipant.cpp index 99502123234c..dc323921faf9 100644 --- a/td/telegram/GroupCallParticipant.cpp +++ b/td/telegram/GroupCallParticipant.cpp @@ -8,6 +8,7 @@ #include "td/telegram/Global.h" #include "td/telegram/MessageSender.h" +#include "td/telegram/telegram_api.h" #include "td/utils/logging.h" diff --git a/td/telegram/GroupCallVideoPayload.cpp b/td/telegram/GroupCallVideoPayload.cpp index 901ab59fee04..c34715691732 100644 --- a/td/telegram/GroupCallVideoPayload.cpp +++ b/td/telegram/GroupCallVideoPayload.cpp @@ -6,6 +6,8 @@ // #include "td/telegram/GroupCallVideoPayload.h" +#include "td/telegram/telegram_api.h" + #include "td/utils/algorithm.h" namespace td { diff --git a/td/telegram/InlineQueriesManager.cpp b/td/telegram/InlineQueriesManager.cpp index 7c6ca44700a0..85987ed3a6d0 100644 --- a/td/telegram/InlineQueriesManager.cpp +++ b/td/telegram/InlineQueriesManager.cpp @@ -36,7 +36,7 @@ #include "td/telegram/Td.h" #include "td/telegram/td_api.hpp" #include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Venue.h" @@ -176,7 +176,7 @@ class RequestSimpleWebViewQuery final : public Td::ResultHandler { explicit RequestSimpleWebViewQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(tl_object_ptr &&input_user, const string &url, + void send(tl_object_ptr &&input_user, string url, const td_api::object_ptr &theme, string &&platform) { tl_object_ptr theme_parameters; int32 flags = 0; @@ -186,17 +186,30 @@ class RequestSimpleWebViewQuery final : public Td::ResultHandler { theme_parameters = make_tl_object(string()); theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false); } + string start_parameter; if (ends_with(url, "#kb")) { // a URL from keyboard button + url.resize(url.size() - 3); + flags |= telegram_api::messages_requestSimpleWebView::URL_MASK; } else if (ends_with(url, "#iq")) { // a URL from inline query results button + url.resize(url.size() - 3); flags |= telegram_api::messages_requestSimpleWebView::FROM_SWITCH_WEBVIEW_MASK; + flags |= telegram_api::messages_requestSimpleWebView::URL_MASK; + } else if (url.empty()) { + flags |= telegram_api::messages_requestSimpleWebView::FROM_SIDE_MENU_MASK; + } else if (begins_with(url, "start://")) { + start_parameter = url.substr(8); + url = string(); + + flags |= telegram_api::messages_requestSimpleWebView::FROM_SIDE_MENU_MASK; + flags |= telegram_api::messages_requestSimpleWebView::START_PARAM_MASK; } else { return on_error(Status::Error(400, "Invalid URL specified")); } - send_query(G()->net_query_creator().create(telegram_api::messages_requestSimpleWebView( - flags, false /*ignored*/, std::move(input_user), url.substr(0, url.size() - 3), std::move(theme_parameters), - platform))); + send_query(G()->net_query_creator().create( + telegram_api::messages_requestSimpleWebView(flags, false /*ignored*/, false /*ignored*/, std::move(input_user), + url, start_parameter, std::move(theme_parameters), platform))); } void on_result(BufferSlice packet) final { @@ -547,7 +560,7 @@ void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string && TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); td_->create_handler(std::move(promise)) - ->send(std::move(input_user), url, theme, std::move(platform)); + ->send(std::move(input_user), std::move(url), theme, std::move(platform)); } void InlineQueriesManager::send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, @@ -956,7 +969,7 @@ Result> InlineQueriesManager:: if (width > 0 && height > 0) { if ((duration > 0 || type == "video" || content_type == "video/mp4") && !begins_with(content_type, "image/")) { attributes.push_back(make_tl_object( - 0, false /*ignored*/, false /*ignored*/, duration, width, height)); + 0, false /*ignored*/, false /*ignored*/, false /*ignored*/, duration, width, height, 0)); } else { attributes.push_back(make_tl_object(width, height)); } @@ -1778,11 +1791,11 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaGeo::ID) { auto inline_message_geo = static_cast(result->send_message_.get()); - Location l(inline_message_geo->geo_); + Location l(td_, inline_message_geo->geo_); location->location_ = l.get_location_object(); } else { auto latitude_longitude = split(Slice(result->description_)); - Location l(to_double(latitude_longitude.first), to_double(latitude_longitude.second), 0.0, 0); + Location l(td_, to_double(latitude_longitude.first), to_double(latitude_longitude.second), 0.0, 0); location->location_ = l.get_location_object(); } location->thumbnail_ = register_thumbnail(std::move(result->thumb_)); @@ -1798,18 +1811,19 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaVenue::ID) { auto inline_message_venue = static_cast(result->send_message_.get()); - Venue v(inline_message_venue->geo_, inline_message_venue->title_, inline_message_venue->address_, + Venue v(td_, inline_message_venue->geo_, inline_message_venue->title_, inline_message_venue->address_, inline_message_venue->provider_, inline_message_venue->venue_id_, inline_message_venue->venue_type_); venue->venue_ = v.get_venue_object(); } else if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaGeo::ID) { auto inline_message_geo = static_cast(result->send_message_.get()); - Venue v(inline_message_geo->geo_, std::move(result->title_), std::move(result->description_), string(), + Venue v(td_, inline_message_geo->geo_, std::move(result->title_), std::move(result->description_), string(), string(), string()); venue->venue_ = v.get_venue_object(); } else { - Venue v(nullptr, std::move(result->title_), std::move(result->description_), string(), string(), string()); + Venue v(td_, nullptr, std::move(result->title_), std::move(result->description_), string(), string(), + string()); venue->venue_ = v.get_venue_object(); } venue->thumbnail_ = register_thumbnail(std::move(result->thumb_)); @@ -1855,9 +1869,17 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI continue; } - vector> attributes; - downcast_call(*result->content_, - [&attributes](auto &web_document) { attributes = std::move(web_document.attributes_); }); + auto attributes = [content = result->content_.get()] { + switch (content->get_id()) { + case telegram_api::webDocument::ID: + return std::move(static_cast(content)->attributes_); + case telegram_api::webDocumentNoProxy::ID: + return std::move(static_cast(content)->attributes_); + default: + UNREACHABLE(); + return vector>(); + } + }(); bool is_animation = result->type_ == "gif" && (content_type == "image/gif" || content_type == "video/mp4"); if (is_animation) { diff --git a/td/telegram/InputInvoice.cpp b/td/telegram/InputInvoice.cpp index 7f48f78382fa..d6cbe336dbf8 100644 --- a/td/telegram/InputInvoice.cpp +++ b/td/telegram/InputInvoice.cpp @@ -13,6 +13,7 @@ #include "td/telegram/PhotoSize.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" diff --git a/td/telegram/JsonValue.cpp b/td/telegram/JsonValue.cpp index dc2302a84e85..091577273fef 100644 --- a/td/telegram/JsonValue.cpp +++ b/td/telegram/JsonValue.cpp @@ -7,6 +7,7 @@ #include "td/telegram/JsonValue.h" #include "td/telegram/misc.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/JsonBuilder.h" @@ -18,14 +19,6 @@ namespace td { -static td_api::object_ptr get_json_value_object(const JsonValue &json_value); - -static td_api::object_ptr get_json_value_member_object( - const std::pair &json_value_member) { - return td_api::make_object(json_value_member.first.str(), - get_json_value_object(json_value_member.second)); -} - static td_api::object_ptr get_json_value_object(const JsonValue &json_value) { switch (json_value.type()) { case JsonValue::Type::Null: @@ -38,9 +31,13 @@ static td_api::object_ptr get_json_value_object(const JsonVal return td_api::make_object(json_value.get_string().str()); case JsonValue::Type::Array: return td_api::make_object(transform(json_value.get_array(), get_json_value_object)); - case JsonValue::Type::Object: - return td_api::make_object( - transform(json_value.get_object(), get_json_value_member_object)); + case JsonValue::Type::Object: { + vector> members; + json_value.get_object().foreach([&members](Slice name, const JsonValue &value) { + members.push_back(td_api::make_object(name.str(), get_json_value_object(value))); + }); + return td_api::make_object(std::move(members)); + } default: UNREACHABLE(); return nullptr; @@ -222,6 +219,9 @@ int64 get_json_value_long(telegram_api::object_ptr &&js if (json_value->get_id() == telegram_api::jsonString::ID) { return to_integer(static_cast(json_value.get())->value_); } + if (json_value->get_id() == telegram_api::jsonNumber::ID) { + return static_cast(static_cast(json_value.get())->value_); + } LOG(ERROR) << "Expected Long as " << name << ", but found " << to_string(json_value); return 0; } diff --git a/td/telegram/LanguagePackManager.cpp b/td/telegram/LanguagePackManager.cpp index 87f8b8becb11..7c8e73ef457e 100644 --- a/td/telegram/LanguagePackManager.cpp +++ b/td/telegram/LanguagePackManager.cpp @@ -10,7 +10,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" -#include "td/telegram/td_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/db/DbKey.h" #include "td/db/SqliteDb.h" diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index 7c0faeac5081..1d0139658700 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -21,6 +21,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/Proxy.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StoryId.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" @@ -44,7 +45,7 @@ namespace td { static bool is_valid_start_parameter(Slice start_parameter) { - return start_parameter.size() <= 64 && is_base64url_characters(start_parameter); + return is_base64url_characters(start_parameter); } static bool is_valid_phone_number(Slice phone_number) { @@ -67,6 +68,11 @@ static bool is_valid_web_app_name(Slice name) { return name.size() >= 3 && is_valid_username(name); } +static bool is_valid_story_id(Slice story_id) { + auto r_story_id = to_integer_safe(story_id); + return r_story_id.is_ok() && StoryId(r_story_id.ok()).is_server(); +} + static string get_url_query_hash(bool is_tg, const HttpUrlQuery &url_query) { const auto &path = url_query.path_; if (is_tg) { @@ -591,6 +597,22 @@ class LinkManager::InternalLinkSettings final : public InternalLink { } }; +class LinkManager::InternalLinkSideMenuBot final : public InternalLink { + string bot_username_; + string url_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(bot_username_, url_); + } + + public: + InternalLinkSideMenuBot(string bot_username, string start_parameter) : bot_username_(std::move(bot_username)) { + if (!start_parameter.empty()) { + url_ = PSTRING() << "start://" << start_parameter; + } + } +}; + class LinkManager::InternalLinkStickerSet final : public InternalLink { string sticker_set_name_; bool expect_custom_emoji_; @@ -605,6 +627,20 @@ class LinkManager::InternalLinkStickerSet final : public InternalLink { } }; +class LinkManager::InternalLinkStory final : public InternalLink { + string story_sender_username_; + StoryId story_id_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(story_sender_username_, story_id_.get()); + } + + public: + InternalLinkStory(string story_sender_username, StoryId story_id) + : story_sender_username_(std::move(story_sender_username)), story_id_(story_id) { + } +}; + class LinkManager::InternalLinkTheme final : public InternalLink { string theme_name_; @@ -1199,6 +1235,15 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que return td::make_unique(std::move(username), arg.second, url_query.get_arg("startapp").str()); } + if (arg.first == "story" && is_valid_story_id(arg.second)) { + // resolve?domain=&story= + return td::make_unique(std::move(username), StoryId(to_integer(arg.second))); + } + } + if (url_query.has_arg("startapp") && !url_query.has_arg("appname")) { + // resolve?domain=&startapp= + // resolve?domain=&startapp= + return td::make_unique(std::move(username), url_query.get_arg("startapp").str()); } if (!url_query.get_arg("attach").empty()) { // resolve?domain=&attach= @@ -1554,12 +1599,16 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q << "&post=" << post << copy_arg("single") << thread << copy_arg("comment") << copy_arg("t")); } + auto username = path[0]; + 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]))); + } if (path.size() == 2 && is_valid_web_app_name(path[1])) { // // // //?startapp= - return td::make_unique(path[0], path[1], url_query.get_arg("startapp").str()); + return td::make_unique(std::move(username), path[1], url_query.get_arg("startapp").str()); } - auto username = path[0]; for (auto &arg : url_query.args_) { if (arg.first == "voicechat" || arg.first == "videochat" || arg.first == "livestream") { // /?videochat @@ -1588,6 +1637,11 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q return td::make_unique(std::move(username), std::move(administrator_rights)); } } + if (arg.first == "startapp" && is_valid_start_parameter(arg.second)) { + // /?startapp + // /?startapp= + return td::make_unique(std::move(username), arg.second); + } if (arg.first == "game" && is_valid_game_name(arg.second)) { // /?game= return td::make_unique(std::move(username), arg.second); @@ -1699,7 +1753,11 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp if (!begins_with(link->url_, "start://")) { return Status::Error(400, "Unsupported link URL specified"); } - start_parameter = PSTRING() << '=' << Slice(link->url_).substr(8); + auto start_parameter_slice = Slice(link->url_).substr(8); + if (start_parameter_slice.empty() || !is_valid_start_parameter(start_parameter_slice)) { + return Status::Error(400, "Invalid start parameter specified"); + } + start_parameter = PSTRING() << '=' << start_parameter_slice; } if (link->target_chat_ == nullptr) { return Status::Error(400, "Target chat must be non-empty"); @@ -1889,6 +1947,28 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp return Status::Error("HTTP link is unavailable for the link type"); } return "tg://settings/auto_delete"; + case td_api::internalLinkTypeSideMenuBot::ID: { + auto link = static_cast(type_ptr); + if (!is_valid_username(link->bot_username_)) { + return Status::Error(400, "Invalid bot username specified"); + } + string start_parameter; + if (!link->url_.empty()) { + if (!begins_with(link->url_, "start://")) { + return Status::Error(400, "Unsupported link URL specified"); + } + auto start_parameter_slice = Slice(link->url_).substr(8); + if (start_parameter_slice.empty() || !is_valid_start_parameter(start_parameter_slice)) { + return Status::Error(400, "Invalid start parameter specified"); + } + start_parameter = PSTRING() << '=' << start_parameter_slice; + } + if (is_internal) { + return PSTRING() << "tg://resolve?domain=" << link->bot_username_ << "&startapp" << start_parameter; + } else { + return PSTRING() << get_t_me_url() << link->bot_username_ << "?startapp" << start_parameter; + } + } case td_api::internalLinkTypeEditProfileSettings::ID: if (!is_internal) { return Status::Error("HTTP link is unavailable for the link type"); @@ -2067,6 +2147,20 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp << url_encode(link->sticker_set_name_); } } + case td_api::internalLinkTypeStory::ID: { + auto link = static_cast(type_ptr); + if (!is_valid_username(link->story_sender_username_)) { + return Status::Error(400, "Invalid story sender username specified"); + } + if (!StoryId(link->story_id_).is_server()) { + return Status::Error(400, "Invalid story identifier specified"); + } + if (is_internal) { + return PSTRING() << "tg://resolve?domain=" << link->story_sender_username_ << "&story=" << link->story_id_; + } else { + return PSTRING() << get_t_me_url() << link->story_sender_username_ << "/s/" << link->story_id_; + } + } case td_api::internalLinkTypeTheme::ID: { auto link = static_cast(type_ptr); if (link->theme_name_.empty()) { @@ -2155,7 +2249,7 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp } string start_parameter; if (!link->start_parameter_.empty()) { - start_parameter = PSTRING() << (is_internal ? '&' : '?') << "startapp=" << url_encode(link->start_parameter_); + start_parameter = PSTRING() << (is_internal ? '&' : '?') << "startapp=" << link->start_parameter_; } if (is_internal) { return PSTRING() << "tg://resolve?domain=" << link->bot_username_ << "&appname=" << link->web_app_short_name_ diff --git a/td/telegram/LinkManager.h b/td/telegram/LinkManager.h index 0620815d94bd..8c4d1999911d 100644 --- a/td/telegram/LinkManager.h +++ b/td/telegram/LinkManager.h @@ -143,7 +143,9 @@ class LinkManager final : public Actor { class InternalLinkQrCodeAuthentication; class InternalLinkRestorePurchases; class InternalLinkSettings; + class InternalLinkSideMenuBot; class InternalLinkStickerSet; + class InternalLinkStory; class InternalLinkTheme; class InternalLinkThemeSettings; class InternalLinkUnknownDeepLink; diff --git a/td/telegram/Location.cpp b/td/telegram/Location.cpp index 6d87ca1b1adc..c342002d610f 100644 --- a/td/telegram/Location.cpp +++ b/td/telegram/Location.cpp @@ -6,6 +6,9 @@ // #include "td/telegram/Location.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/Td.h" + #include namespace td { @@ -20,26 +23,28 @@ double Location::fix_accuracy(double accuracy) { return accuracy; } -void Location::init(double latitude, double longitude, double horizontal_accuracy, int64 access_hash) { +void Location::init(Td *td, double latitude, double longitude, double horizontal_accuracy, int64 access_hash) { if (std::isfinite(latitude) && std::isfinite(longitude) && std::abs(latitude) <= 90 && std::abs(longitude) <= 180) { is_empty_ = false; latitude_ = latitude; longitude_ = longitude; horizontal_accuracy_ = fix_accuracy(horizontal_accuracy); access_hash_ = access_hash; - G()->add_location_access_hash(latitude_, longitude_, access_hash_); + if (td != nullptr && !td->auth_manager_->is_bot()) { + G()->add_location_access_hash(latitude_, longitude_, access_hash_); + } } } -Location::Location(double latitude, double longitude, double horizontal_accuracy, int64 access_hash) { - init(latitude, longitude, horizontal_accuracy, access_hash); +Location::Location(Td *td, double latitude, double longitude, double horizontal_accuracy, int64 access_hash) { + init(td, latitude, longitude, horizontal_accuracy, access_hash); } Location::Location(const tl_object_ptr &geo_point) - : Location(geo_point->lat_, geo_point->long_, 0.0, 0) { + : Location(nullptr, geo_point->lat_, geo_point->long_, 0.0, 0) { } -Location::Location(const tl_object_ptr &geo_point_ptr) { +Location::Location(Td *td, const tl_object_ptr &geo_point_ptr) { if (geo_point_ptr == nullptr) { return; } @@ -48,7 +53,7 @@ Location::Location(const tl_object_ptr &geo_point_ptr) { break; case telegram_api::geoPoint::ID: { auto geo_point = static_cast(geo_point_ptr.get()); - init(geo_point->lat_, geo_point->long_, geo_point->accuracy_radius_, geo_point->access_hash_); + init(td, geo_point->lat_, geo_point->long_, geo_point->accuracy_radius_, geo_point->access_hash_); break; } default: @@ -62,7 +67,7 @@ Location::Location(const tl_object_ptr &location) { return; } - init(location->latitude_, location->longitude_, location->horizontal_accuracy_, 0); + init(nullptr, location->latitude_, location->longitude_, location->horizontal_accuracy_, 0); } bool Location::empty() const { @@ -95,6 +100,19 @@ tl_object_ptr Location::get_input_geo_point() const static_cast(std::ceil(horizontal_accuracy_))); } +telegram_api::object_ptr Location::get_fake_geo_point() const { + if (empty()) { + return make_tl_object(); + } + + int32 flags = 0; + if (horizontal_accuracy_ > 0) { + flags |= telegram_api::geoPoint::ACCURACY_RADIUS_MASK; + } + return telegram_api::make_object(flags, longitude_, latitude_, 0, + static_cast(std::ceil(horizontal_accuracy_))); +} + tl_object_ptr Location::get_input_media_geo_point() const { return make_tl_object(get_input_geo_point()); } diff --git a/td/telegram/Location.h b/td/telegram/Location.h index 1e1fcad12a16..2a3702cb6de3 100644 --- a/td/telegram/Location.h +++ b/td/telegram/Location.h @@ -19,6 +19,8 @@ namespace td { +class Td; + class Location { bool is_empty_ = true; double latitude_ = 0.0; @@ -31,18 +33,18 @@ class Location { friend StringBuilder &operator<<(StringBuilder &string_builder, const Location &location); - void init(double latitude, double longitude, double horizontal_accuracy, int64 access_hash); + void init(Td *td, double latitude, double longitude, double horizontal_accuracy, int64 access_hash); static double fix_accuracy(double accuracy); public: Location() = default; - Location(double latitude, double longitude, double horizontal_accuracy, int64 access_hash); + Location(Td *td, double latitude, double longitude, double horizontal_accuracy, int64 access_hash); explicit Location(const tl_object_ptr &geo_point); - explicit Location(const tl_object_ptr &geo_point_ptr); + Location(Td *td, const tl_object_ptr &geo_point_ptr); explicit Location(const tl_object_ptr &location); @@ -54,6 +56,8 @@ class Location { tl_object_ptr get_input_geo_point() const; + telegram_api::object_ptr get_fake_geo_point() const; + tl_object_ptr get_input_media_geo_point() const; double get_latitude() const { diff --git a/td/telegram/MediaArea.cpp b/td/telegram/MediaArea.cpp new file mode 100644 index 000000000000..9d1182902317 --- /dev/null +++ b/td/telegram/MediaArea.cpp @@ -0,0 +1,153 @@ +// +// 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/MediaArea.h" + +#include "td/telegram/InlineQueriesManager.h" +#include "td/telegram/Td.h" + +#include "td/utils/logging.h" + +namespace td { + +MediaArea::MediaArea(Td *td, telegram_api::object_ptr &&media_area_ptr) { + CHECK(media_area_ptr != nullptr); + switch (media_area_ptr->get_id()) { + case telegram_api::mediaAreaGeoPoint::ID: { + auto area = telegram_api::move_object_as(media_area_ptr); + coordinates_ = MediaAreaCoordinates(area->coordinates_); + location_ = Location(td, area->geo_); + if (coordinates_.is_valid() && !location_.empty()) { + type_ = Type::Location; + } else { + LOG(ERROR) << "Receive " << to_string(area); + } + break; + } + case telegram_api::mediaAreaVenue::ID: { + auto area = telegram_api::move_object_as(media_area_ptr); + coordinates_ = MediaAreaCoordinates(area->coordinates_); + venue_ = Venue(td, area->geo_, std::move(area->title_), std::move(area->address_), std::move(area->provider_), + std::move(area->venue_id_), std::move(area->venue_type_)); + if (coordinates_.is_valid() && !venue_.empty()) { + type_ = Type::Venue; + } else { + LOG(ERROR) << "Receive " << to_string(area); + } + break; + } + case telegram_api::inputMediaAreaVenue::ID: + LOG(ERROR) << "Receive " << to_string(media_area_ptr); + break; + default: + UNREACHABLE(); + } +} + +MediaArea::MediaArea(Td *td, td_api::object_ptr &&input_story_area, + const vector &old_media_areas) { + if (input_story_area == nullptr || input_story_area->position_ == nullptr || input_story_area->type_ == nullptr) { + return; + } + coordinates_ = MediaAreaCoordinates(input_story_area->position_); + if (!coordinates_.is_valid()) { + return; + } + switch (input_story_area->type_->get_id()) { + case td_api::inputStoryAreaTypeLocation::ID: { + auto type = td_api::move_object_as(input_story_area->type_); + location_ = Location(type->location_); + if (!location_.empty()) { + type_ = Type::Location; + } + break; + } + case td_api::inputStoryAreaTypeFoundVenue::ID: { + auto type = td_api::move_object_as(input_story_area->type_); + const InlineMessageContent *inline_message_content = + td->inline_queries_manager_->get_inline_message_content(type->query_id_, type->result_id_); + if (inline_message_content == nullptr || inline_message_content->message_content == nullptr) { + break; + } + auto venue_ptr = get_message_content_venue(inline_message_content->message_content.get()); + if (venue_ptr == nullptr || venue_ptr->empty()) { + break; + } + venue_ = *venue_ptr; + input_query_id_ = type->query_id_; + input_result_id_ = std::move(type->result_id_); + type_ = Type::Venue; + break; + } + case td_api::inputStoryAreaTypePreviousVenue::ID: { + auto type = td_api::move_object_as(input_story_area->type_); + for (auto &old_media_area : old_media_areas) { + if (old_media_area.type_ == Type::Venue && !old_media_area.venue_.empty() && + old_media_area.venue_.is_same(type->venue_provider_, type->venue_id_)) { + venue_ = old_media_area.venue_; + input_query_id_ = old_media_area.input_query_id_; + input_result_id_ = old_media_area.input_result_id_; + type_ = Type::Venue; + break; + } + } + break; + } + default: + UNREACHABLE(); + } +} + +td_api::object_ptr MediaArea::get_story_area_object() const { + CHECK(is_valid()); + td_api::object_ptr type; + switch (type_) { + case Type::Location: + type = td_api::make_object(location_.get_location_object()); + break; + case Type::Venue: + type = td_api::make_object(venue_.get_venue_object()); + break; + default: + UNREACHABLE(); + } + return td_api::make_object(coordinates_.get_story_area_position_object(), std::move(type)); +} + +telegram_api::object_ptr MediaArea::get_input_media_area() const { + CHECK(is_valid()); + switch (type_) { + case Type::Location: + return telegram_api::make_object(coordinates_.get_input_media_area_coordinates(), + location_.get_fake_geo_point()); + case Type::Venue: + if (input_query_id_ != 0) { + return telegram_api::make_object( + coordinates_.get_input_media_area_coordinates(), input_query_id_, input_result_id_); + } + return venue_.get_input_media_area_venue(coordinates_.get_input_media_area_coordinates()); + default: + UNREACHABLE(); + return nullptr; + } +} + +bool operator==(const MediaArea &lhs, const MediaArea &rhs) { + return lhs.type_ == rhs.type_ && lhs.coordinates_ == rhs.coordinates_ && lhs.location_ == rhs.location_ && + lhs.venue_ == rhs.venue_ && lhs.input_query_id_ == rhs.input_query_id_ && + lhs.input_result_id_ == rhs.input_result_id_; +} + +bool operator!=(const MediaArea &lhs, const MediaArea &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MediaArea &media_area) { + return string_builder << "StoryArea[" << media_area.coordinates_ << ": " << media_area.location_ << '/' + << media_area.venue_ << ']'; +} + +} // namespace td diff --git a/td/telegram/MediaArea.h b/td/telegram/MediaArea.h new file mode 100644 index 000000000000..6e821234381c --- /dev/null +++ b/td/telegram/MediaArea.h @@ -0,0 +1,64 @@ +// +// 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/Location.h" +#include "td/telegram/MediaAreaCoordinates.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/Venue.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Td; + +class MediaArea { + enum class Type : int32 { None, Location, Venue }; + Type type_ = Type::None; + MediaAreaCoordinates coordinates_; + Location location_; + Venue venue_; + int64 input_query_id_ = 0; + string input_result_id_; + + friend bool operator==(const MediaArea &lhs, const MediaArea &rhs); + friend bool operator!=(const MediaArea &lhs, const MediaArea &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MediaArea &media_area); + + public: + MediaArea() = default; + + MediaArea(Td *td, telegram_api::object_ptr &&media_area_ptr); + + MediaArea(Td *td, td_api::object_ptr &&input_story_area, + const vector &old_media_areas); + + td_api::object_ptr get_story_area_object() const; + + telegram_api::object_ptr get_input_media_area() const; + + bool is_valid() const { + return type_ != Type::None; + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const MediaArea &lhs, const MediaArea &rhs); +bool operator!=(const MediaArea &lhs, const MediaArea &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const MediaArea &media_area); + +} // namespace td diff --git a/td/telegram/MediaArea.hpp b/td/telegram/MediaArea.hpp new file mode 100644 index 000000000000..3641365041d7 --- /dev/null +++ b/td/telegram/MediaArea.hpp @@ -0,0 +1,66 @@ +// +// 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/MediaArea.h" +#include "td/telegram/MediaAreaCoordinates.hpp" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MediaArea::store(StorerT &storer) const { + using td::store; + bool has_input_query_id = input_query_id_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_input_query_id); + END_STORE_FLAGS(); + store(type_, storer); + store(coordinates_, storer); + switch (type_) { + case Type::Location: + store(location_, storer); + break; + case Type::Venue: + store(venue_, storer); + if (has_input_query_id) { + store(input_query_id_, storer); + store(input_result_id_, storer); + } + break; + default: + UNREACHABLE(); + } +} + +template +void MediaArea::parse(ParserT &parser) { + using td::parse; + bool has_input_query_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_input_query_id); + END_PARSE_FLAGS(); + parse(type_, parser); + parse(coordinates_, parser); + switch (type_) { + case Type::Location: + parse(location_, parser); + break; + case Type::Venue: + parse(venue_, parser); + if (has_input_query_id) { + parse(input_query_id_, parser); + parse(input_result_id_, parser); + } + break; + default: + parser.set_error("Load invalid area type"); + } +} + +} // namespace td diff --git a/td/telegram/MediaAreaCoordinates.cpp b/td/telegram/MediaAreaCoordinates.cpp new file mode 100644 index 000000000000..bb20c2d157b6 --- /dev/null +++ b/td/telegram/MediaAreaCoordinates.cpp @@ -0,0 +1,75 @@ +// +// 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/MediaAreaCoordinates.h" + +#include + +namespace td { + +static double fix_double(double &value, double max_value = 100.0) { + if (!std::isfinite(value) || value < 0.0) { + return 0.0; + } + if (value > max_value) { + return max_value; + } + return value; +} + +void MediaAreaCoordinates::init(double x, double y, double width, double height, double rotation_angle) { + x_ = fix_double(x); + y_ = fix_double(y); + width_ = fix_double(width); + height_ = fix_double(height); + rotation_angle_ = fix_double(rotation_angle, 360.0); +} + +MediaAreaCoordinates::MediaAreaCoordinates( + const telegram_api::object_ptr &coordinates) { + if (coordinates == nullptr) { + return; + } + init(coordinates->x_, coordinates->y_, coordinates->w_, coordinates->h_, coordinates->rotation_); +} + +MediaAreaCoordinates::MediaAreaCoordinates(const td_api::object_ptr &position) { + if (position == nullptr) { + return; + } + + init(position->x_percentage_, position->y_percentage_, position->width_percentage_, position->height_percentage_, + position->rotation_angle_); +} + +td_api::object_ptr MediaAreaCoordinates::get_story_area_position_object() const { + CHECK(is_valid()); + return td_api::make_object(x_, y_, width_, height_, rotation_angle_); +} + +telegram_api::object_ptr MediaAreaCoordinates::get_input_media_area_coordinates() + const { + CHECK(is_valid()); + return telegram_api::make_object(x_, y_, width_, height_, rotation_angle_); +} + +bool operator==(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs) { + return std::abs(lhs.x_ - rhs.x_) < 1e-6 && std::abs(lhs.y_ - rhs.y_) < 1e-6 && + std::abs(lhs.width_ - rhs.width_) < 1e-6 && std::abs(lhs.height_ - rhs.height_) < 1e-6 && + std::abs(lhs.rotation_angle_ - rhs.rotation_angle_) < 1e-6; +} + +bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MediaAreaCoordinates &coordinates) { + return string_builder << "StoryAreaPosition[" << coordinates.x_ << ", " << coordinates.y_ << ", " + << coordinates.width_ << ", " << coordinates.height_ << ", " << coordinates.rotation_angle_ + << ']'; +} + +} // namespace td diff --git a/td/telegram/MediaAreaCoordinates.h b/td/telegram/MediaAreaCoordinates.h new file mode 100644 index 000000000000..9b4dd1108e3b --- /dev/null +++ b/td/telegram/MediaAreaCoordinates.h @@ -0,0 +1,58 @@ +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class MediaAreaCoordinates { + double x_ = 0.0; + double y_ = 0.0; + double width_ = 0.0; + double height_ = 0.0; + double rotation_angle_ = 0.0; + + friend bool operator==(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); + friend bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MediaAreaCoordinates &coordinates); + + void init(double x, double y, double width, double height, double rotation_angle); + + public: + MediaAreaCoordinates() = default; + + explicit MediaAreaCoordinates(const telegram_api::object_ptr &coordinates); + + explicit MediaAreaCoordinates(const td_api::object_ptr &position); + + td_api::object_ptr get_story_area_position_object() const; + + telegram_api::object_ptr get_input_media_area_coordinates() const; + + bool is_valid() const { + return width_ > 0.0 && height_ > 0.0; + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); +bool operator!=(const MediaAreaCoordinates &lhs, const MediaAreaCoordinates &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const MediaAreaCoordinates &coordinates); + +} // namespace td diff --git a/td/telegram/MediaAreaCoordinates.hpp b/td/telegram/MediaAreaCoordinates.hpp new file mode 100644 index 000000000000..d30bfbdd26b9 --- /dev/null +++ b/td/telegram/MediaAreaCoordinates.hpp @@ -0,0 +1,45 @@ +// +// 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/MediaAreaCoordinates.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MediaAreaCoordinates::store(StorerT &storer) const { + using td::store; + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + store(x_, storer); + store(y_, storer); + store(width_, storer); + store(height_, storer); + store(rotation_angle_, storer); +} + +template +void MediaAreaCoordinates::parse(ParserT &parser) { + using td::parse; + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + double x; + double y; + double width; + double height; + double rotation_angle; + parse(x, parser); + parse(y, parser); + parse(width, parser); + parse(height, parser); + parse(rotation_angle, parser); + init(x, y, width, height, rotation_angle); +} + +} // namespace td diff --git a/td/telegram/MessageContent.cpp b/td/telegram/MessageContent.cpp index c56f5a411623..26613a4a8414 100644 --- a/td/telegram/MessageContent.cpp +++ b/td/telegram/MessageContent.cpp @@ -52,7 +52,6 @@ #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" -#include "td/telegram/net/DcId.h" #include "td/telegram/OptionManager.h" #include "td/telegram/OrderInfo.h" #include "td/telegram/OrderInfo.hpp" @@ -60,7 +59,6 @@ #include "td/telegram/Photo.hpp" #include "td/telegram/PhotoFormat.h" #include "td/telegram/PhotoSize.h" -#include "td/telegram/PhotoSizeSource.h" #include "td/telegram/PollId.h" #include "td/telegram/PollId.hpp" #include "td/telegram/PollManager.h" @@ -71,7 +69,11 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/StickersManager.hpp" #include "td/telegram/StickerType.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UserId.h" #include "td/telegram/Venue.h" @@ -473,7 +475,7 @@ class MessageChatSetTtl final : public MessageContent { class MessageUnsupported final : public MessageContent { public: - static constexpr int32 CURRENT_VERSION = 18; + static constexpr int32 CURRENT_VERSION = 19; int32 version = CURRENT_VERSION; MessageUnsupported() = default; @@ -913,6 +915,27 @@ class MessageSetBackground final : public MessageContent { } }; +class MessageStory final : public MessageContent { + public: + StoryFullId story_full_id; + bool via_mention = false; + + MessageStory() = default; + MessageStory(StoryFullId story_full_id, bool via_mention) : story_full_id(story_full_id), via_mention(via_mention) { + } + + MessageContentType get_type() const final { + return MessageContentType::Story; + } +}; + +class MessageWriteAccessAllowedByRequest final : public MessageContent { + public: + MessageContentType get_type() const final { + return MessageContentType::WriteAccessAllowedByRequest; + } +}; + template static void store(const MessageContent *content, StorerT &storer) { CHECK(content != nullptr); @@ -1305,6 +1328,16 @@ static void store(const MessageContent *content, StorerT &storer) { store(m->background_info, storer); break; } + case MessageContentType::Story: { + const auto *m = static_cast(content); + BEGIN_STORE_FLAGS(); + STORE_FLAG(m->via_mention); + END_STORE_FLAGS(); + store(m->story_full_id, storer); + break; + } + case MessageContentType::WriteAccessAllowedByRequest: + break; default: UNREACHABLE(); } @@ -1836,8 +1869,23 @@ static void parse(unique_ptr &content, ParserT &parser) { content = std::move(m); break; } + case MessageContentType::Story: { + auto m = make_unique(); + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(m->via_mention); + END_PARSE_FLAGS(); + parse(m->story_full_id, parser); + if (!m->story_full_id.is_server()) { + is_bad = true; + } + content = std::move(m); + break; + } + case MessageContentType::WriteAccessAllowedByRequest: + content = make_unique(); + break; default: - LOG(FATAL) << "Have unknown message content type " << static_cast(content_type); + is_bad = true; } if (is_bad) { LOG(ERROR) << "Load a message with an invalid content of type " << content_type; @@ -1901,10 +1949,10 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, auto inline_message = move_tl_object_as(bot_inline_message); if (inline_message->period_ > 0) { result.message_content = - make_unique(Location(inline_message->geo_), inline_message->period_, + make_unique(Location(td, inline_message->geo_), inline_message->period_, inline_message->heading_, inline_message->proximity_notification_radius_); } else { - result.message_content = make_unique(Location(inline_message->geo_)); + result.message_content = make_unique(Location(td, inline_message->geo_)); } reply_markup = std::move(inline_message->reply_markup_); break; @@ -1912,7 +1960,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, case telegram_api::botInlineMessageMediaVenue::ID: { auto inline_message = move_tl_object_as(bot_inline_message); result.message_content = make_unique( - Venue(inline_message->geo_, std::move(inline_message->title_), std::move(inline_message->address_), + Venue(td, inline_message->geo_, std::move(inline_message->title_), std::move(inline_message->address_), std::move(inline_message->provider_), std::move(inline_message->venue_id_), std::move(inline_message->venue_type_))); reply_markup = std::move(inline_message->reply_markup_); @@ -2001,7 +2049,7 @@ static Result create_input_message_content( bool clear_draft = false; unique_ptr content; UserId via_bot_user_id; - int32 ttl = 0; + td_api::object_ptr self_destruct_type; string emoji; bool is_bot = td->auth_manager_->is_bot(); bool is_secret = dialog_id.get_type() == DialogType::SecretChat; @@ -2074,7 +2122,7 @@ static Result create_input_message_content( case td_api::inputMessagePhoto::ID: { auto input_photo = static_cast(input_message_content.get()); - ttl = input_photo->self_destruct_time_; + self_destruct_type = std::move(input_photo->self_destruct_type_); TRY_RESULT(photo, create_photo(td->file_manager_.get(), file_id, std::move(thumbnail), input_photo->width_, input_photo->height_, std::move(sticker_file_ids))); @@ -2098,13 +2146,14 @@ static Result create_input_message_content( case td_api::inputMessageVideo::ID: { auto input_video = static_cast(input_message_content.get()); - ttl = input_video->self_destruct_time_; + self_destruct_type = std::move(input_video->self_destruct_type_); bool has_stickers = !sticker_file_ids.empty(); - td->videos_manager_->create_video( - file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids), - std::move(file_name), std::move(mime_type), input_video->duration_, - get_dimensions(input_video->width_, input_video->height_, nullptr), input_video->supports_streaming_, false); + td->videos_manager_->create_video(file_id, string(), thumbnail, AnimationSize(), has_stickers, + std::move(sticker_file_ids), std::move(file_name), std::move(mime_type), + input_video->duration_, input_video->duration_, + get_dimensions(input_video->width_, input_video->height_, nullptr), + input_video->supports_streaming_, false, 0, false); content = make_unique(file_id, std::move(caption), input_video->has_spoiler_ && !is_secret); break; @@ -2248,9 +2297,51 @@ static Result create_input_message_content( std::move(explanation), open_period, close_date, is_closed)); break; } + case td_api::inputMessageStory::ID: { + auto input_story = static_cast(input_message_content.get()); + DialogId story_sender_dialog_id(input_story->story_sender_chat_id_); + StoryId story_id(input_story->story_id_); + StoryFullId story_full_id(story_sender_dialog_id, story_id); + if (!td->story_manager_->have_story_force(story_full_id) || + story_sender_dialog_id.get_type() != DialogType::User) { + return Status::Error(400, "Story not found"); + } + if (!story_id.is_server()) { + return Status::Error(400, "Story can't be forwarded"); + } + if (td->contacts_manager_->get_input_user(story_sender_dialog_id.get_user_id()).is_error()) { + return Status::Error(400, "Can't access the user"); + } + content = make_unique(story_full_id, false); + break; + } default: UNREACHABLE(); } + + if (self_destruct_type != nullptr && dialog_id.get_type() != DialogType::User) { + return Status::Error(400, "Messages can self-destruct only in can be specified only in private chats"); + } + int32 ttl = 0; + if (self_destruct_type != nullptr) { + switch (self_destruct_type->get_id()) { + case td_api::messageSelfDestructTypeTimer::ID: { + ttl = static_cast(self_destruct_type.get())->self_destruct_time_; + + static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit + if (ttl <= 0 || ttl > MAX_PRIVATE_MESSAGE_TTL) { + return Status::Error(400, "Invalid message content self-destruct time specified"); + } + break; + } + case td_api::messageSelfDestructTypeImmediately::ID: + ttl = 0x7FFFFFFF; + break; + default: + UNREACHABLE(); + } + } + return InputMessageContent{std::move(content), disable_web_page_preview, clear_draft, ttl, via_bot_user_id, std::move(emoji)}; } @@ -2258,6 +2349,9 @@ static Result create_input_message_content( Result get_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, bool is_premium) { LOG(INFO) << "Get input message content from " << to_string(input_message_content); + if (input_message_content == nullptr) { + return Status::Error(400, "Input message content must be non-empty"); + } td_api::object_ptr input_file; auto file_type = FileType::None; @@ -2363,6 +2457,12 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s return is_server || static_cast(content)->game.has_input_media(); case MessageContentType::Poll: return td->poll_manager_->has_input_media(static_cast(content)->poll_id); + 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(); + } case MessageContentType::Unsupported: case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: @@ -2402,6 +2502,7 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return false; case MessageContentType::Animation: case MessageContentType::Audio: @@ -2492,6 +2593,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::Invoice: case MessageContentType::LiveLocation: case MessageContentType::Poll: + case MessageContentType::Story: case MessageContentType::Unsupported: case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: @@ -2530,6 +2632,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: break; default: UNREACHABLE(); @@ -2601,6 +2704,10 @@ static tl_object_ptr get_input_media_impl( return td->stickers_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), emoji); } + case MessageContentType::Story: { + const auto *m = static_cast(content); + return td->story_manager_->get_input_media(m->story_full_id); + } case MessageContentType::Venue: { const auto *m = static_cast(content); return m->venue.get_input_media_venue(); @@ -2658,6 +2765,7 @@ static tl_object_ptr get_input_media_impl( case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: break; default: UNREACHABLE(); @@ -2730,7 +2838,7 @@ tl_object_ptr get_fake_input_media(Td *td, tl_object_p } string mime_type = MimeType::from_extension(path_view.extension()); int32 flags = 0; - if (file_type == FileType::Video) { + if (file_type == FileType::Video || file_type == FileType::VideoStory) { flags |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; } if (file_type == FileType::DocumentAsFile) { @@ -2740,7 +2848,7 @@ tl_object_ptr get_fake_input_media(Td *td, tl_object_p flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes), vector>(), 0); } else { - CHECK(file_type == FileType::Photo); + CHECK(file_type == FileType::Photo || file_type == FileType::PhotoStory); int32 flags = 0; return make_tl_object( flags, false /*ignored*/, std::move(input_file), vector>(), 0); @@ -2786,6 +2894,7 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) { case MessageContentType::Game: case MessageContentType::LiveLocation: case MessageContentType::Location: + case MessageContentType::Story: case MessageContentType::Venue: case MessageContentType::VoiceNote: case MessageContentType::Text: @@ -2829,6 +2938,7 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) { case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: break; default: UNREACHABLE(); @@ -2938,13 +3048,18 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten } break; case MessageContentType::Sticker: - if (!permissions.can_send_stickers()) { + if (!permissions.can_send_messages()) { return Status::Error(400, "Not enough rights to send stickers to the chat"); } if (get_message_content_sticker_type(td, content) == StickerType::CustomEmoji) { return Status::Error(400, "Can't send emoji stickers in messages"); } break; + case MessageContentType::Story: + if (!permissions.can_send_photos() || !permissions.can_send_videos()) { + return Status::Error(400, "Not enough rights to send stories to the chat"); + } + break; case MessageContentType::Text: if (!permissions.can_send_messages()) { return Status::Error(400, "Not enough rights to send text messages to the chat"); @@ -3018,6 +3133,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: UNREACHABLE(); } return Status::OK(); @@ -3114,6 +3230,7 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, case MessageContentType::LiveLocation: case MessageContentType::Location: case MessageContentType::Sticker: + case MessageContentType::Story: case MessageContentType::Unsupported: case MessageContentType::Venue: case MessageContentType::ChatCreate: @@ -3154,6 +3271,7 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return 0; default: UNREACHABLE(); @@ -3232,13 +3350,159 @@ std::pair get_message_content_group_call_info(const Mess return {m->input_group_call_id, m->duration >= 0}; } -UserId get_message_content_contact_user_id(const MessageContent *content) { - switch (content->get_type()) { - case MessageContentType::Contact: - return static_cast(content)->contact.get_user_id(); +vector get_message_content_min_user_ids(const Td *td, const MessageContent *message_content) { + 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_user_ids(content->web_page_id); + } + break; + } + case MessageContentType::Animation: + break; + case MessageContentType::Audio: + break; + case MessageContentType::Contact: { + const auto *content = static_cast(message_content); + auto user_id = content->contact.get_user_id(); + if (user_id.is_valid()) { + return {user_id}; + } + break; + } + case MessageContentType::Document: + break; + case MessageContentType::Game: { + // not supported server-side + // const auto *content = static_cast(message_content); + // return {content->game.get_bot_user_id())}; + break; + } + case MessageContentType::Invoice: + break; + case MessageContentType::LiveLocation: + break; + case MessageContentType::Location: + break; + case MessageContentType::Photo: + break; + case MessageContentType::Sticker: + break; + case MessageContentType::Venue: + break; + case MessageContentType::Video: + break; + case MessageContentType::VideoNote: + break; + case MessageContentType::VoiceNote: + break; + case MessageContentType::ChatCreate: { + const auto *content = static_cast(message_content); + return content->participant_user_ids; + } + case MessageContentType::ChatChangeTitle: + break; + case MessageContentType::ChatChangePhoto: + break; + case MessageContentType::ChatDeletePhoto: + break; + case MessageContentType::ChatDeleteHistory: + break; + case MessageContentType::ChatAddUsers: { + const auto *content = static_cast(message_content); + return content->user_ids; + } + case MessageContentType::ChatJoinedByLink: + break; + case MessageContentType::ChatDeleteUser: { + const auto *content = static_cast(message_content); + return {content->user_id}; + } + case MessageContentType::ChatMigrateTo: + break; + case MessageContentType::ChannelCreate: + break; + case MessageContentType::ChannelMigrateFrom: + break; + case MessageContentType::PinMessage: + break; + case MessageContentType::GameScore: + break; + case MessageContentType::ScreenshotTaken: + break; + case MessageContentType::ChatSetTtl: + // the content->from_user_id user can't be min + break; + case MessageContentType::Unsupported: + break; + case MessageContentType::Call: + break; + case MessageContentType::PaymentSuccessful: + break; + case MessageContentType::ContactRegistered: + break; + case MessageContentType::ExpiredPhoto: + break; + case MessageContentType::ExpiredVideo: + break; + case MessageContentType::CustomServiceAction: + break; + case MessageContentType::WebsiteConnected: + break; + case MessageContentType::PassportDataSent: + break; + case MessageContentType::PassportDataReceived: + break; + case MessageContentType::Poll: + break; + case MessageContentType::Dice: + break; + case MessageContentType::ProximityAlertTriggered: + break; + case MessageContentType::GroupCall: + break; + case MessageContentType::InviteToGroupCall: { + const auto *content = static_cast(message_content); + return content->user_ids; + } + case MessageContentType::ChatSetTheme: + break; + case MessageContentType::WebViewDataSent: + break; + case MessageContentType::WebViewDataReceived: + break; + case MessageContentType::GiftPremium: + break; + case MessageContentType::TopicCreate: + break; + case MessageContentType::TopicEdit: + break; + case MessageContentType::SuggestProfilePhoto: + break; + case MessageContentType::WriteAccessAllowed: + break; + case MessageContentType::RequestedDialog: + break; + case MessageContentType::WebViewWriteAccessAllowed: + break; + case MessageContentType::SetBackground: + 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::User) { + return {dialog_id.get_user_id()}; + } + break; + } + case MessageContentType::WriteAccessAllowedByRequest: + break; default: - return UserId(); + UNREACHABLE(); + break; } + return {}; } vector get_message_content_added_user_ids(const MessageContent *content) { @@ -3282,6 +3546,15 @@ bool get_message_content_poll_is_closed(const Td *td, const MessageContent *cont } } +const Venue *get_message_content_venue(const MessageContent *content) { + switch (content->get_type()) { + case MessageContentType::Venue: + return &static_cast(content)->venue; + default: + return nullptr; + } +} + bool has_message_content_web_page(const MessageContent *content) { if (content->get_type() == MessageContentType::Text) { return static_cast(content)->web_page_id.is_valid(); @@ -3298,6 +3571,7 @@ bool can_message_content_have_media_timestamp(const MessageContent *content) { CHECK(content != nullptr); switch (content->get_type()) { case MessageContentType::Audio: + case MessageContentType::Story: case MessageContentType::Video: case MessageContentType::VideoNote: case MessageContentType::VoiceNote: @@ -3320,7 +3594,7 @@ void set_message_content_poll_answer(Td *td, const MessageContent *content, Full void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit, - Promise>> &&promise) { + Promise> &&promise) { CHECK(content->get_type() == MessageContentType::Poll); td->poll_manager_->get_poll_voters(static_cast(content)->poll_id, full_message_id, option_id, offset, limit, std::move(promise)); @@ -3919,6 +4193,16 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo } 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) { + need_update = true; + } + break; + } + case MessageContentType::WriteAccessAllowedByRequest: + break; default: UNREACHABLE(); break; @@ -4015,6 +4299,7 @@ bool merge_message_content_file_id(Td *td, MessageContent *message_content, File case MessageContentType::Invoice: case MessageContentType::LiveLocation: case MessageContentType::Location: + case MessageContentType::Story: case MessageContentType::Text: case MessageContentType::Venue: case MessageContentType::ChatCreate: @@ -4058,6 +4343,7 @@ bool merge_message_content_file_id(Td *td, MessageContent *message_content, File 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; break; default: @@ -4118,6 +4404,9 @@ void register_message_content(Td *td, const MessageContent *content, FullMessage case MessageContentType::SuggestProfilePhoto: return td->contacts_manager_->register_suggested_profile_photo( static_cast(content)->photo); + case MessageContentType::Story: + return td->story_manager_->register_story(static_cast(content)->story_full_id, + full_message_id, source); default: return; } @@ -4171,6 +4460,12 @@ void reregister_message_content(Td *td, const MessageContent *old_content, const return; } break; + case MessageContentType::Story: + if (static_cast(old_content)->story_full_id == + static_cast(new_content)->story_full_id) { + return; + } + break; default: return; } @@ -4208,6 +4503,9 @@ void unregister_message_content(Td *td, const MessageContent *content, FullMessa case MessageContentType::GiftPremium: return td->stickers_manager_->unregister_premium_gift(static_cast(content)->months, full_message_id, source); + case MessageContentType::Story: + return td->story_manager_->unregister_story(static_cast(content)->story_full_id, + full_message_id, source); default: return; } @@ -4254,7 +4552,7 @@ static auto secret_to_telegram(secret_api::documentAttributeAnimated &animated) // documentAttributeSticker23 = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeSticker23 &sticker) { return make_tl_object( - 0, false /*ignored*/, "", make_tl_object(), nullptr); + 0, false, "", make_tl_object(), nullptr); } static auto secret_to_telegram(secret_api::inputStickerSetEmpty &sticker_set) { @@ -4274,14 +4572,13 @@ static auto secret_to_telegram(secret_api::documentAttributeSticker &sticker) { sticker.alt_.clear(); } return make_tl_object( - 0, false /*ignored*/, sticker.alt_, secret_to_telegram(*sticker.stickerset_), - nullptr); + 0, false, sticker.alt_, secret_to_telegram(*sticker.stickerset_), nullptr); } // documentAttributeVideo23 duration:int w:int h:int = DocumentAttribute; static auto secret_to_telegram(secret_api::documentAttributeVideo23 &video) { - return make_tl_object(0, false /*ignored*/, false /*ignored*/, video.duration_, - video.w_, video.h_); + return make_tl_object(0, false, false, false, video.duration_, video.w_, + video.h_, 0); } // documentAttributeFilename file_name:string = DocumentAttribute; @@ -4296,7 +4593,7 @@ static auto secret_to_telegram(secret_api::documentAttributeFilename &filename) static auto secret_to_telegram(secret_api::documentAttributeVideo &video) { return make_tl_object( video.round_message_ ? telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK : 0, video.round_message_, false, - video.duration_, video.w_, video.h_); + false, video.duration_, video.w_, video.h_, 0); } static auto telegram_documentAttributeAudio(bool is_voice_note, int duration, string title, string performer, @@ -4521,9 +4818,9 @@ unique_ptr get_secret_message_content( media->venue_id_.clear(); } - auto m = make_unique(Venue(Location(media->lat_, media->long_, 0.0, 0), std::move(media->title_), - std::move(media->address_), std::move(media->provider_), - std::move(media->venue_id_), string())); + auto m = make_unique(Venue(Location(td, media->lat_, media->long_, 0.0, 0), + std::move(media->title_), std::move(media->address_), + std::move(media->provider_), std::move(media->venue_id_), string())); if (m->venue.empty()) { is_media_empty = true; break; @@ -4679,7 +4976,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, case telegram_api::messageMediaGeo::ID: { auto media = move_tl_object_as(media_ptr); - auto m = make_unique(Location(media->geo_)); + auto m = make_unique(Location(td, media->geo_)); if (m->location.empty()) { break; } @@ -4688,7 +4985,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, } case telegram_api::messageMediaGeoLive::ID: { auto media = move_tl_object_as(media_ptr); - auto location = Location(media->geo_); + auto location = Location(td, media->geo_); if (location.empty()) { break; } @@ -4703,7 +5000,7 @@ unique_ptr get_message_content(Td *td, FormattedText message, } case telegram_api::messageMediaVenue::ID: { auto media = move_tl_object_as(media_ptr); - auto m = make_unique(Venue(media->geo_, std::move(media->title_), std::move(media->address_), + auto m = make_unique(Venue(td, media->geo_, std::move(media->title_), std::move(media->address_), std::move(media->provider_), std::move(media->venue_id_), std::move(media->venue_type_))); if (m->venue.empty()) { @@ -4778,6 +5075,24 @@ unique_ptr get_message_content(Td *td, FormattedText message, } return make_unique(poll_id); } + case telegram_api::messageMediaStory::ID: { + auto media = move_tl_object_as(media_ptr); + auto dialog_id = DialogId(UserId(media->user_id_)); + auto story_id = StoryId(media->id_); + auto story_full_id = StoryFullId(dialog_id, story_id); + if (!story_full_id.is_server()) { + LOG(ERROR) << "Receive " << to_string(media); + break; + } + if (media->story_ != nullptr) { + auto actual_story_id = td->story_manager_->on_get_story(dialog_id, std::move(media->story_)); + if (story_id != actual_story_id) { + LOG(ERROR) << "Receive " << actual_story_id << " instead of " << story_id; + } + } + td->messages_manager_->force_create_dialog(dialog_id, "messageMediaStory"); + return make_unique(story_full_id, media->via_mention_); + } case telegram_api::messageMediaUnsupported::ID: return make_unique(); default: @@ -4927,6 +5242,9 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } result->photo.photos.clear(); + result->photo.animations.clear(); + result->photo.sticker_photo_size = nullptr; + bool has_thumbnail = thumbnail.type != 0; if (has_thumbnail) { thumbnail.type = 't'; @@ -4963,6 +5281,8 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const CHECK(result->file_id.is_valid()); return std::move(result); } + case MessageContentType::Story: + return make_unique(static_cast(content)->story_full_id, false); case MessageContentType::Text: { auto result = make_unique(*static_cast(content)); if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) { @@ -5046,6 +5366,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return nullptr; default: UNREACHABLE(); @@ -5248,6 +5569,9 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr( WebApp(td, telegram_api::move_object_as(action->app_), owner_dialog_id)); } + if (action->from_request_) { + return td::make_unique(); + } return td::make_unique(); } case telegram_api::messageActionSecureValuesSent::ID: { @@ -5728,7 +6052,7 @@ tl_object_ptr get_message_content_object(const MessageCo return make_tl_object(std::move(photo)); } case MessageContentType::WriteAccessAllowed: - return make_tl_object(nullptr); + return make_tl_object(nullptr, false); case MessageContentType::RequestedDialog: { const auto *m = static_cast(content); if (m->dialog_id.get_type() == DialogType::User) { @@ -5750,13 +6074,21 @@ 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)); + return td_api::make_object(m->web_app.get_web_app_object(td), false); } case MessageContentType::SetBackground: { const auto *m = static_cast(content); return td_api::make_object(m->old_message_id.get(), m->background_info.get_chat_background_object(td)); } + case MessageContentType::Story: { + const auto *m = static_cast(content); + return td_api::make_object( + td->messages_manager_->get_chat_id_object(m->story_full_id.get_dialog_id(), "messageStory"), + m->story_full_id.get_story_id().get(), m->via_mention); + } + case MessageContentType::WriteAccessAllowedByRequest: + return make_tl_object(nullptr, true); default: UNREACHABLE(); return nullptr; @@ -5856,7 +6188,7 @@ int32 get_message_content_duration(const MessageContent *content, const Td *td) return td->voice_notes_manager_->get_voice_note_duration(voice_file_id); } default: - return 0; + return -1; } } @@ -5869,6 +6201,10 @@ int32 get_message_content_media_duration(const MessageContent *content, const Td } case MessageContentType::Invoice: return static_cast(content)->input_invoice.get_duration(td); + case MessageContentType::Story: { + auto story_full_id = static_cast(content)->story_full_id; + return td->story_manager_->get_story_duration(story_full_id); + } case MessageContentType::Text: { auto web_page_id = static_cast(content)->web_page_id; return td->web_pages_manager_->get_web_page_media_duration(web_page_id); @@ -6064,11 +6400,26 @@ vector get_message_content_file_ids(const MessageContent *content, const case MessageContentType::SetBackground: // background file references are repaired independently return {}; + case MessageContentType::Story: + // story file references are repaired independently + return {}; default: return {}; } } +StoryFullId get_message_content_story_full_id(const Td *td, const MessageContent *content) { + CHECK(content != nullptr); + switch (content->get_type()) { + case MessageContentType::Text: + return td->web_pages_manager_->get_web_page_story_full_id(static_cast(content)->web_page_id); + case MessageContentType::Story: + return static_cast(content)->story_full_id; + default: + return StoryFullId(); + } +} + string get_message_content_search_text(const Td *td, const MessageContent *content) { switch (content->get_type()) { case MessageContentType::Text: { @@ -6121,6 +6472,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte case MessageContentType::LiveLocation: case MessageContentType::Location: case MessageContentType::Sticker: + case MessageContentType::Story: case MessageContentType::Unsupported: case MessageContentType::Venue: case MessageContentType::VideoNote: @@ -6162,6 +6514,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return string(); default: UNREACHABLE(); @@ -6462,6 +6815,13 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC break; case MessageContentType::SetBackground: break; + case MessageContentType::Story: { + const auto *content = static_cast(message_content); + dependencies.add(content->story_full_id); + break; + } + case MessageContentType::WriteAccessAllowedByRequest: + break; default: UNREACHABLE(); break; diff --git a/td/telegram/MessageContent.h b/td/telegram/MessageContent.h index cb743ff9e090..40618df150b8 100644 --- a/td/telegram/MessageContent.h +++ b/td/telegram/MessageContent.h @@ -22,6 +22,7 @@ #include "td/telegram/secret_api.h" #include "td/telegram/SecretInputMedia.h" #include "td/telegram/StickerType.h" +#include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TopDialogCategory.h" @@ -43,6 +44,7 @@ class Game; class MultiPromiseActor; struct Photo; class Td; +class Venue; // Do not forget to update merge_message_contents when one of the inheritors of this class changes class MessageContent { @@ -144,7 +146,7 @@ FullMessageId get_message_content_replied_message_id(DialogId dialog_id, const M std::pair get_message_content_group_call_info(const MessageContent *content); -UserId get_message_content_contact_user_id(const MessageContent *content); +vector get_message_content_min_user_ids(const Td *td, const MessageContent *message_content); vector get_message_content_added_user_ids(const MessageContent *content); @@ -156,6 +158,8 @@ bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *c bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content); +const Venue *get_message_content_venue(const MessageContent *content); + bool has_message_content_web_page(const MessageContent *content); void remove_message_content_web_page(MessageContent *content); @@ -167,7 +171,7 @@ void set_message_content_poll_answer(Td *td, const MessageContent *content, Full void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit, - Promise>> &&promise); + Promise> &&promise); void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id, unique_ptr &&reply_markup, Promise &&promise); @@ -237,6 +241,8 @@ FileId get_message_content_thumbnail_file_id(const MessageContent *content, cons vector get_message_content_file_ids(const MessageContent *content, const Td *td); +StoryFullId get_message_content_story_full_id(const Td *td, const MessageContent *content); + string get_message_content_search_text(const Td *td, const MessageContent *content); bool update_message_content_extended_media(MessageContent *content, diff --git a/td/telegram/MessageContentType.cpp b/td/telegram/MessageContentType.cpp index 639512f885bf..025ea06b3409 100644 --- a/td/telegram/MessageContentType.cpp +++ b/td/telegram/MessageContentType.cpp @@ -124,9 +124,12 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont return string_builder << "WebAppWriteAccessAllowed"; case MessageContentType::SetBackground: return string_builder << "SetBackground"; + case MessageContentType::Story: + return string_builder << "Story"; + case MessageContentType::WriteAccessAllowedByRequest: + return string_builder << "WriteAccessAllowedByRequest"; default: - UNREACHABLE(); - return string_builder; + return string_builder << "Invalid type " << static_cast(content_type); } } @@ -189,6 +192,8 @@ bool is_allowed_media_group_content(MessageContentType content_type) { case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::Story: + case MessageContentType::WriteAccessAllowedByRequest: return false; default: UNREACHABLE(); @@ -202,7 +207,7 @@ bool is_homogenous_media_group_content(MessageContentType content_type) { bool is_secret_message_content(int32 ttl, MessageContentType content_type) { if (ttl <= 0 || ttl > 60) { - return false; + return ttl == 0x7FFFFFFF; } switch (content_type) { case MessageContentType::Animation: @@ -262,6 +267,8 @@ bool is_secret_message_content(int32 ttl, MessageContentType content_type) { case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::Story: + case MessageContentType::WriteAccessAllowedByRequest: return false; default: UNREACHABLE(); @@ -291,6 +298,7 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::ExpiredVideo: case MessageContentType::Poll: case MessageContentType::Dice: + case MessageContentType::Story: return false; case MessageContentType::ChatCreate: case MessageContentType::ChatChangeTitle: @@ -328,6 +336,7 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return true; default: UNREACHABLE(); @@ -394,6 +403,8 @@ bool can_have_message_content_caption(MessageContentType content_type) { case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::Story: + case MessageContentType::WriteAccessAllowedByRequest: return false; default: UNREACHABLE(); diff --git a/td/telegram/MessageContentType.h b/td/telegram/MessageContentType.h index 8e71557fb3fe..ff962e4daa23 100644 --- a/td/telegram/MessageContentType.h +++ b/td/telegram/MessageContentType.h @@ -69,7 +69,9 @@ enum class MessageContentType : int32 { WriteAccessAllowed, RequestedDialog, WebViewWriteAccessAllowed, - SetBackground + SetBackground, + Story, + WriteAccessAllowedByRequest }; // increase MessageUnsupported::CURRENT_VERSION each time a new message content type is added diff --git a/td/telegram/MessageCopyOptions.h b/td/telegram/MessageCopyOptions.h index 2335b30e7a51..6b19b1a625ce 100644 --- a/td/telegram/MessageCopyOptions.h +++ b/td/telegram/MessageCopyOptions.h @@ -7,7 +7,7 @@ #pragma once #include "td/telegram/MessageEntity.h" -#include "td/telegram/MessageId.h" +#include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/ReplyMarkup.h" #include "td/utils/common.h" @@ -19,7 +19,7 @@ struct MessageCopyOptions { bool send_copy = false; bool replace_caption = false; FormattedText new_caption; - MessageId reply_to_message_id; + MessageInputReplyTo input_reply_to; unique_ptr reply_markup; MessageCopyOptions() = default; @@ -30,7 +30,7 @@ struct MessageCopyOptions { if (!send_copy) { return true; } - if ((replace_caption && !new_caption.text.empty()) || reply_to_message_id.is_valid() || reply_markup != nullptr) { + if ((replace_caption && !new_caption.text.empty()) || input_reply_to.is_valid() || reply_markup != nullptr) { return false; } return true; @@ -43,8 +43,8 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, MessageCopyOptio if (copy_options.replace_caption) { string_builder << ", new_caption = " << copy_options.new_caption; } - if (copy_options.reply_to_message_id.is_valid()) { - string_builder << ", in reply to " << copy_options.reply_to_message_id; + if (copy_options.input_reply_to.is_valid()) { + string_builder << ", in reply to " << copy_options.input_reply_to; } if (copy_options.reply_markup != nullptr) { string_builder << ", with reply markup"; diff --git a/td/telegram/MessageDb.cpp b/td/telegram/MessageDb.cpp index e0d15cd7e701..09ff99c4a6a7 100644 --- a/td/telegram/MessageDb.cpp +++ b/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::DialogDbCreated) || version > current_db_version()) { + } else if (version < static_cast(DbVersion::CreateDialogDb) || version > current_db_version()) { TRY_STATUS(drop_message_db(db, version)); version = 0; } @@ -142,19 +142,19 @@ Status init_message_db(SqliteDb &db, int32 version) { version = current_db_version(); } - if (version < static_cast(DbVersion::MessageDbMediaIndex)) { + if (version < static_cast(DbVersion::AddMessageDbMediaIndex)) { TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN index_mask INT4")); TRY_STATUS(add_media_indices(0, MESSAGE_DB_INDEX_COUNT_OLD)); } - if (version < static_cast(DbVersion::MessageDb30MediaIndex)) { + if (version < static_cast(DbVersion::AddMessageDb30MediaIndex)) { TRY_STATUS(add_media_indices(MESSAGE_DB_INDEX_COUNT_OLD, MESSAGE_DB_INDEX_COUNT)); } - if (version < static_cast(DbVersion::MessageDbFts)) { + if (version < static_cast(DbVersion::AddMessageDbFts)) { TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN search_id INT8")); TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN text STRING")); TRY_STATUS(add_fts()); } - if (version < static_cast(DbVersion::MessagesCallIndex)) { + if (version < static_cast(DbVersion::AddMessagesCallIndex)) { TRY_STATUS(add_call_index()); } if (version < static_cast(DbVersion::AddNotificationsSupport)) { diff --git a/td/telegram/MessageEntity.h b/td/telegram/MessageEntity.h index a0d8a4b7b63b..ea20d5e815fa 100644 --- a/td/telegram/MessageEntity.h +++ b/td/telegram/MessageEntity.h @@ -135,9 +135,9 @@ struct FormattedTextHash { uint32 operator()(const FormattedText &formatted_text) const { auto hash = Hash()(formatted_text.text); for (auto &entity : formatted_text.entities) { - hash = hash * 2023654985u + Hash()(static_cast(entity.type)); - hash = hash * 2023654985u + Hash()(entity.length); - hash = hash * 2023654985u + Hash()(entity.offset); + hash = combine_hashes(hash, Hash()(static_cast(entity.type))); + hash = combine_hashes(hash, Hash()(entity.length)); + hash = combine_hashes(hash, Hash()(entity.offset)); } return hash; } diff --git a/td/telegram/MessageExtendedMedia.cpp b/td/telegram/MessageExtendedMedia.cpp index 322ead66b296..da98a4d8642c 100644 --- a/td/telegram/MessageExtendedMedia.cpp +++ b/td/telegram/MessageExtendedMedia.cpp @@ -12,6 +12,7 @@ #include "td/telegram/MessageContentType.h" #include "td/telegram/PhotoSize.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/VideosManager.h" #include "td/utils/algorithm.h" @@ -217,7 +218,7 @@ void MessageExtendedMedia::delete_thumbnail(Td *td) { int32 MessageExtendedMedia::get_duration(const Td *td) const { if (!has_media_timestamp()) { - return 0; + return -1; } return td->videos_manager_->get_video_duration(video_file_id_); } diff --git a/td/telegram/MessageInputReplyTo.cpp b/td/telegram/MessageInputReplyTo.cpp new file mode 100644 index 000000000000..1005e85a4845 --- /dev/null +++ b/td/telegram/MessageInputReplyTo.cpp @@ -0,0 +1,78 @@ +// +// 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/MessageInputReplyTo.h" + +#include "td/telegram/ContactsManager.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/Td.h" + +#include "td/utils/logging.h" + +namespace td { + +MessageInputReplyTo::MessageInputReplyTo(const td_api::object_ptr &reply_to_ptr) { + if (reply_to_ptr == 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_); + 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_)}; + break; + } + default: + UNREACHABLE(); + } +} + +telegram_api::object_ptr MessageInputReplyTo::get_input_reply_to( + Td *td, MessageId top_thread_message_id) const { + if (story_full_id_.is_valid()) { + auto dialog_id = story_full_id_.get_dialog_id(); + CHECK(dialog_id.get_type() == DialogType::User); + auto r_input_user = td->contacts_manager_->get_input_user(dialog_id.get_user_id()); + if (r_input_user.is_error()) { + LOG(ERROR) << "Failed to get input user for " << story_full_id_; + return nullptr; + } + return telegram_api::make_object(r_input_user.move_as_ok(), + story_full_id_.get_story_id().get()); + } + auto reply_to_message_id = message_id_; + if (reply_to_message_id == MessageId()) { + if (top_thread_message_id == MessageId()) { + return nullptr; + } + reply_to_message_id = top_thread_message_id; + } + CHECK(reply_to_message_id.is_server()); + int32 flags = 0; + if (top_thread_message_id != MessageId()) { + CHECK(top_thread_message_id.is_server()); + flags |= telegram_api::inputReplyToMessage::TOP_MSG_ID_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()); +} + +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.story_full_id_.is_valid()) { + return string_builder << input_reply_to.story_full_id_; + } + return string_builder << "nothing"; +} + +} // namespace td diff --git a/td/telegram/MessageInputReplyTo.h b/td/telegram/MessageInputReplyTo.h new file mode 100644 index 000000000000..441a1657108a --- /dev/null +++ b/td/telegram/MessageInputReplyTo.h @@ -0,0 +1,45 @@ +// +// 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/MessageId.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Td; + +struct MessageInputReplyTo { + MessageId message_id_; + // or + StoryFullId story_full_id_; + + bool is_valid() const { + return message_id_.is_valid() || story_full_id_.is_valid(); + } + + MessageInputReplyTo() = default; + + 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()); + } + + explicit MessageInputReplyTo(const td_api::object_ptr &reply_to_ptr); + + telegram_api::object_ptr get_input_reply_to(Td *td, + MessageId top_thread_message_id) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageInputReplyTo &input_reply_to); + +} // namespace td diff --git a/td/telegram/MessageReaction.cpp b/td/telegram/MessageReaction.cpp index a8a2d6db8ddc..a4eafe984a40 100644 --- a/td/telegram/MessageReaction.cpp +++ b/td/telegram/MessageReaction.cpp @@ -7,33 +7,25 @@ #include "td/telegram/MessageReaction.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ConfigManager.h" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Global.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" -#include "td/telegram/misc.h" -#include "td/telegram/OptionManager.h" #include "td/telegram/ServerMessageId.h" -#include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/actor/actor.h" +#include "td/actor/SleepActor.h" #include "td/utils/algorithm.h" -#include "td/utils/as.h" -#include "td/utils/base64.h" #include "td/utils/buffer.h" -#include "td/utils/crypto.h" -#include "td/utils/emoji.h" #include "td/utils/FlatHashSet.h" #include "td/utils/logging.h" #include "td/utils/Slice.h" -#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" -#include "td/utils/utf8.h" #include #include @@ -47,81 +39,6 @@ static size_t get_max_reaction_count() { max(static_cast(1), static_cast(G()->get_option_integer(option_key, is_premium ? 3 : 1)))); } -static int64 get_custom_emoji_id(const string &reaction) { - auto r_decoded = base64_decode(Slice(&reaction[1], reaction.size() - 1)); - CHECK(r_decoded.is_ok()); - CHECK(r_decoded.ok().size() == 8); - return as(r_decoded.ok().c_str()); -} - -static string get_custom_emoji_string(int64 custom_emoji_id) { - char s[8]; - as(&s) = custom_emoji_id; - return PSTRING() << '#' << base64_encode(Slice(s, 8)); -} - -telegram_api::object_ptr get_input_reaction(const string &reaction) { - if (reaction.empty()) { - return telegram_api::make_object(); - } - if (is_custom_reaction(reaction)) { - return telegram_api::make_object(get_custom_emoji_id(reaction)); - } - return telegram_api::make_object(reaction); -} - -string get_message_reaction_string(const telegram_api::object_ptr &reaction) { - if (reaction == nullptr) { - return string(); - } - switch (reaction->get_id()) { - case telegram_api::reactionEmpty::ID: - return string(); - case telegram_api::reactionEmoji::ID: { - const string &emoji = static_cast(reaction.get())->emoticon_; - if (is_custom_reaction(emoji)) { - return string(); - } - return emoji; - } - case telegram_api::reactionCustomEmoji::ID: - return get_custom_emoji_string( - static_cast(reaction.get())->document_id_); - default: - UNREACHABLE(); - return string(); - } -} - -td_api::object_ptr get_reaction_type_object(const string &reaction) { - CHECK(!reaction.empty()); - if (is_custom_reaction(reaction)) { - return td_api::make_object(get_custom_emoji_id(reaction)); - } - return td_api::make_object(reaction); -} - -string get_message_reaction_string(const td_api::object_ptr &type) { - if (type == nullptr) { - return string(); - } - switch (type->get_id()) { - case td_api::reactionTypeEmoji::ID: { - const string &emoji = static_cast(type.get())->emoji_; - if (!check_utf8(emoji) || is_custom_reaction(emoji)) { - return string(); - } - return emoji; - } - case td_api::reactionTypeCustomEmoji::ID: - return get_custom_emoji_string( - static_cast(type.get())->custom_emoji_id_); - default: - UNREACHABLE(); - return string(); - } -} - class GetMessagesReactionsQuery final : public Td::ResultHandler { DialogId dialog_id_; vector message_ids_; @@ -184,7 +101,7 @@ class SendReactionQuery final : public Td::ResultHandler { explicit SendReactionQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(FullMessageId full_message_id, vector reactions, bool is_big, bool add_to_recent) { + void send(FullMessageId full_message_id, vector reaction_types, bool is_big, bool add_to_recent) { dialog_id_ = full_message_id.get_dialog_id(); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); @@ -193,7 +110,7 @@ class SendReactionQuery final : public Td::ResultHandler { } int32 flags = 0; - if (!reactions.empty()) { + if (!reaction_types.empty()) { flags |= telegram_api::messages_sendReaction::REACTION_MASK; if (is_big) { @@ -206,9 +123,11 @@ class SendReactionQuery final : public Td::ResultHandler { } send_query(G()->net_query_creator().create( - telegram_api::messages_sendReaction(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), - full_message_id.get_message_id().get_server_message_id().get(), - transform(reactions, get_input_reaction)), + telegram_api::messages_sendReaction( + flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), + full_message_id.get_message_id().get_server_message_id().get(), + transform(reaction_types, + [](const ReactionType &reaction_type) { return reaction_type.get_input_reaction(); })), {{dialog_id_}, {full_message_id}})); } @@ -236,7 +155,7 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; MessageId message_id_; - string reaction_; + ReactionType reaction_type_; string offset_; public: @@ -244,10 +163,10 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(FullMessageId full_message_id, string reaction, string offset, int32 limit) { + void send(FullMessageId full_message_id, ReactionType reaction_type, string offset, int32 limit) { dialog_id_ = full_message_id.get_dialog_id(); message_id_ = full_message_id.get_message_id(); - reaction_ = std::move(reaction); + reaction_type_ = std::move(reaction_type); offset_ = std::move(offset); auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read); @@ -256,7 +175,7 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { } int32 flags = 0; - if (!reaction_.empty()) { + if (!reaction_type_.is_empty()) { flags |= telegram_api::messages_getMessageReactionsList::REACTION_MASK; } if (!offset_.empty()) { @@ -266,7 +185,7 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { send_query(G()->net_query_creator().create( telegram_api::messages_getMessageReactionsList(flags, std::move(input_peer), message_id_.get_server_message_id().get(), - get_input_reaction(reaction_), offset_, limit), + reaction_type_.get_input_reaction(), offset_, limit), {{full_message_id}})); } @@ -290,29 +209,30 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { } vector> reactions; - FlatHashMap> recent_reactions; + FlatHashMap, ReactionTypeHash> recent_reaction_types; for (const auto &reaction : ptr->reactions_) { DialogId dialog_id(reaction->peer_id_); - auto reaction_str = get_message_reaction_string(reaction->reaction_); - if (!dialog_id.is_valid() || (reaction_.empty() ? reaction_str.empty() : reaction_ != reaction_str)) { + auto reaction_type = ReactionType(reaction->reaction_); + if (!dialog_id.is_valid() || + (reaction_type_.is_empty() ? reaction_type.is_empty() : reaction_type_ != reaction_type)) { LOG(ERROR) << "Receive unexpected " << to_string(reaction); continue; } if (offset_.empty()) { - recent_reactions[reaction_str].push_back(dialog_id); + recent_reaction_types[reaction_type].push_back(dialog_id); } auto message_sender = get_min_message_sender_object(td_, dialog_id, "GetMessageReactionsListQuery"); if (message_sender != nullptr) { - reactions.push_back(td_api::make_object(get_reaction_type_object(reaction_str), + reactions.push_back(td_api::make_object(reaction_type.get_reaction_type_object(), std::move(message_sender), reaction->date_)); } } if (offset_.empty()) { - td_->messages_manager_->on_get_message_reaction_list({dialog_id_, message_id_}, reaction_, - std::move(recent_reactions), total_count); + td_->messages_manager_->on_get_message_reaction_list({dialog_id_, message_id_}, reaction_type_, + std::move(recent_reaction_types), total_count); } promise_.set_value( @@ -325,45 +245,6 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { } }; -class SetDefaultReactionQuery final : public Td::ResultHandler { - string reaction_; - - public: - void send(const string &reaction) { - reaction_ = reaction; - send_query( - G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(get_input_reaction(reaction)))); - } - - 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()); - } - - if (!result_ptr.ok()) { - return on_error(Status::Error(400, "Receive false")); - } - - auto default_reaction = td_->option_manager_->get_option_string("default_reaction", "-"); - if (default_reaction != reaction_) { - send_set_default_reaction_query(td_); - } else { - td_->option_manager_->set_option_empty("default_reaction_needs_sync"); - } - } - - void on_error(Status status) final { - if (G()->close_flag()) { - return; - } - - LOG(INFO) << "Receive error for SetDefaultReactionQuery: " << status; - td_->option_manager_->set_option_empty("default_reaction_needs_sync"); - send_closure(G()->config_manager(), &ConfigManager::request_config, false); - } -}; - class ReportReactionQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -402,7 +283,23 @@ class ReportReactionQuery final : public Td::ResultHandler { } }; +MessageReaction::MessageReaction(ReactionType reaction_type, int32 choose_count, bool is_chosen, + DialogId my_recent_chooser_dialog_id, vector &&recent_chooser_dialog_ids, + vector> &&recent_chooser_min_channels) + : reaction_type_(std::move(reaction_type)) + , choose_count_(choose_count) + , is_chosen_(is_chosen) + , my_recent_chooser_dialog_id_(my_recent_chooser_dialog_id) + , recent_chooser_dialog_ids_(std::move(recent_chooser_dialog_ids)) + , recent_chooser_min_channels_(std::move(recent_chooser_min_channels)) { + if (my_recent_chooser_dialog_id_.is_valid()) { + CHECK(td::contains(recent_chooser_dialog_ids_, my_recent_chooser_dialog_id_)); + } +} + void MessageReaction::add_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"; @@ -410,8 +307,24 @@ void MessageReaction::add_recent_chooser_dialog_id(DialogId dialog_id) { } } -bool MessageReaction::remove_recent_chooser_dialog_id(DialogId dialog_id) { - return td::remove(recent_chooser_dialog_ids_, dialog_id); +bool MessageReaction::remove_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); + my_recent_chooser_dialog_id_ = DialogId(); + return true; + } + return false; +} + +void MessageReaction::update_from(const MessageReaction &old_reaction) { + CHECK(old_reaction.is_chosen()); + is_chosen_ = true; + + auto my_dialog_id = old_reaction.get_my_recent_chooser_dialog_id(); + if (my_dialog_id.is_valid() && td::contains(recent_chooser_dialog_ids_, my_dialog_id)) { + my_recent_chooser_dialog_id_ = my_dialog_id; + } } void MessageReaction::update_recent_chooser_dialog_ids(const MessageReaction &old_reaction) { @@ -419,33 +332,49 @@ void MessageReaction::update_recent_chooser_dialog_ids(const MessageReaction &ol return; } CHECK(is_chosen_ && old_reaction.is_chosen_); - CHECK(reaction_ == old_reaction.reaction_); + CHECK(reaction_type_ == old_reaction.reaction_type_); CHECK(old_reaction.recent_chooser_dialog_ids_.size() == MAX_RECENT_CHOOSERS + 1); for (size_t i = 0; i < MAX_RECENT_CHOOSERS; i++) { if (recent_chooser_dialog_ids_[i] != old_reaction.recent_chooser_dialog_ids_[i]) { return; } } + my_recent_chooser_dialog_id_ = old_reaction.my_recent_chooser_dialog_id_; recent_chooser_dialog_ids_ = old_reaction.recent_chooser_dialog_ids_; recent_chooser_min_channels_ = old_reaction.recent_chooser_min_channels_; } -void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers) { - if (is_chosen_ == is_chosen) { - return; +void MessageReaction::set_as_chosen(DialogId my_dialog_id, bool have_recent_choosers) { + CHECK(!is_chosen_); + + is_chosen_ = true; + choose_count_++; + if (have_recent_choosers) { + remove_recent_chooser_dialog_id(); + add_recent_chooser_dialog_id(my_dialog_id); } +} - is_chosen_ = is_chosen; +void MessageReaction::unset_as_chosen() { + CHECK(is_chosen_); - if (chooser_dialog_id.is_valid()) { - choose_count_ += is_chosen_ ? 1 : -1; - if (have_recent_choosers) { - remove_recent_chooser_dialog_id(chooser_dialog_id); - if (is_chosen_) { - add_recent_chooser_dialog_id(chooser_dialog_id); - } + is_chosen_ = false; + choose_count_--; + remove_recent_chooser_dialog_id(); +} + +void MessageReaction::set_my_recent_chooser_dialog_id(DialogId my_dialog_id) { + if (!my_recent_chooser_dialog_id_.is_valid() || my_recent_chooser_dialog_id_ == my_dialog_id) { + return; + } + td::remove(recent_chooser_dialog_ids_, my_dialog_id); + for (auto &dialog_id : recent_chooser_dialog_ids_) { + if (dialog_id == my_recent_chooser_dialog_id_) { + dialog_id = my_dialog_id; } } + CHECK(td::contains(recent_chooser_dialog_ids_, my_dialog_id)); + my_recent_chooser_dialog_id_ = my_dialog_id; } td_api::object_ptr MessageReaction::get_message_reaction_object(Td *td, UserId my_user_id, @@ -478,19 +407,23 @@ td_api::object_ptr MessageReaction::get_message_reactio } } } - return td_api::make_object(get_reaction_type_object(reaction_), choose_count_, is_chosen_, - std::move(recent_choosers)); + return td_api::make_object(reaction_type_.get_reaction_type_object(), choose_count_, + is_chosen_, std::move(recent_choosers)); } bool operator==(const MessageReaction &lhs, const MessageReaction &rhs) { - return lhs.reaction_ == rhs.reaction_ && lhs.choose_count_ == rhs.choose_count_ && lhs.is_chosen_ == rhs.is_chosen_ && + return lhs.reaction_type_ == rhs.reaction_type_ && lhs.choose_count_ == rhs.choose_count_ && + lhs.is_chosen_ == rhs.is_chosen_ && lhs.my_recent_chooser_dialog_id_ == rhs.my_recent_chooser_dialog_id_ && lhs.recent_chooser_dialog_ids_ == rhs.recent_chooser_dialog_ids_; } StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction) { - string_builder << '[' << reaction.reaction_ << (reaction.is_chosen_ ? " X " : " x ") << reaction.choose_count_; + string_builder << '[' << reaction.reaction_type_ << (reaction.is_chosen_ ? " X " : " x ") << reaction.choose_count_; if (!reaction.recent_chooser_dialog_ids_.empty()) { string_builder << " by " << reaction.recent_chooser_dialog_ids_; + if (reaction.my_recent_chooser_dialog_id_.is_valid()) { + string_builder << " and my " << reaction.my_recent_chooser_dialog_id_; + } } return string_builder << ']'; } @@ -500,16 +433,17 @@ td_api::object_ptr UnreadMessageReaction::get_unread_rea if (sender_id == nullptr) { return nullptr; } - return td_api::make_object(get_reaction_type_object(reaction_), std::move(sender_id), + return td_api::make_object(reaction_type_.get_reaction_type_object(), std::move(sender_id), is_big_); } bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) { - return lhs.reaction_ == rhs.reaction_ && lhs.sender_dialog_id_ == rhs.sender_dialog_id_ && lhs.is_big_ == rhs.is_big_; + return lhs.reaction_type_ == rhs.reaction_type_ && lhs.sender_dialog_id_ == rhs.sender_dialog_id_ && + lhs.is_big_ == rhs.is_big_; } StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &unread_reaction) { - return string_builder << '[' << unread_reaction.reaction_ << (unread_reaction.is_big_ ? " BY " : " by ") + return string_builder << '[' << unread_reaction.reaction_type_ << (unread_reaction.is_big_ ? " BY " : " by ") << unread_reaction.sender_dialog_id_ << ']'; } @@ -523,34 +457,49 @@ unique_ptr MessageReactions::get_message_reactions( result->can_get_added_reactions_ = reactions->can_see_list_; result->is_min_ = reactions->min_; - FlatHashSet reaction_strings; - vector> chosen_reaction_order; + DialogId my_dialog_id; + for (auto &peer_reaction : reactions->recent_reactions_) { + if (peer_reaction->my_) { + DialogId dialog_id(peer_reaction->peer_id_); + if (!dialog_id.is_valid()) { + continue; + } + if (my_dialog_id.is_valid() && dialog_id != my_dialog_id) { + LOG(ERROR) << "Receive my reactions with " << dialog_id << " and " << my_dialog_id; + } + my_dialog_id = dialog_id; + } + } + + FlatHashSet reaction_types; + vector> chosen_reaction_order; for (auto &reaction_count : reactions->results_) { - auto reaction_str = get_message_reaction_string(reaction_count->reaction_); + auto reaction_type = ReactionType(reaction_count->reaction_); if (reaction_count->count_ <= 0 || reaction_count->count_ >= MessageReaction::MAX_CHOOSE_COUNT || - reaction_str.empty()) { - LOG(ERROR) << "Receive reaction " << reaction_str << " with invalid count " << reaction_count->count_; + reaction_type.is_empty()) { + LOG(ERROR) << "Receive " << reaction_type << " with invalid count " << reaction_count->count_; continue; } - if (!reaction_strings.insert(reaction_str).second) { - LOG(ERROR) << "Receive duplicate reaction " << reaction_str; + if (!reaction_types.insert(reaction_type).second) { + LOG(ERROR) << "Receive duplicate " << reaction_type; continue; } FlatHashSet recent_choosers; + DialogId my_recent_chooser_dialog_id; vector recent_chooser_dialog_ids; vector> recent_chooser_min_channels; for (auto &peer_reaction : reactions->recent_reactions_) { - auto peer_reaction_str = get_message_reaction_string(peer_reaction->reaction_); - if (peer_reaction_str == reaction_str) { + auto peer_reaction_type = ReactionType(peer_reaction->reaction_); + if (peer_reaction_type == reaction_type) { DialogId dialog_id(peer_reaction->peer_id_); if (!dialog_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser for reaction " << reaction_str; + LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser for " << reaction_type; continue; } if (!recent_choosers.insert(dialog_id).second) { - LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser for reaction " << reaction_str; + LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser for " << reaction_type; continue; } if (!td->messages_manager_->have_dialog_info(dialog_id)) { @@ -576,8 +525,11 @@ unique_ptr MessageReactions::get_message_reactions( } recent_chooser_dialog_ids.push_back(dialog_id); + if (dialog_id == my_dialog_id) { + my_recent_chooser_dialog_id = dialog_id; + } if (peer_reaction->unread_) { - result->unread_reactions_.emplace_back(std::move(peer_reaction_str), dialog_id, peer_reaction->big_); + result->unread_reactions_.emplace_back(std::move(peer_reaction_type), dialog_id, peer_reaction->big_); } if (recent_chooser_dialog_ids.size() == MessageReaction::MAX_RECENT_CHOOSERS) { break; @@ -587,31 +539,32 @@ unique_ptr MessageReactions::get_message_reactions( bool is_chosen = (reaction_count->flags_ & telegram_api::reactionCount::CHOSEN_ORDER_MASK) != 0; if (is_chosen) { - chosen_reaction_order.emplace_back(reaction_count->chosen_order_, reaction_str); + chosen_reaction_order.emplace_back(reaction_count->chosen_order_, reaction_type); } - result->reactions_.push_back({std::move(reaction_str), reaction_count->count_, is_chosen, - std::move(recent_chooser_dialog_ids), std::move(recent_chooser_min_channels)}); + result->reactions_.push_back({std::move(reaction_type), reaction_count->count_, is_chosen, + my_recent_chooser_dialog_id, std::move(recent_chooser_dialog_ids), + std::move(recent_chooser_min_channels)}); } if (chosen_reaction_order.size() > 1) { std::sort(chosen_reaction_order.begin(), chosen_reaction_order.end()); result->chosen_reaction_order_ = - transform(chosen_reaction_order, [](const std::pair &order) { return order.second; }); + transform(chosen_reaction_order, [](const std::pair &order) { return order.second; }); } return result; } -MessageReaction *MessageReactions::get_reaction(const string &reaction) { +MessageReaction *MessageReactions::get_reaction(const ReactionType &reaction_type) { for (auto &added_reaction : reactions_) { - if (added_reaction.get_reaction() == reaction) { + if (added_reaction.get_reaction_type() == reaction_type) { return &added_reaction; } } return nullptr; } -const MessageReaction *MessageReactions::get_reaction(const string &reaction) const { +const MessageReaction *MessageReactions::get_reaction(const ReactionType &reaction_type) const { for (auto &added_reaction : reactions_) { - if (added_reaction.get_reaction() == reaction) { + if (added_reaction.get_reaction_type() == reaction_type) { return &added_reaction; } } @@ -620,23 +573,28 @@ const MessageReaction *MessageReactions::get_reaction(const string &reaction) co void MessageReactions::update_from(const MessageReactions &old_reactions) { if (is_min_ && !old_reactions.is_min_) { - // chosen reaction was known, keep it + // chosen reactions were known, keep them is_min_ = false; + chosen_reaction_order_ = old_reactions.chosen_reaction_order_; for (const auto &old_reaction : old_reactions.reactions_) { if (old_reaction.is_chosen()) { - auto *reaction = get_reaction(old_reaction.get_reaction()); + auto *reaction = get_reaction(old_reaction.get_reaction_type()); if (reaction != nullptr) { - reaction->set_is_chosen(true, DialogId(), false); + reaction->update_from(old_reaction); } + } else { + td::remove(chosen_reaction_order_, old_reaction.get_reaction_type()); } } unread_reactions_ = old_reactions.unread_reactions_; - chosen_reaction_order_ = old_reactions.chosen_reaction_order_; + if (chosen_reaction_order_.size() == 1) { + reset_to_empty(chosen_reaction_order_); + } } for (const auto &old_reaction : old_reactions.reactions_) { if (old_reaction.is_chosen() && old_reaction.get_recent_chooser_dialog_ids().size() == MessageReaction::MAX_RECENT_CHOOSERS + 1) { - auto *reaction = get_reaction(old_reaction.get_reaction()); + auto *reaction = get_reaction(old_reaction.get_reaction_type()); if (reaction != nullptr && reaction->is_chosen()) { reaction->update_recent_chooser_dialog_ids(old_reaction); } @@ -644,30 +602,33 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { } } -bool MessageReactions::add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id, +bool MessageReactions::add_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, bool have_recent_choosers) { - vector new_chosen_reaction_order = get_chosen_reactions(); + vector new_chosen_reaction_order = get_chosen_reaction_types(); - auto added_reaction = get_reaction(reaction); + auto added_reaction = get_reaction(reaction_type); if (added_reaction == nullptr) { vector recent_chooser_dialog_ids; + DialogId my_recent_chooser_dialog_id; if (have_recent_choosers) { - recent_chooser_dialog_ids.push_back(chooser_dialog_id); + recent_chooser_dialog_ids.push_back(my_dialog_id); + my_recent_chooser_dialog_id = my_dialog_id; } - reactions_.push_back({reaction, 1, true, std::move(recent_chooser_dialog_ids), Auto()}); - new_chosen_reaction_order.emplace_back(reaction); + reactions_.push_back( + {reaction_type, 1, true, my_recent_chooser_dialog_id, std::move(recent_chooser_dialog_ids), Auto()}); + new_chosen_reaction_order.emplace_back(reaction_type); } else if (!added_reaction->is_chosen()) { - added_reaction->set_is_chosen(true, chooser_dialog_id, have_recent_choosers); - new_chosen_reaction_order.emplace_back(reaction); + added_reaction->set_as_chosen(my_dialog_id, have_recent_choosers); + new_chosen_reaction_order.emplace_back(reaction_type); } else if (!is_big) { return false; } auto max_reaction_count = get_max_reaction_count(); while (new_chosen_reaction_order.size() > max_reaction_count) { - auto index = new_chosen_reaction_order[0] == reaction ? 1 : 0; + 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], chooser_dialog_id, have_recent_choosers); + bool is_removed = do_remove_reaction(new_chosen_reaction_order[index]); CHECK(is_removed); new_chosen_reaction_order.erase(new_chosen_reaction_order.begin() + index); } @@ -676,18 +637,24 @@ bool MessageReactions::add_reaction(const string &reaction, bool is_big, DialogI new_chosen_reaction_order.clear(); } chosen_reaction_order_ = std::move(new_chosen_reaction_order); + + for (auto &message_reaction : reactions_) { + message_reaction.set_my_recent_chooser_dialog_id(my_dialog_id); + } + return true; } -bool MessageReactions::remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers) { - if (do_remove_reaction(reaction, chooser_dialog_id, have_recent_choosers)) { +bool MessageReactions::remove_reaction(const ReactionType &reaction_type, DialogId my_dialog_id) { + if (do_remove_reaction(reaction_type)) { if (!chosen_reaction_order_.empty()) { - bool is_removed = td::remove(chosen_reaction_order_, reaction); + bool is_removed = td::remove(chosen_reaction_order_, reaction_type); CHECK(is_removed); + // 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], chooser_dialog_id, have_recent_choosers); + is_removed = do_remove_reaction(chosen_reaction_order_[0]); CHECK(is_removed); chosen_reaction_order_.erase(chosen_reaction_order_.begin()); } @@ -697,18 +664,21 @@ bool MessageReactions::remove_reaction(const string &reaction, DialogId chooser_ } } + for (auto &message_reaction : reactions_) { + message_reaction.set_my_recent_chooser_dialog_id(my_dialog_id); + } + return true; } return false; } -bool MessageReactions::do_remove_reaction(const string &reaction, DialogId chooser_dialog_id, - bool have_recent_choosers) { +bool MessageReactions::do_remove_reaction(const ReactionType &reaction_type) { for (auto it = reactions_.begin(); it != reactions_.end(); ++it) { auto &message_reaction = *it; - if (message_reaction.get_reaction() == reaction) { + if (message_reaction.get_reaction_type() == reaction_type) { if (message_reaction.is_chosen()) { - message_reaction.set_is_chosen(false, chooser_dialog_id, have_recent_choosers); + message_reaction.unset_as_chosen(); if (message_reaction.is_empty()) { it = reactions_.erase(it); } @@ -720,58 +690,69 @@ bool MessageReactions::do_remove_reaction(const string &reaction, DialogId choos return false; } -void MessageReactions::sort_reactions(const FlatHashMap &active_reaction_pos) { +void MessageReactions::sort_reactions(const FlatHashMap &active_reaction_pos) { std::sort(reactions_.begin(), reactions_.end(), [&active_reaction_pos](const MessageReaction &lhs, const MessageReaction &rhs) { if (lhs.get_choose_count() != rhs.get_choose_count()) { return lhs.get_choose_count() > rhs.get_choose_count(); } - auto lhs_it = active_reaction_pos.find(lhs.get_reaction()); + auto lhs_it = active_reaction_pos.find(lhs.get_reaction_type()); auto lhs_pos = lhs_it != active_reaction_pos.end() ? lhs_it->second : active_reaction_pos.size(); - auto rhs_it = active_reaction_pos.find(rhs.get_reaction()); + auto rhs_it = active_reaction_pos.find(rhs.get_reaction_type()); auto rhs_pos = rhs_it != active_reaction_pos.end() ? rhs_it->second : active_reaction_pos.size(); if (lhs_pos != rhs_pos) { return lhs_pos < rhs_pos; } - return lhs.get_reaction() < rhs.get_reaction(); + return lhs.get_reaction_type() < rhs.get_reaction_type(); }); } -void MessageReactions::fix_chosen_reaction(DialogId my_dialog_id) { - bool need_fix = false; +void MessageReactions::fix_chosen_reaction() { + DialogId my_dialog_id; for (auto &reaction : reactions_) { - if (!reaction.is_chosen() && reaction.remove_recent_chooser_dialog_id(my_dialog_id)) { + 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; - need_fix = true; + reaction.remove_recent_chooser_dialog_id(); } } - if (!need_fix) { + if (!my_dialog_id.is_valid()) { return; } for (auto &reaction : reactions_) { - if (reaction.is_chosen() && !td::contains(reaction.get_recent_chooser_dialog_ids(), my_dialog_id)) { + if (reaction.is_chosen() && !reaction.get_my_recent_chooser_dialog_id().is_valid()) { reaction.add_recent_chooser_dialog_id(my_dialog_id); } } } -vector MessageReactions::get_chosen_reactions() const { +void MessageReactions::fix_my_recent_chooser_dialog_id(DialogId my_dialog_id) { + for (auto &reaction : reactions_) { + if (reaction.is_chosen() && !reaction.get_my_recent_chooser_dialog_id().is_valid() && + td::contains(reaction.get_recent_chooser_dialog_ids(), my_dialog_id)) { + reaction.my_recent_chooser_dialog_id_ = my_dialog_id; + } + } +} + +vector MessageReactions::get_chosen_reaction_types() const { if (!chosen_reaction_order_.empty()) { return chosen_reaction_order_; } - vector reaction_order; + vector reaction_order; for (auto &reaction : reactions_) { if (reaction.is_chosen()) { - reaction_order.push_back(reaction.get_reaction()); + reaction_order.push_back(reaction.get_reaction_type()); } } return reaction_order; } -bool MessageReactions::are_consistent_with_list(const string &reaction, FlatHashMap> reactions, - int32 total_count) const { +bool MessageReactions::are_consistent_with_list( + const ReactionType &reaction_type, FlatHashMap, ReactionTypeHash> reaction_types, + int32 total_count) const { auto are_consistent = [](const vector &lhs, const vector &rhs) { size_t i = 0; size_t max_i = td::min(lhs.size(), rhs.size()); @@ -781,27 +762,27 @@ bool MessageReactions::are_consistent_with_list(const string &reaction, FlatHash return i == max_i; }; - if (reaction.empty()) { + if (reaction_type.is_empty()) { // received list and total_count for all reactions int32 old_total_count = 0; for (const auto &message_reaction : reactions_) { - CHECK(!message_reaction.get_reaction().empty()); - if (!are_consistent(reactions[message_reaction.get_reaction()], + CHECK(!message_reaction.get_reaction_type().is_empty()); + if (!are_consistent(reaction_types[message_reaction.get_reaction_type()], message_reaction.get_recent_chooser_dialog_ids())) { return false; } old_total_count += message_reaction.get_choose_count(); - reactions.erase(message_reaction.get_reaction()); + reaction_types.erase(message_reaction.get_reaction_type()); } - return old_total_count == total_count && reactions.empty(); + return old_total_count == total_count && reaction_types.empty(); } // received list and total_count for a single reaction - const auto *message_reaction = get_reaction(reaction); + const auto *message_reaction = get_reaction(reaction_type); if (message_reaction == nullptr) { - return reactions.count(reaction) == 0 && total_count == 0; + return reaction_types.count(reaction_type) == 0 && total_count == 0; } else { - return are_consistent(reactions[reaction], message_reaction->get_recent_chooser_dialog_ids()) && + return are_consistent(reaction_types[reaction_type], message_reaction->get_recent_chooser_dialog_ids()) && message_reaction->get_choose_count() == total_count; } } @@ -870,17 +851,15 @@ StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &active_reaction_pos) { - return !reaction.empty() && (is_custom_reaction(reaction) || active_reaction_pos.count(reaction) > 0); -} - void reload_message_reactions(Td *td, DialogId dialog_id, vector &&message_ids) { if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) || dialog_id.get_type() == DialogType::SecretChat || message_ids.empty()) { + create_actor( + "RetryReloadMessageReactionsActor", 0.2, + PromiseCreator::lambda([actor_id = G()->messages_manager(), dialog_id](Result result) mutable { + send_closure(actor_id, &MessagesManager::try_reload_message_reactions, dialog_id, true); + })) + .release(); return; } @@ -892,14 +871,14 @@ void reload_message_reactions(Td *td, DialogId dialog_id, vector &&me td->create_handler()->send(dialog_id, std::move(message_ids)); } -void send_message_reaction(Td *td, FullMessageId full_message_id, vector reactions, bool is_big, +void send_message_reaction(Td *td, FullMessageId full_message_id, vector reaction_types, bool is_big, bool add_to_recent, Promise &&promise) { td->create_handler(std::move(promise)) - ->send(full_message_id, std::move(reactions), is_big, add_to_recent); + ->send(full_message_id, std::move(reaction_types), is_big, add_to_recent); } -void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, - Promise> &&promise) { +void get_message_added_reactions(Td *td, FullMessageId full_message_id, ReactionType reaction_type, string offset, + int32 limit, Promise> &&promise) { if (!td->messages_manager_->have_message_force(full_message_id, "get_message_added_reactions")) { return promise.set_error(Status::Error(400, "Message not found")); } @@ -919,36 +898,7 @@ void get_message_added_reactions(Td *td, FullMessageId full_message_id, string r } td->create_handler(std::move(promise)) - ->send(full_message_id, std::move(reaction), std::move(offset), limit); -} - -void set_default_reaction(Td *td, string reaction, Promise &&promise) { - if (reaction.empty()) { - return promise.set_error(Status::Error(400, "Default reaction must be non-empty")); - } - if (!is_custom_reaction(reaction) && !td->stickers_manager_->is_active_reaction(reaction)) { - return promise.set_error(Status::Error(400, "Can't set incative reaction as default")); - } - - if (td->option_manager_->get_option_string("default_reaction", "-") != reaction) { - td->option_manager_->set_option_string("default_reaction", reaction); - if (!td->option_manager_->get_option_boolean("default_reaction_needs_sync")) { - td->option_manager_->set_option_boolean("default_reaction_needs_sync", true); - send_set_default_reaction_query(td); - } - } - promise.set_value(Unit()); -} - -void send_set_default_reaction_query(Td *td) { - td->create_handler()->send(td->option_manager_->get_option_string("default_reaction")); -} - -td_api::object_ptr get_update_default_reaction_type(const string &default_reaction) { - if (default_reaction.empty()) { - return nullptr; - } - return td_api::make_object(get_reaction_type_object(default_reaction)); + ->send(full_message_id, std::move(reaction_type), std::move(offset), limit); } void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id, @@ -982,38 +932,4 @@ void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId ch td->create_handler(std::move(promise))->send(dialog_id, message_id, chooser_dialog_id); } -vector get_recent_reactions(Td *td) { - return td->stickers_manager_->get_recent_reactions(); -} - -vector get_top_reactions(Td *td) { - return td->stickers_manager_->get_top_reactions(); -} - -void add_recent_reaction(Td *td, const string &reaction) { - td->stickers_manager_->add_recent_reaction(reaction); -} - -int64 get_reactions_hash(const vector &reactions) { - vector numbers; - for (auto &reaction : reactions) { - if (is_custom_reaction(reaction)) { - auto custom_emoji_id = static_cast(get_custom_emoji_id(reaction)); - numbers.push_back(custom_emoji_id >> 32); - numbers.push_back(custom_emoji_id & 0xFFFFFFFF); - } else { - auto emoji = remove_emoji_selectors(reaction); - unsigned char hash[16]; - md5(emoji, {hash, sizeof(hash)}); - auto get = [hash](int num) { - return static_cast(hash[num]); - }; - - numbers.push_back(0); - numbers.push_back(static_cast((get(0) << 24) + (get(1) << 16) + (get(2) << 8) + get(3))); - } - } - return get_vector_hash(numbers); -} - } // namespace td diff --git a/td/telegram/MessageReaction.h b/td/telegram/MessageReaction.h index caad4b65bfd0..d39d540b10f1 100644 --- a/td/telegram/MessageReaction.h +++ b/td/telegram/MessageReaction.h @@ -11,6 +11,7 @@ #include "td/telegram/FullMessageId.h" #include "td/telegram/MessageId.h" #include "td/telegram/MinChannel.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -33,9 +34,10 @@ class MessageReaction { static constexpr size_t MAX_RECENT_CHOOSERS = 3; - string reaction_; + ReactionType reaction_type_; int32 choose_count_ = 0; bool is_chosen_ = false; + DialogId my_recent_chooser_dialog_id_; vector recent_chooser_dialog_ids_; vector> recent_chooser_min_channels_; @@ -45,14 +47,9 @@ class MessageReaction { friend struct MessageReactions; - MessageReaction(string reaction, int32 choose_count, bool is_chosen, vector &&recent_chooser_dialog_ids, - vector> &&recent_chooser_min_channels) - : reaction_(std::move(reaction)) - , choose_count_(choose_count) - , is_chosen_(is_chosen) - , recent_chooser_dialog_ids_(std::move(recent_chooser_dialog_ids)) - , recent_chooser_min_channels_(std::move(recent_chooser_min_channels)) { - } + MessageReaction(ReactionType reaction_type, int32 choose_count, bool is_chosen, DialogId my_recent_chooser_dialog_id, + vector &&recent_chooser_dialog_ids, + vector> &&recent_chooser_min_channels); bool is_empty() const { return choose_count_ <= 0; @@ -62,11 +59,15 @@ class MessageReaction { return is_chosen_; } - void set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers); + void set_as_chosen(DialogId my_dialog_id, bool have_recent_choosers); + + void unset_as_chosen(); void add_recent_chooser_dialog_id(DialogId dialog_id); - bool remove_recent_chooser_dialog_id(DialogId dialog_id); + bool remove_recent_chooser_dialog_id(); + + void update_from(const MessageReaction &old_reaction); void update_recent_chooser_dialog_ids(const MessageReaction &old_reaction); @@ -74,6 +75,12 @@ class MessageReaction { return choose_count_; } + void set_my_recent_chooser_dialog_id(DialogId my_dialog_id); + + DialogId get_my_recent_chooser_dialog_id() const { + return my_recent_chooser_dialog_id_; + } + const vector &get_recent_chooser_dialog_ids() const { return recent_chooser_dialog_ids_; } @@ -88,8 +95,8 @@ class MessageReaction { public: MessageReaction() = default; - const string &get_reaction() const { - return reaction_; + const ReactionType &get_reaction_type() const { + return reaction_type_; } template @@ -108,7 +115,7 @@ inline bool operator!=(const MessageReaction &lhs, const MessageReaction &rhs) { StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction); class UnreadMessageReaction { - string reaction_; + ReactionType reaction_type_; DialogId sender_dialog_id_; bool is_big_ = false; @@ -119,8 +126,8 @@ class UnreadMessageReaction { public: UnreadMessageReaction() = default; - UnreadMessageReaction(string reaction, DialogId sender_dialog_id, bool is_big) - : reaction_(std::move(reaction)), sender_dialog_id_(sender_dialog_id), is_big_(is_big) { + UnreadMessageReaction(ReactionType reaction_type, DialogId sender_dialog_id, bool is_big) + : reaction_type_(std::move(reaction_type)), sender_dialog_id_(sender_dialog_id), is_big_(is_big) { } td_api::object_ptr get_unread_reaction_object(Td *td) const; @@ -143,7 +150,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReac struct MessageReactions { vector reactions_; vector unread_reactions_; - vector chosen_reaction_order_; + vector chosen_reaction_order_; bool is_min_ = false; bool need_polling_ = true; bool can_get_added_reactions_ = false; @@ -154,23 +161,26 @@ struct MessageReactions { tl_object_ptr &&reactions, bool is_bot); - MessageReaction *get_reaction(const string &reaction); + MessageReaction *get_reaction(const ReactionType &reaction_type); - const MessageReaction *get_reaction(const string &reaction) const; + const MessageReaction *get_reaction(const ReactionType &reaction_type) const; void update_from(const MessageReactions &old_reactions); - bool add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id, bool have_recent_choosers); + bool add_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_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers); + void sort_reactions(const FlatHashMap &active_reaction_pos); - void sort_reactions(const FlatHashMap &active_reaction_pos); + void fix_chosen_reaction(); - void fix_chosen_reaction(DialogId my_dialog_id); + void fix_my_recent_chooser_dialog_id(DialogId my_dialog_id); - vector get_chosen_reactions() const; + vector get_chosen_reaction_types() const; - bool are_consistent_with_list(const string &reaction, FlatHashMap> reactions, + bool are_consistent_with_list(const ReactionType &reaction_type, + FlatHashMap, ReactionTypeHash> reaction_types, int32 total_count) const; vector> get_message_reactions_object(Td *td, UserId my_user_id, @@ -193,48 +203,22 @@ struct MessageReactions { void parse(ParserT &parser); private: - bool do_remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers); + bool do_remove_reaction(const ReactionType &reaction_type); }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions); StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &reactions); -telegram_api::object_ptr get_input_reaction(const string &reaction); - -td_api::object_ptr get_reaction_type_object(const string &reaction); - -string get_message_reaction_string(const telegram_api::object_ptr &reaction); - -string get_message_reaction_string(const td_api::object_ptr &type); - -bool is_custom_reaction(const string &reaction); - -bool is_active_reaction(const string &reaction, const FlatHashMap &active_reaction_pos); - void reload_message_reactions(Td *td, DialogId dialog_id, vector &&message_ids); -void send_message_reaction(Td *td, FullMessageId full_message_id, vector reactions, bool is_big, +void send_message_reaction(Td *td, FullMessageId full_message_id, vector reaction_types, bool is_big, bool add_to_recent, Promise &&promise); -void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit, - Promise> &&promise); - -void set_default_reaction(Td *td, string reaction, Promise &&promise); - -void send_set_default_reaction_query(Td *td); - -td_api::object_ptr get_update_default_reaction_type(const string &default_reaction); +void get_message_added_reactions(Td *td, FullMessageId full_message_id, ReactionType reaction_type, string offset, + int32 limit, Promise> &&promise); void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id, Promise &&promise); -vector get_recent_reactions(Td *td); - -vector get_top_reactions(Td *td); - -void add_recent_reaction(Td *td, const string &reaction); - -int64 get_reactions_hash(const vector &reactions); - } // namespace td diff --git a/td/telegram/MessageReaction.hpp b/td/telegram/MessageReaction.hpp index 6fda93ccb65a..295343fc1c8b 100644 --- a/td/telegram/MessageReaction.hpp +++ b/td/telegram/MessageReaction.hpp @@ -7,7 +7,10 @@ #pragma once #include "td/telegram/MessageReaction.h" +#include "td/telegram/MinChannel.hpp" +#include "td/telegram/ReactionType.hpp" +#include "td/utils/algorithm.h" #include "td/utils/common.h" #include "td/utils/tl_helpers.h" @@ -18,12 +21,14 @@ void MessageReaction::store(StorerT &storer) const { CHECK(!is_empty()); bool has_recent_chooser_dialog_ids = !recent_chooser_dialog_ids_.empty(); bool has_recent_chooser_min_channels = !recent_chooser_min_channels_.empty(); + bool has_my_recent_chooser_dialog_id = my_recent_chooser_dialog_id_.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_chosen_); STORE_FLAG(has_recent_chooser_dialog_ids); STORE_FLAG(has_recent_chooser_min_channels); + STORE_FLAG(has_my_recent_chooser_dialog_id); END_STORE_FLAGS(); - td::store(reaction_, storer); + td::store(reaction_type_, storer); td::store(choose_count_, storer); if (has_recent_chooser_dialog_ids) { td::store(recent_chooser_dialog_ids_, storer); @@ -31,18 +36,23 @@ void MessageReaction::store(StorerT &storer) const { if (has_recent_chooser_min_channels) { td::store(recent_chooser_min_channels_, storer); } + if (has_my_recent_chooser_dialog_id) { + td::store(my_recent_chooser_dialog_id_, storer); + } } template void MessageReaction::parse(ParserT &parser) { bool has_recent_chooser_dialog_ids; bool has_recent_chooser_min_channels; + bool has_my_recent_chooser_dialog_id; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_chosen_); PARSE_FLAG(has_recent_chooser_dialog_ids); PARSE_FLAG(has_recent_chooser_min_channels); + PARSE_FLAG(has_my_recent_chooser_dialog_id); END_PARSE_FLAGS(); - td::parse(reaction_, parser); + td::parse(reaction_type_, parser); td::parse(choose_count_, parser); if (has_recent_chooser_dialog_ids) { td::parse(recent_chooser_dialog_ids_, parser); @@ -50,8 +60,13 @@ void MessageReaction::parse(ParserT &parser) { if (has_recent_chooser_min_channels) { td::parse(recent_chooser_min_channels_, parser); } + if (has_my_recent_chooser_dialog_id) { + td::parse(my_recent_chooser_dialog_id_, parser); + CHECK(my_recent_chooser_dialog_id_.is_valid()); + CHECK(td::contains(recent_chooser_dialog_ids_, my_recent_chooser_dialog_id_)); + } CHECK(!is_empty()); - CHECK(!reaction_.empty()); + CHECK(!reaction_type_.is_empty()); } template @@ -59,7 +74,7 @@ void UnreadMessageReaction::store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_big_); END_STORE_FLAGS(); - td::store(reaction_, storer); + td::store(reaction_type_, storer); td::store(sender_dialog_id_, storer); } @@ -68,9 +83,9 @@ void UnreadMessageReaction::parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_big_); END_PARSE_FLAGS(); - td::parse(reaction_, parser); + td::parse(reaction_type_, parser); td::parse(sender_dialog_id_, parser); - CHECK(!reaction_.empty()); + CHECK(!reaction_type_.is_empty()); } template diff --git a/td/telegram/MessageReplyHeader.cpp b/td/telegram/MessageReplyHeader.cpp index 30cef73a5ac0..e5919010d5e5 100644 --- a/td/telegram/MessageReplyHeader.cpp +++ b/td/telegram/MessageReplyHeader.cpp @@ -9,53 +9,68 @@ #include "td/telegram/FullMessageId.h" #include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/UserId.h" #include "td/utils/logging.h" namespace td { -MessageReplyHeader::MessageReplyHeader(tl_object_ptr &&reply_header, +MessageReplyHeader::MessageReplyHeader(tl_object_ptr &&reply_header_ptr, DialogId dialog_id, MessageId message_id, int32 date, bool can_have_thread) { - if (reply_header == nullptr) { + if (reply_header_ptr == nullptr) { return; } + if (reply_header_ptr->get_id() == telegram_api::messageReplyStoryHeader::ID) { + auto reply_header = telegram_api::move_object_as(reply_header_ptr); + UserId user_id(reply_header->user_id_); + StoryId story_id(reply_header->story_id_); + if (!user_id.is_valid() || !story_id.is_server()) { + LOG(ERROR) << "Receive " << to_string(reply_header); + } else { + story_full_id_ = {DialogId(user_id), story_id}; + } + return; + } + CHECK(reply_header_ptr->get_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); + 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 " << FullMessageId{reply_in_dialog_id, reply_to_message_id} << " in " + reply_in_dialog_id_ = DialogId(reply_to_peer_id); + LOG(ERROR) << "Receive reply to " << FullMessageId{reply_in_dialog_id_, reply_to_message_id_} << " in " << FullMessageId{dialog_id, message_id}; - reply_to_message_id = MessageId(); - reply_in_dialog_id = DialogId(); + reply_to_message_id_ = MessageId(); + reply_in_dialog_id_ = DialogId(); } } else { - LOG(ERROR) << "Receive reply to " << reply_to_message_id << " in " << FullMessageId{dialog_id, message_id}; - reply_to_message_id = MessageId(); + LOG(ERROR) << "Receive reply to " << reply_to_message_id_ << " in " << FullMessageId{dialog_id, message_id}; + reply_to_message_id_ = MessageId(); } } else { - reply_to_message_id = MessageId(ServerMessageId(reply_header->reply_to_msg_id_)); + 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()) { + 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(); + 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 (reply_in_dialog_id_ == dialog_id) { + reply_in_dialog_id_ = DialogId(); // just in case } } - if (reply_to_message_id.is_valid() && !message_id.is_scheduled() && !reply_in_dialog_id.is_valid() && + 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_)); + top_thread_message_id_ = MessageId(ServerMessageId(reply_header->reply_to_top_id_)); } else { - top_thread_message_id = reply_to_message_id; + top_thread_message_id_ = reply_to_message_id_; } - is_topic_message = reply_header->forum_topic_; + is_topic_message_ = reply_header->forum_topic_; } } } diff --git a/td/telegram/MessageReplyHeader.h b/td/telegram/MessageReplyHeader.h index 40ba95fc028e..5f596768ba24 100644 --- a/td/telegram/MessageReplyHeader.h +++ b/td/telegram/MessageReplyHeader.h @@ -8,6 +8,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/StoryFullId.h" #include "td/telegram/telegram_api.h" #include "td/utils/common.h" @@ -15,14 +16,18 @@ namespace td { struct MessageReplyHeader { - MessageId reply_to_message_id; - DialogId reply_in_dialog_id; - MessageId top_thread_message_id; - bool is_topic_message = false; + MessageId reply_to_message_id_; + DialogId reply_in_dialog_id_; + MessageId top_thread_message_id_; + bool is_topic_message_ = false; + + // or + + StoryFullId story_full_id_; MessageReplyHeader() = default; - MessageReplyHeader(tl_object_ptr &&reply_header, DialogId dialog_id, + MessageReplyHeader(tl_object_ptr &&reply_header_ptr, DialogId dialog_id, MessageId message_id, int32 date, bool can_have_thread); }; diff --git a/td/telegram/MessageReplyInfo.cpp b/td/telegram/MessageReplyInfo.cpp index 831482c27fef..865b58ee6e48 100644 --- a/td/telegram/MessageReplyInfo.cpp +++ b/td/telegram/MessageReplyInfo.cpp @@ -101,6 +101,10 @@ bool MessageReplyInfo::need_update_to(const MessageReplyInfo &other) const { // ignore updates to empty reply info, because we will hide the info ourselves // return true; } + if (other.is_comment_ != is_comment_ && !other.was_dropped()) { + LOG(ERROR) << "Reply info has changed from " << *this << " to " << other; + return true; + } if (other.pts_ < pts_ && !other.was_dropped()) { return false; } @@ -222,10 +226,11 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageReplyInfo if (reply_info.is_comment_) { return string_builder << reply_info.reply_count_ << " comments in " << reply_info.channel_id_ << " by " << reply_info.recent_replier_dialog_ids_ << " read up to " - << reply_info.last_read_inbox_message_id_ << "/" << reply_info.last_read_outbox_message_id_; + << reply_info.last_read_inbox_message_id_ << '/' << reply_info.last_read_outbox_message_id_ + << " with PTS " << reply_info.pts_; } else { return string_builder << reply_info.reply_count_ << " replies read up to " << reply_info.last_read_inbox_message_id_ - << "/" << reply_info.last_read_outbox_message_id_; + << "/" << reply_info.last_read_outbox_message_id_ << " with PTS " << reply_info.pts_; } } diff --git a/td/telegram/MessageSender.cpp b/td/telegram/MessageSender.cpp index 7f9a1a8558d2..8bb403d5c01a 100644 --- a/td/telegram/MessageSender.cpp +++ b/td/telegram/MessageSender.cpp @@ -140,7 +140,7 @@ Result get_message_sender_dialog_id(Td *td, } return Status::Error(400, "Invalid user identifier specified"); } - bool know_user = td->contacts_manager_->have_user_force(user_id); + bool know_user = td->contacts_manager_->have_user_force(user_id, "get_message_sender_dialog_id"); if (check_access && !know_user) { return Status::Error(400, "Unknown user identifier specified"); } @@ -154,9 +154,10 @@ Result get_message_sender_dialog_id(Td *td, } return Status::Error(400, "Invalid chat identifier specified"); } - bool know_dialog = dialog_id.get_type() == DialogType::User - ? td->contacts_manager_->have_user_force(dialog_id.get_user_id()) - : td->messages_manager_->have_dialog_force(dialog_id, "get_message_sender_dialog_id"); + bool know_dialog = + dialog_id.get_type() == DialogType::User + ? td->contacts_manager_->have_user_force(dialog_id.get_user_id(), "get_message_sender_dialog_id 2") + : td->messages_manager_->have_dialog_force(dialog_id, "get_message_sender_dialog_id"); if (check_access && !know_dialog) { return Status::Error(400, "Unknown chat identifier specified"); } diff --git a/td/telegram/MessageSource.cpp b/td/telegram/MessageSource.cpp index 1e75ab0d4250..af36a4d68540 100644 --- a/td/telegram/MessageSource.cpp +++ b/td/telegram/MessageSource.cpp @@ -28,6 +28,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageSource source) { return string_builder << "DialogEventLog"; case MessageSource::Notification: return string_builder << "Notification"; + case MessageSource::Screenshot: + return string_builder << "Screenshot"; case MessageSource::Other: return string_builder << "Other"; default: @@ -56,6 +58,8 @@ MessageSource get_message_source(const td_api::object_ptr return MessageSource::DialogEventLog; case td_api::messageSourceNotification::ID: return MessageSource::Notification; + case td_api::messageSourceScreenshot::ID: + return MessageSource::Screenshot; case td_api::messageSourceOther::ID: return MessageSource::Other; default: diff --git a/td/telegram/MessageSource.h b/td/telegram/MessageSource.h index 778fd58715a4..33223e022901 100644 --- a/td/telegram/MessageSource.h +++ b/td/telegram/MessageSource.h @@ -23,6 +23,7 @@ enum class MessageSource : int32 { Search, DialogEventLog, Notification, + Screenshot, Other }; diff --git a/td/telegram/MessageViewer.cpp b/td/telegram/MessageViewer.cpp index c6520e9ab064..e876f05d1720 100644 --- a/td/telegram/MessageViewer.cpp +++ b/td/telegram/MessageViewer.cpp @@ -9,11 +9,12 @@ #include "td/telegram/ContactsManager.h" #include "td/utils/algorithm.h" +#include "td/utils/logging.h" namespace td { MessageViewer::MessageViewer(telegram_api::object_ptr &&read_date) - : user_id_(read_date->user_id_), date_(read_date->date_) { + : MessageViewer(UserId(read_date->user_id_), read_date->date_) { } td_api::object_ptr MessageViewer::get_message_viewer_object( @@ -26,11 +27,19 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewer &vi return string_builder << '[' << viewer.user_id_ << " at " << viewer.date_ << ']'; } -MessageViewers::MessageViewers(vector> &&read_dates) - : message_viewers_( - transform(std::move(read_dates), [](telegram_api::object_ptr &&read_date) { - return MessageViewer(std::move(read_date)); - })) { +MessageViewers::MessageViewers(vector> &&read_dates) { + for (auto &read_date : read_dates) { + message_viewers_.emplace_back(std::move(read_date)); + auto user_id = message_viewers_.back().get_user_id(); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id << " as a viewer of a message"; + message_viewers_.pop_back(); + } + } +} + +vector MessageViewers::get_user_ids() const { + return transform(message_viewers_, [](auto &viewer) { return viewer.get_user_id(); }); } td_api::object_ptr MessageViewers::get_message_viewers_object( diff --git a/td/telegram/MessageViewer.h b/td/telegram/MessageViewer.h index fb071af1b3a6..72939d337a46 100644 --- a/td/telegram/MessageViewer.h +++ b/td/telegram/MessageViewer.h @@ -26,20 +26,34 @@ class MessageViewer { public: explicit MessageViewer(telegram_api::object_ptr &&read_date); + MessageViewer(UserId user_id, int32 date) : user_id_(user_id), date_(td::max(date, static_cast(0))) { + } + UserId get_user_id() const { return user_id_; } + bool is_empty() const { + return user_id_ == UserId() && date_ == 0; + } + td_api::object_ptr get_message_viewer_object(ContactsManager *contacts_manager) const; }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewer &viewer); -struct MessageViewers { +class MessageViewers { vector message_viewers_; + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewers &viewers); + + public: + MessageViewers() = default; + explicit MessageViewers(vector> &&read_dates); + vector get_user_ids() const; + td_api::object_ptr get_message_viewers_object(ContactsManager *contacts_manager) const; }; diff --git a/td/telegram/MessagesInfo.cpp b/td/telegram/MessagesInfo.cpp index f7d263808434..d294c849f94b 100644 --- a/td/telegram/MessagesInfo.cpp +++ b/td/telegram/MessagesInfo.cpp @@ -9,6 +9,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/logging.h" #include "td/utils/misc.h" diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 1bf7d09c47c3..ab8fd4359409 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -6,11 +6,14 @@ // #include "td/telegram/MessagesManager.h" +#include "td/telegram/AccountManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundInfo.hpp" +#include "td/telegram/BlockListId.h" #include "td/telegram/ChainId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" +#include "td/telegram/ChatReactions.hpp" #include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogActionBar.h" @@ -46,23 +49,31 @@ #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQuery.h" +#include "td/telegram/NotificationGroupInfo.hpp" #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/NotificationObjectId.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSound.h" #include "td/telegram/NotificationType.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PollId.h" #include "td/telegram/PublicDialogType.h" +#include "td/telegram/ReactionManager.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReplyMarkup.hpp" +#include "td/telegram/ReportReason.h" #include "td/telegram/SecretChatsManager.h" #include "td/telegram/SponsoredMessageManager.h" #include "td/telegram/StickerPhotoSize.h" #include "td/telegram/StickerType.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/TopDialogCategory.h" +#include "td/telegram/TopDialogManager.h" #include "td/telegram/TranslationManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/Version.h" @@ -651,7 +662,8 @@ class SearchPublicDialogsQuery final : public Td::ResultHandler { public: void send(const string &query) { query_ = query; - send_query(G()->net_query_creator().create(telegram_api::contacts_search(query, 3 /* ignored server-side */))); + send_query( + G()->net_query_creator().create(telegram_api::contacts_search(query, 20 /* mostly ignored server-side */))); } void on_result(BufferSlice packet) final { @@ -739,11 +751,17 @@ class GetBlockedDialogsQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(int32 offset, int32 limit) { + void send(BlockListId block_list_id, int32 offset, int32 limit) { offset_ = offset; limit_ = limit; - send_query(G()->net_query_creator().create(telegram_api::contacts_getBlocked(offset, limit))); + int32 flags = 0; + if (block_list_id == BlockListId::stories()) { + flags |= telegram_api::contacts_getBlocked::MY_STORIES_FROM_MASK; + } + + send_query( + G()->net_query_creator().create(telegram_api::contacts_getBlocked(flags, false /*ignored*/, offset, limit))); } void on_result(BufferSlice packet) final { @@ -975,8 +993,9 @@ class InitHistoryImportQuery final : public Td::ResultHandler { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - // TODO support FILE_PART_*_MISSING + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); @@ -1028,8 +1047,9 @@ class UploadImportedMediaQuery final : public Td::ResultHandler { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - // TODO support FILE_PART_*_MISSING + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); @@ -1605,23 +1625,29 @@ class ToggleDialogTranslationsQuery final : public Td::ResultHandler { class ToggleDialogIsBlockedQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; - bool is_blocked_; public: explicit ToggleDialogIsBlockedQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(DialogId dialog_id, bool is_blocked) { + void send(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories) { dialog_id_ = dialog_id; - is_blocked_ = is_blocked; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Know); CHECK(input_peer != nullptr && input_peer->get_id() != telegram_api::inputPeerEmpty::ID); + + int32 flags = 0; + if (is_blocked_for_stories) { + flags |= telegram_api::contacts_block::MY_STORIES_FROM_MASK; + } vector chain_ids{{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}}; - auto query = is_blocked ? G()->net_query_creator().create(telegram_api::contacts_block(std::move(input_peer)), - std::move(chain_ids)) - : G()->net_query_creator().create(telegram_api::contacts_unblock(std::move(input_peer)), - std::move(chain_ids)); + auto query = + is_blocked || is_blocked_for_stories + ? G()->net_query_creator().create( + telegram_api::contacts_block(flags, false /*ignored*/, std::move(input_peer)), std::move(chain_ids)) + : G()->net_query_creator().create( + telegram_api::contacts_unblock(flags, false /*ignored*/, std::move(input_peer)), + std::move(chain_ids)); send_query(std::move(query)); } @@ -1644,7 +1670,6 @@ class ToggleDialogIsBlockedQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive error for ToggleDialogIsBlockedQuery: " << status; } if (!G()->close_flag()) { - td_->messages_manager_->on_update_dialog_is_blocked(dialog_id_, !is_blocked_); td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "ToggleDialogIsBlockedQuery"); td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ToggleDialogIsBlockedQuery"); } @@ -1759,7 +1784,6 @@ class ReadMessagesContentsQuery final : public Td::ResultHandler { } auto affected_messages = result_ptr.move_as_ok(); - CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID); LOG(INFO) << "Receive result for ReadMessagesContentsQuery: " << to_string(affected_messages); if (affected_messages->pts_count_ > 0) { @@ -1980,7 +2004,6 @@ class ReadHistoryQuery final : public Td::ResultHandler { } auto affected_messages = result_ptr.move_as_ok(); - CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID); LOG(INFO) << "Receive result for ReadHistoryQuery: " << to_string(affected_messages); if (affected_messages->pts_count_ > 0) { @@ -3131,7 +3154,7 @@ class SendMessageQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date, + 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) { @@ -3143,6 +3166,11 @@ class SendMessageQuery final : public Td::ResultHandler { return on_error(Status::Error(400, "Have no write access to the chat")); } + auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); + + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; + } if (!entities.empty()) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES; } @@ -3150,13 +3178,10 @@ class SendMessageQuery final : public Td::ResultHandler { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } - CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); - CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server()); 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), reply_to_message_id.get_server_message_id().get(), - top_thread_message_id.get_server_message_id().get(), text, random_id, std::move(reply_markup), + 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}}); @@ -3206,7 +3231,7 @@ class SendMessageQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for SendMessage: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMessageQuery"); @@ -3257,7 +3282,7 @@ class StartBotQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for StartBotQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message should be re-sent + // do not send error, message should be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartBotQuery"); @@ -3271,25 +3296,27 @@ class SendInlineBotResultQuery final : public Td::ResultHandler { public: NetQueryRef send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date, int64 random_id, - int64 query_id, const string &result_id) { + 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; auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); + auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); + + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendInlineBotResult::REPLY_TO_MASK; + } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } - CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); - CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server()); auto query = G()->net_query_creator().create( telegram_api::messages_sendInlineBotResult( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), - reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(), - random_id, query_id, result_id, schedule_date, std::move(as_input_peer)), + std::move(reply_to), random_id, query_id, result_id, schedule_date, std::move(as_input_peer)), {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}); auto send_query_ref = query.get_weak(); send_query(std::move(query)); @@ -3311,7 +3338,7 @@ class SendInlineBotResultQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for SendInlineBotResultQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendInlineBotResultQuery"); @@ -3327,7 +3354,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, - MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date, + 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) { @@ -3344,19 +3371,21 @@ class SendMultiMediaQuery final : public Td::ResultHandler { return on_error(Status::Error(400, "Have no write access to the chat")); } + auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); + + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMultiMedia::REPLY_TO_MASK; + } if (as_input_peer != nullptr) { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } // no quick ack, because file reference errors are very likely to happen - CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); - CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server()); 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), - reply_to_message_id.get_server_message_id().get(), - top_thread_message_id.get_server_message_id().get(), - std::move(input_single_media), schedule_date, std::move(as_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}})); } @@ -3410,7 +3439,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for SendMultiMedia: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { @@ -3443,7 +3472,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, MessageId reply_to_message_id, + tl_object_ptr as_input_peer, MessageInputReplyTo input_reply_to, MessageId top_thread_message_id, int32 schedule_date, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, @@ -3462,6 +3491,11 @@ class SendMediaQuery final : public Td::ResultHandler { return on_error(Status::Error(400, "Have no write access to the chat")); } + auto reply_to = input_reply_to.get_input_reply_to(td_, top_thread_message_id); + + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMedia::REPLY_TO_MASK; + } if (!entities.empty()) { flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; } @@ -3469,13 +3503,10 @@ class SendMediaQuery final : public Td::ResultHandler { flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS; } - CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server()); - CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server()); 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), reply_to_message_id.get_server_message_id().get(), - top_thread_message_id.get_server_message_id().get(), std::move(input_media), text, random_id, + 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_) { @@ -3510,7 +3541,7 @@ class SendMediaQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for SendMedia: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } if (was_uploaded_) { @@ -3521,9 +3552,9 @@ class SendMediaQuery final : public Td::ResultHandler { } CHECK(file_id_.is_valid()); - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - td_->messages_manager_->on_send_message_file_part_missing(random_id_, - to_integer(status.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->messages_manager_->on_send_message_file_parts_missing(random_id_, std::move(bad_parts)); return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { @@ -3598,7 +3629,7 @@ class UploadMediaQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadMediaQuery"); @@ -3610,9 +3641,10 @@ class UploadMediaQuery final : public Td::ResultHandler { } CHECK(file_id_.is_valid()); - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - td_->messages_manager_->on_upload_message_media_file_part_missing( - dialog_id_, message_id_, to_integer(status.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->messages_manager_->on_upload_message_media_file_parts_missing(dialog_id_, message_id_, + std::move(bad_parts)); return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { @@ -3740,7 +3772,7 @@ class EditMessageQuery final : public Td::ResultHandler { } void on_error(Status status) final { - LOG(INFO) << "Receive error for EditMessage: " << status; + LOG(INFO) << "Receive error for EditMessageQuery: " << status; if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") { return promise_.set_value(0); } @@ -3906,7 +3938,7 @@ class ForwardMessagesQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for forward messages: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, messages should be re-sent + // do not send error, messages will be re-sent after restart return; } // no on_get_dialog_error call, because two dialogs are involved @@ -3940,7 +3972,8 @@ class SendScreenshotNotificationQuery final : public Td::ResultHandler { CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( - telegram_api::messages_sendScreenshotNotification(std::move(input_peer), 0, random_id), + telegram_api::messages_sendScreenshotNotification( + std::move(input_peer), telegram_api::make_object(0, 0, 0), random_id), {{dialog_id, MessageContentType::Text}})); } @@ -3960,7 +3993,7 @@ class SendScreenshotNotificationQuery final : public Td::ResultHandler { void on_error(Status status) final { LOG(INFO) << "Receive error for SendScreenshotNotificationQuery: " << status; if (G()->close_flag() && G()->use_message_database()) { - // do not send error, messages should be re-sent + // do not send error, messages will be re-sent after restart return; } td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScreenshotNotificationQuery"); @@ -4360,7 +4393,6 @@ class ReportPeerQuery final : public Td::ResultHandler { } void on_error(Status status) final { - LOG(INFO) << "Receive error for report peer: " << status; td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportPeerQuery"); td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ReportPeerQuery"); promise_.set_error(std::move(status)); @@ -4503,7 +4535,7 @@ class GetChannelDifferenceQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok()); + td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok(), Status::OK()); } void on_error(Status status) final { @@ -4512,7 +4544,7 @@ class GetChannelDifferenceQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive error for GetChannelDifferenceQuery for " << dialog_id_ << " with PTS " << pts_ << " and limit " << limit_ << ": " << status; } - td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr); + td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr, std::move(status)); } }; @@ -4686,6 +4718,7 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_reactions = reactions != nullptr; 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(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); @@ -4761,6 +4794,7 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(update_stickersets_order); STORE_FLAG(is_topic_message); STORE_FLAG(has_history_generation); + STORE_FLAG(is_reply_to_story); END_STORE_FLAGS(); } @@ -4888,6 +4922,9 @@ void MessagesManager::Message::store(StorerT &storer) const { if (has_history_generation) { store(history_generation, storer); } + if (is_reply_to_story) { + store(reply_to_story_full_id, storer); + } } // do not forget to resolve message dependencies @@ -4936,6 +4973,7 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_reactions = false; bool has_available_reactions_generation = false; bool has_history_generation = false; + bool is_reply_to_story = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); @@ -5011,6 +5049,7 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(update_stickersets_order); PARSE_FLAG(is_topic_message); PARSE_FLAG(has_history_generation); + PARSE_FLAG(is_reply_to_story); END_PARSE_FLAGS(); } @@ -5148,6 +5187,9 @@ void MessagesManager::Message::parse(ParserT &parser) { if (has_history_generation) { parse(history_generation, parser); } + if (is_reply_to_story) { + parse(reply_to_story_full_id, parser); + } CHECK(content != nullptr); is_content_secret |= @@ -5157,28 +5199,6 @@ void MessagesManager::Message::parse(ParserT &parser) { } } -template -void MessagesManager::NotificationGroupInfo::store(StorerT &storer) const { - using td::store; - store(group_id, storer); - store(last_notification_date, storer); - store(last_notification_id, storer); - store(max_removed_notification_id, storer); - store(max_removed_message_id, storer); -} - -template -void MessagesManager::NotificationGroupInfo::parse(ParserT &parser) { - using td::parse; - parse(group_id, parser); - parse(last_notification_date, parser); - parse(last_notification_id, parser); - parse(max_removed_notification_id, parser); - if (parser.version() >= static_cast(Version::AddNotificationGroupInfoMaxRemovedMessageId)) { - parse(max_removed_message_id, parser); - } -} - template void MessagesManager::Dialog::store(StorerT &storer) const { using td::store; @@ -5200,21 +5220,19 @@ void MessagesManager::Dialog::store(StorerT &storer) const { bool has_deleted_last_message = delete_last_message_date > 0; bool has_last_clear_history_message_id = last_clear_history_message_id.is_valid(); bool has_last_database_message_id = !has_last_database_message && last_database_message_id.is_valid(); - bool has_message_notification_group = notification_info != nullptr && - notification_info->message_notification_group_.group_id.is_valid() && - !notification_info->message_notification_group_.try_reuse; - bool has_mention_notification_group = notification_info != nullptr && - notification_info->mention_notification_group_.group_id.is_valid() && - !notification_info->mention_notification_group_.try_reuse; + bool has_message_notification_group = + notification_info != nullptr && notification_info->message_notification_group_.is_active(); + bool has_mention_notification_group = + notification_info != nullptr && notification_info->mention_notification_group_.is_active(); bool has_new_secret_chat_notification_id = notification_info != nullptr && notification_info->new_secret_chat_notification_id_.is_valid(); bool has_pinned_message_notification = notification_info != nullptr && notification_info->pinned_message_notification_message_id_.is_valid(); bool has_last_pinned_message_id = last_pinned_message_id.is_valid(); bool has_flags2 = true; - bool has_max_notification_message_id = notification_info != nullptr && - notification_info->max_notification_message_id_.is_valid() && - notification_info->max_notification_message_id_ > last_new_message_id; + bool has_max_push_notification_message_id = + notification_info != nullptr && notification_info->max_push_notification_message_id_.is_valid() && + notification_info->max_push_notification_message_id_ > last_new_message_id; bool has_folder_id = folder_id != FolderId(); bool has_pending_read_channel_inbox = pending_read_channel_inbox_pts != 0; bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent(); @@ -5270,7 +5288,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { if (has_flags2) { BEGIN_STORE_FLAGS(); - STORE_FLAG(has_max_notification_message_id); + STORE_FLAG(has_max_push_notification_message_id); STORE_FLAG(has_folder_id); STORE_FLAG(is_folder_id_inited); STORE_FLAG(has_pending_read_channel_inbox); @@ -5320,6 +5338,8 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(need_repair_unread_mention_count); STORE_FLAG(is_background_inited); STORE_FLAG(has_background); + STORE_FLAG(is_blocked_for_stories); + STORE_FLAG(is_is_blocked_for_stories_inited); END_STORE_FLAGS(); } @@ -5390,8 +5410,8 @@ void MessagesManager::Dialog::store(StorerT &storer) const { if (has_last_pinned_message_id) { store(last_pinned_message_id, storer); } - if (has_max_notification_message_id) { - store(notification_info->max_notification_message_id_, storer); + if (has_max_push_notification_message_id) { + store(notification_info->max_push_notification_message_id_, storer); } if (has_folder_id) { store(folder_id, storer); @@ -5464,7 +5484,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { bool has_pinned_message_notification; bool has_last_pinned_message_id; bool has_flags2; - bool has_max_notification_message_id = false; + bool has_max_push_notification_message_id = false; bool has_folder_id = false; bool has_pending_read_channel_inbox = false; bool has_active_group_call_id = false; @@ -5526,7 +5546,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { if (has_flags2) { BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_max_notification_message_id); + PARSE_FLAG(has_max_push_notification_message_id); PARSE_FLAG(has_folder_id); PARSE_FLAG(is_folder_id_inited); PARSE_FLAG(has_pending_read_channel_inbox); @@ -5591,11 +5611,15 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(need_repair_unread_mention_count); PARSE_FLAG(is_background_inited); PARSE_FLAG(has_background); + PARSE_FLAG(is_blocked_for_stories); + PARSE_FLAG(is_is_blocked_for_stories_inited); END_PARSE_FLAGS(); } else { need_repair_action_bar = false; is_available_reactions_inited = false; is_background_inited = false; + is_blocked_for_stories = false; + is_is_blocked_for_stories_inited = false; } parse(last_new_message_id, parser); @@ -5700,8 +5724,8 @@ void MessagesManager::Dialog::parse(ParserT &parser) { if (has_last_pinned_message_id) { parse(last_pinned_message_id, parser); } - if (has_max_notification_message_id) { - parse(add_dialog_notification_info(this)->max_notification_message_id_, parser); + if (has_max_push_notification_message_id) { + parse(add_dialog_notification_info(this)->max_push_notification_message_id_, parser); } if (has_folder_id) { parse(folder_id, parser); @@ -5740,9 +5764,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) { if (has_available_reactions) { parse(available_reactions, parser); } else if (has_legacy_available_reactions) { - vector legacy_available_reactions; - parse(legacy_available_reactions, parser); - available_reactions = ChatReactions(std::move(legacy_available_reactions)); + vector legacy_available_reaction_types; + parse(legacy_available_reaction_types, parser); + available_reactions = ChatReactions(std::move(legacy_available_reaction_types)); } if (has_available_reactions_generation) { parse(available_reactions_generation, parser); @@ -5889,13 +5913,13 @@ MessagesManager::~MessagesManager() { G()->get_gc_scheduler_id(), ttl_nodes_, ttl_heap_, being_sent_messages_, update_message_ids_, update_scheduled_message_ids_, message_id_to_dialog_id_, last_clear_history_message_id_to_dialog_id_, dialogs_, postponed_chat_read_inbox_updates_, found_public_dialogs_, found_on_server_dialogs_, found_common_dialogs_, - message_embedding_codes_[0], message_embedding_codes_[1], replied_by_media_timestamp_messages_, - notification_group_id_to_dialog_id_, pending_get_channel_differences_, active_get_channel_differences_, - get_channel_difference_to_log_event_id_, channel_get_difference_retry_timeouts_, is_channel_difference_finished_, - expected_channel_pts_, expected_channel_max_message_id_, resolved_usernames_, inaccessible_resolved_usernames_, - dialog_bot_command_message_ids_, full_message_id_to_file_source_id_, last_outgoing_forwarded_message_date_, - dialog_viewed_messages_, dialog_online_member_counts_, previous_repaired_read_inbox_max_message_id_, - failed_to_load_dialogs_); + message_embedding_codes_[0], message_embedding_codes_[1], message_to_replied_media_timestamp_messages_, + story_to_replied_media_timestamp_messages_, notification_group_id_to_dialog_id_, pending_get_channel_differences_, + active_get_channel_differences_, get_channel_difference_to_log_event_id_, channel_get_difference_retry_timeouts_, + is_channel_difference_finished_, expected_channel_pts_, expected_channel_max_message_id_, resolved_usernames_, + inaccessible_resolved_usernames_, dialog_bot_command_message_ids_, full_message_id_to_file_source_id_, + last_outgoing_forwarded_message_date_, dialog_viewed_messages_, dialog_online_member_counts_, + previous_repaired_read_inbox_max_message_id_, failed_to_load_dialogs_); } void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { @@ -5966,7 +5990,7 @@ void MessagesManager::on_pending_unload_dialog_timeout_callback(void *messages_m auto messages_manager = static_cast(messages_manager_ptr); send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::unload_dialog, - DialogId(dialog_id_int)); + DialogId(dialog_id_int), -1); } void MessagesManager::on_dialog_unmute_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) { @@ -6059,23 +6083,19 @@ void MessagesManager::save_dialog_to_database(DialogId dialog_id) { CHECK(d != nullptr); LOG(INFO) << "Save " << dialog_id << " to database"; vector changed_group_keys; - bool can_reuse_notification_group = false; if (d->notification_info != nullptr) { - auto add_group_key = [&](auto &group_info) { - if (group_info.is_changed) { - can_reuse_notification_group |= group_info.try_reuse; - changed_group_keys.emplace_back(group_info.group_id, group_info.try_reuse ? DialogId() : dialog_id, - group_info.last_notification_date); - group_info.is_changed = false; - } - }; - add_group_key(d->notification_info->message_notification_group_); - add_group_key(d->notification_info->mention_notification_group_); + d->notification_info->message_notification_group_.add_group_key_if_changed(changed_group_keys, dialog_id); + d->notification_info->mention_notification_group_.add_group_key_if_changed(changed_group_keys, dialog_id); + } + bool can_reuse_notification_group = false; + for (auto &group_key : changed_group_keys) { + if (group_key.dialog_id == DialogId()) { + can_reuse_notification_group = true; + } } - auto fixed_folder_id = d->folder_id == FolderId::archive() ? FolderId::archive() : FolderId::main(); G()->td_db()->get_dialog_db_async()->add_dialog( - dialog_id, fixed_folder_id, d->is_folder_id_inited ? d->order : 0, get_dialog_database_value(d), - std::move(changed_group_keys), PromiseCreator::lambda([dialog_id, can_reuse_notification_group](Result<> result) { + dialog_id, d->folder_id, d->order, get_dialog_database_value(d), std::move(changed_group_keys), + PromiseCreator::lambda([dialog_id, can_reuse_notification_group](Result<> result) { send_closure(G()->messages_manager(), &MessagesManager::on_save_dialog_to_database, dialog_id, can_reuse_notification_group, result.is_ok()); })); @@ -6097,26 +6117,11 @@ void MessagesManager::on_save_dialog_to_database(DialogId dialog_id, bool can_re } void MessagesManager::try_reuse_notification_group(NotificationGroupInfo &group_info) { - if (!group_info.try_reuse) { - return; - } - if (group_info.is_changed) { - LOG(ERROR) << "Failed to reuse changed " << group_info.group_id; - return; - } - group_info.try_reuse = false; - if (!group_info.group_id.is_valid()) { - LOG(ERROR) << "Failed to reuse invalid " << group_info.group_id; - return; + auto group_id = group_info.get_reused_group_id(); + if (group_id.is_valid()) { + send_closure_later(G()->notification_manager(), &NotificationManager::try_reuse_notification_group_id, group_id); + notification_group_id_to_dialog_id_.erase(group_id); } - CHECK(group_info.last_notification_id == NotificationId()); - CHECK(group_info.last_notification_date == 0); - send_closure_later(G()->notification_manager(), &NotificationManager::try_reuse_notification_group_id, - group_info.group_id); - notification_group_id_to_dialog_id_.erase(group_info.group_id); - group_info.group_id = NotificationGroupId(); - group_info.max_removed_notification_id = NotificationId(); - group_info.max_removed_message_id = MessageId(); } void MessagesManager::invalidate_message_indexes(Dialog *d) { @@ -6395,11 +6400,11 @@ bool MessagesManager::have_dialog_info(DialogId dialog_id) const { } } -bool MessagesManager::have_dialog_info_force(DialogId dialog_id) const { +bool MessagesManager::have_dialog_info_force(DialogId dialog_id, const char *source) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); - return td_->contacts_manager_->have_user_force(user_id); + return td_->contacts_manager_->have_user_force(user_id, source); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); @@ -6461,33 +6466,29 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptr(update.get()); auto full_message_id = FullMessageId::get_full_message_id(update_new_message->message_, false); if (update_message_ids_.count(full_message_id) > 0) { - if (new_pts == old_pts || old_pts == std::numeric_limits::max()) { - // apply sent message anyway if it is definitely non-deleted or being skipped because of PTS overflow - auto added_full_message_id = on_get_message(std::move(update_new_message->message_), true, false, false, - "updateNewMessage with an awaited message"); - if (added_full_message_id != full_message_id) { - LOG(ERROR) << "Failed to add an awaited " << full_message_id << " from " << source; - } - return; - } else { - LOG(ERROR) << "Receive awaited sent " << full_message_id << " from " << source << " with PTS " << new_pts - << " and pts_count " << pts_count << ", but current PTS is " << old_pts; + // apply the sent message anyway, even it could have been deleted or edited already + + CHECK(full_message_id.get_dialog_id().get_type() == DialogType::User || + full_message_id.get_dialog_id().get_type() == DialogType::Chat); // checked in check_pts_update + delete_messages_from_updates({full_message_id.get_message_id()}, false); + + auto added_full_message_id = on_get_message(std::move(update_new_message->message_), true, false, false, + "updateNewMessage with an awaited message"); + if (added_full_message_id != full_message_id) { + LOG(ERROR) << "Failed to add an awaited " << full_message_id << " from " << source; } + return; } } if (update->get_id() == updateSentMessage::ID) { auto update_sent_message = static_cast(update.get()); if (being_sent_messages_.count(update_sent_message->random_id_) > 0) { - if (new_pts == old_pts || old_pts == std::numeric_limits::max()) { - // apply sent message anyway if it is definitely non-deleted or being skipped because of PTS overflow - on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, - update_sent_message->date_, update_sent_message->ttl_period_, FileId(), - "process old updateSentMessage"); - return; - } else if (update_sent_message->random_id_ != 0) { - LOG(ERROR) << "Receive awaited sent " << update_sent_message->message_id_ << " from " << source << " with PTS " - << new_pts << " and pts_count " << pts_count << ", but current PTS is " << old_pts; - } + // apply the sent message anyway, even it could have been deleted or edited already + delete_messages_from_updates({update_sent_message->message_id_}, false); + on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_, + update_sent_message->date_, update_sent_message->ttl_period_, FileId(), + "process old updateSentMessage"); + return; } return; } @@ -6504,6 +6505,37 @@ MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() { return get_dialog(service_notifications_dialog_id); } +void MessagesManager::extract_authentication_codes(DialogId dialog_id, const Message *m, + vector &authentication_codes) { + CHECK(m != nullptr); + if (dialog_id != DialogId(ContactsManager::get_service_notifications_user_id()) || !m->message_id.is_valid() || + !m->message_id.is_server() || m->content->get_type() != MessageContentType::Text || m->is_outgoing) { + return; + } + auto *formatted_text = get_message_content_text(m->content.get()); + CHECK(formatted_text != nullptr); + const string &text = formatted_text->text; + for (size_t i = 0; i < text.size(); i++) { + if (is_digit(text[i])) { + string code; + do { + if (is_digit(text[i])) { + code += text[i++]; + continue; + } + if (text[i] == '-') { + i++; + continue; + } + break; + } while (true); + if (5 <= code.size() && code.size() <= 7) { + authentication_codes.push_back(code); + } + } + } +} + void MessagesManager::save_auth_notification_ids() { auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME; vector stored_ids; @@ -6595,6 +6627,12 @@ void MessagesManager::on_update_service_notification(tl_object_ptrflags_ & telegram_api::updateReadChannelInbox::FOLDER_ID_MASK) != 0) { - folder_id = FolderId(update->folder_id_); - } - on_update_dialog_folder_id(DialogId(channel_id), folder_id); + on_update_dialog_folder_id(DialogId(channel_id), FolderId(update->folder_id_)); on_read_channel_inbox(channel_id, MessageId(ServerMessageId(update->max_id_)), update->still_unread_count_, update->pts_, "updateReadChannelInbox"); } @@ -6701,6 +6734,10 @@ void MessagesManager::on_update_channel_too_long(tl_object_ptrcontacts_manager_->have_channel_force(channel_id)) { + LOG(INFO) << "Skip updateChannelTooLong about unknown " << channel_id; + return; + } DialogId dialog_id = DialogId(channel_id); auto d = get_dialog_force(dialog_id, "on_update_channel_too_long 4"); @@ -6783,8 +6820,9 @@ void MessagesManager::update_message_reactions(FullMessageId full_message_id, update_message_interaction_info(full_message_id, -1, -1, false, nullptr, true, std::move(reactions)); } -void MessagesManager::on_get_message_reaction_list(FullMessageId full_message_id, const string &reaction, - FlatHashMap> reactions, int32 total_count) { +void MessagesManager::on_get_message_reaction_list( + FullMessageId full_message_id, const ReactionType &reaction_type, + FlatHashMap, ReactionTypeHash> reaction_types, int32 total_count) { const Message *m = get_message_force(full_message_id, "on_get_message_reaction_list"); if (m == nullptr || m->reactions == nullptr) { return; @@ -6792,7 +6830,7 @@ void MessagesManager::on_get_message_reaction_list(FullMessageId full_message_id // it's impossible to use received reactions to update message reactions, because there is no way to find, // which reactions are chosen by the current user, so just reload message reactions for consistency - if (m->reactions->are_consistent_with_list(reaction, std::move(reactions), total_count)) { + if (m->reactions->are_consistent_with_list(reaction_type, std::move(reaction_types), total_count)) { return; } @@ -7094,15 +7132,16 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int } } } + FullMessageId full_message_id{dialog_id, m->message_id}; if (has_reactions) { - auto it = pending_reactions_.find({dialog_id, m->message_id}); + auto it = pending_reactions_.find(full_message_id); if (it != pending_reactions_.end()) { - LOG(INFO) << "Ignore reactions, because have a pending message reaction"; + LOG(INFO) << "Ignore reactions for " << full_message_id << ", because they are being changed"; has_reactions = false; it->second.was_updated = true; } - if (has_reactions && pending_read_reactions_.count({dialog_id, m->message_id}) > 0) { - LOG(INFO) << "Ignore reactions, because have a pending message reaction read"; + if (has_reactions && pending_read_reactions_.count(full_message_id) > 0) { + LOG(INFO) << "Ignore reactions for " << full_message_id << ", because they are being read"; has_reactions = false; } } @@ -7111,11 +7150,7 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int reactions->update_from(*m->reactions); } reactions->sort_reactions(active_reaction_pos_); - reactions->fix_chosen_reaction(get_my_dialog_id()); - if (d->default_send_message_as_dialog_id.is_valid()) { - // the reaction could be set by previous owner of the broadcast - // reactions->fix_chosen_reaction(d->default_send_message_as_dialog_id); - } + reactions->fix_chosen_reaction(); } bool need_update_reactions = has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get()); @@ -7125,9 +7160,13 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int m->reactions->chosen_reaction_order_ != reactions->chosen_reaction_order_; if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info || need_update_reactions || need_update_unread_reactions || need_update_chosen_reaction_order) { - LOG(DEBUG) << "Update interaction info of " << FullMessageId{dialog_id, m->message_id} << " from " << m->view_count - << '/' << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/' - << forward_count << '/' << reply_info << '/' << reactions; + LOG(DEBUG) << "Update interaction info of " << full_message_id << " from " << m->view_count << '/' + << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/' + << forward_count << '/' << reply_info << '/' << reactions + << ", need_update_reply_info = " << need_update_reply_info + << ", need_update_reactions = " << need_update_reactions + << ", need_update_unread_reactions = " << need_update_unread_reactions + << ", need_update_chosen_reaction_order = " << need_update_chosen_reaction_order; bool need_update = false; if (view_count > m->view_count) { m->view_count = view_count; @@ -7140,8 +7179,8 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int if (need_update_reply_info) { if (m->reply_info.channel_id_ != reply_info.channel_id_) { if (m->reply_info.channel_id_.is_valid() && reply_info.channel_id_.is_valid() && m->message_id.is_server()) { - LOG(ERROR) << "Reply info of " << FullMessageId{dialog_id, m->message_id} << " changed from " << m->reply_info - << " to " << reply_info << " from " << source; + LOG(ERROR) << "Reply info of " << full_message_id << " changed from " << m->reply_info << " to " << reply_info + << " from " << source; } } m->reply_info = std::move(reply_info); @@ -7402,7 +7441,7 @@ void MessagesManager::on_read_channel_inbox(ChannelId channel_id, MessageId max_ // update from the future, keep it until it can be applied if (pts >= d->pending_read_channel_inbox_pts) { if (d->pending_read_channel_inbox_pts == 0) { - schedule_get_channel_difference(dialog_id, pts, MessageId(), 0.001); + schedule_get_channel_difference(dialog_id, pts, MessageId(), 0.001, "on_read_channel_inbox"); } d->pending_read_channel_inbox_pts = pts; d->pending_read_channel_inbox_max_message_id = max_message_id; @@ -7421,20 +7460,20 @@ void MessagesManager::on_read_channel_outbox(ChannelId channel_id, MessageId max } void MessagesManager::on_update_channel_max_unavailable_message_id(ChannelId channel_id, - MessageId max_unavailable_message_id) { + MessageId max_unavailable_message_id, + const char *source) { if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive max_unavailable_message_id in invalid " << channel_id; + LOG(ERROR) << "Receive max_unavailable_message_id in invalid " << channel_id << " from " << source; return; } DialogId dialog_id(channel_id); CHECK(!max_unavailable_message_id.is_scheduled()); if (!max_unavailable_message_id.is_valid() && max_unavailable_message_id != MessageId()) { - LOG(ERROR) << "Receive wrong max_unavailable_message_id: " << max_unavailable_message_id; + LOG(ERROR) << "Receive wrong max_unavailable_message_id: " << max_unavailable_message_id << " from " << source; max_unavailable_message_id = MessageId(); } - set_dialog_max_unavailable_message_id(dialog_id, max_unavailable_message_id, true, - "on_update_channel_max_unavailable_message_id"); + set_dialog_max_unavailable_message_id(dialog_id, max_unavailable_message_id, true, source); } void MessagesManager::on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, @@ -7592,7 +7631,7 @@ void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_ return; } } else { - if (!have_dialog_info_force(typing_dialog_id)) { + if (!have_dialog_info_force(typing_dialog_id, "on_dialog_action")) { LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id; return; } @@ -7741,21 +7780,6 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p return; } - if (new_pts <= pts && new_pts >= pts - 19999) { - LOG(INFO) << "There is no need to process an update with PTS " << new_pts << " in " << dialog_id << " with PTS " - << pts; - promise.set_value(Unit()); - return; - } - - if (new_pts > pts && pts != new_pts - pts_count) { - LOG(INFO) << "Found a gap in unknown " << dialog_id << " with PTS = " << pts << ". new_pts = " << new_pts - << ", pts_count = " << pts_count << " in update from " << source; - add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); - get_channel_difference(dialog_id, pts, new_pts, MessageId(), true, "add_pending_channel_update 3"); - return; - } - d = add_dialog(dialog_id, "add_pending_channel_update 4"); CHECK(d != nullptr); CHECK(d->pts == pts); @@ -7812,7 +7836,10 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p return; } - if (old_pts != new_pts - pts_count) { + if (old_pts == 0) { + old_pts = new_pts - pts_count; + LOG(INFO) << "Receive first update in " << dialog_id << " with PTS = " << new_pts << " from " << source; + } else if (old_pts != new_pts - pts_count) { LOG(INFO) << "Found a gap in the " << dialog_id << " with PTS = " << old_pts << ". new_pts = " << new_pts << ", pts_count = " << pts_count << " in update from " << source; if (d->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() || @@ -7879,13 +7906,10 @@ void MessagesManager::process_pts_update(tl_object_ptr &&u break; } case telegram_api::updateReadMessagesContents::ID: { - if (td_->auth_manager_->is_bot()) { - break; - } auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateReadMessageContents"; for (auto &message_id : update->messages_) { - read_message_content_from_updates(MessageId(ServerMessageId(message_id))); + read_message_content_from_updates(MessageId(ServerMessageId(message_id)), update->date_); } break; } @@ -7905,18 +7929,14 @@ void MessagesManager::process_pts_update(tl_object_ptr &&u for (auto message : update->messages_) { message_ids.push_back(MessageId(ServerMessageId(message))); } - delete_messages_from_updates(message_ids); + delete_messages_from_updates(message_ids, true); break; } case telegram_api::updateReadHistoryInbox::ID: { auto update = move_tl_object_as(update_ptr); LOG(INFO) << "Process updateReadHistoryInbox"; DialogId dialog_id(update->peer_); - FolderId folder_id; - if ((update->flags_ & telegram_api::updateReadHistoryInbox::FOLDER_ID_MASK) != 0) { - folder_id = FolderId(update->folder_id_); - } - on_update_dialog_folder_id(dialog_id, folder_id); + on_update_dialog_folder_id(dialog_id, FolderId(update->folder_id_)); read_history_inbox(dialog_id, MessageId(ServerMessageId(update->max_id_)), -1 /*update->still_unread_count*/, "updateReadHistoryInbox"); break; @@ -8072,7 +8092,7 @@ bool MessagesManager::update_dialog_notification_settings(DialogId dialog_id, remove_all_dialog_notifications(d, false, "update_dialog_notification_settings 2"); } if (is_dialog_pinned_message_notifications_disabled(d) && d->notification_info != nullptr && - d->notification_info->mention_notification_group_.group_id.is_valid() && + d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid()) { remove_dialog_pinned_message_notification(d, "update_dialog_notification_settings 3"); } @@ -8390,19 +8410,20 @@ void MessagesManager::hide_dialog_message_reactions(Dialog *d) { } } -void MessagesManager::set_active_reactions(vector active_reactions) { - if (active_reactions == active_reactions_) { +void MessagesManager::set_active_reactions(vector active_reaction_types) { + if (active_reaction_types == active_reaction_types_) { return; } - LOG(INFO) << "Set active reactions to " << active_reactions; - bool is_changed = active_reactions != active_reactions_; - active_reactions_ = std::move(active_reactions); + LOG(INFO) << "Set active reactions to " << active_reaction_types; + bool is_changed = active_reaction_types != active_reaction_types_; + active_reaction_types_ = std::move(active_reaction_types); auto old_active_reaction_pos_ = std::move(active_reaction_pos_); active_reaction_pos_.clear(); - for (size_t i = 0; i < active_reactions_.size(); i++) { - active_reaction_pos_[active_reactions_[i]] = i; + for (size_t i = 0; i < active_reaction_types_.size(); i++) { + CHECK(!active_reaction_types_[i].is_empty()); + active_reaction_pos_[active_reaction_types_[i]] = i; } if (td_->auth_manager_->is_bot()) { @@ -8563,7 +8584,10 @@ void MessagesManager::try_reload_message_reactions(DialogId dialog_id, bool is_f vector message_ids; for (auto message_id_it = it->second.message_ids.begin(); message_id_it != it->second.message_ids.end() && message_ids.size() < MAX_MESSAGE_IDS; ++message_id_it) { - message_ids.push_back(*message_id_it); + auto message_id = *message_id_it; + if (pending_read_reactions_.count({dialog_id, message_id}) == 0) { + message_ids.push_back(message_id); + } } for (auto message_id : message_ids) { it->second.message_ids.erase(message_id); @@ -8613,7 +8637,7 @@ void MessagesManager::reget_dialog_action_bar(DialogId dialog_id, const char *so LOG(INFO) << "Reget action bar in " << dialog_id << " from " << source; switch (dialog_id.get_type()) { case DialogType::User: - td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto()); + td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto(), source); return; case DialogType::Chat: case DialogType::Channel: @@ -9589,7 +9613,7 @@ void MessagesManager::after_get_difference() { }), "get missing"); } else if (dialog_id.get_type() == DialogType::Channel) { - schedule_get_channel_difference(dialog_id, 0, message_id, 0.001); + schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "after_get_difference"); } break; } @@ -9735,7 +9759,11 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit; CHECK(-limit < offset && offset <= 0); CHECK(offset < 0 || from_the_end); - CHECK(!from_message_id.is_scheduled()); + if (from_the_end) { + CHECK(from_message_id == MessageId()); + } else { + CHECK(!from_message_id.is_scheduled()); + } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); @@ -9745,11 +9773,7 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ last_received_message_id < d->last_new_message_id) { // new server messages were added to the dialog since the request was sent, but weren't received // they should have been received, so we must repeat the request to get them - if (from_the_end) { - get_history_from_the_end_impl(d, false, false, std::move(promise), "on_get_history"); - } else { - get_history_impl(d, from_message_id, offset, limit, false, false, std::move(promise)); - } + get_history_impl(d, from_message_id, offset, limit, false, false, std::move(promise), "on_get_history"); return; } @@ -10482,8 +10506,12 @@ void MessagesManager::on_get_scheduled_server_messages(DialogId dialog_id, uint3 for (auto &message : messages) { auto message_dialog_id = DialogId::get_message_dialog_id(message); if (message_dialog_id != dialog_id) { - LOG(ERROR) << "Receive " << MessageId::get_message_id(message, true) << " in wrong " << message_dialog_id - << " instead of " << dialog_id << ": " << oneline(to_string(message)); + // server can send messageEmpty for deleted scheduled messages + auto message_id = MessageId::get_message_id(message, true); + if (message_id.is_valid() || message_dialog_id.is_valid()) { + LOG(ERROR) << "Receive " << message_id << " in wrong " << message_dialog_id << " instead of " << dialog_id + << ": " << oneline(to_string(message)); + } continue; } @@ -10594,7 +10622,7 @@ void MessagesManager::on_get_message_public_forwards(int32 total_count, promise.set_value(td_api::make_object(total_count, std::move(result), next_offset)); } -void MessagesManager::delete_messages_from_updates(const vector &message_ids) { +void MessagesManager::delete_messages_from_updates(const vector &message_ids, bool is_permanent) { FlatHashMap, DialogIdHash> deleted_message_ids; FlatHashMap need_update_dialog_pos; vector> deleted_messages; @@ -10606,8 +10634,8 @@ void MessagesManager::delete_messages_from_updates(const vector &mess Dialog *d = get_dialog_by_message_id(message_id); if (d != nullptr) { - auto message = - delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates"); + auto message = delete_message(d, message_id, is_permanent, &need_update_dialog_pos[d->dialog_id], + "delete_messages_from_updates"); CHECK(message != nullptr); LOG_CHECK(message->message_id == message_id) << message_id << " " << message->message_id << " " << d->dialog_id; deleted_message_ids[d->dialog_id].push_back(message->message_id.get()); @@ -10616,8 +10644,8 @@ void MessagesManager::delete_messages_from_updates(const vector &mess if (last_clear_history_message_id_to_dialog_id_.count(message_id)) { d = get_dialog(last_clear_history_message_id_to_dialog_id_[message_id]); CHECK(d != nullptr); - auto message = - delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates"); + auto message = delete_message(d, message_id, is_permanent, &need_update_dialog_pos[d->dialog_id], + "delete_messages_from_updates"); CHECK(message == nullptr); } } @@ -10634,7 +10662,7 @@ void MessagesManager::delete_messages_from_updates(const vector &mess } for (auto &it : deleted_message_ids) { auto dialog_id = it.first; - send_update_delete_messages(dialog_id, std::move(it.second), true); + send_update_delete_messages(dialog_id, std::move(it.second), is_permanent); } } @@ -11231,11 +11259,15 @@ MessagesManager::CanDeleteDialog MessagesManager::can_delete_dialog(const Dialog case DialogType::Chat: // chats can be deleted only for self and can be deleted for everyone by their creator return {true, td_->contacts_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()}; - case DialogType::Channel: - // private supergroups can be deleted for self - return {!is_broadcast_channel(d->dialog_id) && - !td_->contacts_manager_->is_channel_public(d->dialog_id.get_channel_id()), - td_->contacts_manager_->get_channel_can_be_deleted(d->dialog_id.get_channel_id())}; + case DialogType::Channel: { + // private non-forum joined supergroups can be deleted for self + auto channel_id = d->dialog_id.get_channel_id(); + return {!td_->contacts_manager_->is_broadcast_channel(channel_id) && + !td_->contacts_manager_->is_channel_public(channel_id) && + !td_->contacts_manager_->is_forum_channel(channel_id) && + td_->contacts_manager_->get_channel_status(channel_id).is_member(), + td_->contacts_manager_->get_channel_can_be_deleted(channel_id)}; + } case DialogType::SecretChat: if (td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { // in a closed secret chats there is no way to delete messages for both users @@ -11779,10 +11811,13 @@ double MessagesManager::get_next_unload_dialog_delay(Dialog *d) const { return delay + delay * 1e-9 * d->unload_dialog_delay_seed; } -void MessagesManager::unload_dialog(DialogId dialog_id) { +void MessagesManager::unload_dialog(DialogId dialog_id, int32 delay) { if (G()->close_flag()) { return; } + if (delay < 0) { + delay = get_unload_dialog_delay() - 2; + } Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); @@ -11802,7 +11837,7 @@ void MessagesManager::unload_dialog(DialogId dialog_id) { bool has_left_to_unload_messages = false; auto to_unload_message_ids = - find_unloadable_messages(d, G()->unix_time_cached() - get_unload_dialog_delay() + 2, has_left_to_unload_messages); + find_unloadable_messages(d, G()->unix_time_cached() - delay, has_left_to_unload_messages); vector unloaded_message_ids; vector> unloaded_messages; @@ -11933,12 +11968,11 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3"); if (d->notification_info != nullptr) { - d->notification_info->message_notification_group_.max_removed_notification_id = - NotificationId(); // it is not needed anymore - d->notification_info->message_notification_group_.max_removed_message_id = MessageId(); // it is not needed anymore - d->notification_info->mention_notification_group_.max_removed_notification_id = - NotificationId(); // it is not needed anymore - d->notification_info->mention_notification_group_.max_removed_message_id = MessageId(); // it is not needed anymore + // they aren't needed anymore + delete_all_dialog_notifications(d, MessageId::max(), "delete_all_dialog_messages 4"); + + d->notification_info->message_notification_group_.drop_max_removed_notification_id(); + d->notification_info->mention_notification_group_.drop_max_removed_notification_id(); d->notification_info->notification_id_to_message_id_.clear(); } @@ -11994,7 +12028,7 @@ void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, MessageId top return promise.set_error(Status::Error(400, "Chat not found")); } - TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId())); + TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat is not accessible")); @@ -12097,7 +12131,7 @@ void MessagesManager::read_all_dialog_reactions(DialogId dialog_id, MessageId to return promise.set_error(Status::Error(400, "Chat not found")); } - TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId())); + TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!have_input_peer(dialog_id, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Chat is not accessible")); @@ -12188,7 +12222,7 @@ void MessagesManager::read_all_dialog_reactions_on_server(DialogId dialog_id, ui get_erase_log_event_promise(log_event_id, std::move(promise))); } -void MessagesManager::read_message_content_from_updates(MessageId message_id) { +void MessagesManager::read_message_content_from_updates(MessageId message_id, int32 read_date) { if (!message_id.is_valid() || !message_id.is_server()) { LOG(ERROR) << "Incoming update tries to read content of " << message_id; return; @@ -12198,7 +12232,7 @@ void MessagesManager::read_message_content_from_updates(MessageId message_id) { if (d != nullptr) { Message *m = get_message(d, message_id); CHECK(m != nullptr); - read_message_content(d, m, false, "read_message_content_from_updates"); + read_message_content(d, m, false, read_date, "read_message_content_from_updates"); } } @@ -12214,7 +12248,7 @@ void MessagesManager::read_channel_message_content_from_updates(Dialog *d, Messa Message *m = get_message_force(d, message_id, "read_channel_message_content_from_updates"); if (m != nullptr) { - read_message_content(d, m, false, "read_channel_message_content_from_updates"); + read_message_content(d, m, false, 0, "read_channel_message_content_from_updates"); } else { if (!have_input_peer(d->dialog_id, AccessRights::Read)) { LOG(INFO) << "Ignore updateChannelReadMessagesContents in inaccessible " << d->dialog_id; @@ -12235,12 +12269,13 @@ void MessagesManager::read_channel_message_content_from_updates(Dialog *d, Messa } } -bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source) { +bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_read, int32 read_date, + const char *source) { LOG_CHECK(m != nullptr) << source; CHECK(!m->message_id.is_scheduled()); bool is_mention_read = update_message_contains_unread_mention(d, m, false, "read_message_content"); bool is_content_read = update_opened_message_content(m->content.get()); - if (ttl_on_open(d, m, Time::now(), is_local_read)) { + if (ttl_on_open(d, m, Time::now(), is_local_read, read_date)) { is_content_read = true; } @@ -12478,15 +12513,19 @@ void MessagesManager::read_history_inbox(Dialog *d, MessageId max_message_id, in LOG(ERROR) << "Have unknown " << unread_count << " unread messages up to " << max_message_id << " in " << dialog_id << " with last_new_message_id = " << d->last_new_message_id << ", last_message_id = " << d->last_message_id - << ", last_database_message_id = " << d->last_database_message_id << " from " << source; + << ", last_database_message_id = " << d->last_database_message_id << ", and " << d->server_unread_count + << " unread messages up to " << d->last_read_inbox_message_id << " from " << source; + unread_count = d->server_unread_count; + } else { + unread_count = 0; } - unread_count = 0; } - LOG_IF(INFO, - d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id && - (d->notification_info != nullptr && max_message_id > d->notification_info->max_notification_message_id_) && - max_message_id.is_server() && dialog_id.get_type() != DialogType::Channel && !running_get_difference_) + LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id && + (d->notification_info != nullptr && + max_message_id > d->notification_info->max_push_notification_message_id_) && + max_message_id.is_server() && dialog_id.get_type() != DialogType::Channel && + !running_get_difference_) << "Receive read inbox update up to unknown " << max_message_id << " in " << dialog_id << " from " << source << ". Last new is " << d->last_new_message_id << ", unread_count = " << unread_count << ". Possible only for deleted incoming message"; @@ -12496,7 +12535,7 @@ void MessagesManager::read_history_inbox(Dialog *d, MessageId max_message_id, in } if (max_message_id > d->last_new_message_id && dialog_id.get_type() == DialogType::Channel) { - schedule_get_channel_difference(dialog_id, 0, max_message_id, 0.001); + schedule_get_channel_difference(dialog_id, 0, max_message_id, 0.001, "read_history_inbox"); } int32 server_unread_count = calc_new_unread_count(d, max_message_id, MessageType::Server, unread_count); @@ -12858,7 +12897,7 @@ void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId << " after updating last read inbox message to " << message_id << " and unread message count to " << server_unread_count << " + " << local_unread_count << " from " << source; - if (d->notification_info != nullptr && d->notification_info->message_notification_group_.group_id.is_valid()) { + if (d->notification_info != nullptr && d->notification_info->message_notification_group_.is_valid()) { auto total_count = get_dialog_pending_notification_count(d, false); if (total_count == 0) { set_dialog_last_notification(d->dialog_id, d->notification_info->message_notification_group_, 0, @@ -12881,12 +12920,12 @@ void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId total_count = 0; } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, - d->notification_info->message_notification_group_.group_id, NotificationId(), + d->notification_info->message_notification_group_.get_group_id(), NotificationId(), d->last_read_inbox_message_id, total_count, Slice(source) == Slice("view_messages"), Promise()); } - if (d->notification_info != nullptr && d->notification_info->mention_notification_group_.group_id.is_valid() && + if (d->notification_info != nullptr && d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->pinned_message_notification_message_id_ <= d->last_read_inbox_message_id) { // remove pinned message notification when it is read @@ -13149,7 +13188,7 @@ int32 MessagesManager::get_message_date(const tl_object_ptr MessagesManager::get_message_user_ids(const Message *m) { +vector MessagesManager::get_message_user_ids(const Message *m) const { vector user_ids; if (m->sender_user_id.is_valid()) { user_ids.push_back(m->sender_user_id); @@ -13160,17 +13199,7 @@ vector MessagesManager::get_message_user_ids(const Message *m) { 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->content->get_type() == MessageContentType::ChatAddUsers) { - append(user_ids, get_message_content_added_user_ids(m->content.get())); - } - auto deleted_user_id = get_message_content_deleted_user_id(m->content.get()); - if (deleted_user_id.is_valid()) { - user_ids.push_back(deleted_user_id); - } - auto contact_user_id = get_message_content_contact_user_id(m->content.get()); - if (contact_user_id.is_valid()) { - user_ids.push_back(contact_user_id); - } + append(user_ids, get_message_content_min_user_ids(td_, m->content.get())); return user_ids; } @@ -13231,13 +13260,30 @@ void MessagesManager::ttl_on_view(const Dialog *d, Message *m, double view_date, } } -bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read) { +bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read, int32 read_date) { CHECK(!m->message_id.is_scheduled()); if (m->ttl > 0 && m->ttl_expires_at == 0) { - if (!is_local_read && d->dialog_id.get_type() != DialogType::SecretChat) { + int32 passed_after_read_time = 0; + auto can_destroy_immediately = [&] { + if (m->ttl == 0x7FFFFFFF) { + return true; + } + if (is_local_read) { + return false; + } + if (read_date <= 0) { + return d->dialog_id.get_type() != DialogType::SecretChat; + } + passed_after_read_time = max(G()->unix_time() - read_date, 0); + if (m->ttl <= passed_after_read_time) { + return true; + } + return false; + }(); + if (can_destroy_immediately) { on_message_ttl_expired(d, m); } else { - m->ttl_expires_at = m->ttl + now; + m->ttl_expires_at = m->ttl + now - passed_after_read_time; ttl_register_message(d->dialog_id, m, now); } return true; @@ -13465,6 +13511,7 @@ void MessagesManager::hangup() { fail_promise_map(load_scheduled_messages_from_database_queries_); fail_promise_map(run_after_get_channel_difference_); fail_promise_map(search_public_dialogs_queries_); + fail_promise_map(get_history_queries_); while (!pending_channel_on_get_dialogs_.empty()) { auto it = pending_channel_on_get_dialogs_.begin(); auto promise = std::move(it->second.promise); @@ -13501,7 +13548,7 @@ void MessagesManager::init() { is_inited_ = true; td_->notification_settings_manager_->init(); // load scope notification settings - init_stickers_manager(td_); // load available reactions + td_->reaction_manager_->init(); // load available reactions start_time_ = Time::now(); last_channel_pts_jump_warning_time_ = start_time_ - 3600; @@ -13796,11 +13843,12 @@ void MessagesManager::on_send_secret_message_error(int64 random_id, Status error auto file_id = get_message_content_upload_file_id(m->content.get()); if (file_id.is_valid()) { if (G()->close_flag() && G()->use_message_database()) { - // do not send error, message will be re-sent + // do not send error, message will be re-sent after restart return; } - if (begins_with(error.message(), "FILE_PART_") && ends_with(error.message(), "_MISSING")) { - on_send_message_file_part_missing(random_id, to_integer(error.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(error); + if (!bad_parts.empty()) { + on_send_message_file_parts_missing(random_id, std::move(bad_parts)); return; } @@ -13985,7 +14033,7 @@ void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 ran return; } - read_message_content(d, m, false, "open_secret_message"); + read_message_content(d, m, false, 0, "open_secret_message"); } void MessagesManager::on_update_secret_chat_state(SecretChatId secret_chat_id, SecretChatState state) { @@ -13996,15 +14044,13 @@ void MessagesManager::on_update_secret_chat_state(SecretChatId secret_chat_id, S if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { remove_new_secret_chat_notification(d, true); } - if (d->notification_info->message_notification_group_.group_id.is_valid() && + if (d->notification_info->message_notification_group_.is_valid() && get_dialog_pending_notification_count(d, false) == 0 && - !d->notification_info->message_notification_group_.last_notification_id.is_valid()) { - CHECK(d->notification_info->message_notification_group_.last_notification_date == 0); - d->notification_info->message_notification_group_.try_reuse = true; - d->notification_info->message_notification_group_.is_changed = true; + !d->notification_info->message_notification_group_.get_last_notification_id().is_valid()) { + d->notification_info->message_notification_group_.try_reuse(); on_dialog_updated(d->dialog_id, "on_update_secret_chat_state"); } - CHECK(!d->notification_info->mention_notification_group_.group_id + CHECK(!d->notification_info->mention_notification_group_ .is_valid()); // there can't be unread mentions in secret chats } } @@ -14035,10 +14081,10 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId message_info.sender_user_id = user_id; message_info.date = date; message_info.random_id = message->random_id_; - message_info.ttl = message->ttl_; + message_info.ttl = min(message->ttl_, 0x7FFFFFFE); Dialog *d = get_dialog_force(message_info.dialog_id, "on_get_secret_message"); - if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) { + if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_get_secret_message")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } @@ -14055,14 +14101,14 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId 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 = + 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()) { + if (!message_info.reply_header.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; + message_info.reply_header.reply_to_message_id_ = message_it->second; } } } @@ -14134,7 +14180,7 @@ void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_i message_info.content = create_screenshot_taken_message_content(); Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_screenshot_taken"); - if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) { + if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_secret_chat_screenshot_taken")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } @@ -14172,7 +14218,7 @@ void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, Us 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"); - if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) { + if (d == nullptr && have_dialog_info_force(message_info.dialog_id, "on_secret_chat_ttl_changed")) { force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true); d = get_dialog(message_info.dialog_id); } @@ -14357,10 +14403,11 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.reply_header = MessageReplyHeader(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.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.story_full_id_ = StoryFullId(); break; } default: @@ -14460,7 +14507,7 @@ std::pair> MessagesManager::creat // it is safer to completely ignore the message and re-get it through getChannelDifference Dialog *d = get_dialog(dialog_id); if (d != nullptr) { - schedule_get_channel_difference(dialog_id, 0, message_id, 0.001); + schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "create_message"); return {DialogId(), nullptr}; } } @@ -14473,13 +14520,21 @@ 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; + 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 && + reply_to_story_full_id.get_dialog_id() != dialog_id))) { + LOG(ERROR) << "Receive reply to " << reply_to_story_full_id << " in " << dialog_id; + reply_to_story_full_id = {}; + } + UserId via_bot_user_id = message_info.via_bot_user_id; if (!via_bot_user_id.is_valid()) { via_bot_user_id = UserId(); @@ -14513,6 +14568,9 @@ std::pair> MessagesManager::creat } int32 ttl = message_info.ttl; + if (dialog_type == DialogType::SecretChat && ttl == 0x7FFFFFFF) { + ttl = 0x7FFFFFFE; + } bool is_content_secret = is_secret_message_content(ttl, content_type); // must be calculated before TTL is adjusted if (ttl < 0 || (message_id.is_scheduled() && ttl != 0)) { LOG(ERROR) << "Wrong self-destruct time " << ttl << " received in " << message_id << " in " << dialog_id; @@ -14523,11 +14581,11 @@ std::pair> MessagesManager::creat if (message_id.is_scheduled()) { if (message_info.reply_info != nullptr) { - LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reply info"; + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.reply_info); message_info.reply_info = nullptr; } if (message_info.reactions != nullptr) { - LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reactions"; + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.reactions); message_info.reactions = nullptr; } } @@ -14558,16 +14616,33 @@ std::pair> MessagesManager::creat MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot()); if (reactions != nullptr) { reactions->sort_reactions(active_reaction_pos_); - reactions->fix_chosen_reaction(get_my_dialog_id()); + reactions->fix_chosen_reaction(); } bool has_forward_info = message_info.forward_header != nullptr; - if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && have_dialog_info_force(sender_dialog_id)) { + if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && + have_dialog_info_force(sender_dialog_id, "create_message")) { CHECK(sender_dialog_id.get_type() != DialogType::User); force_create_dialog(sender_dialog_id, "create_message", true); } + 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(); + reply_to_story_full_id = StoryFullId(); + noforwards = false; + is_content_secret = false; + } + + if (is_pinned && message_id.is_scheduled()) { + LOG(ERROR) << "Receive pinned " << message_id << " in " << dialog_id; + is_pinned = false; + } + LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id; auto message = make_unique(); @@ -14586,12 +14661,13 @@ std::pair> MessagesManager::creat 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; + message->reply_to_story_full_id = reply_to_story_full_id; message->restriction_reasons = std::move(message_info.restriction_reasons); 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_outgoing && dialog_type != DialogType::User && !is_expired && ((flags & MESSAGE_FLAG_HAS_MENTION) != 0 || content_type == MessageContentType::PinMessage) && !td_->auth_manager_->is_bot(); message->contains_unread_mention = @@ -14613,19 +14689,12 @@ std::pair> MessagesManager::creat 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); - - if (content_type == MessageContentType::ExpiredPhoto || content_type == MessageContentType::ExpiredVideo) { - CHECK(message->ttl == 0); // self-destruct time is ignored/set to 0 if the message has already been expired - if (message->reply_markup != nullptr) { - if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { - message->had_reply_markup = true; - } - message->reply_markup = nullptr; + if (message->reply_markup != nullptr && is_expired) { + // just in case + if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) { + message->had_reply_markup = true; } - message->reply_to_message_id = MessageId(); - message->reply_to_random_id = 0; - message->reply_in_dialog_id = DialogId(); - message->linked_top_thread_message_id = MessageId(); + message->reply_markup = nullptr; } if (message_info.media_album_id != 0) { @@ -14729,7 +14798,7 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, const LOG(INFO) << "Ignore " << old_message_id << "/" << message_id << " received not through update from " << source << ": " << oneline(to_string(get_message_object(dialog_id, new_message.get(), "on_get_message"))); if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) { - schedule_get_channel_difference(dialog_id, 0, message_id, 0.001); + schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "on_get_message"); } return FullMessageId(); } @@ -14782,6 +14851,11 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, const if (d == nullptr) { d = add_dialog_for_new_message(dialog_id, from_update, &need_update_dialog_pos, source); } + Dependencies dependencies; + add_message_dependencies(dependencies, new_message.get()); + for (auto dependent_dialog_id : dependencies.get_dialog_ids()) { + force_create_dialog(dependent_dialog_id, source, true); + } const Message *m = add_message_to_dialog(d, std::move(new_message), false, from_update, &need_update, &need_update_dialog_pos, source); @@ -14925,6 +14999,8 @@ void MessagesManager::remove_dialog_newer_messages(Dialog *d, MessageId from_mes CHECK(!d->last_new_message_id.is_valid()); CHECK(!td_->auth_manager_->is_bot()); + // the function is called to handle channel gaps, and hence must always delete all database messages, + // even first_database_message_id and last_database_message_id are empty delete_all_dialog_messages_from_database(d, MessageId::max(), "remove_dialog_newer_messages"); set_dialog_first_database_message_id(d, MessageId(), "remove_dialog_newer_messages"); set_dialog_last_database_message_id(d, MessageId(), source); @@ -15268,8 +15344,8 @@ void MessagesManager::set_dialog_pinned_message_notification(Dialog *d, MessageI remove_message_notification_id(d, m, true, false, true); on_message_changed(d, m, false, source); } else { - send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id, - notification_info->mention_notification_group_.group_id, old_message_id, false, source); + send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, + notification_info->mention_notification_group_.get_group_id(), old_message_id, false, source); } } notification_info->pinned_message_notification_message_id_ = message_id; @@ -15285,7 +15361,7 @@ void MessagesManager::remove_scope_pinned_message_notifications(NotificationSett dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { Dialog *d = dialog.get(); if (d->notification_settings.use_default_disable_pinned_message_notifications && d->notification_info != nullptr && - d->notification_info->mention_notification_group_.group_id.is_valid() && + d->notification_info->mention_notification_group_.is_valid() && d->notification_info->pinned_message_notification_message_id_.is_valid() && get_dialog_notification_setting_scope(dialog_id) == scope) { remove_dialog_pinned_message_notification(d, "remove_scope_pinned_message_notifications"); @@ -15310,14 +15386,8 @@ void MessagesManager::on_update_scope_mention_notifications(NotificationSettings } void MessagesManager::remove_dialog_mention_notifications(Dialog *d) { - if (d->notification_info == nullptr) { - return; - } - auto notification_group_id = d->notification_info->mention_notification_group_.group_id; - if (!notification_group_id.is_valid()) { - return; - } - if (d->unread_mention_count == 0) { + if (d->notification_info == nullptr || !d->notification_info->mention_notification_group_.is_valid() || + d->unread_mention_count == 0) { return; } CHECK(!d->being_added_message_id.is_valid()); @@ -15337,6 +15407,8 @@ void MessagesManager::remove_dialog_mention_notifications(Dialog *d) { } } + auto notification_group_id = d->notification_info->mention_notification_group_.get_group_id(); + CHECK(notification_group_id.is_valid()); message_ids = td_->notification_manager_->get_notification_group_message_ids(notification_group_id); VLOG(notifications) << "Found active mention notifications in " << message_ids; for (auto &message_id : message_ids) { @@ -15361,20 +15433,20 @@ void MessagesManager::remove_dialog_mention_notifications(Dialog *d) { } } -bool MessagesManager::set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, +void MessagesManager::set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date, NotificationId last_notification_id, const char *source) { - if (group_info.last_notification_date != last_notification_date || - group_info.last_notification_id != last_notification_id) { - VLOG(notifications) << "Set " << group_info.group_id << '/' << dialog_id << " last notification to " - << last_notification_id << " sent at " << last_notification_date << " from " << source; - group_info.last_notification_date = last_notification_date; - group_info.last_notification_id = last_notification_id; - group_info.is_changed = true; + if (group_info.set_last_notification(last_notification_date, last_notification_id, source)) { on_dialog_updated(dialog_id, "set_dialog_last_notification"); - return true; } - return false; +} + +void MessagesManager::set_dialog_last_notification_checked(DialogId dialog_id, NotificationGroupInfo &group_info, + int32 last_notification_date, + NotificationId last_notification_id, const char *source) { + bool is_changed = group_info.set_last_notification(last_notification_date, last_notification_id, source); + CHECK(is_changed); + on_dialog_updated(dialog_id, "set_dialog_last_notification_checked"); } void MessagesManager::on_update_sent_text_message(int64 random_id, @@ -15664,9 +15736,9 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorttl_period_ << " as message auto-delete time in " << dialog_id; dialog->ttl_period_ = 0; } - if (!d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) { - // asynchronously get is_blocked from the server - // TODO add is_blocked to telegram_api::dialog + if (!d->is_is_blocked_for_stories_inited && !td_->auth_manager_->is_bot()) { + // asynchronously get is_blocked_for_stories from the server + // TODO add is_blocked/is_blocked_for_stories to telegram_api::dialog reload_dialog_info_full(dialog_id, "on_get_dialogs init is_blocked"); } else if (!d->is_has_bots_inited && !td_->auth_manager_->is_bot()) { // asynchronously get has_bots from the server @@ -15837,7 +15909,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectororder == DEFAULT_ORDER) { - get_history_from_the_end_impl(d, false, false, Auto(), "on_get_dialog"); + load_last_dialog_message(d, "on_get_dialog"); } } @@ -15991,7 +16063,9 @@ void MessagesManager::add_random_id_to_message_id_correspondence(Dialog *d, int6 auto it = d->random_id_to_message_id.find(random_id); if (it == d->random_id_to_message_id.end() || it->second.get() < message_id.get()) { LOG(INFO) << "Add correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id; - d->random_id_to_message_id[random_id] = message_id; + if (random_id != 0) { + d->random_id_to_message_id[random_id] = message_id; + } } } @@ -16038,6 +16112,22 @@ void MessagesManager::delete_notification_id_to_message_id_correspondence(Notifi } } +bool MessagesManager::is_notification_info_group_id(const NotificationInfo *notification_info, + NotificationGroupId group_id) { + if (!group_id.is_valid() || notification_info == nullptr) { + return false; + } + return notification_info->message_notification_group_.has_group_id(group_id) || + notification_info->mention_notification_group_.has_group_id(group_id); +} + +bool MessagesManager::is_dialog_notification_group_id(const Dialog *d, NotificationGroupId group_id) { + if (d == nullptr) { + return false; + } + return is_notification_info_group_id(d->notification_info.get(), group_id); +} + void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update, bool ignore_pinned_message_notification_removal) { CHECK(d != nullptr); @@ -16049,15 +16139,16 @@ void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool auto from_mentions = is_from_mention_notification_group(m); auto &group_info = get_notification_group_info(d, m); - if (!group_info.group_id.is_valid()) { + if (!group_info.is_valid()) { return; } bool had_active_notification = is_message_notification_active(d, m); auto notification_id = m->notification_id; - VLOG(notifications) << "Remove " << notification_id << " from " << m->message_id << " in " << group_info.group_id - << '/' << d->dialog_id << " from database, was_active = " << had_active_notification + VLOG(notifications) << "Remove " << notification_id << " from " << m->message_id << " in " + << group_info.get_group_id() << '/' << d->dialog_id + << " from database, was_active = " << had_active_notification << ", is_permanent = " << is_permanent; delete_notification_id_to_message_id_correspondence(d->notification_info.get(), notification_id, m->message_id); m->removed_notification_id = m->notification_id; @@ -16067,15 +16158,15 @@ void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool remove_dialog_pinned_message_notification( d, "remove_message_notification_id"); // must be called after notification_id is removed } - if (group_info.last_notification_id == notification_id) { + if (group_info.get_last_notification_id() == notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, from_mentions, m->message_id); } if (is_permanent) { if (had_active_notification) { - send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id, - notification_id, is_permanent, force_update, Promise(), + send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, + group_info.get_group_id(), notification_id, is_permanent, force_update, Promise(), "remove_message_notification_id"); } @@ -16092,15 +16183,14 @@ void MessagesManager::remove_new_secret_chat_notification(Dialog *d, bool is_per auto notification_id = d->notification_info->new_secret_chat_notification_id_; CHECK(notification_id.is_valid()); VLOG(notifications) << "Remove " << notification_id << " about new secret " << d->dialog_id << " from " - << d->notification_info->message_notification_group_.group_id; + << d->notification_info->message_notification_group_.get_group_id(); d->notification_info->new_secret_chat_notification_id_ = NotificationId(); - bool is_fixed = set_dialog_last_notification(d->dialog_id, d->notification_info->message_notification_group_, 0, - NotificationId(), "remove_new_secret_chat_notification"); - CHECK(is_fixed); + set_dialog_last_notification_checked(d->dialog_id, d->notification_info->message_notification_group_, 0, + NotificationId(), "remove_new_secret_chat_notification"); if (is_permanent) { - CHECK(d->notification_info->message_notification_group_.group_id.is_valid()); + CHECK(d->notification_info->message_notification_group_.is_valid()); send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, - d->notification_info->message_notification_group_.group_id, notification_id, true, true, + d->notification_info->message_notification_group_.get_group_id(), notification_id, true, true, Promise(), "remove_new_secret_chat_notification"); } } @@ -16112,18 +16202,18 @@ void MessagesManager::fix_dialog_last_notification_id(Dialog *d, bool from_menti return; } CHECK(!td_->auth_manager_->is_bot()); - auto it = d->ordered_messages.get_const_iterator(message_id); auto &group_info = get_notification_group_info(d, from_mentions); - VLOG(notifications) << "Trying to fix last notification identifier in " << group_info.group_id << " from " - << d->dialog_id << " from " << message_id << "/" << group_info.last_notification_id; + CHECK(group_info.is_valid()); + auto it = d->ordered_messages.get_const_iterator(message_id); + VLOG(notifications) << "Trying to fix last notification identifier in " << group_info.get_group_id() << " from " + << d->dialog_id << " from " << message_id << '/' << group_info.get_last_notification_id(); if (*it != nullptr && ((*it)->get_message_id() == message_id || (*it)->have_next())) { while (*it != nullptr) { const Message *m = get_message(d, (*it)->get_message_id()); if (is_from_mention_notification_group(m) == from_mentions && m->notification_id.is_valid() && is_message_notification_active(d, m) && m->message_id != message_id) { - bool is_fixed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id, - "fix_dialog_last_notification_id"); - CHECK(is_fixed); + set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id, + "fix_dialog_last_notification_id"); return; } --it; @@ -16131,10 +16221,10 @@ void MessagesManager::fix_dialog_last_notification_id(Dialog *d, bool from_menti } if (G()->use_message_database()) { get_message_notifications_from_database( - d->dialog_id, group_info.group_id, group_info.last_notification_id, message_id, 1, + d->dialog_id, group_info.get_group_id(), group_info.get_last_notification_id(), message_id, 1, PromiseCreator::lambda( [actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions, - prev_last_notification_id = group_info.last_notification_id](Result> result) { + prev_last_notification_id = group_info.get_last_notification_id()](Result> result) { send_closure(actor_id, &MessagesManager::do_fix_dialog_last_notification_id, dialog_id, from_mentions, prev_last_notification_id, std::move(result)); })); @@ -16154,9 +16244,12 @@ void MessagesManager::do_fix_dialog_last_notification_id(DialogId dialog_id, boo return; } auto &group_info = get_notification_group_info(d, from_mentions); - VLOG(notifications) << "Receive " << result.ok().size() << " message notifications in " << group_info.group_id << '/' - << dialog_id << " from " << prev_last_notification_id; - if (group_info.last_notification_id != prev_last_notification_id) { + if (!group_info.is_valid()) { + return; + } + VLOG(notifications) << "Receive " << result.ok().size() << " message notifications in " << group_info.get_group_id() + << '/' << dialog_id << " from " << prev_last_notification_id; + if (group_info.get_last_notification_id() != prev_last_notification_id) { // last_notification_id was changed return; } @@ -16171,9 +16264,8 @@ void MessagesManager::do_fix_dialog_last_notification_id(DialogId dialog_id, boo last_notification_id = notifications[0].notification_id; } - bool is_fixed = set_dialog_last_notification(dialog_id, group_info, last_notification_date, last_notification_id, - "do_fix_dialog_last_notification_id"); - CHECK(is_fixed); + set_dialog_last_notification(dialog_id, group_info, last_notification_date, last_notification_id, + "do_fix_dialog_last_notification_id"); } // DO NOT FORGET TO ADD ALL CHANGES OF THIS FUNCTION AS WELL TO delete_all_dialog_messages @@ -16340,8 +16432,7 @@ unique_ptr MessagesManager::do_delete_message(Dialog * d->being_deleted_message_id = MessageId(); if (need_get_history) { - send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, d->dialog_id, true, false, - Promise()); + send_closure_later(actor_id(this), &MessagesManager::load_last_dialog_message_later, d->dialog_id); } on_message_deleted(d, result.get(), is_permanently_deleted, source); @@ -16440,7 +16531,7 @@ void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanen } break; case DialogType::Channel: - if (m->message_id.is_server()) { + if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { td_->contacts_manager_->unregister_message_users({d->dialog_id, m->message_id}, get_message_user_ids(m)); td_->contacts_manager_->unregister_message_channels({d->dialog_id, m->message_id}, get_message_channel_ids(m)); } @@ -16700,7 +16791,7 @@ vector MessagesManager::get_dialogs(DialogListId dialog_list_id, Dialo auto dialog_id = input_dialog_id.get_dialog_id(); if (!have_dialog_force(dialog_id, "get_dialogs")) { if (dialog_id.get_type() == DialogType::SecretChat) { - if (have_dialog_info_force(dialog_id)) { + if (have_dialog_info_force(dialog_id, "get_dialogs")) { force_create_dialog(dialog_id, "get_dialogs"); } } else { @@ -16890,7 +16981,7 @@ void MessagesManager::load_folder_dialog_list(FolderId folder_id, int32 limit, b is_query_sent = true; } if (folder_id == FolderId::main() && folder.last_server_dialog_date_ == MIN_DIALOG_DATE) { - // do not pass promise to not wait for drafts before showing chat list + // do not pass promise to not wait for drafts before showing the chat list load_all_draft_messages(td_); } lock.set_value(Unit()); @@ -16979,8 +17070,8 @@ void MessagesManager::on_get_dialogs_from_database(FolderId folder_id, int32 lim continue; } if (d->folder_id != folder_id) { - LOG(WARNING) << "Skip " << d->dialog_id << " received from database, because it is in " << d->folder_id - << " instead of " << folder_id; + LOG(INFO) << "Skip " << d->dialog_id << " received from database, because it is in " << d->folder_id + << " instead of " << folder_id; dialogs_skipped++; continue; } @@ -17219,12 +17310,12 @@ vector MessagesManager::search_public_dialogs(const string &query, Pro username = short_username.str(); auto resolved_username = resolved_usernames_.get(username); if (!resolved_username.dialog_id.is_valid()) { - td_->create_handler(std::move(promise))->send(username); + send_resolve_dialog_username_query(username, std::move(promise)); return {}; } if (resolved_username.expires_at < Time::now()) { - td_->create_handler(Promise())->send(username); + send_resolve_dialog_username_query(username, Promise()); } auto dialog_id = resolved_username.dialog_id; @@ -17291,6 +17382,25 @@ std::pair> MessagesManager::search_dialogs(const string return {narrow_cast(result.first), std::move(dialog_ids)}; } +std::pair> MessagesManager::search_recently_found_dialogs(const string &query, int32 limit, + Promise &&promise) { + auto result = recently_found_dialogs_.get_dialogs(query.empty() ? limit : 50, std::move(promise)); + if (result.first == 0 || query.empty()) { + return result; + } + + Hints hints; + int rating = 1; + for (auto dialog_id : result.second) { + hints.add(dialog_id.get(), td_->contacts_manager_->get_dialog_search_text(dialog_id)); + hints.set_rating(dialog_id.get(), ++rating); + } + + auto hints_result = hints.search(query, limit, false); + return {narrow_cast(hints_result.first), + transform(hints_result.second, [](int64 key) { return DialogId(key); })}; +} + std::pair> MessagesManager::get_recently_opened_dialogs(int32 limit, Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); return recently_opened_dialogs_.get_dialogs(limit, std::move(promise)); @@ -17581,8 +17691,8 @@ bool MessagesManager::is_dialog_blocked(DialogId dialog_id) const { return d != nullptr && d->is_blocked; } -void MessagesManager::get_blocked_dialogs(int32 offset, int32 limit, - Promise> &&promise) { +void MessagesManager::get_blocked_dialogs(const td_api::object_ptr &block_list, int32 offset, + int32 limit, Promise> &&promise) { if (offset < 0) { return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); } @@ -17591,7 +17701,12 @@ void MessagesManager::get_blocked_dialogs(int32 offset, int32 limit, return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } - td_->create_handler(std::move(promise))->send(offset, limit); + auto block_list_id = BlockListId(block_list); + if (!block_list_id.is_valid()) { + return promise.set_error(Status::Error(400, "Block list must be non-empty")); + } + + td_->create_handler(std::move(promise))->send(block_list_id, offset, limit); } void MessagesManager::on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count, @@ -17658,6 +17773,9 @@ MessagesManager::Message *MessagesManager::get_message_force(FullMessageId full_ } FullMessageId MessagesManager::get_replied_message_id(DialogId dialog_id, const Message *m) { + if (m->reply_to_story_full_id.is_valid()) { + return {}; + } auto full_message_id = get_message_content_replied_message_id(dialog_id, m->content.get()); if (full_message_id.get_message_id().is_valid()) { CHECK(m->reply_to_message_id == MessageId()); @@ -17729,7 +17847,7 @@ FullMessageId MessagesManager::get_replied_message(DialogId dialog_id, MessageId auto replied_message_id = get_replied_message_id(dialog_id, m); if (replied_message_id.get_dialog_id() != dialog_id) { dialog_id = replied_message_id.get_dialog_id(); - if (!have_dialog_info_force(dialog_id)) { + if (!have_dialog_info_force(dialog_id, "get_replied_message")) { promise.set_value(Unit()); return {}; } @@ -18115,13 +18233,8 @@ void MessagesManager::on_get_message_viewers(DialogId dialog_id, MessageViewers Promise> &&promise) { if (!is_recursive) { bool need_participant_list = false; - for (auto message_viewer : message_viewers.message_viewers_) { - auto user_id = message_viewer.get_user_id(); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id << " as viewer of a message in " << dialog_id; - continue; - } - if (!td_->contacts_manager_->have_user_force(user_id)) { + for (auto user_id : message_viewers.get_user_ids()) { + if (!td_->contacts_manager_->have_user_force(user_id, "on_get_message_viewers")) { need_participant_list = true; } } @@ -18135,7 +18248,8 @@ void MessagesManager::on_get_message_viewers(DialogId dialog_id, MessageViewers switch (dialog_id.get_type()) { case DialogType::Chat: - return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise)); + return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise), + "on_get_message_viewers"); case DialogType::Channel: return td_->contacts_manager_->get_channel_participants( dialog_id.get_channel_id(), td_api::make_object(), string(), 0, @@ -18224,11 +18338,11 @@ void MessagesManager::reload_dialog_info_full(DialogId dialog_id, const char *so switch (dialog_id.get_type()) { case DialogType::User: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_user_full, dialog_id.get_user_id(), - Promise()); + Promise(), source); return; case DialogType::Chat: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_chat_full, dialog_id.get_chat_id(), - Promise()); + Promise(), source); return; case DialogType::Channel: send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_channel_full, @@ -18243,6 +18357,18 @@ void MessagesManager::reload_dialog_info_full(DialogId dialog_id, const char *so } } +void MessagesManager::reload_dialog_notification_settings(DialogId dialog_id, Promise &&promise, + const char *source) { + LOG(INFO) << "Reload notification settings for " << dialog_id << " from " << source; + const Dialog *d = get_dialog(dialog_id); + if (d != nullptr) { + td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id, MessageId(), + std::move(promise)); + } else { + send_get_dialog_query(dialog_id, std::move(promise), 0, source); + } +} + void MessagesManager::on_dialog_info_full_invalidated(DialogId dialog_id) { Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->open_count > 0) { @@ -18475,9 +18601,6 @@ bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Mes if (m->message_id.is_scheduled() || !m->message_id.is_server()) { return false; } - if (m->message_id.is_scheduled() || !m->message_id.is_server()) { - return false; - } if (is_discussion_message(dialog_id, m)) { return false; } @@ -18676,53 +18799,24 @@ void MessagesManager::get_message_link_info(Slice url, Promise } auto info = r_message_link_info.move_as_ok(); - CHECK(info.username.empty() == info.channel_id.is_valid()); - - bool have_dialog = info.username.empty() ? td_->contacts_manager_->have_channel_force(info.channel_id) - : resolve_dialog_username(info.username).is_valid(); - if (!have_dialog) { - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), info, promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - return promise.set_value(std::move(info)); - } - send_closure(actor_id, &MessagesManager::on_get_message_link_dialog, std::move(info), std::move(promise)); - }); - if (info.username.empty()) { - td_->contacts_manager_->reload_channel(info.channel_id, std::move(query_promise)); - } else { - td_->create_handler(std::move(query_promise))->send(info.username); - } - return; - } - - return on_get_message_link_dialog(std::move(info), std::move(promise)); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), info, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_value(std::move(info)); + } + send_closure(actor_id, &MessagesManager::on_get_message_link_dialog, std::move(info), result.ok(), + std::move(promise)); + }); + resolve_dialog(info.username, info.channel_id, std::move(query_promise)); } -void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, Promise &&promise) { +void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, DialogId dialog_id, + Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - DialogId dialog_id; - if (info.username.empty()) { - if (!td_->contacts_manager_->have_channel(info.channel_id)) { - return promise.set_error(Status::Error(500, "Chat info not found")); - } - - dialog_id = DialogId(info.channel_id); - force_create_dialog(dialog_id, "on_get_message_link_dialog"); - } else { - dialog_id = resolve_dialog_username(info.username); - if (dialog_id.is_valid()) { - force_create_dialog(dialog_id, "on_get_message_link_dialog", true); - } - } Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog"); - if (d == nullptr) { - return promise.set_error(Status::Error(500, "Chat not found")); - } - - auto message_id = info.message_id; - get_message_force_from_server(d, message_id, + CHECK(d != nullptr); + get_message_force_from_server(d, info.message_id, PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info), dialog_id, promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { @@ -18932,7 +19026,7 @@ Status MessagesManager::set_dialog_draft_message(DialogId dialog_id, MessageId t } TRY_STATUS(can_send_message(dialog_id)); - TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, MessageId())); + TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); TRY_RESULT(new_draft_message, DraftMessage::get_draft_message(td_, dialog_id, top_thread_message_id, std::move(draft_message))); @@ -19417,13 +19511,17 @@ void MessagesManager::toggle_dialog_is_translatable_on_server(DialogId dialog_id ->send(dialog_id, is_translatable); } -Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_ptr &sender, - bool is_blocked) { +Status MessagesManager::set_message_sender_block_list(const td_api::object_ptr &sender, + const td_api::object_ptr &block_list) { TRY_RESULT(dialog_id, get_message_sender_dialog_id(td_, sender, true, false)); + BlockListId block_list_id(block_list); + bool is_blocked = block_list_id == BlockListId::main(); + bool is_blocked_for_stories = block_list_id == BlockListId::stories(); switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { - return Status::Error(400, is_blocked ? Slice("Can't block self") : Slice("Can't unblock self")); + return Status::Error( + 400, is_blocked || is_blocked_for_stories ? Slice("Can't block self") : Slice("Can't unblock self")); } break; case DialogType::Chat: @@ -19433,7 +19531,7 @@ Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_pt break; case DialogType::SecretChat: { auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id)) { + if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id, "set_message_sender_block_list")) { return Status::Error(400, "The secret chat can't be blocked"); } dialog_id = DialogId(user_id); @@ -19444,21 +19542,21 @@ Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_pt UNREACHABLE(); } - Dialog *d = get_dialog_force(dialog_id, "toggle_message_sender_is_blocked"); + Dialog *d = get_dialog_force(dialog_id, "set_message_sender_block_list"); if (!have_input_peer(dialog_id, AccessRights::Know)) { return Status::Error(400, "Message sender isn't accessible"); } if (d != nullptr) { - if (is_blocked == d->is_blocked) { + if (is_blocked == d->is_blocked && is_blocked_for_stories == d->is_blocked_for_stories) { return Status::OK(); } - set_dialog_is_blocked(d, is_blocked); + set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } else { CHECK(dialog_id.get_type() == DialogType::User); - td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked); + td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } - toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, 0); + toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, is_blocked_for_stories, 0); return Status::OK(); } @@ -19466,11 +19564,13 @@ class MessagesManager::ToggleDialogIsBlockedOnServerLogEvent { public: DialogId dialog_id_; bool is_blocked_; + bool is_blocked_for_stories_; template void store(StorerT &storer) const { BEGIN_STORE_FLAGS(); STORE_FLAG(is_blocked_); + STORE_FLAG(is_blocked_for_stories_); END_STORE_FLAGS(); td::store(dialog_id_, storer); @@ -19480,25 +19580,28 @@ class MessagesManager::ToggleDialogIsBlockedOnServerLogEvent { void parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_blocked_); + PARSE_FLAG(is_blocked_for_stories_); END_PARSE_FLAGS(); td::parse(dialog_id_, parser); } }; -uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked) { - ToggleDialogIsBlockedOnServerLogEvent log_event{dialog_id, is_blocked}; +uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked, + bool is_blocked_for_stories) { + ToggleDialogIsBlockedOnServerLogEvent log_event{dialog_id, is_blocked, is_blocked_for_stories}; return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsBlockedOnServer, get_log_event_storer(log_event)); } -void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id) { +void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, + bool is_blocked_for_stories, uint64 log_event_id) { if (log_event_id == 0 && G()->use_message_database()) { - log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked); + log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked, is_blocked_for_stories); } td_->create_handler(get_erase_log_event_promise(log_event_id)) - ->send(dialog_id, is_blocked); + ->send(dialog_id, is_blocked, is_blocked_for_stories); } Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) { @@ -19640,7 +19743,7 @@ bool MessagesManager::is_dialog_mention_notifications_disabled(const Dialog *d) void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise &&promise) { if (!have_input_peer(dialog_id, AccessRights::Read)) { - if (!have_dialog_info_force(dialog_id)) { + if (!have_dialog_info_force(dialog_id, "create dialog")) { return promise.set_error(Status::Error(400, "Chat info not found")); } if (!have_input_peer(dialog_id, AccessRights::Read)) { @@ -19847,6 +19950,17 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess source == MessageSource::DialogList || source == MessageSource::Other; bool need_mark_download_as_viewed = is_dialog_history || source == MessageSource::HistoryPreview || source == MessageSource::Search || source == MessageSource::Other; + bool need_invalidate_authentication_code = + dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && + source == MessageSource::Screenshot; + auto dialog_type = dialog_id.get_type(); + bool need_screenshot_notification = source == MessageSource::Screenshot && + (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) && + can_send_message(dialog_id).is_ok(); + + if (source == MessageSource::DialogList && dialog_type == DialogType::User) { + td_->contacts_manager_->on_view_user_active_stories({dialog_id.get_user_id()}); + } // keep only valid message identifiers size_t pos = 0; @@ -19892,7 +20006,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess // get information about thread of the messages MessageId top_thread_message_id; if (source == MessageSource::MessageThreadHistory) { - if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) { + if (dialog_type != DialogType::Channel || is_broadcast_channel(dialog_id)) { return Status::Error(400, "There are no message threads in the chat"); } @@ -19972,6 +20086,8 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess vector read_reaction_message_ids; vector new_viewed_message_ids; vector viewed_reaction_message_ids; + vector authentication_codes; + vector screenshotted_secret_message_ids; for (auto message_id : message_ids) { auto *m = get_message_force(d, message_id, "view_messages 4"); if (m != nullptr) { @@ -20014,6 +20130,11 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess } } + auto story_full_id = get_message_content_story_full_id(td_, m->content.get()); + if (story_full_id.is_valid()) { + td_->story_manager_->view_story_message(story_full_id); + } + if (m->message_id.is_server() && d->open_count > 0) { auto &info = dialog_viewed_messages_[dialog_id]; if (info == nullptr) { @@ -20031,8 +20152,17 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess view_id = ++info->current_view_id; info->recently_viewed_messages[view_id] = message_id; } + + if (need_invalidate_authentication_code) { + extract_authentication_codes(dialog_id, m, authentication_codes); + } + if (need_screenshot_notification && !m->is_outgoing) { + if ((dialog_type == DialogType::User && m->is_content_secret) || dialog_type == DialogType::SecretChat) { + screenshotted_secret_message_ids.push_back(m->message_id); + } + } } else if (!message_id.is_yet_unsent() && message_id > max_message_id) { - if ((d->notification_info != nullptr && message_id <= d->notification_info->max_notification_message_id_) || + if ((d->notification_info != nullptr && message_id <= d->notification_info->max_push_notification_message_id_) || message_id <= d->last_new_message_id || message_id <= max_thread_message_id) { max_message_id = message_id; } @@ -20076,6 +20206,12 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess if (td_->is_online() && dialog_viewed_messages_.count(dialog_id) != 0) { update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD); } + if (!authentication_codes.empty()) { + td_->account_manager_->invalidate_authentication_codes(std::move(authentication_codes)); + } + if (!screenshotted_secret_message_ids.empty()) { + send_screenshot_taken_notification_message(d); + } if (!need_read) { return Status::OK(); @@ -20126,7 +20262,11 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess return Status::OK(); } if (source == MessageSource::ForumTopicHistory) { - // TODO read forum topic history + if (!forum_topic_id.is_valid() || !max_message_id.is_valid()) { + return Status::OK(); + } + + td_->forum_topic_manager_->read_forum_topic_messages(dialog_id, forum_topic_id, max_message_id); return Status::OK(); } @@ -20214,7 +20354,7 @@ Status MessagesManager::open_message_content(FullMessageId full_message_id) { return Status::OK(); } - if (read_message_content(d, m, true, "open_message_content") && + if (read_message_content(d, m, true, 0, "open_message_content") && (m->message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat)) { read_message_contents_on_server(dialog_id, {m->message_id}, 0, Auto()); } @@ -20367,6 +20507,7 @@ void MessagesManager::open_dialog(Dialog *d) { switch (dialog_id.get_type()) { case DialogType::User: + td_->contacts_manager_->on_view_user_active_stories({dialog_id.get_user_id()}); break; case DialogType::Chat: td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id()); @@ -20402,7 +20543,7 @@ void MessagesManager::open_dialog(Dialog *d) { // to repair dialog action bar auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { - td_->contacts_manager_->reload_user_full(user_id, Promise()); + td_->contacts_manager_->reload_user_full(user_id, Promise(), "open_dialog"); } break; } @@ -20473,6 +20614,11 @@ void MessagesManager::close_dialog(Dialog *d) { CHECK(!d->has_unload_timeout); pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; + + if (d->need_unload_on_close) { + unload_dialog(dialog_id, 0); + d->need_unload_on_close = false; + } } dialog_viewed_messages_.erase(dialog_id); @@ -20651,13 +20797,14 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * !need_hide_dialog_draft_message(d->dialog_id) ? get_draft_message_object(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_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), - get_dialog_has_protected_content(d->dialog_id), is_translatable, d->is_marked_as_unread, d->is_blocked, + get_chat_positions_object(d), get_default_message_sender_object(d), block_list_id.get_block_list_object(), + get_dialog_has_protected_content(d->dialog_id), is_translatable, d->is_marked_as_unread, get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_, can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), @@ -20701,7 +20848,8 @@ std::pair MessagesManager::get_dialog_mute_until(DialogId dialog_id int64 MessagesManager::get_dialog_notification_ringtone_id(DialogId dialog_id, const Dialog *d) const { CHECK(!td_->auth_manager_->is_bot()); - if (d == nullptr || !d->notification_settings.is_synchronized || d->notification_settings.use_default_mute_until) { + if (d == nullptr || !d->notification_settings.is_synchronized || + is_notification_sound_default(d->notification_settings.sound)) { auto scope = get_dialog_notification_setting_scope(dialog_id); return get_notification_sound_ringtone_id(td_->notification_settings_manager_->get_scope_notification_sound(scope)); } @@ -20725,6 +20873,47 @@ NotificationSettingsScope MessagesManager::get_dialog_notification_setting_scope } } +StoryNotificationSettings MessagesManager::get_story_notification_settings(DialogId dialog_id) { + bool need_dialog_settings = false; + bool need_top_dialogs = false; + bool are_muted = false; + bool hide_sender = false; + int64 ringtone_id = 0; + + const Dialog *d = get_dialog_force(dialog_id, "get_story_notification_settings"); + if (d == nullptr || !d->notification_settings.is_synchronized) { + need_dialog_settings = true; + } + auto scope = get_dialog_notification_setting_scope(dialog_id); + if (need_dialog_settings || d->notification_settings.use_default_mute_stories) { + bool use_default; + std::tie(use_default, are_muted) = td_->notification_settings_manager_->get_scope_mute_stories(scope); + if (use_default) { + auto is_top_dialog = td_->top_dialog_manager_->is_top_dialog(TopDialogCategory::Correspondent, 5, dialog_id); + if (is_top_dialog == -1) { + need_top_dialogs = true; + } else { + are_muted = is_top_dialog != 0; + } + } + } else { + are_muted = d->notification_settings.mute_stories; + } + if (need_dialog_settings || d->notification_settings.use_default_hide_story_sender) { + hide_sender = td_->notification_settings_manager_->get_scope_hide_story_sender(scope); + } else { + hide_sender = d->notification_settings.hide_story_sender; + } + if (need_dialog_settings || is_notification_sound_default(d->notification_settings.story_sound)) { + ringtone_id = get_notification_sound_ringtone_id( + td_->notification_settings_manager_->get_scope_story_notification_sound(scope)); + } else { + ringtone_id = get_notification_sound_ringtone_id(d->notification_settings.story_sound); + } + + return {need_dialog_settings, need_top_dialogs, are_muted, hide_sender, ringtone_id}; +} + vector MessagesManager::get_dialog_notification_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, bool force, Promise &&promise) { @@ -20899,7 +21088,7 @@ tl_object_ptr MessagesManager::get_dialog_history(DialogId dia LOG(INFO) << "Get " << (only_local ? "local " : "") << "history in " << dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit << ", " << left_tries - << " tries left, have_full_history = " << d->have_full_history + << " tries left, is_empty = " << d->is_empty << ", have_full_history = " << d->have_full_history << ", have_full_history_source = " << d->have_full_history_source; auto message_ids = d->ordered_messages.get_history(d->last_message_id, from_message_id, offset, limit, @@ -20909,11 +21098,10 @@ tl_object_ptr MessagesManager::get_dialog_history(DialogId dia CHECK(offset == 0); preload_newer_messages(d, message_ids[0]); preload_older_messages(d, message_ids.back()); - } else if (message_ids.size() < static_cast(limit) && left_tries != 0 && - !(d->is_empty && d->have_full_history && left_tries < 3)) { + } else if (limit > 0 && left_tries != 0 && !(d->is_empty && d->have_full_history && left_tries < 3)) { // there can be more messages in the database or on the server, need to load them - send_closure_later(actor_id(this), &MessagesManager::load_messages, dialog_id, from_message_id, offset, - limit - static_cast(message_ids.size()), left_tries, only_local, std::move(promise)); + send_closure_later(actor_id(this), &MessagesManager::load_messages, dialog_id, from_message_id, offset, limit, + left_tries, only_local, std::move(promise)); return nullptr; } @@ -21063,14 +21251,15 @@ void MessagesManager::do_read_history_on_server(DialogId dialog_id) { auto it = updated_read_history_message_ids_.find(dialog_id); if (it != updated_read_history_message_ids_.end()) { - for (auto top_thread_message_id : it->second) { + auto top_thread_message_ids = std::move(it->second); + updated_read_history_message_ids_.erase(it); + for (auto top_thread_message_id : top_thread_message_ids) { if (!top_thread_message_id.is_valid()) { read_history_on_server_impl(d, MessageId()); } else { read_message_thread_history_on_server_impl(d, top_thread_message_id, MessageId()); } } - updated_read_history_message_ids_.erase(it); } } @@ -22639,8 +22828,8 @@ void MessagesManager::on_get_affected_history(DialogId dialog_id, AffectedHistor } std::function MessagesManager::get_get_message_date(const Dialog *d) const { - return [this, d](MessageId message_id) { - auto *m = get_message(d, message_id); + return [d](MessageId message_id) { + const auto *m = get_message_static(d, message_id); CHECK(m != nullptr); return m->date; }; @@ -23034,6 +23223,9 @@ unique_ptr MessagesManager::parse_message(Dialog *d, M break; } } + if (m->reactions != nullptr) { + m->reactions->fix_my_recent_chooser_dialog_id(get_my_dialog_id()); + } } if (m->contains_mention && td_->auth_manager_->is_bot()) { m->contains_mention = false; @@ -23056,6 +23248,9 @@ unique_ptr MessagesManager::parse_message(Dialog *d, M break; } } + if (m->is_pinned && is_scheduled) { + m->is_pinned = false; + } LOG(INFO) << "Loaded " << m->message_id << " in " << dialog_id << " of size " << value.size() << " from database"; return message; @@ -23063,9 +23258,10 @@ unique_ptr MessagesManager::parse_message(Dialog *d, M void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, MessageId old_last_database_message_id, int32 offset, int32 limit, - bool from_the_end, bool only_local, - vector &&messages, Promise &&promise) { + bool only_local, vector &&messages, + Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); + bool from_the_end = from_message_id == MessageId::max(); CHECK(-limit < offset && offset <= 0); CHECK(offset < 0 || from_the_end); CHECK(!from_message_id.is_scheduled()); @@ -23079,11 +23275,8 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId LOG(INFO) << "Reget chat history from database in " << dialog_id << ", because last database message was changed from " << old_last_database_message_id << " to " << d->last_database_message_id; - if (from_the_end) { - get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 20"); - } else { - get_history_impl(d, from_message_id, offset, limit, true, only_local, std::move(promise)); - } + get_history_impl(d, from_message_id, offset, limit, true, only_local, std::move(promise), + "on_get_history_from_database 20"); return; } @@ -23185,7 +23378,8 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId if (first_received_message_id < d->last_database_message_id) { set_dialog_last_database_message_id(d, first_received_message_id, "on_get_history_from_database 12"); - get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 21"); + get_history_impl(d, MessageId::max(), 0, -1, true, only_local, std::move(promise), + "on_get_history_from_database 21"); return; } @@ -23216,6 +23410,11 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId } } + if (!first_added_message_id.is_valid() && !only_local && dialog_id.get_type() != DialogType::SecretChat) { + load_messages_impl(d, from_message_id, offset, limit, 1, false, std::move(promise)); + return; + } + bool need_update_dialog_pos = false; if (from_the_end && last_added_message_id.is_valid()) { CHECK(next_message_id.is_valid()); @@ -23274,72 +23473,45 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId promise.set_value(Unit()); } -void MessagesManager::get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local, - Promise &&promise) { - get_history_from_the_end_impl(get_dialog(dialog_id), from_database, only_local, std::move(promise), - "get_history_from_the_end"); +void MessagesManager::load_last_dialog_message_later(DialogId dialog_id) { + if (G()->close_flag()) { + return; + } + load_last_dialog_message(get_dialog(dialog_id), "load_last_dialog_message"); } -void MessagesManager::get_history_from_the_end_impl(const Dialog *d, bool from_database, bool only_local, - Promise &&promise, const char *source) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(d != nullptr); +void MessagesManager::load_last_dialog_message(const Dialog *d, const char *source) { + get_history_impl(d, MessageId::max(), 0, -1, true, false, Promise(), source); +} - auto dialog_id = d->dialog_id; - if (!have_input_peer(dialog_id, AccessRights::Read)) { - // can't get history in dialogs without read access - return promise.set_value(Unit()); - } - if (!d->first_database_message_id.is_valid() && !d->have_full_history) { - from_database = false; - } - int32 limit = MAX_GET_HISTORY; - if (from_database && G()->use_message_database()) { - if (!promise) { - // repair last database message ID - limit = 10; - } - LOG(INFO) << "Get history from the end of " << dialog_id << " from database from " << source; - MessageDbMessagesQuery db_query; - db_query.dialog_id = dialog_id; - db_query.from_message_id = MessageId::max(); - db_query.limit = limit; - G()->td_db()->get_message_db_async()->get_messages( - db_query, PromiseCreator::lambda([dialog_id, old_last_database_message_id = d->last_database_message_id, - only_local, limit, actor_id = actor_id(this), promise = std::move(promise)]( - vector messages) mutable { - send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, MessageId::max(), - old_last_database_message_id, 0, limit, true, only_local, std::move(messages), - std::move(promise)); - })); - } else { - if (only_local || dialog_id.get_type() == DialogType::SecretChat || d->last_message_id.is_valid()) { - // if last message is known, there are no reasons to get message history from server from the end - promise.set_value(Unit()); - return; - } - if (!promise && !G()->use_message_database()) { - // repair last message ID - limit = 10; - } +void MessagesManager::on_get_history_finished(const PendingGetHistoryQuery &query, Result &&result) { + G()->ignore_result_if_closing(result); - LOG(INFO) << "Get history from the end of " << dialog_id << " from server from " << source; - td_->create_handler(std::move(promise)) - ->send_get_from_the_end(dialog_id, d->last_new_message_id, limit); + auto it = get_history_queries_.find(query); + if (it == get_history_queries_.end()) { + return; } -} + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_history_queries_.erase(it); -void MessagesManager::get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, - bool from_database, bool only_local, Promise &&promise) { - get_history_impl(get_dialog(dialog_id), from_message_id, offset, limit, from_database, only_local, - std::move(promise)); + if (result.is_ok()) { + set_promises(promises); + } else { + fail_promises(promises, result.move_as_error()); + } } void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, - bool from_database, bool only_local, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); + bool from_database, bool only_local, Promise &&promise, + const char *source) { CHECK(d != nullptr); - CHECK(from_message_id.is_valid()); + bool from_the_end = from_message_id == MessageId() || from_message_id == MessageId::max(); + if (!from_the_end) { + CHECK(from_message_id.is_valid()); + } else { + from_message_id = MessageId::max(); + } auto dialog_id = d->dialog_id; if (!have_input_peer(dialog_id, AccessRights::Read)) { @@ -23350,9 +23522,51 @@ void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_i !d->have_full_history) { from_database = false; } - if (from_database && G()->use_message_database()) { + if (!G()->use_message_database()) { + from_database = false; + } + + if (from_the_end) { + // load only 10 messages when repairing the last message and can't save the result to the database + limit = !promise && (from_database || !G()->use_message_database()) ? max(limit, 10) : MAX_GET_HISTORY; + offset = 0; + } else if (offset >= -1) { + // get history before some server or local message + limit = clamp(limit + offset + 1, MAX_GET_HISTORY / 2, MAX_GET_HISTORY); + offset = -1; + } else { + // get history around some server or local message + int32 messages_to_load = max(MAX_GET_HISTORY, limit); + int32 max_add = max(messages_to_load - limit - 2, 0); + offset -= max_add; + limit = MAX_GET_HISTORY; + } + + PendingGetHistoryQuery query; + query.dialog_id_ = dialog_id; + query.from_message_id_ = from_message_id; + query.offset_ = offset; + query.limit_ = limit; + query.from_database_ = from_database; + query.only_local_ = only_local; + + if (from_database) { LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset - << " and limit " << limit << " from database"; + << " and limit " << limit << " from database from " << source; + + query.old_last_message_id_ = d->last_database_message_id; + + auto &promises = get_history_queries_[query]; + promises.push_back(std::move(promise)); + if (promises.size() != 1) { + // query has already been sent, just wait for the result + return; + } + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), query](Result &&result) { + send_closure(actor_id, &MessagesManager::on_get_history_finished, query, std::move(result)); + }); + MessageDbMessagesQuery db_query; db_query.dialog_id = dialog_id; db_query.from_message_id = from_message_id; @@ -23360,22 +23574,43 @@ void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_i db_query.limit = limit; G()->td_db()->get_message_db_async()->get_messages( db_query, - PromiseCreator::lambda([dialog_id, from_message_id, old_last_database_message_id = d->last_database_message_id, - offset, limit, only_local, actor_id = actor_id(this), - promise = std::move(promise)](vector messages) mutable { + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_message_id, + old_last_database_message_id = d->last_database_message_id, offset, limit, only_local, + promise = std::move(query_promise)](vector messages) mutable { send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, from_message_id, - old_last_database_message_id, offset, limit, false, only_local, std::move(messages), + old_last_database_message_id, offset, limit, only_local, std::move(messages), std::move(promise)); })); } else { - if (only_local || dialog_id.get_type() == DialogType::SecretChat) { + if (only_local || dialog_id.get_type() == DialogType::SecretChat || + (from_the_end && d->last_message_id.is_valid())) { + // if the last message is known, there are no reasons to get message history from server from the end return promise.set_value(Unit()); } - LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset - << " and limit " << limit << " from server"; - td_->create_handler(std::move(promise)) - ->send(dialog_id, from_message_id.get_next_server_message_id(), d->last_new_message_id, offset, limit); + query.old_last_message_id_ = d->last_new_message_id; + + auto &promises = get_history_queries_[query]; + promises.push_back(std::move(promise)); + if (promises.size() != 1) { + // query has already been sent, just wait for the result + return; + } + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), query](Result &&result) { + send_closure(actor_id, &MessagesManager::on_get_history_finished, query, std::move(result)); + }); + + if (from_the_end) { + LOG(INFO) << "Get history from the end of " << dialog_id << " from server from " << source; + td_->create_handler(std::move(query_promise)) + ->send_get_from_the_end(dialog_id, d->last_new_message_id, limit); + } else { + LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset + << " and limit " << limit << " from server from " << source; + td_->create_handler(std::move(query_promise)) + ->send(dialog_id, from_message_id.get_next_server_message_id(), d->last_new_message_id, offset, limit); + } } } @@ -23398,22 +23633,8 @@ void MessagesManager::load_messages_impl(const Dialog *d, MessageId from_message only_local = true; } bool from_database = (left_tries > 2 || only_local) && G()->use_message_database(); - if (from_message_id == MessageId()) { - get_history_from_the_end_impl(d, from_database, only_local, std::move(promise), "load_messages_impl"); - return; - } - if (offset >= -1) { - // get history before some server or local message - limit = min(max(limit + offset + 1, MAX_GET_HISTORY / 2), MAX_GET_HISTORY); - offset = -1; - } else { - // get history around some server or local message - int32 messages_to_load = max(MAX_GET_HISTORY, limit); - int32 max_add = max(messages_to_load - limit - 2, 0); - offset -= max_add; - limit = MAX_GET_HISTORY; - } - get_history_impl(d, from_message_id, offset, limit, from_database, only_local, std::move(promise)); + get_history_impl(d, from_message_id, offset, limit, from_database, only_local, std::move(promise), + "load_messages_impl"); } vector MessagesManager::get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result, @@ -23510,7 +23731,7 @@ void MessagesManager::load_dialog_scheduled_messages(DialogId dialog_id, bool fr if (queries.size() == 1) { G()->td_db()->get_message_db_async()->get_scheduled_messages( dialog_id, 1000, - PromiseCreator::lambda([dialog_id, actor_id = actor_id(this)](vector messages) { + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](vector messages) { send_closure(actor_id, &MessagesManager::on_get_scheduled_messages_from_database, dialog_id, std::move(messages)); })); @@ -23577,10 +23798,6 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id Result> MessagesManager::get_message_available_reactions( FullMessageId full_message_id, int32 row_size) { - if (row_size < 5 || row_size > 25) { - row_size = 8; - } - auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions"); if (d == nullptr) { @@ -23592,102 +23809,12 @@ Result> MessagesManager::get_mess return Status::Error(400, "Message not found"); } - auto available_reactions = get_message_available_reactions(d, m, false); - bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); - bool show_premium = is_premium; - - auto recent_reactions = get_recent_reactions(td_); - auto top_reactions = get_top_reactions(td_); - auto active_reactions = get_message_active_reactions(d, m); - LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions - << " and recent reactions " << recent_reactions; - if (active_reactions.allow_custom_ && active_reactions.allow_all_) { - for (auto &reaction : recent_reactions) { - if (is_custom_reaction(reaction)) { - show_premium = true; - } - } - for (auto &reaction : top_reactions) { - if (is_custom_reaction(reaction)) { - show_premium = true; - } - } - } - - FlatHashSet all_available_reactions; - for (const auto &reaction : available_reactions.reactions_) { - CHECK(!reaction.empty()); - all_available_reactions.insert(reaction); - } - - vector> top_reaction_objects; - vector> recent_reaction_objects; - vector> popular_reaction_objects; - vector> last_reaction_objects; - - FlatHashSet added_custom_reactions; - auto add_reactions = [&](vector> &reaction_objects, - const vector &reactions) { - for (auto &reaction : reactions) { - if (all_available_reactions.erase(reaction) != 0) { - // add available reaction - if (is_custom_reaction(reaction)) { - added_custom_reactions.insert(reaction); - } - reaction_objects.push_back( - td_api::make_object(get_reaction_type_object(reaction), false)); - } else if (is_custom_reaction(reaction) && available_reactions.allow_custom_ && - added_custom_reactions.insert(reaction).second) { - // add implicitly available custom reaction - reaction_objects.push_back( - td_api::make_object(get_reaction_type_object(reaction), !is_premium)); - } else { - // skip the reaction - } - } - }; - if (show_premium) { - if (top_reactions.size() > 2 * static_cast(row_size)) { - top_reactions.resize(2 * static_cast(row_size)); - } - add_reactions(top_reaction_objects, top_reactions); - - if (!recent_reactions.empty()) { - add_reactions(recent_reaction_objects, recent_reactions); - } - } else { - add_reactions(top_reaction_objects, top_reactions); - } - add_reactions(last_reaction_objects, active_reactions_); - add_reactions(last_reaction_objects, available_reactions.reactions_); - - if (show_premium) { - if (recent_reactions.empty()) { - popular_reaction_objects = std::move(last_reaction_objects); - } else { - auto max_objects = 10 * static_cast(row_size); - if (recent_reaction_objects.size() + last_reaction_objects.size() > max_objects) { - if (last_reaction_objects.size() < max_objects) { - recent_reaction_objects.resize(max_objects - last_reaction_objects.size()); - } else { - recent_reaction_objects.clear(); - } - } - append(recent_reaction_objects, std::move(last_reaction_objects)); - } - } else { - append(top_reaction_objects, std::move(last_reaction_objects)); - } - - CHECK(all_available_reactions.empty()); - - return td_api::make_object( - std::move(top_reaction_objects), std::move(recent_reaction_objects), std::move(popular_reaction_objects), - available_reactions.allow_custom_); + return td_->reaction_manager_->get_sorted_available_reactions(get_message_available_reactions(d, m, false), + get_message_active_reactions(d, m), row_size); } ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m, - bool dissalow_custom_for_non_premium) { + bool disallow_custom_for_non_premium) { CHECK(d != nullptr); CHECK(m != nullptr); auto active_reactions = get_message_active_reactions(d, m); @@ -23714,26 +23841,26 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, } if (active_reactions.allow_all_) { - active_reactions.reactions_ = active_reactions_; + active_reactions.reaction_types_ = active_reaction_types_; active_reactions.allow_all_ = false; } - if (m->reactions != nullptr) { + if (can_use_reactions && m->reactions != nullptr) { for (const auto &reaction : m->reactions->reactions_) { // an already used reaction can be added if it is an active reaction - const string &reaction_str = reaction.get_reaction(); - if (can_use_reactions && is_active_reaction(reaction_str, active_reaction_pos_) && - !td::contains(active_reactions.reactions_, reaction_str)) { - active_reactions.reactions_.push_back(reaction_str); + const auto &reaction_type = reaction.get_reaction_type(); + if (reaction_type.is_active_reaction(active_reaction_pos_) && + !td::contains(active_reactions.reaction_types_, reaction_type)) { + active_reactions.reaction_types_.push_back(reaction_type); } } } - if (dissalow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) { + if (disallow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) { active_reactions.allow_custom_ = false; } return active_reactions; } -void MessagesManager::add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, +void MessagesManager::add_message_reaction(FullMessageId full_message_id, ReactionType reaction_type, bool is_big, bool add_to_recent, Promise &&promise) { auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "add_message_reaction"); @@ -23746,7 +23873,7 @@ void MessagesManager::add_message_reaction(FullMessageId full_message_id, string return promise.set_error(Status::Error(400, "Message not found")); } - if (!get_message_available_reactions(d, m, true).is_allowed_reaction(reaction)) { + if (!get_message_available_reactions(d, m, true).is_allowed_reaction_type(reaction_type)) { return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); } @@ -23757,18 +23884,21 @@ void MessagesManager::add_message_reaction(FullMessageId full_message_id, string m->available_reactions_generation = d->available_reactions_generation; } - if (!m->reactions->add_reaction(reaction, is_big, get_my_dialog_id(), have_recent_choosers)) { + 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)) { return promise.set_value(Unit()); } if (add_to_recent) { - add_recent_reaction(td_, reaction); + td_->reaction_manager_->add_recent_reaction(reaction_type); } set_message_reactions(d, m, is_big, add_to_recent, std::move(promise)); } -void MessagesManager::remove_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise) { +void MessagesManager::remove_message_reaction(FullMessageId full_message_id, ReactionType reaction_type, + Promise &&promise) { auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "remove_message_reaction"); if (d == nullptr) { @@ -23780,12 +23910,13 @@ void MessagesManager::remove_message_reaction(FullMessageId full_message_id, str return promise.set_error(Status::Error(400, "Message not found")); } - if (reaction.empty()) { + if (reaction_type.is_empty()) { return promise.set_error(Status::Error(400, "Invalid reaction specified")); } - bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m); - if (m->reactions == nullptr || !m->reactions->remove_reaction(reaction, get_my_dialog_id(), have_recent_choosers)) { + 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)) { return promise.set_value(Unit()); } @@ -23811,7 +23942,7 @@ void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, send_closure(actor_id, &MessagesManager::on_set_message_reactions, full_message_id, std::move(result), std::move(promise)); }); - send_message_reaction(td_, full_message_id, m->reactions->get_chosen_reactions(), is_big, add_to_recent, + send_message_reaction(td_, full_message_id, m->reactions->get_chosen_reaction_types(), is_big, add_to_recent, std::move(query_promise)); } @@ -24000,11 +24131,10 @@ td_api::object_ptr MessagesManager::get_dialog_event_log_messag get_message_own_max_media_timestamp(m)); 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, m->is_pinned, 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(), 0, 0, 0, 0, 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, 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)); } tl_object_ptr MessagesManager::get_message_object(FullMessageId full_message_id, const char *source) { @@ -24058,7 +24188,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial (!forward_info->from_dialog_id.is_valid() && !is_forward_info_sender_hidden(forward_info)); } - double ttl_expires_in = m->ttl_expires_at != 0 ? clamp(m->ttl_expires_at - Time::now(), 1e-3, m->ttl - 1e-3) : m->ttl; + double ttl_expires_in = m->ttl_expires_at != 0 ? clamp(m->ttl_expires_at - Time::now(), 1e-3, m->ttl - 1e-3) : 0.0; double auto_delete_in = m->ttl_period == 0 ? 0.0 : clamp(m->date + m->ttl_period - G()->server_time(), 1e-3, m->ttl_period - 1e-3); auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, source); @@ -24076,37 +24206,54 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto can_get_media_timestamp_links = can_get_media_timestamp_link(dialog_id, m).is_ok(); 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_message_id = m->reply_to_message_id.get(); - auto reply_in_dialog_id = - reply_to_message_id == 0 ? DialogId() : (m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_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()) { + 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()); + } + if (m->reply_to_story_full_id.is_valid()) { + return td_api::make_object( + get_chat_id_object(m->reply_to_story_full_id.get_dialog_id(), "messageReplyToStory"), + m->reply_to_story_full_id.get_story_id().get()); + } + return nullptr; + }(); auto top_thread_message_id = m->top_thread_message_id.get(); auto date = is_scheduled ? 0 : m->date; auto edit_date = m->hide_edit_date ? 0 : m->edit_date; - auto is_pinned = is_scheduled ? false : m->is_pinned; - auto has_timestamped_media = reply_to_message_id == 0 || m->max_own_media_timestamp >= 0; + 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); - - if (m->is_topic_message && reply_in_dialog_id == dialog_id && reply_to_message_id == top_thread_message_id && - !td_->auth_manager_->is_bot()) { - reply_in_dialog_id = DialogId(); - reply_to_message_id = 0; - } + auto self_destruct_type = [&]() -> td_api::object_ptr { + if (m->ttl == 0x7FFFFFFF) { + return td_api::make_object(); + } + if (m->ttl > 0) { + return td_api::make_object(m->ttl); + } + return nullptr; + }(); 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, is_pinned, can_be_edited, can_be_forwarded, + 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), - get_chat_id_object(reply_in_dialog_id, "get_message_object reply"), reply_to_message_id, top_thread_message_id, - m->ttl, 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)); + 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)); } tl_object_ptr MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id, @@ -24188,9 +24335,10 @@ DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId return d->default_send_message_as_dialog_id; } -MessageId MessagesManager::get_reply_to_message_id(DialogId dialog_id, MessageId top_thread_message_id, - MessageId message_id, bool for_draft) { - return get_reply_to_message_id(get_dialog(dialog_id), top_thread_message_id, message_id, 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); } int64 MessagesManager::generate_new_random_id(const Dialog *d) { @@ -24203,7 +24351,7 @@ int64 MessagesManager::generate_new_random_id(const Dialog *d) { } unique_ptr MessagesManager::create_message_to_send( - Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options, + 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 { CHECK(d != nullptr); @@ -24217,23 +24365,23 @@ unique_ptr MessagesManager::create_message_to_send( int64 reply_to_random_id = 0; bool is_topic_message = false; - if (reply_to_message_id.is_valid()) { - // the message was forcely preloaded in get_reply_to_message_id + if (input_reply_to.message_id_.is_valid()) { + // 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, reply_to_message_id); + const Message *reply_m = get_message(d, input_reply_to.message_id_); if (reply_m != nullptr) { 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 || reply_to_message_id.is_yet_unsent()) { + if (dialog_type == DialogType::SecretChat || input_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()); - reply_to_message_id = MessageId(); + input_reply_to.message_id_ = MessageId(); } } } else if (top_thread_message_id.is_valid()) { @@ -24278,8 +24426,9 @@ 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 = reply_to_message_id; + m->reply_to_message_id = input_reply_to.message_id_; 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; @@ -24305,7 +24454,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 !reply_to_message_id.is_valid(); + return !input_reply_to.is_valid(); }()) { m->reply_info.reply_count_ = 0; if (is_channel_post) { @@ -24331,7 +24480,7 @@ unique_ptr MessagesManager::create_message_to_send( if (dialog_type == DialogType::SecretChat) { CHECK(!is_scheduled); - m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()); + m->ttl = min(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), 0x7FFFFFFE); if (is_service_message_content(m->content->get_type())) { m->ttl = 0; } @@ -24342,12 +24491,12 @@ unique_ptr MessagesManager::create_message_to_send( } MessagesManager::Message *MessagesManager::get_message_to_send( - Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options, + 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, 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, reply_to_message_id, options, std::move(content), + 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); MessageId message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date) @@ -24421,38 +24570,63 @@ MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId return message_id; } -MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId top_thread_message_id, MessageId message_id, - 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_reply_to_message_id 1")) { - LOG(INFO) << "Have reply to " << message_id << " in the thread of unknown " << top_thread_message_id; + 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) { + 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; + return {top_thread_message_id, StoryFullId()}; } - return MessageId(); + return {}; } message_id = get_persistent_message_id(d, message_id); - const Message *m = get_message_force(d, message_id, "get_reply_to_message_id 2"); + 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_notification_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; + return {message_id, StoryFullId()}; } if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { - return top_thread_message_id; + return {top_thread_message_id, StoryFullId()}; } // 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 MessageId(); + return {}; } - return m->message_id; + return {m->message_id, StoryFullId()}; } void MessagesManager::fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id, @@ -24630,6 +24804,7 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, const 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); + 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) { @@ -24809,7 +24984,7 @@ class MessagesManager::SendMessageLogEvent { }; Result> MessagesManager::send_message( - DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id, + 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) { @@ -24821,14 +24996,12 @@ Result> MessagesManager::send_message( return Status::Error(400, "Chat not found"); } - LOG(INFO) << "Begin to send message to " << dialog_id << " in reply to " << reply_to_message_id; - - reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false); + auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { auto input_message = td_api::move_object_as(input_message_content); TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_))); - copy_options.reply_to_message_id = reply_to_message_id; + copy_options.input_reply_to = std::move(input_reply_to); TRY_RESULT_ASSIGN(copy_options.reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup))); return forward_message(dialog_id, top_thread_message_id, DialogId(input_message->from_chat_id_), MessageId(input_message->message_id_), std::move(options), input_message->in_game_share_, @@ -24840,12 +25013,12 @@ Result> MessagesManager::send_message( TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); - TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id)); + TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); // there must be no errors after get_message_to_send call bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options, + 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()); @@ -24882,11 +25055,7 @@ Result> MessagesManager::send_message( Result MessagesManager::process_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content) { - if (input_message_content == nullptr) { - return Status::Error(400, "Can't send message without content"); - } - - if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { + if (input_message_content != nullptr && input_message_content->get_id() == td_api::inputMessageForwarded::ID) { // for sendMessageAlbum/editMessageMedia/addLocalMessage auto input_message = td_api::move_object_as(input_message_content); TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_))); @@ -24931,13 +25100,6 @@ Result MessagesManager::process_input_message_content( bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_, is_premium)); - if (content.ttl < 0 || content.ttl > MAX_PRIVATE_MESSAGE_TTL) { - return Status::Error(400, "Invalid message content self-destruct time specified"); - } - if (content.ttl > 0 && dialog_id.get_type() != DialogType::User) { - return Status::Error(400, "Message content self-destruct time can be specified only in private chats"); - } - if (dialog_id != DialogId()) { TRY_STATUS(can_send_message_content(dialog_id, content.content.get(), false, td_)); } @@ -25020,7 +25182,7 @@ Status MessagesManager::can_use_message_send_options(const MessageSendOptions &o } Status MessagesManager::can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id, - MessageId reply_to_message_id) { + const MessageInputReplyTo &input_reply_to) { if (top_thread_message_id == MessageId()) { return Status::OK(); } @@ -25031,8 +25193,11 @@ 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 (reply_to_message_id.is_valid()) { - const Message *reply_m = get_message_force(d, reply_to_message_id, "can_use_top_thread_message_id 1"); + if (input_reply_to.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"); 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"); @@ -25059,7 +25224,7 @@ int64 MessagesManager::generate_new_media_album_id() { } Result> MessagesManager::send_message_group( - DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id, + 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) { if (input_message_contents.size() > MAX_GROUPED_MESSAGES) { @@ -25098,8 +25263,8 @@ Result> MessagesManager::send_message_group } } - reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false); - TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id)); + auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); + TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); int64 media_album_id = 0; if (message_contents.size() > 1) { @@ -25115,7 +25280,7 @@ Result> MessagesManager::send_message_group unique_ptr message; Message *m; if (only_preview) { - message = create_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options, + 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()); MessageId new_message_id = message_send_options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date) @@ -25123,7 +25288,7 @@ Result> MessagesManager::send_message_group message->message_id = new_message_id; m = message.get(); } else { - m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options, + 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(), MessageContentDupType::Send, MessageCopyOptions()), &need_update_dialog_pos, i != 0); @@ -25218,7 +25383,8 @@ void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vect auto input_media = get_input_media(content, td_, m->ttl, m->send_emoji, td_->auth_manager_->is_bot() && bad_parts.empty()); if (input_media == nullptr) { - if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll) { + if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll || + content_type == MessageContentType::Story) { return; } if (get_main_file_type(file_view.get_type()) == FileType::Photo) { @@ -25288,13 +25454,12 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Messag CHECK(input_media != nullptr); const FormattedText *caption = get_message_content_caption(m->content.get()); - LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id << " in reply to " - << m->reply_to_message_id; + LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id; 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->top_thread_message_id, get_message_schedule_date(m), - get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), + {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(), 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); @@ -25462,8 +25627,8 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag m->message_id, std::move(result)); } -void MessagesManager::on_upload_message_media_file_part_missing(DialogId dialog_id, MessageId message_id, - int bad_part) { +void MessagesManager::on_upload_message_media_file_parts_missing(DialogId dialog_id, MessageId message_id, + vector &&bad_parts) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); @@ -25483,7 +25648,7 @@ void MessagesManager::on_upload_message_media_file_part_missing(DialogId dialog_ CHECK(dialog_id.get_type() != DialogType::SecretChat); - do_send_message(dialog_id, m, {bad_part}); + do_send_message(dialog_id, m, std::move(bad_parts)); } void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error) { @@ -25594,7 +25759,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { vector random_ids; vector> input_single_media; tl_object_ptr as_input_peer; - MessageId reply_to_message_id; + MessageInputReplyTo input_reply_to; MessageId top_thread_message_id; int32 flags = 0; int32 schedule_date = 0; @@ -25607,7 +25772,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { continue; } - reply_to_message_id = m->reply_to_message_id; + input_reply_to = {m->reply_to_message_id, m->reply_to_story_full_id}; top_thread_message_id = m->top_thread_message_id; flags = get_message_flags(m); schedule_date = get_message_schedule_date(m); @@ -25674,7 +25839,7 @@ 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), reply_to_message_id, + 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); } @@ -25706,8 +25871,8 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI 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->top_thread_message_id, get_message_schedule_date(m), + 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); @@ -25787,7 +25952,6 @@ void MessagesManager::on_yet_unsent_media_queue_updated(DialogId dialog_id) { Result MessagesManager::send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string ¶meter) { - LOG(INFO) << "Begin to send bot start message to " << dialog_id; CHECK(!td_->auth_manager_->is_bot()); TRY_RESULT(bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); @@ -25858,7 +26022,7 @@ 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(), MessageId(), MessageSendOptions(), + Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_text_message_content(text, std::move(text_entities), WebPageId()), &need_update_dialog_pos); m->is_bot_start_message = true; @@ -25951,13 +26115,9 @@ 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, - MessageId reply_to_message_id, - tl_object_ptr &&options, - int64 query_id, const string &result_id, - bool hide_via_bot) { - LOG(INFO) << "Begin to send inline query result message to " << dialog_id << " in reply to " << reply_to_message_id; - +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) { return Status::Error(400, "Chat not found"); @@ -25992,13 +26152,13 @@ Result MessagesManager::send_inline_query_result_message(DialogId dia return Status::Error(400, "Inline query result not found"); } - reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false); + auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); TRY_STATUS(can_use_message_send_options(message_send_options, content->message_content, 0)); 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, reply_to_message_id)); + TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); bool need_update_dialog_pos = false; - Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options, + 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); @@ -26099,8 +26259,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->top_thread_message_id, - get_message_schedule_date(m), random_id, query_id, result_id); + 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 { @@ -26239,6 +26399,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo } return !get_message_content_poll_is_closed(td_, m->content.get()); } + case MessageContentType::Story: case MessageContentType::Contact: case MessageContentType::Dice: case MessageContentType::Location: @@ -26287,6 +26448,7 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo case MessageContentType::RequestedDialog: case MessageContentType::WebViewWriteAccessAllowed: case MessageContentType::SetBackground: + case MessageContentType::WriteAccessAllowedByRequest: return false; default: UNREACHABLE(); @@ -26416,7 +26578,6 @@ void MessagesManager::edit_message_text(FullMessageId full_message_id, return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText")); } - LOG(INFO) << "Begin to edit text of " << full_message_id; auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_text"); if (d == nullptr) { @@ -26470,7 +26631,6 @@ void MessagesManager::edit_message_live_location(FullMessageId full_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_location, int32 heading, int32 proximity_alert_radius, Promise &&promise) { - LOG(INFO) << "Begin to edit live location of " << full_message_id; auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_live_location"); if (d == nullptr) { @@ -26587,9 +26747,9 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess td_->file_manager_->delete_partial_remote_location(thumbnail_file_id); } CHECK(file_id.is_valid()); - auto error_message = result.error().message(); - if (begins_with(error_message, "FILE_PART_") && ends_with(error_message, "_MISSING")) { - do_send_message(dialog_id, m, {to_integer(error_message.substr(10))}); + auto bad_parts = FileManager::get_missing_file_parts(result.error()); + if (!bad_parts.empty()) { + do_send_message(dialog_id, m, std::move(bad_parts)); return; } @@ -26643,7 +26803,6 @@ void MessagesManager::edit_message_media(FullMessageId full_message_id, return promise.set_error(Status::Error(400, "Unsupported input message content type")); } - LOG(INFO) << "Begin to edit media of " << full_message_id; auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_media"); if (d == nullptr) { @@ -26719,8 +26878,6 @@ void MessagesManager::edit_message_caption(FullMessageId full_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_caption, Promise &&promise) { - LOG(INFO) << "Begin to edit caption of " << full_message_id; - auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_caption"); if (d == nullptr) { @@ -26769,7 +26926,6 @@ void MessagesManager::edit_message_reply_markup(FullMessageId full_message_id, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); - LOG(INFO) << "Begin to edit reply markup of " << full_message_id; auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_reply_markup"); if (d == nullptr) { @@ -26902,7 +27058,6 @@ void MessagesManager::edit_inline_message_media(const string &inline_message_id, } InputMessageContent content = r_input_message_content.move_as_ok(); if (content.ttl > 0) { - LOG(ERROR) << "Have message content with self-destruct time " << content.ttl; return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); } @@ -26986,8 +27141,6 @@ void MessagesManager::edit_message_scheduling_state( } auto schedule_date = r_schedule_date.move_as_ok(); - LOG(INFO) << "Begin to reschedule " << full_message_id << " to " << schedule_date; - auto dialog_id = full_message_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "edit_message_scheduling_state"); if (d == nullptr) { @@ -27089,6 +27242,15 @@ void MessagesManager::update_message_max_reply_media_timestamp(const Dialog *d, // replied message isn't deleted and isn't loaded yet return; } + } else if (m->reply_to_story_full_id != StoryFullId()) { + if (!td_->story_manager_->have_story(m->reply_to_story_full_id)) { + if (!td_->story_manager_->is_inaccessible_story(m->reply_to_story_full_id)) { + // replied story isn't loaded yet + return; + } + } else { + new_max_reply_media_timestamp = td_->story_manager_->get_story_duration(m->reply_to_story_full_id); + } } if (m->max_reply_media_timestamp == new_max_reply_media_timestamp) { @@ -27140,8 +27302,8 @@ void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messag } FullMessageId full_message_id{dialog_id, reply_to_message_id}; - auto it = replied_by_media_timestamp_messages_.find(full_message_id); - if (it == replied_by_media_timestamp_messages_.end()) { + auto it = message_to_replied_media_timestamp_messages_.find(full_message_id); + if (it == message_to_replied_media_timestamp_messages_.end()) { return; } @@ -27158,30 +27320,77 @@ void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messag } } +void MessagesManager::update_story_max_reply_media_timestamp_in_replied_messages(StoryFullId story_full_id) { + auto it = story_to_replied_media_timestamp_messages_.find(story_full_id); + if (it == story_to_replied_media_timestamp_messages_.end()) { + return; + } + + LOG(INFO) << "Update max_reply_media_timestamp for replies of " << story_full_id; + + for (auto replied_full_message_id : it->second) { + auto replied_dialog_id = replied_full_message_id.get_dialog_id(); + Dialog *d = get_dialog(replied_dialog_id); + auto m = get_message(d, replied_full_message_id.get_message_id()); + CHECK(m != nullptr); + CHECK(m->reply_to_story_full_id == story_full_id); + update_message_max_reply_media_timestamp(d, m, true); + } +} + +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()) { + return true; + } + if (m->reply_to_story_full_id.is_valid()) { + return true; + } + return false; +} + void MessagesManager::register_message_reply(DialogId dialog_id, const Message *m) { - if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) { + if (!can_register_message_reply(m)) { return; } if (has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits::max())) { - FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; - LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << full_message_id; - bool is_inserted = replied_by_media_timestamp_messages_[full_message_id].insert({dialog_id, m->message_id}).second; - CHECK(is_inserted); + if (m->reply_to_story_full_id.is_valid()) { + LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_story_full_id; + bool is_inserted = story_to_replied_media_timestamp_messages_[m->reply_to_story_full_id] + .insert({dialog_id, m->message_id}) + .second; + CHECK(is_inserted); + } else { + FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, + m->reply_to_message_id}; + LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << full_message_id; + bool is_inserted = + message_to_replied_media_timestamp_messages_[full_message_id].insert({dialog_id, m->message_id}).second; + CHECK(is_inserted); + } } } void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message *m) { - if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) { + if (!can_register_message_reply(m)) { return; } - FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; - auto it = replied_by_media_timestamp_messages_.find(full_message_id); - bool was_registered = - it != replied_by_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; + bool was_registered = false; + if (m->reply_to_story_full_id.is_valid()) { + auto it = story_to_replied_media_timestamp_messages_.find(m->reply_to_story_full_id); + was_registered = + it != story_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; + } else { + FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, + m->reply_to_message_id}; + auto it = message_to_replied_media_timestamp_messages_.find(full_message_id); + was_registered = + it != message_to_replied_media_timestamp_messages_.end() && it->second.count({dialog_id, m->message_id}) > 0; + } bool need_register = has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits::max()); if (was_registered == need_register) { @@ -27195,21 +27404,38 @@ void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message } void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message *m) { - if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) { - return; - } - FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, - m->reply_to_message_id}; - auto it = replied_by_media_timestamp_messages_.find(full_message_id); - if (it == replied_by_media_timestamp_messages_.end()) { + if (!can_register_message_reply(m)) { return; } - auto is_deleted = it->second.erase({dialog_id, m->message_id}) > 0; - if (is_deleted) { - LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << full_message_id; - if (it->second.empty()) { - replied_by_media_timestamp_messages_.erase(it); + if (m->reply_to_story_full_id.is_valid()) { + auto it = story_to_replied_media_timestamp_messages_.find(m->reply_to_story_full_id); + if (it == story_to_replied_media_timestamp_messages_.end()) { + return; + } + + auto is_deleted = it->second.erase({dialog_id, m->message_id}) > 0; + if (is_deleted) { + LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " + << m->reply_to_story_full_id; + if (it->second.empty()) { + story_to_replied_media_timestamp_messages_.erase(it); + } + } + } else { + FullMessageId full_message_id{m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, + m->reply_to_message_id}; + auto it = message_to_replied_media_timestamp_messages_.find(full_message_id); + if (it == message_to_replied_media_timestamp_messages_.end()) { + return; + } + + auto is_deleted = it->second.erase({dialog_id, m->message_id}) > 0; + if (is_deleted) { + LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << full_message_id; + if (it->second.empty()) { + message_to_replied_media_timestamp_messages_.erase(it); + } } } } @@ -27226,14 +27452,6 @@ bool MessagesManager::get_message_disable_web_page_preview(const Message *m) { int32 MessagesManager::get_message_flags(const Message *m) { int32 flags = 0; - if (m->reply_to_message_id.is_valid()) { - CHECK(m->reply_to_message_id.is_server()); - flags |= SEND_MESSAGE_FLAG_IS_REPLY; - } - if (m->top_thread_message_id.is_valid()) { - CHECK(m->top_thread_message_id.is_server()); - flags |= SEND_MESSAGE_FLAG_IS_FROM_THREAD; - } if (m->disable_web_page_preview) { flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } @@ -27641,7 +27859,8 @@ Result> MessagesManager::forward_message( 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(); - if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio) { + if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio || + content_type == MessageContentType::Story) { return nullptr; } @@ -27786,7 +28005,7 @@ Result MessagesManager::get_forwarded_messag TRY_STATUS(can_send_message(to_dialog_id)); TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false)); - TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageId())); + TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageInputReplyTo())); { MessageId last_message_id; @@ -27865,7 +28084,7 @@ Result MessagesManager::get_forwarded_messag auto type = need_copy ? (is_local_copy ? MessageContentDupType::Copy : MessageContentDupType::ServerCopy) : MessageContentDupType::Forward; - auto reply_to_message_id = copy_options[i].reply_to_message_id; + auto input_reply_to = std::move(copy_options[i].input_reply_to); auto reply_markup = std::move(copy_options[i].reply_markup); unique_ptr content = dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type, std::move(copy_options[i])); @@ -27874,8 +28093,6 @@ Result MessagesManager::get_forwarded_messag continue; } - reply_to_message_id = get_reply_to_message_id(to_dialog, top_thread_message_id, reply_to_message_id, false); - auto can_send_status = can_send_message_content(to_dialog_id, content.get(), !is_local_copy, td_); if (can_send_status.is_error()) { LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message(); @@ -27888,7 +28105,7 @@ Result MessagesManager::get_forwarded_messag continue; } - if (can_use_top_thread_message_id(to_dialog, top_thread_message_id, reply_to_message_id).is_error()) { + if (can_use_top_thread_message_id(to_dialog, top_thread_message_id, input_reply_to).is_error()) { LOG(INFO) << "Ignore invalid message thread ID " << top_thread_message_id; top_thread_message_id = MessageId(); } @@ -27910,7 +28127,7 @@ 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), reply_to_message_id, forwarded_message->message_id, + 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}); @@ -28008,9 +28225,9 @@ Result> MessagesManager::forward_messages( unique_ptr message; Message *m; if (only_preview) { - message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options, - std::move(content), j + 1 != forwarded_message_contents.size(), - std::move(forward_info), false, DialogId()); + 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()); MessageId new_message_id = message_send_options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date) @@ -28018,9 +28235,9 @@ 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, message_send_options, - std::move(content), &need_update_dialog_pos, j + 1 != forwarded_message_contents.size(), - std::move(forward_info)); + 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, + 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, drop_author); @@ -28048,6 +28265,9 @@ Result> MessagesManager::forward_messages( bool is_secret = to_dialog_id.get_type() == DialogType::SecretChat; bool is_copy = !is_secret; + bool need_invalidate_authentication_code = + from_dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && !td_->auth_manager_->is_bot(); + vector authentication_codes; for (const auto &copied_message : copied_messages) { if (forwarded_message_id_to_new_message_id.count(copied_message.original_reply_to_message_id) > 0) { is_copy = true; @@ -28056,18 +28276,18 @@ Result> MessagesManager::forward_messages( forwarded_message_id_to_new_message_id.emplace(copied_message.original_message_id, MessageId()); } for (auto &copied_message : copied_messages) { - MessageId reply_to_message_id = copied_message.reply_to_message_id; - if (!reply_to_message_id.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) { + auto input_reply_to = 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()) { - reply_to_message_id = it->second; + input_reply_to.message_id_ = it->second; } } unique_ptr message; Message *m; if (only_preview) { - message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options, + 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()); MessageId new_message_id = message_send_options.schedule_date != 0 @@ -28076,7 +28296,13 @@ 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, message_send_options, + if (need_invalidate_authentication_code) { + const Message *forwarded_message = get_message(from_dialog, copied_message.original_message_id); + CHECK(forwarded_message != nullptr); + 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->disable_web_page_preview = copied_message.disable_web_page_preview; @@ -28100,6 +28326,10 @@ Result> MessagesManager::forward_messages( send_update_chat_last_message(to_dialog, "forward_messages"); } + if (!authentication_codes.empty()) { + td_->account_manager_->invalidate_authentication_codes(std::move(authentication_codes)); + } + return get_messages_object(-1, std::move(result), false); } @@ -28196,8 +28426,7 @@ Result> MessagesManager::resend_messages(DialogId dialog_id, v message->update_stickersets_order, message->noforwards, get_message_schedule_date(message.get()), message->sending_id); Message *m = get_message_to_send( - d, message->top_thread_message_id, - get_reply_to_message_id(d, message->top_thread_message_id, message->reply_to_message_id, false), options, + 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())); m->reply_markup = std::move(message->reply_markup); @@ -28226,39 +28455,25 @@ Result> MessagesManager::resend_messages(DialogId dialog_id, v return result; } -Status MessagesManager::send_screenshot_taken_notification_message(DialogId dialog_id) { - auto dialog_type = dialog_id.get_type(); - if (dialog_type != DialogType::User && dialog_type != DialogType::SecretChat) { - return Status::Error(400, "Notification about taken screenshot can be sent only in private and secret chats"); - } - - LOG(INFO) << "Begin to send notification about taken screenshot in " << dialog_id; - - Dialog *d = get_dialog_force(dialog_id, "send_screenshot_taken_notification_message"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - - TRY_STATUS(can_send_message(dialog_id)); - +void MessagesManager::send_screenshot_taken_notification_message(Dialog *d) { + LOG(INFO) << "Begin to send notification about taken screenshot in " << d->dialog_id; + auto dialog_type = d->dialog_id.get_type(); if (dialog_type == DialogType::User) { bool need_update_dialog_pos = false; - const Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(), + const Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_screenshot_taken_message_content(), &need_update_dialog_pos); - do_send_screenshot_taken_notification_message(dialog_id, m, 0); + do_send_screenshot_taken_notification_message(d->dialog_id, m, 0); send_update_new_message(d, m); if (need_update_dialog_pos) { send_update_chat_last_message(d, "send_screenshot_taken_notification_message"); } } else { + CHECK(dialog_type == DialogType::SecretChat); send_closure(td_->secret_chats_manager_, &SecretChatsManager::notify_screenshot_taken, - dialog_id.get_secret_chat_id(), - Promise()); // TODO Promise + d->dialog_id.get_secret_chat_id(), Promise()); } - - return Status::OK(); } class MessagesManager::SendScreenshotTakenNotificationMessageLogEvent { @@ -28342,13 +28557,13 @@ void MessagesManager::share_dialog_with_bot(FullMessageId full_message_id, int32 } Result MessagesManager::add_local_message( - DialogId dialog_id, td_api::object_ptr &&sender, MessageId reply_to_message_id, - bool disable_notification, tl_object_ptr &&input_message_content) { + DialogId dialog_id, td_api::object_ptr &&sender, + 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"); } - LOG(INFO) << "Begin to add local message to " << dialog_id << " in reply to " << reply_to_message_id; Dialog *d = get_dialog_force(dialog_id, "add_local_message"); if (d == nullptr) { return Status::Error(400, "Chat not found"); @@ -28406,6 +28621,8 @@ Result MessagesManager::add_local_message( } } + auto input_reply_to = get_message_input_reply_to(d, MessageId(), std::move(reply_to), false); + MessageId message_id = get_next_local_message_id(d); auto message = make_unique(); @@ -28422,7 +28639,8 @@ Result MessagesManager::add_local_message( m->sender_dialog_id = sender_dialog_id; } m->date = G()->unix_time(); - m->reply_to_message_id = get_reply_to_message_id(d, MessageId(), reply_to_message_id, false); + 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); if (reply_m != nullptr) { @@ -28443,7 +28661,7 @@ Result MessagesManager::add_local_message( m->disable_web_page_preview = message_content.disable_web_page_preview; m->clear_draft = message_content.clear_draft; if (dialog_type == DialogType::SecretChat) { - m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()); + m->ttl = min(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), 0x7FFFFFFE); if (is_service_message_content(m->content->get_type())) { m->ttl = 0; } @@ -28718,33 +28936,29 @@ void MessagesManager::send_update_new_message(const Dialog *d, const Message *m) td_api::make_object(get_message_object(d->dialog_id, m, "send_update_new_message"))); } -MessagesManager::NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, bool from_mentions) { +NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, bool from_mentions) { CHECK(d != nullptr); auto notification_info = add_dialog_notification_info(d); return from_mentions ? notification_info->mention_notification_group_ : notification_info->message_notification_group_; } -MessagesManager::NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, const Message *m) { +NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, const Message *m) { return get_notification_group_info(d, is_from_mention_notification_group(m)); } NotificationGroupId MessagesManager::get_dialog_notification_group_id(DialogId dialog_id, NotificationGroupInfo &group_info) { - if (td_->auth_manager_->is_bot()) { - // just in case - return NotificationGroupId(); - } - if (!group_info.group_id.is_valid()) { + CHECK(!td_->auth_manager_->is_bot()); + if (!group_info.is_valid()) { NotificationGroupId next_notification_group_id; do { next_notification_group_id = td_->notification_manager_->get_next_notification_group_id(); if (!next_notification_group_id.is_valid()) { return NotificationGroupId(); } - } while (get_message_notification_group_force(next_notification_group_id).dialog_id.is_valid()); - group_info.group_id = next_notification_group_id; - group_info.is_changed = true; + } while (td_->notification_manager_->have_group_force(next_notification_group_id)); + group_info = NotificationGroupInfo(next_notification_group_id); VLOG(notifications) << "Assign " << next_notification_group_id << " to " << dialog_id; on_dialog_updated(dialog_id, "get_dialog_notification_group_id"); @@ -28755,14 +28969,13 @@ NotificationGroupId MessagesManager::get_dialog_notification_group_id(DialogId d next_notification_group_id); } } - - CHECK(group_info.group_id.is_valid()); + CHECK(group_info.is_valid()); // notification group must be preloaded to guarantee that there is no race between // get_message_notifications_from_database_force and new notifications added right now - td_->notification_manager_->load_group_force(group_info.group_id); + td_->notification_manager_->load_group_force(group_info.get_group_id()); - return group_info.group_id; + return group_info.get_group_id(); } Result MessagesManager::get_message_push_notification_info( @@ -28790,17 +29003,17 @@ Result MessagesManager::get_messag bool is_new_pinned = is_pinned && message_id.is_valid() && - (d->notification_info == nullptr || message_id > d->notification_info->max_notification_message_id_); + (d->notification_info == nullptr || message_id > d->notification_info->max_push_notification_message_id_); CHECK(!message_id.is_scheduled()); if (message_id.is_valid()) { if (message_id <= d->last_new_message_id) { return Status::Error("Ignore notification about known message"); } if (!is_from_binlog && d->notification_info != nullptr) { - if (message_id == d->notification_info->max_notification_message_id_) { + if (message_id == d->notification_info->max_push_notification_message_id_) { return Status::Error("Ignore previously added message push notification"); } - if (message_id < d->notification_info->max_notification_message_id_) { + if (message_id < d->notification_info->max_push_notification_message_id_) { return Status::Error("Ignore out of order message push notification"); } } @@ -28867,13 +29080,13 @@ Result MessagesManager::get_messag if (message_id.is_valid()) { auto notification_info = add_dialog_notification_info(d); - if (message_id > notification_info->max_notification_message_id_) { + if (message_id > notification_info->max_push_notification_message_id_) { if (is_new_pinned) { set_dialog_pinned_message_notification(d, contains_mention ? message_id : MessageId(), "get_message_push_notification_info"); } - notification_info->max_notification_message_id_ = message_id; - on_dialog_updated(dialog_id, "set_max_notification_message_id"); + notification_info->max_push_notification_message_id_ = message_id; + on_dialog_updated(dialog_id, "set_max_push_notification_message_id"); } } @@ -28897,19 +29110,15 @@ NotificationId MessagesManager::get_next_notification_id(NotificationInfo *notif } } while (notification_info->notification_id_to_message_id_.count(notification_id) != 0 || notification_info->new_secret_chat_notification_id_ == notification_id || - notification_id.get() <= notification_info->message_notification_group_.last_notification_id.get() || - notification_id.get() <= notification_info->message_notification_group_.max_removed_notification_id.get() || - notification_id.get() <= notification_info->mention_notification_group_.last_notification_id.get() || - notification_id.get() <= - notification_info->mention_notification_group_.max_removed_notification_id.get()); // just in case + notification_info->message_notification_group_.is_used_notification_id(notification_id) || + notification_info->mention_notification_group_.is_used_notification_id(notification_id)); // just in case if (message_id.is_valid()) { add_notification_id_to_message_id_correspondence(notification_info, notification_id, message_id); } return notification_id; } -MessagesManager::MessageNotificationGroup MessagesManager::get_message_notification_group_force( - NotificationGroupId group_id) { +NotificationGroupFromDatabase MessagesManager::get_message_notification_group_force(NotificationGroupId group_id) { CHECK(!td_->auth_manager_->is_bot()); CHECK(group_id.is_valid()); Dialog *d = nullptr; @@ -28932,33 +29141,27 @@ MessagesManager::MessageNotificationGroup MessagesManager::get_message_notificat } if (d == nullptr || d->notification_info == nullptr) { - return MessageNotificationGroup(); + return NotificationGroupFromDatabase(); } - if (d->notification_info->message_notification_group_.group_id != group_id && - d->notification_info->mention_notification_group_.group_id != group_id) { + if (!is_dialog_notification_group_id(d, group_id)) { if (d->dialog_id.get_type() == DialogType::SecretChat && - !d->notification_info->message_notification_group_.group_id.is_valid() && - !d->notification_info->mention_notification_group_.group_id.is_valid()) { + !d->notification_info->message_notification_group_.is_valid() && + !d->notification_info->mention_notification_group_.is_valid()) { // the group was reused, but wasn't deleted from the database, trying to resave it auto &group_info = d->notification_info->message_notification_group_; - group_info.group_id = group_id; - group_info.is_changed = true; - group_info.try_reuse = true; + group_info = NotificationGroupInfo(group_id); + group_info.try_reuse(); save_dialog_to_database(d->dialog_id); - group_info.group_id = NotificationGroupId(); - group_info.is_changed = false; - group_info.try_reuse = false; + group_info = NotificationGroupInfo(); } + CHECK(is_dialog_notification_group_id(d, group_id)); } - LOG_CHECK(d->notification_info->message_notification_group_.group_id == group_id || - d->notification_info->mention_notification_group_.group_id == group_id); - - bool from_mentions = d->notification_info->mention_notification_group_.group_id == group_id; + bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); auto &group_info = get_notification_group_info(d, from_mentions); - MessageNotificationGroup result; - VLOG(notifications) << "Found " << (from_mentions ? "Mentions " : "Messages ") << group_info.group_id << '/' + NotificationGroupFromDatabase result; + VLOG(notifications) << "Found " << (from_mentions ? "Mentions " : "Messages ") << group_info.get_group_id() << '/' << d->dialog_id << " by " << group_id << " with " << d->unread_mention_count << " unread mentions, " << d->unread_reaction_count << " unread reactions, pinned " << d->notification_info->pinned_message_notification_message_id_ << ", new secret chat " @@ -28992,15 +29195,8 @@ MessagesManager::MessageNotificationGroup MessagesManager::get_message_notificat last_notification_date = result.notifications[0].date; last_notification_id = result.notifications[0].notification_id; } - if (last_notification_date != group_info.last_notification_date || - last_notification_id != group_info.last_notification_id) { - LOG(ERROR) << "Fix last notification date in " << d->dialog_id << " from " << group_info.last_notification_date - << " to " << last_notification_date << " and last notification identifier from " - << group_info.last_notification_id << " to " << last_notification_id << " in " << group_id << " of type " - << result.type; - set_dialog_last_notification(d->dialog_id, group_info, last_notification_date, last_notification_id, - "get_message_notification_group_force"); - } + set_dialog_last_notification(d->dialog_id, group_info, last_notification_date, last_notification_id, + "get_message_notification_group_force"); std::reverse(result.notifications.begin(), result.notifications.end()); @@ -29052,15 +29248,13 @@ bool MessagesManager::is_message_notification_active(const Dialog *d, const Mess } } if (is_from_mention_notification_group(m)) { - return m->notification_id.get() > - d->notification_info->mention_notification_group_.max_removed_notification_id.get() && - m->message_id > d->notification_info->mention_notification_group_.max_removed_message_id && + return !d->notification_info->mention_notification_group_.is_removed_notification(m->notification_id, + m->message_id) && (m->contains_unread_mention || m->message_id == d->notification_info->pinned_message_notification_message_id_); } else { - return m->notification_id.get() > - d->notification_info->message_notification_group_.max_removed_notification_id.get() && - m->message_id > d->notification_info->message_notification_group_.max_removed_message_id && + return !d->notification_info->message_notification_group_.is_removed_notification(m->notification_id, + m->message_id) && m->message_id > d->last_read_inbox_message_id; } } @@ -29079,8 +29273,7 @@ void MessagesManager::try_add_pinned_message_notification(Dialog *d, vectornotification_id.get() > d->notification_info->mention_notification_group_.max_removed_notification_id.get() && - m->message_id > d->notification_info->mention_notification_group_.max_removed_message_id && + !d->notification_info->mention_notification_group_.is_removed_notification(m->notification_id, m->message_id) && m->message_id > d->last_read_inbox_message_id && !is_dialog_pinned_message_notifications_disabled(d)) { if (m->notification_id.get() < max_notification_id.get()) { VLOG(notifications) << "Add " << m->notification_id << " about pinned " << message_id << " in " << d->dialog_id; @@ -29092,11 +29285,12 @@ void MessagesManager::try_add_pinned_message_notification(Dialog *d, vectornotification_id, m->date, m->disable_notification, create_new_message_notification(message_id, is_message_preview_enabled(d, m, true))); - while (pos > 0 && res[pos - 1].type->get_message_id() < message_id) { + NotificationObjectId object_id(message_id); + while (pos > 0 && res[pos - 1].type->get_object_id() < object_id) { std::swap(res[pos - 1], res[pos]); pos--; } - if (pos > 0 && res[pos - 1].type->get_message_id() == message_id) { + if (pos > 0 && res[pos - 1].type->get_object_id() == object_id) { res.erase(res.begin() + pos); // notification was already there } if (res.size() > static_cast(limit)) { @@ -29132,7 +29326,7 @@ vector MessagesManager::get_message_notifications_from_database_fo bool is_found = false; VLOG(notifications) << "Loaded " << messages.size() << (from_mentions ? " mention" : "") - << " messages with notifications from database in " << group_info.group_id << '/' + << " messages with notifications from database in " << group_info.get_group_id() << '/' << d->dialog_id; for (auto &message : messages) { auto m = on_get_message_from_database(d, message, false, "get_message_notifications_from_database_force"); @@ -29143,8 +29337,13 @@ vector MessagesManager::get_message_notifications_from_database_fo auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id; if (!notification_id.is_valid()) { - LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id - << " with from_mentions = " << from_mentions; + if (from_mentions) { + VLOG(notifications) << "Receive " << m->message_id << " with unread mention, but without notification"; + is_found = false; + } else { + LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id + << " with from_mentions = " << from_mentions; + } continue; } CHECK(m->message_id.is_valid()); @@ -29168,11 +29367,9 @@ vector MessagesManager::get_message_notifications_from_database_fo is_found = true; } - if (notification_id.get() <= group_info.max_removed_notification_id.get() || - m->message_id <= group_info.max_removed_message_id || + if (group_info.is_removed_notification(notification_id, m->message_id) || (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) { - // if message still has notification_id, but it was removed via max_removed_notification_id, - // or max_removed_message_id, or last_read_inbox_message_id, + // if message still has notification_id, but it was removed, // then there will be no more messages with active notifications is_found = false; break; @@ -29261,9 +29458,7 @@ vector MessagesManager::get_message_notification_group_key CHECK(group_key.group_id.is_valid()); CHECK(group_key.dialog_id.is_valid()); const Dialog *d = get_dialog_force(group_key.dialog_id, "get_message_notification_group_keys_from_database"); - if (d == nullptr || d->notification_info == nullptr || - (d->notification_info->message_notification_group_.group_id != group_key.group_id && - d->notification_info->mention_notification_group_.group_id != group_key.group_id)) { + if (!is_dialog_notification_group_id(d, group_key.group_id)) { continue; } @@ -29295,14 +29490,13 @@ void MessagesManager::get_message_notifications_from_database(DialogId dialog_id auto d = get_dialog(dialog_id); CHECK(d != nullptr); - if (d->notification_info == nullptr || (d->notification_info->message_notification_group_.group_id != group_id && - d->notification_info->mention_notification_group_.group_id != group_id)) { + if (!is_dialog_notification_group_id(d, group_id)) { return promise.set_value(vector()); } VLOG(notifications) << "Get " << limit << " message notifications from database in " << group_id << " from " << dialog_id << " from " << from_notification_id << "/" << from_message_id; - bool from_mentions = d->notification_info->mention_notification_group_.group_id == group_id; + bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { CHECK(dialog_id.get_type() == DialogType::SecretChat); vector notifications; @@ -29331,8 +29525,7 @@ void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool CHECK(!from_message_id.is_scheduled()); auto &group_info = get_notification_group_info(d, from_mentions); - if (from_notification_id.get() <= group_info.max_removed_notification_id.get() || - from_message_id <= group_info.max_removed_message_id || + if (!group_info.is_valid() || group_info.is_removed_notification(from_notification_id, from_message_id) || (!from_mentions && from_message_id <= d->last_read_inbox_message_id)) { return promise.set_value(vector()); } @@ -29347,12 +29540,12 @@ void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool auto *db = G()->td_db()->get_message_db_async(); if (!from_mentions) { - VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << group_info.group_id + VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << group_info.get_group_id() << '/' << dialog_id << " from " << from_notification_id; return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit, std::move(new_promise)); } else { - VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " << group_info.group_id - << '/' << dialog_id << " from " << from_message_id; + VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " + << group_info.get_group_id() << '/' << dialog_id << " from " << from_message_id; // ignore first_db_message_id, notifications can be nonconsecutive MessageDbMessagesQuery db_query; @@ -29379,7 +29572,7 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog CHECK(d != nullptr); auto &group_info = get_notification_group_info(d, from_mentions); - if (!group_info.group_id.is_valid()) { + if (!group_info.is_valid()) { return promise.set_error(Status::Error("Notification group was deleted")); } @@ -29388,7 +29581,7 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog res.reserve(messages.size()); NotificationId from_notification_id; MessageId from_message_id; - VLOG(notifications) << "Loaded " << messages.size() << " messages with notifications in " << group_info.group_id + VLOG(notifications) << "Loaded " << messages.size() << " messages with notifications in " << group_info.get_group_id() << '/' << dialog_id << " from database"; for (auto &message : messages) { auto m = on_get_message_from_database(d, message, false, "on_get_message_notifications_from_database"); @@ -29399,8 +29592,13 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id; if (!notification_id.is_valid()) { - LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id - << " with from_mentions = " << from_mentions; + if (from_mentions) { + VLOG(notifications) << "Receive " << m->message_id << " with unread mention, but without notification"; + from_notification_id = NotificationId(); // stop requesting database + } else { + LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id + << " with from_mentions = " << from_mentions; + } continue; } CHECK(m->message_id.is_valid()); @@ -29421,8 +29619,7 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog from_message_id = m->message_id; } - if (notification_id.get() <= group_info.max_removed_notification_id.get() || - m->message_id <= group_info.max_removed_message_id || + if (group_info.is_removed_notification(notification_id, m->message_id) || (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) { // if message still has notification_id, but it was removed via max_removed_notification_id, // or max_removed_message_id, or last_read_inbox_message_id, @@ -29478,12 +29675,7 @@ void MessagesManager::on_get_message_notifications_from_database(DialogId dialog void MessagesManager::remove_message_notification(DialogId dialog_id, NotificationGroupId group_id, NotificationId notification_id) { Dialog *d = get_dialog_force(dialog_id, "remove_message_notification"); - if (d == nullptr) { - LOG(ERROR) << "Can't find " << dialog_id; - return; - } - if (d->notification_info == nullptr || (d->notification_info->message_notification_group_.group_id != group_id && - d->notification_info->mention_notification_group_.group_id != group_id)) { + if (!is_dialog_notification_group_id(d, group_id)) { LOG(ERROR) << "There is no " << group_id << " in " << dialog_id; return; } @@ -29491,7 +29683,7 @@ void MessagesManager::remove_message_notification(DialogId dialog_id, Notificati return; // there can be no notification with this ID } - bool from_mentions = d->notification_info->mention_notification_group_.group_id == group_id; + bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { if (!from_mentions && d->notification_info->new_secret_chat_notification_id_ == notification_id) { return remove_new_secret_chat_notification(d, false); @@ -29514,8 +29706,8 @@ void MessagesManager::remove_message_notification(DialogId dialog_id, Notificati if (G()->use_message_database()) { G()->td_db()->get_message_db_async()->get_messages_from_notification_id( dialog_id, NotificationId(notification_id.get() + 1), 1, - PromiseCreator::lambda([dialog_id, from_mentions, notification_id, - actor_id = actor_id(this)](vector result) { + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_mentions, + notification_id](vector result) { send_closure(actor_id, &MessagesManager::do_remove_message_notification, dialog_id, from_mentions, notification_id, std::move(result)); })); @@ -29541,11 +29733,11 @@ void MessagesManager::remove_message_notifications_by_message_ids(DialogId dialo if (message == nullptr) { LOG(INFO) << "Can't delete " << message_id << " because it is not found"; // call synchronously to remove them before ProcessPush returns - td_->notification_manager_->remove_temporary_notification_by_message_id( - d->notification_info->message_notification_group_.group_id, message_id, true, + td_->notification_manager_->remove_temporary_notification_by_object_id( + d->notification_info->message_notification_group_.get_group_id(), message_id, true, "remove_message_notifications_by_message_ids"); - td_->notification_manager_->remove_temporary_notification_by_message_id( - d->notification_info->mention_notification_group_.group_id, message_id, true, + td_->notification_manager_->remove_temporary_notification_by_object_id( + d->notification_info->mention_notification_group_.get_group_id(), message_id, true, "remove_message_notifications_by_message_ids"); continue; } @@ -29579,12 +29771,7 @@ void MessagesManager::do_remove_message_notification(DialogId dialog_id, bool fr void MessagesManager::remove_message_notifications(DialogId dialog_id, NotificationGroupId group_id, NotificationId max_notification_id, MessageId max_message_id) { Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications"); - if (d == nullptr) { - LOG(ERROR) << "Can't find " << dialog_id; - return; - } - if (d->notification_info == nullptr || (d->notification_info->message_notification_group_.group_id != group_id && - d->notification_info->mention_notification_group_.group_id != group_id)) { + if (!is_dialog_notification_group_id(d, group_id)) { LOG(ERROR) << "There is no " << group_id << " in " << dialog_id; return; } @@ -29593,7 +29780,7 @@ void MessagesManager::remove_message_notifications(DialogId dialog_id, Notificat } CHECK(!max_message_id.is_scheduled()); - bool from_mentions = d->notification_info->mention_notification_group_.group_id == group_id; + bool from_mentions = d->notification_info->mention_notification_group_.has_group_id(group_id); if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { if (!from_mentions && d->notification_info->new_secret_chat_notification_id_.get() <= max_notification_id.get()) { return remove_new_secret_chat_notification(d, false); @@ -29601,25 +29788,9 @@ void MessagesManager::remove_message_notifications(DialogId dialog_id, Notificat return; } auto &group_info = get_notification_group_info(d, from_mentions); - if (max_notification_id.get() <= group_info.max_removed_notification_id.get()) { - return; - } - if (max_message_id > group_info.max_removed_message_id) { - VLOG(notifications) << "Set max_removed_message_id in " << group_info.group_id << '/' << dialog_id << " to " - << max_message_id; - group_info.max_removed_message_id = max_message_id.get_prev_server_message_id(); - } - - VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << dialog_id << " to " - << max_notification_id; - group_info.max_removed_notification_id = max_notification_id; - on_dialog_updated(dialog_id, "remove_message_notifications"); - - if (group_info.last_notification_id.is_valid() && - max_notification_id.get() >= group_info.last_notification_id.get()) { - bool is_changed = - set_dialog_last_notification(dialog_id, group_info, 0, NotificationId(), "remove_message_notifications"); - CHECK(is_changed); + if (group_info.set_max_removed_notification_id(max_notification_id, max_message_id.get_prev_server_message_id(), + "remove_message_notifications")) { + on_dialog_updated(dialog_id, "remove_message_notifications"); } } @@ -29647,7 +29818,7 @@ int32 MessagesManager::get_dialog_pending_notification_count(const Dialog *d, bo void MessagesManager::update_dialog_mention_notification_count(const Dialog *d) { CHECK(d != nullptr); if (td_->auth_manager_->is_bot() || d->notification_info == nullptr || - !d->notification_info->mention_notification_group_.group_id.is_valid()) { + !d->notification_info->mention_notification_group_.is_valid()) { return; } auto total_count = get_dialog_pending_notification_count(d, true) - @@ -29658,7 +29829,7 @@ void MessagesManager::update_dialog_mention_notification_count(const Dialog *d) total_count = 0; } send_closure_later(G()->notification_manager(), &NotificationManager::set_notification_total_count, - d->notification_info->mention_notification_group_.group_id, total_count); + d->notification_info->mention_notification_group_.get_group_id(), total_count); } bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Message *m) const { @@ -29757,13 +29928,15 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f CHECK(m->message_id.is_valid()); if (!force && d->notification_info != nullptr) { - if (d->notification_info->message_notification_group_.group_id.is_valid()) { + if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications, - d->notification_info->message_notification_group_.group_id, "add_new_message_notification 1"); + d->notification_info->message_notification_group_.get_group_id(), + "add_new_message_notification 1"); } - if (d->notification_info->mention_notification_group_.group_id.is_valid()) { + if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications, - d->notification_info->mention_notification_group_.group_id, "add_new_message_notification 2"); + d->notification_info->mention_notification_group_.get_group_id(), + "add_new_message_notification 2"); } } @@ -29778,7 +29951,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f from_mentions ? m->contains_unread_mention || is_pinned : m->message_id > d->last_read_inbox_message_id; if (is_active) { auto &group_info = get_notification_group_info(d, from_mentions); - if (group_info.max_removed_message_id >= m->message_id) { + if (group_info.is_removed_object_id(m->message_id)) { is_active = false; } } @@ -29853,16 +30026,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions, settings_dialog_id); }); - if (settings_dialog == nullptr && have_input_peer(settings_dialog_id, AccessRights::Read)) { - force_create_dialog(settings_dialog_id, "add_new_message_notification 2"); - settings_dialog = get_dialog(settings_dialog_id); - } - if (settings_dialog != nullptr) { - td_->notification_settings_manager_->send_get_dialog_notification_settings_query( - settings_dialog_id, MessageId() /* TODO */, std::move(promise)); - } else { - send_get_dialog_query(settings_dialog_id, std::move(promise), 0, "add_new_message_notification"); - } + reload_dialog_notification_settings(settings_dialog_id, std::move(promise), "add_new_message_notification"); } if (missing_pinned_message_id.is_valid()) { VLOG(notifications) << "Fetch pinned " << missing_pinned_message_id; @@ -29891,9 +30055,8 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f if (!m->notification_id.is_valid()) { return false; } - bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id, - "add_new_message_notification 3"); - CHECK(is_changed); + set_dialog_last_notification_checked(d->dialog_id, group_info, m->date, m->notification_id, + "add_new_message_notification 3"); if (is_pinned) { set_dialog_pinned_message_notification(d, from_mentions ? m->message_id : MessageId(), "add_new_message_notification"); @@ -29902,8 +30065,8 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f // protection from accidental notification_id removal in set_dialog_pinned_message_notification return false; } - VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " << group_info.group_id - << '/' << d->dialog_id; + VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " + << group_info.get_group_id() << '/' << d->dialog_id; int32 min_delay_ms = 0; if (need_delay_message_content_notification(m->content.get(), td_->contacts_manager_->get_my_id())) { min_delay_ms = 3000; // 3 seconds @@ -29911,7 +30074,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f min_delay_ms = 1000; // 1 second } auto ringtone_id = get_dialog_notification_ringtone_id(settings_dialog_id, settings_dialog); - bool is_silent = m->disable_notification || m->message_id <= notification_info->max_notification_message_id_; + bool is_silent = m->disable_notification || m->message_id <= notification_info->max_push_notification_message_id_; send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id, from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages, d->dialog_id, m->date, settings_dialog_id, m->disable_notification, is_silent ? 0 : ringtone_id, min_delay_ms, @@ -29948,7 +30111,9 @@ void MessagesManager::flush_pending_new_message_notifications(DialogId dialog_id auto it = pending_notifications.begin(); while (it != pending_notifications.end() && it->first == DialogId()) { auto m = get_message(d, it->second); - if (m != nullptr && add_new_message_notification(d, m, true)) { + if (m == nullptr) { + VLOG(notifications) << "Can't find " << it->second << " in " << dialog_id << " with pending notification"; + } else if (add_new_message_notification(d, m, true)) { on_message_changed(d, m, false, "flush_pending_new_message_notifications"); } ++it; @@ -29962,21 +30127,18 @@ void MessagesManager::flush_pending_new_message_notifications(DialogId dialog_id } void MessagesManager::remove_all_dialog_notifications(Dialog *d, bool from_mentions, const char *source) { - // removes up to group_info.last_notification_id + // removes up to group_info.get_last_notification_id() CHECK(!td_->auth_manager_->is_bot()); if (d->notification_info == nullptr) { return; } NotificationGroupInfo &group_info = get_notification_group_info(d, from_mentions); - if (group_info.group_id.is_valid() && group_info.last_notification_id.is_valid() && - group_info.max_removed_notification_id != group_info.last_notification_id) { - VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << d->dialog_id << " to " - << group_info.last_notification_id << " from " << source; - group_info.max_removed_notification_id = group_info.last_notification_id; - if (d->notification_info->max_notification_message_id_ > group_info.max_removed_message_id) { - group_info.max_removed_message_id = - d->notification_info->max_notification_message_id_.get_prev_server_message_id(); - } + if (group_info.is_valid() && group_info.get_last_notification_id().is_valid()) { + auto last_notification_id = group_info.get_last_notification_id(); + group_info.set_max_removed_notification_id(last_notification_id, + d->notification_info->max_push_notification_message_id_, source); + on_dialog_updated(d->dialog_id, source); + if (!d->notification_info->pending_new_message_notifications_.empty()) { for (auto &it : d->notification_info->pending_new_message_notifications_) { it.first = DialogId(); @@ -29985,14 +30147,9 @@ void MessagesManager::remove_all_dialog_notifications(Dialog *d, bool from_menti } // remove_message_notifications will be called by NotificationManager send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, - group_info.group_id, group_info.last_notification_id, MessageId(), 0, true, Promise()); - if (d->notification_info->new_secret_chat_notification_id_.is_valid() && - &group_info == &d->notification_info->message_notification_group_) { - remove_new_secret_chat_notification(d, false); - } else { - bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), source); - CHECK(is_changed); - } + group_info.get_group_id(), last_notification_id, MessageId(), 0, true, Promise()); + d->notification_info->new_secret_chat_notification_id_ = NotificationId(); + set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), source); // just in case } } @@ -30005,11 +30162,11 @@ void MessagesManager::remove_message_dialog_notifications(Dialog *d, MessageId m return; } NotificationGroupInfo &group_info = get_notification_group_info(d, from_mentions); - if (!group_info.group_id.is_valid()) { + if (!group_info.is_valid()) { return; } - VLOG(notifications) << "Remove message dialog notifications in " << group_info.group_id << '/' << d->dialog_id + VLOG(notifications) << "Remove message dialog notifications in " << group_info.get_group_id() << '/' << d->dialog_id << " up to " << max_message_id << " from " << source; if (!d->notification_info->pending_new_message_notifications_.empty()) { @@ -30034,8 +30191,9 @@ void MessagesManager::remove_message_dialog_notifications(Dialog *d, MessageId m LOG(FATAL) << "TODO support notification deletion up to " << max_message_id << " if it would be ever needed"; } - send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, group_info.group_id, - NotificationId(), max_notification_message_id, 0, true, Promise()); + send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, + group_info.get_group_id(), NotificationId(), max_notification_message_id, 0, true, + Promise()); } void MessagesManager::send_update_message_send_succeeded(const Dialog *d, MessageId old_message_id, const Message *m) { @@ -30220,10 +30378,12 @@ void MessagesManager::send_update_unread_message_count(DialogList &list, DialogI auto dialog_list_id = list.dialog_list_id; CHECK(list.is_message_unread_count_inited_); if (list.unread_message_muted_count_ < 0 || list.unread_message_muted_count_ > list.unread_message_total_count_) { - LOG(ERROR) << "Unread message count became invalid in " << dialog_list_id << ": " - << list.unread_message_total_count_ << '/' - << list.unread_message_total_count_ - list.unread_message_muted_count_ << " from " << source << " and " - << dialog_id; + if (!list.need_unread_count_recalc_) { + LOG(ERROR) << "Unread message count became invalid in " << dialog_list_id << ": " + << list.unread_message_total_count_ << '/' + << list.unread_message_total_count_ - list.unread_message_muted_count_ << " from " << source << " and " + << dialog_id; + } if (list.unread_message_muted_count_ < 0) { list.unread_message_muted_count_ = 0; } @@ -30265,11 +30425,13 @@ void MessagesManager::send_update_unread_chat_count(DialogList &list, DialogId d list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_ || list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ < list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) { - LOG(ERROR) << "Unread chat count became invalid in " << dialog_list_id << ": " << list.unread_dialog_total_count_ - << '/' << list.unread_dialog_total_count_ - list.unread_dialog_muted_count_ << '/' - << list.unread_dialog_marked_count_ << '/' - << list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_ << " from " << source - << " and " << dialog_id; + if (!list.need_unread_count_recalc_) { + LOG(ERROR) << "Unread chat count became invalid in " << dialog_list_id << ": " << list.unread_dialog_total_count_ + << '/' << list.unread_dialog_total_count_ - list.unread_dialog_muted_count_ << '/' + << list.unread_dialog_marked_count_ << '/' + << list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_ << " from " << source + << " and " << dialog_id; + } if (list.unread_dialog_muted_marked_count_ < 0) { list.unread_dialog_muted_marked_count_ = 0; } @@ -30652,6 +30814,22 @@ void MessagesManager::on_send_message_get_quick_ack(int64 random_id) { get_chat_id_object(dialog_id, "updateMessageSendAcknowledged"), message_id.get())); } +bool MessagesManager::is_invalid_poll_message(const telegram_api::Message *message) { + CHECK(message != nullptr); + auto constructor_id = message->get_id(); + if (constructor_id != telegram_api::message::ID) { + return false; + } + + auto media = static_cast(message)->media_.get(); + if (media == nullptr || media->get_id() != telegram_api::messageMediaPoll::ID) { + return false; + } + + auto poll = static_cast(media)->poll_.get(); + return !PollId(poll->id_).is_valid(); +} + void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog_id, const telegram_api::Updates *updates_ptr, const char *source) { CHECK(updates_ptr != nullptr); @@ -30659,25 +30837,6 @@ void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog auto sent_messages = UpdatesManager::get_new_messages(updates_ptr); auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates_ptr); - auto is_invalid_poll_message = [](const telegram_api::Message *message) { - CHECK(message != nullptr); - auto constructor_id = message->get_id(); - if (constructor_id == telegram_api::messageEmpty::ID) { - return true; - } - if (constructor_id != telegram_api::message::ID) { - return false; - } - - auto media = static_cast(message)->media_.get(); - if (media == nullptr || media->get_id() != telegram_api::messageMediaPoll::ID) { - return false; - } - - auto poll = static_cast(media)->poll_.get(); - return !PollId(poll->id_).is_valid(); - }; - if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u || *sent_messages_random_ids.begin() != random_id || DialogId::get_message_dialog_id(sent_messages[0].first) != dialog_id || @@ -30835,13 +30994,12 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI return {dialog_id, new_message_id}; } -void MessagesManager::on_send_message_file_part_missing(int64 random_id, int bad_part) { +void MessagesManager::on_send_message_file_parts_missing(int64 random_id, vector &&bad_parts) { auto it = being_sent_messages_.find(random_id); if (it == being_sent_messages_.end()) { // we can't receive fail more than once // but message can be successfully sent before - LOG(WARNING) << "Receive FILE_PART_" << bad_part - << "_MISSING about successfully sent message with random_id = " << random_id; + LOG(INFO) << "Receive error for successfully sent message with random_id = " << random_id; return; } @@ -30877,7 +31035,7 @@ void MessagesManager::on_send_message_file_part_missing(int64 random_id, int bad get_log_event_storer(log_event)); } - do_send_message(dialog_id, m, {bad_part}); + do_send_message(dialog_id, m, std::move(bad_parts)); } void MessagesManager::on_send_message_file_reference_error(int64 random_id) { @@ -31100,6 +31258,8 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) { error_message = "Wrong poll data specified"; } else if (content_type == MessageContentType::Contact) { error_message = "Wrong phone number specified"; + } else if (content_type == MessageContentType::Story) { + error_message = "Wrong story data specified"; } else { error_message = "Wrong file identifier/HTTP URL specified"; } @@ -31516,13 +31676,13 @@ void MessagesManager::set_dialog_is_translatable(Dialog *d, bool is_translatable } } -void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked) { +void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pinned message in invalid " << dialog_id; return; } if (dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked); + td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_blocked"); @@ -31531,33 +31691,37 @@ void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_bl return; } - if (d->is_blocked == is_blocked) { - if (!d->is_is_blocked_inited) { - CHECK(is_blocked == false); + if (d->is_blocked == is_blocked && d->is_blocked_for_stories == is_blocked_for_stories) { + if (!d->is_is_blocked_for_stories_inited) { + CHECK(is_blocked_for_stories == false); d->is_is_blocked_inited = true; + d->is_is_blocked_for_stories_inited = true; on_dialog_updated(dialog_id, "on_update_dialog_is_blocked"); } return; } - set_dialog_is_blocked(d, is_blocked); + set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } -void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked) { +void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_blocked_for_stories) { CHECK(d != nullptr); - CHECK(d->is_blocked != is_blocked); + CHECK(d->is_blocked != is_blocked || d->is_blocked_for_stories != is_blocked_for_stories); d->is_blocked = is_blocked; + d->is_blocked_for_stories = is_blocked_for_stories; d->is_is_blocked_inited = true; + d->is_is_blocked_for_stories_inited = true; on_dialog_updated(d->dialog_id, "set_dialog_is_blocked"); - LOG(INFO) << "Set " << d->dialog_id << " is_blocked to " << is_blocked; + LOG(INFO) << "Set " << d->dialog_id << " is_blocked to " << is_blocked << '/' << is_blocked_for_stories; LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_blocked"; + auto block_list_id = BlockListId(d->is_blocked, d->is_blocked_for_stories); send_closure(G()->td(), &Td::send_update, - td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatIsBlocked"), - is_blocked)); + td_api::make_object(get_chat_id_object(d->dialog_id, "updateChatBlockList"), + block_list_id.get_block_list_object())); if (d->dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked); + td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); if (d->know_action_bar) { if (is_blocked) { @@ -31571,11 +31735,12 @@ void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked) { } td_->contacts_manager_->for_each_secret_chat_with_user( - d->dialog_id.get_user_id(), [this, is_blocked](SecretChatId secret_chat_id) { + d->dialog_id.get_user_id(), [this, is_blocked, is_blocked_for_stories](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog - if (d != nullptr && d->is_update_new_chat_sent && d->is_blocked != is_blocked) { - set_dialog_is_blocked(d, is_blocked); + if (d != nullptr && d->is_update_new_chat_sent && + (d->is_blocked != is_blocked || d->is_blocked_for_stories != is_blocked_for_stories)) { + set_dialog_is_blocked(d, is_blocked, is_blocked_for_stories); } }); } @@ -31936,26 +32101,28 @@ void MessagesManager::set_dialog_folder_id(Dialog *d, FolderId folder_id) { void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) { CHECK(!td_->auth_manager_->is_bot()); - if (d->folder_id == folder_id && d->is_folder_id_inited) { + bool is_changed = d->folder_id != folder_id; + if (!is_changed && d->is_folder_id_inited) { return; } - - d->folder_id = folder_id; d->is_folder_id_inited = true; - if (d->dialog_id.get_type() == DialogType::SecretChat) { - // need to change action bar only for the secret chat and keep unarchive for the main chat - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); - if (d->is_update_new_chat_sent && user_id.is_valid()) { - const Dialog *user_d = get_dialog(DialogId(user_id)); - if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) { - send_closure(G()->td(), &Td::send_update, - td_api::make_object( - get_chat_id_object(d->dialog_id, "updateChatActionBar"), get_chat_action_bar_object(d))); + if (is_changed) { + d->folder_id = folder_id; + if (d->dialog_id.get_type() == DialogType::SecretChat) { + // need to change action bar only for the secret chat and keep unarchive for the main chat + auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); + if (d->is_update_new_chat_sent && user_id.is_valid()) { + const Dialog *user_d = get_dialog(DialogId(user_id)); + if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) { + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + get_chat_id_object(d->dialog_id, "updateChatActionBar"), get_chat_action_bar_object(d))); + } } + } else if (folder_id != FolderId::archive() && d->action_bar != nullptr && d->action_bar->on_dialog_unarchived()) { + send_update_chat_action_bar(d); } - } else if (folder_id != FolderId::archive() && d->action_bar != nullptr && d->action_bar->on_dialog_unarchived()) { - send_update_chat_action_bar(d); } on_dialog_updated(d->dialog_id, "do_set_dialog_folder_id"); @@ -32049,7 +32216,8 @@ void MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id(Dial if (default_join_as_dialog_id.is_valid()) { if (default_join_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_join_as_dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id"); - } else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id()) || + } else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id(), + "on_update_dialog_default_join_group_call_as_dialog_id") || default_join_as_dialog_id != get_my_dialog_id()) { default_join_as_dialog_id = DialogId(); } @@ -32089,7 +32257,8 @@ void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogI if (default_send_as_dialog_id.is_valid()) { if (default_send_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_send_as_dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); - } else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id()) || + } else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id(), + "on_update_dialog_default_send_message_as_dialog_id") || default_send_as_dialog_id != get_my_dialog_id()) { default_send_as_dialog_id = DialogId(); } @@ -32401,6 +32570,58 @@ void MessagesManager::on_dialog_linked_channel_updated(DialogId dialog_id, Chann } } +void MessagesManager::send_resolve_dialog_username_query(const string &username, Promise &&promise) { + td_->create_handler(std::move(promise))->send(username); +} + +void MessagesManager::resolve_dialog(const string &username, ChannelId channel_id, Promise promise) { + CHECK(username.empty() == channel_id.is_valid()); + + bool have_dialog = username.empty() ? td_->contacts_manager_->have_channel_force(channel_id) + : resolve_dialog_username(username).is_valid(); + if (!have_dialog) { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), username, channel_id, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &MessagesManager::on_resolve_dialog, username, channel_id, std::move(promise)); + }); + if (username.empty()) { + td_->contacts_manager_->reload_channel(channel_id, std::move(query_promise)); + } else { + send_resolve_dialog_username_query(username, std::move(query_promise)); + } + return; + } + + return on_resolve_dialog(username, channel_id, std::move(promise)); +} + +void MessagesManager::on_resolve_dialog(const string &username, ChannelId channel_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + DialogId dialog_id; + if (username.empty()) { + if (!td_->contacts_manager_->have_channel(channel_id)) { + return promise.set_error(Status::Error(500, "Chat info not found")); + } + + dialog_id = DialogId(channel_id); + force_create_dialog(dialog_id, "on_resolve_dialog"); + } else { + dialog_id = resolve_dialog_username(username); + if (dialog_id.is_valid()) { + force_create_dialog(dialog_id, "on_resolve_dialog", true); + } + } + Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog"); + if (d == nullptr) { + return promise.set_error(Status::Error(500, "Chat not found")); + } + promise.set_value(std::move(dialog_id)); +} + DialogId MessagesManager::resolve_dialog_username(const string &username) const { auto cleaned_username = clean_username(username); auto resolved_username = resolved_usernames_.get(cleaned_username); @@ -32425,7 +32646,7 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search, auto resolved_username = resolved_usernames_.get(username); if (resolved_username.dialog_id.is_valid()) { if (resolved_username.expires_at < Time::now()) { - td_->create_handler(Promise())->send(username); + send_resolve_dialog_username_query(username, Promise()); } dialog_id = resolved_username.dialog_id; } else { @@ -32465,7 +32686,7 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search, } } - td_->create_handler(std::move(promise))->send(username); + send_resolve_dialog_username_query(username, std::move(promise)); return DialogId(); } @@ -32574,28 +32795,34 @@ void MessagesManager::on_get_dialog_query_finished(DialogId dialog_id, Status && void MessagesManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames) { CHECK(dialog_id.is_valid()); - const auto *d = get_dialog(dialog_id); - if (d != nullptr) { - update_dialogs_hints(d); - } - if (old_usernames != new_usernames) { - message_embedding_codes_[0].erase(dialog_id); - message_embedding_codes_[1].erase(dialog_id); + LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames; - LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames; - } - if (!old_usernames.is_empty() && old_usernames != new_usernames) { + message_embedding_codes_[0].erase(dialog_id); + message_embedding_codes_[1].erase(dialog_id); + + if (!old_usernames.is_empty()) { for (auto &username : old_usernames.get_active_usernames()) { auto cleaned_username = clean_username(username); resolved_usernames_.erase(cleaned_username); inaccessible_resolved_usernames_.erase(cleaned_username); } } - if (!new_usernames.is_empty()) { - for (auto &username : new_usernames.get_active_usernames()) { + + on_dialog_usernames_received(dialog_id, new_usernames, false); +} + +void MessagesManager::on_dialog_usernames_received(DialogId dialog_id, const Usernames &usernames, bool from_database) { + const auto *d = get_dialog(dialog_id); + if (d != nullptr) { + update_dialogs_hints(d); + } + + if (!usernames.is_empty()) { + for (auto &username : usernames.get_active_usernames()) { auto cleaned_username = clean_username(username); if (!cleaned_username.empty()) { - resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + USERNAME_CACHE_EXPIRE_TIME}; + resolved_usernames_[cleaned_username] = + ResolvedUsername{dialog_id, Time::now() + (from_database ? 0 : USERNAME_CACHE_EXPIRE_TIME)}; } } } @@ -32938,6 +33165,28 @@ void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) { } } +void MessagesManager::get_dialog_filter_dialog_count(td_api::object_ptr filter, + Promise &&promise) { + TRY_RESULT_PROMISE(promise, dialog_filter, + DialogFilter::create_dialog_filter(td_, DialogFilterId(), std::move(filter))); + + int32 total_count = 0; + for (auto folder_id : dialog_filter->get_folder_ids()) { + const auto &folder = *get_dialog_folder(folder_id); + for (const auto &dialog_date : folder.ordered_dialogs_) { + if (dialog_date.get_order() == DEFAULT_ORDER) { + break; + } + + auto dialog_id = dialog_date.get_dialog_id(); + if (dialog_filter->need_dialog(td_, get_dialog_info_for_dialog_filter(get_dialog(dialog_id)))) { + total_count++; + } + } + } + promise.set_value(std::move(total_count)); +} + void MessagesManager::add_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id) { DialogListId dialog_list_id(dialog_filter_id); CHECK(dialog_lists_.count(dialog_list_id) == 0); @@ -33518,7 +33767,7 @@ void MessagesManager::set_dialog_available_reactions( ChatReactions available_reactions(std::move(available_reactions_ptr), !is_broadcast_channel(dialog_id)); auto active_reactions = get_active_reactions(available_reactions); - if (active_reactions.reactions_.size() != available_reactions.reactions_.size()) { + if (active_reactions.reaction_types_.size() != available_reactions.reaction_types_.size()) { return promise.set_error(Status::Error(400, "Invalid reactions specified")); } available_reactions = std::move(active_reactions); @@ -33575,8 +33824,6 @@ void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Prom return promise.set_error(Status::Error(400, "Have no write access to the chat")); } - LOG(INFO) << "Begin to set message auto-delete time in " << dialog_id << " to " << ttl; - switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id() || @@ -33613,7 +33860,7 @@ 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(), MessageId(), MessageSendOptions(), + Message *m = get_message_to_send(d, MessageId(), MessageInputReplyTo(), MessageSendOptions(), create_chat_set_ttl_message_content(ttl, UserId()), &need_update_dialog_pos); send_update_new_message(d, m); @@ -33854,7 +34101,7 @@ void MessagesManager::unpin_all_dialog_messages(DialogId dialog_id, MessageId to return promise.set_error(Status::Error(400, "Chat not found")); } TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id)); - TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId())); + TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); if (!td_->auth_manager_->is_bot()) { auto message_ids = find_dialog_messages(d, [top_thread_message_id](const Message *m) { @@ -33967,6 +34214,10 @@ const MessagesManager::Message *MessagesManager::get_message(const Dialog *d, Me return result; } +const MessagesManager::Message *MessagesManager::get_message_static(const Dialog *d, MessageId message_id) { + return get_message(d, message_id); +} + MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageId message_id, const char *source) { if (!message_id.is_valid() && !message_id.is_valid_scheduled()) { return nullptr; @@ -34265,9 +34516,9 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo if (!(d->have_full_history && from_update) && d->last_message_id.is_valid() && d->last_message_id < MessageId(ServerMessageId(1)) && message_id >= MessageId(ServerMessageId(1))) { - set_dialog_last_message_id(d, MessageId(), "add_message_to_dialog"); + set_dialog_last_message_id(d, MessageId(), "add_message_to_dialog_message_list"); - set_dialog_first_database_message_id(d, MessageId(), "add_message_to_dialog"); + set_dialog_first_database_message_id(d, MessageId(), "add_message_to_dialog_message_list"); set_dialog_last_database_message_id(d, MessageId(), source); d->have_full_history = false; d->have_full_history_source = 0; @@ -34283,7 +34534,7 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo if (from_update && !m->is_failed_to_send && message_id > d->last_new_message_id && !message_id.is_yet_unsent()) { if (dialog_type == DialogType::SecretChat || message_id.is_server()) { // can delete messages, therefore must be called before message attaching/adding - set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog"); + set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog_message_list"); } } @@ -34306,7 +34557,7 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo if (message_id.is_server() && d->last_read_inbox_message_id.is_valid() && d->last_read_inbox_message_id.is_server() && message_id == d->last_read_inbox_message_id.get_next_message_id(MessageType::Server)) { - read_history_inbox(d, message_id, 0, "add_message_to_dialog"); + read_history_inbox(d, message_id, 0, "add_message_to_dialog_message_list"); } } } @@ -34316,13 +34567,13 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo } if (need_update && has_unread_message_reactions(dialog_id, m)) { set_dialog_unread_reaction_count(d, d->unread_reaction_count + 1); - send_update_chat_unread_reaction_count(d, "add_message_to_dialog"); + send_update_chat_unread_reaction_count(d, "add_message_to_dialog_message_list"); } if (need_update) { update_message_count_by_index(d, +1, m); } if (from_update && message_id > d->last_message_id && message_id >= d->last_new_message_id) { - set_dialog_last_message_id(d, message_id, "add_message_to_dialog", m); + set_dialog_last_message_id(d, message_id, "add_message_to_dialog_message_list", m); *need_update_dialog_pos = true; } if (from_update && !message_id.is_yet_unsent() && message_id >= d->last_new_message_id && @@ -34332,9 +34583,9 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo (d->last_database_message_id.is_valid() && message_id > d->last_database_message_id))))) { CHECK(message_id <= d->last_message_id); if (message_id > d->last_database_message_id) { - set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog"); + set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog_message_list"); if (!d->first_database_message_id.is_valid()) { - set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog"); + set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog_message_list"); try_restore_dialog_reply_markup(d, m); } } @@ -34362,8 +34613,7 @@ void MessagesManager::add_message_to_dialog_message_list(const Message *m, Dialo on_dialog_updated(dialog_id, "do delete last message"); - send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, dialog_id, false, false, - Promise()); + send_closure_later(actor_id(this), &MessagesManager::load_last_dialog_message_later, dialog_id); } d->ordered_messages.insert(message_id, from_update, old_last_message_id, source); @@ -34447,7 +34697,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq << to_string(get_message_object(dialog_id, message.get(), "add_message_to_dialog")); if (need_channel_difference_to_add_message(dialog_id, nullptr)) { - schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001); + schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001, "add_message_to_dialog"); } } else { LOG(INFO) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source; @@ -34621,7 +34871,8 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq d->max_added_message_id = message->message_id; } - if (d->open_count == 0 && !d->has_unload_timeout && !d->messages.empty() && is_message_unload_enabled()) { + if (d->open_count == 0 && !d->has_unload_timeout && is_message_unload_enabled() && + (!d->messages.empty() || !from_database)) { LOG(INFO) << "Schedule unload of " << dialog_id; pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d)); d->has_unload_timeout = true; @@ -34703,14 +34954,14 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq auto notification_id = message->notification_id; VLOG(notifications) << "Remove mention " << notification_id << " in " << message_id << " in " << dialog_id; message->notification_id = NotificationId(); - if (d->notification_info->mention_notification_group_.last_notification_id == notification_id) { + if (d->notification_info->mention_notification_group_.get_last_notification_id() == notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, true, message_id); } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, - d->notification_info->mention_notification_group_.group_id, notification_id, false, false, - Promise(), "remove disabled mention notification"); + d->notification_info->mention_notification_group_.get_group_id(), notification_id, false, + false, Promise(), "remove disabled mention notification"); on_message_changed(d, message.get(), false, "remove_mention_notification"); } @@ -34762,6 +35013,16 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq on_dialog_updated(dialog_id, "update_has_contact_registered_message"); } + if (m->sender_user_id.is_valid()) { + auto story_full_id = get_message_content_story_full_id(td_, m->content.get()); + if (story_full_id.is_valid()) { + td_->story_manager_->on_story_replied(story_full_id, m->sender_user_id); + } + if (m->reply_to_story_full_id.is_valid()) { + td_->story_manager_->on_story_replied(m->reply_to_story_full_id, m->sender_user_id); + } + } + reget_message_from_server_if_needed(dialog_id, m); add_message_file_sources(dialog_id, m); @@ -34799,7 +35060,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq switch (dialog_type) { case DialogType::User: td_->contacts_manager_->invalidate_user_full(dialog_id.get_user_id()); - td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Promise()); + td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Promise(), "add_message_to_dialog"); break; case DialogType::Chat: case DialogType::Channel: @@ -34809,7 +35070,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { td_->contacts_manager_->invalidate_user_full(user_id); - td_->contacts_manager_->reload_user_full(user_id, Promise()); + td_->contacts_manager_->reload_user_full(user_id, Promise(), "add_message_to_dialog"); } break; } @@ -34844,7 +35105,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } break; case DialogType::Channel: - if (m->message_id.is_server()) { + if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { td_->contacts_manager_->register_message_users({dialog_id, m->message_id}, get_message_user_ids(m)); td_->contacts_manager_->register_message_channels({dialog_id, m->message_id}, get_message_channel_ids(m)); } @@ -35069,16 +35330,16 @@ void MessagesManager::on_message_notification_changed(Dialog *d, const Message * CHECK(m != nullptr); if (m->notification_id.is_valid() && is_message_notification_active(d, m)) { auto &group_info = get_notification_group_info(d, m); - if (group_info.group_id.is_valid()) { - send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id, - m->notification_id, + if (group_info.is_valid()) { + send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, + group_info.get_group_id(), m->notification_id, create_new_message_notification( m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m)))); } } if (m->is_pinned && d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid() && - d->notification_info->mention_notification_group_.group_id.is_valid()) { + d->notification_info->mention_notification_group_.is_valid()) { auto pinned_message = get_message_force(d, d->notification_info->pinned_message_notification_message_id_, "after update_message"); if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && @@ -35086,7 +35347,7 @@ void MessagesManager::on_message_notification_changed(Dialog *d, const Message * get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) { send_closure_later( G()->notification_manager(), &NotificationManager::edit_notification, - d->notification_info->mention_notification_group_.group_id, pinned_message->notification_id, + d->notification_info->mention_notification_group_.get_group_id(), pinned_message->notification_id, create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true))); } } @@ -35157,22 +35418,26 @@ void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, Auto()); // TODO Promise } +void MessagesManager::delete_all_dialog_notifications(Dialog *d, MessageId max_message_id, const char *source) { + CHECK(d != nullptr); + if (d->notification_info == nullptr) { + return; + } + if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { + remove_new_secret_chat_notification(d, true); + } + if (d->notification_info->pinned_message_notification_message_id_.is_valid() && + d->notification_info->pinned_message_notification_message_id_ <= max_message_id) { + remove_dialog_pinned_message_notification(d, source); + } + remove_message_dialog_notifications(d, max_message_id, false, source); + remove_message_dialog_notifications(d, max_message_id, true, source); +} + void MessagesManager::delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id, const char *source) { CHECK(d != nullptr); CHECK(max_message_id.is_valid()); - if (d->notification_info != nullptr) { - if (d->notification_info->new_secret_chat_notification_id_.is_valid()) { - remove_new_secret_chat_notification(d, true); - } - if (d->notification_info->pinned_message_notification_message_id_.is_valid() && - d->notification_info->pinned_message_notification_message_id_ <= max_message_id) { - remove_dialog_pinned_message_notification(d, source); - } - remove_message_dialog_notifications(d, max_message_id, false, source); - remove_message_dialog_notifications(d, max_message_id, true, source); - } - if (!G()->use_message_database()) { return; } @@ -35197,8 +35462,7 @@ void MessagesManager::delete_all_dialog_messages_from_database(Dialog *d, Messag } } */ - G()->td_db()->get_message_db_async()->delete_all_dialog_messages(dialog_id, max_message_id, - Auto()); // TODO Promise + G()->td_db()->get_message_db_async()->delete_all_dialog_messages(dialog_id, max_message_id, Auto()); // TODO Promise } class MessagesManager::DeleteMessageLogEvent { @@ -35343,22 +35607,23 @@ void MessagesManager::delete_message_from_database(Dialog *d, MessageId message_ auto from_mentions = is_from_mention_notification_group(m); auto &group_info = get_notification_group_info(d, from_mentions); - if (group_info.group_id.is_valid()) { - if (group_info.last_notification_id == m->notification_id) { + if (group_info.is_valid()) { + if (group_info.get_last_notification_id() == m->notification_id) { // last notification is deleted, need to find new last notification fix_dialog_last_notification_id(d, from_mentions, m->message_id); } if (is_message_notification_active(d, m)) { - send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id, - m->notification_id, true, false, Promise(), "delete_message_from_database"); + send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, + group_info.get_group_id(), m->notification_id, true, false, Promise(), + "delete_message_from_database"); } } } else if (!message_id.is_scheduled() && message_id > d->last_new_message_id && d->notification_info != nullptr) { - send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id, - d->notification_info->message_notification_group_.group_id, message_id, false, + send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, + d->notification_info->message_notification_group_.get_group_id(), message_id, false, "delete_message_from_database"); - send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id, - d->notification_info->mention_notification_group_.group_id, message_id, false, + send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, + d->notification_info->mention_notification_group_.get_group_id(), message_id, false, "delete_message_from_database"); } @@ -35621,21 +35886,21 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr 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 (is_new_available && (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)) { - if (!replace_legacy) { + 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 (!replace_legacy && is_new_available) { if (old_message->reply_to_message_id != new_message->reply_to_message_id) { LOG(INFO) << "Update replied message of " << FullMessageId{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 + // 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 deleted message + // 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() && @@ -35647,7 +35912,7 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } 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 + // move of reply to the top thread message after deletion of the replied message } else { 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 @@ -35679,6 +35944,16 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr << old_message->is_topic_message << " to " << new_message->is_topic_message; } } + if (old_message->reply_to_story_full_id != new_message->reply_to_story_full_id) { + if (!message_id.is_yet_unsent() || new_message->reply_to_story_full_id.is_valid() || is_message_in_dialog) { + LOG(ERROR) << message_id << " in " << dialog_id << " has changed replied story from " + << old_message->reply_to_story_full_id << " to " << new_message->reply_to_story_full_id + << ", message content type is " << old_content_type << '/' << new_content_type; + } else { + LOG(INFO) << "Update replied story of " << FullMessageId{dialog_id, message_id} << " from " + << old_message->reply_to_story_full_id << " to " << new_message->reply_to_story_full_id; + } + } } if ((is_top_thread_message_id_changed || is_is_topic_message_changed) && is_message_in_dialog && @@ -35691,10 +35966,11 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr } 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->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() && - old_message->message_id.is_yet_unsent() && + 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); if (replied_m != nullptr) { @@ -35749,8 +36025,8 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr << " to " << new_message->is_channel_post << ", message content type is " << old_content_type << '/' << new_content_type; if (old_message->contains_mention != new_message->contains_mention) { - if (old_message->edit_date == 0 && is_new_available && old_content_type != MessageContentType::PinMessage && - old_content_type != MessageContentType::ExpiredPhoto && old_content_type != MessageContentType::ExpiredVideo && + if (old_message->edit_date == 0 && is_new_available && new_content_type != MessageContentType::PinMessage && + new_content_type != MessageContentType::ExpiredPhoto && new_content_type != MessageContentType::ExpiredVideo && !replace_legacy) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed contains_mention from " << old_message->contains_mention << " to " << new_message->contains_mention @@ -36067,7 +36343,7 @@ MessageId MessagesManager::get_message_id_by_random_id(Dialog *d, int64 random_i } auto it = d->random_id_to_message_id.find(random_id); if (it == d->random_id_to_message_id.end()) { - if (G()->use_message_database() && d->dialog_id.get_type() == DialogType::SecretChat) { + if (G()->use_message_database() && d->dialog_id.get_type() == DialogType::SecretChat && random_id != 0) { auto r_value = G()->td_db()->get_message_db_sync()->get_message_by_random_id(d->dialog_id, random_id); if (r_value.is_ok()) { debug_add_message_to_dialog_fail_reason_ = "not called"; @@ -36138,7 +36414,9 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source auto new_notification_settings = DialogNotificationSettings( user_settings->use_default_mute_until, user_settings->mute_until, dup_notification_sound(user_settings->sound), true /*use_default_show_preview*/, false /*show_preview*/, - user_settings->silent_send_message, true, false, true, false); + user_settings->use_default_mute_stories, user_settings->mute_stories, + dup_notification_sound(user_settings->story_sound), user_settings->use_default_hide_story_sender, + user_settings->hide_story_sender, user_settings->silent_send_message, true, false, true, false); new_notification_settings.is_secret_chat_show_preview_fixed = true; update_dialog_notification_settings(dialog_id, &d->notification_settings, std::move(new_notification_settings)); @@ -36161,10 +36439,9 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source get_next_notification_id(notification_info, notification_group_id, MessageId()); if (notification_info->new_secret_chat_notification_id_.is_valid()) { auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id); - bool is_changed = set_dialog_last_notification(dialog_id, notification_info->message_notification_group_, - date, notification_info->new_secret_chat_notification_id_, - "add_new_secret_chat"); - CHECK(is_changed); + set_dialog_last_notification_checked(dialog_id, notification_info->message_notification_group_, date, + notification_info->new_secret_chat_notification_id_, + "add_new_secret_chat"); VLOG(notifications) << "Create " << notification_info->new_secret_chat_notification_id_ << " with " << secret_chat_id; auto ringtone_id = get_dialog_notification_ringtone_id(dialog_id, d); @@ -36247,6 +36524,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di break; case DialogType::Chat: d->is_is_blocked_inited = true; + d->is_is_blocked_for_stories_inited = true; d->is_background_inited = true; break; case DialogType::Channel: { @@ -36284,6 +36562,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di d->is_background_inited = true; d->is_theme_name_inited = true; d->is_is_blocked_inited = true; + d->is_is_blocked_for_stories_inited = true; if (!d->is_folder_id_inited && !td_->auth_manager_->is_bot()) { do_set_dialog_folder_id( d, td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id())); @@ -36355,12 +36634,12 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di } if (d->notification_info != nullptr) { - if (d->notification_info->message_notification_group_.group_id.is_valid()) { - notification_group_id_to_dialog_id_.emplace(d->notification_info->message_notification_group_.group_id, + if (d->notification_info->message_notification_group_.is_valid()) { + notification_group_id_to_dialog_id_.emplace(d->notification_info->message_notification_group_.get_group_id(), dialog_id); } - if (d->notification_info->mention_notification_group_.group_id.is_valid()) { - notification_group_id_to_dialog_id_.emplace(d->notification_info->mention_notification_group_.group_id, + if (d->notification_info->mention_notification_group_.is_valid()) { + notification_group_id_to_dialog_id_.emplace(d->notification_info->mention_notification_group_.get_group_id(), dialog_id); } } @@ -36426,8 +36705,9 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab force_create_dialog(DialogId(user_id), "add chat with user to load/store action_bar and is_blocked"); Dialog *user_d = get_dialog_force(DialogId(user_id), "fix_new_dialog"); - if (user_d != nullptr && d->is_blocked != user_d->is_blocked) { - set_dialog_is_blocked(d, user_d->is_blocked); + if (user_d != nullptr && + (d->is_blocked != user_d->is_blocked || d->is_blocked_for_stories != user_d->is_blocked_for_stories)) { + set_dialog_is_blocked(d, user_d->is_blocked, user_d->is_blocked_for_stories); } } } @@ -36443,9 +36723,9 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20"); } - if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) { - // asynchronously get is_blocked from the server - reload_dialog_info_full(dialog_id, "fix_new_dialog init is_blocked"); + if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_for_stories_inited && !td_->auth_manager_->is_bot()) { + // asynchronously get is_blocked_for_stories from the server + reload_dialog_info_full(dialog_id, "fix_new_dialog init is_blocked_for_stories"); } else if (being_added_dialog_id_ != dialog_id && !d->is_has_bots_inited && !td_->auth_manager_->is_bot()) { // asynchronously get has_bots from the server reload_dialog_info_full(dialog_id, "fix_new_dialog init has_bots"); @@ -36501,33 +36781,23 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab } 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_; - if (!d->notification_info->mention_notification_group_.group_id.is_valid()) { + if (!d->notification_info->mention_notification_group_.is_valid()) { LOG(ERROR) << "Have pinned message notification in " << pinned_message_id << " in " << dialog_id << ", but there is no mention notification group"; d->notification_info->pinned_message_notification_message_id_ = MessageId(); on_dialog_updated(dialog_id, "fix pinned message notification"); } else if (is_dialog_pinned_message_notifications_disabled(d) || pinned_message_id <= d->last_read_inbox_message_id || - pinned_message_id <= d->notification_info->mention_notification_group_.max_removed_message_id) { + d->notification_info->mention_notification_group_.is_removed_object_id(pinned_message_id)) { VLOG(notifications) << "Remove disabled pinned message notification in " << pinned_message_id << " in " << dialog_id; - send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id, - d->notification_info->mention_notification_group_.group_id, pinned_message_id, true, + send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_object_id, + d->notification_info->mention_notification_group_.get_group_id(), pinned_message_id, true, "fix pinned message notification"); d->notification_info->pinned_message_notification_message_id_ = MessageId(); on_dialog_updated(dialog_id, "fix pinned message notification 2"); } } - if (d->notification_info != nullptr && d->notification_info->new_secret_chat_notification_id_.is_valid()) { - auto &group_info = d->notification_info->message_notification_group_; - if (d->notification_info->new_secret_chat_notification_id_.get() <= group_info.max_removed_notification_id.get() || - (group_info.last_notification_date == 0 && group_info.max_removed_notification_id.get() == 0)) { - VLOG(notifications) << "Fix removing new secret chat " << d->notification_info->new_secret_chat_notification_id_ - << " in " << dialog_id; - d->notification_info->new_secret_chat_notification_id_ = NotificationId(); - on_dialog_updated(dialog_id, "fix new secret chat notification identifier"); - } - } { auto it = pending_add_dialog_last_database_message_dependent_dialogs_.find(dialog_id); @@ -36750,23 +37020,14 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab << d->first_database_message_id << ", last database " << d->last_database_message_id << ", last " << d->last_message_id << " with order " << d->order; if (d->notification_info != nullptr) { - VLOG(notifications) << "Have " << dialog_id << " with message " - << d->notification_info->message_notification_group_.group_id << " with last " - << d->notification_info->message_notification_group_.last_notification_id << " sent at " - << d->notification_info->message_notification_group_.last_notification_date << ", max removed " - << d->notification_info->message_notification_group_.max_removed_notification_id << "/" - << d->notification_info->message_notification_group_.max_removed_message_id + VLOG(notifications) << "Have " << dialog_id << " with message " << d->notification_info->message_notification_group_ << " and new secret chat " << d->notification_info->new_secret_chat_notification_id_; - VLOG(notifications) << "Have " << dialog_id << " with mention " - << d->notification_info->mention_notification_group_.group_id << " with last " - << d->notification_info->mention_notification_group_.last_notification_id << " sent at " - << d->notification_info->mention_notification_group_.last_notification_date << ", max removed " - << d->notification_info->mention_notification_group_.max_removed_notification_id << "/" - << d->notification_info->mention_notification_group_.max_removed_message_id << " and pinned " - << d->notification_info->pinned_message_notification_message_id_; + VLOG(notifications) << "Have " << dialog_id << " with mention " << d->notification_info->mention_notification_group_ + << " and pinned " << d->notification_info->pinned_message_notification_message_id_; VLOG(notifications) << "In " << dialog_id << " have last_read_inbox_message_id = " << d->last_read_inbox_message_id << ", last_new_message_id = " << d->last_new_message_id - << ", max_notification_message_id = " << d->notification_info->max_notification_message_id_; + << ", max_push_notification_message_id = " + << d->notification_info->max_push_notification_message_id_; } LOG_CHECK(d->messages.calc_size() <= 1) @@ -36792,19 +37053,23 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&last_datab d->pending_read_channel_inbox_pts = 0; on_dialog_updated(dialog_id, "fix_new_dialog 14"); } else { - schedule_get_channel_difference(dialog_id, d->pending_read_channel_inbox_pts, MessageId(), 0.001); + schedule_get_channel_difference(dialog_id, d->pending_read_channel_inbox_pts, MessageId(), 0.001, source); } } else { d->pending_read_channel_inbox_pts = 0; } if (need_get_history && !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))) { - get_history_from_the_end_impl(d, true, false, Auto(), "fix_new_dialog"); + load_last_dialog_message(d, "fix_new_dialog"); } if (d->need_repair_server_unread_count && need_unread_counter(d->order)) { CHECK(dialog_type != DialogType::SecretChat); repair_server_unread_count(dialog_id, d->server_unread_count, "fix_new_dialog"); } + if (dialog_type == DialogType::Channel && need_unread_counter(d->order) && d->server_unread_count > 0 && + !td_->auth_manager_->is_bot() && td_->option_manager_->get_option_integer("since_last_open") >= 2 * 86400) { + d->need_repair_channel_server_unread_count = true; + } if (d->need_repair_channel_server_unread_count) { repair_channel_server_unread_count(d); } @@ -36850,7 +37115,7 @@ bool MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptrauth_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))) { - get_history_from_the_end_impl(d, true, false, Auto(), "add_dialog_last_database_message 5"); + load_last_dialog_message(d, "add_dialog_last_database_message 5"); } } @@ -37070,6 +37335,7 @@ void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need if (new_order == DEFAULT_ORDER && !d->is_empty) { LOG(INFO) << "There are no known messages in the chat, just leave it where it is"; new_order = d->order; + load_last_dialog_message(d, source); } } @@ -37141,7 +37407,7 @@ bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_sen auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::Channel && is_added && being_added_dialog_id_ != dialog_id) { repair_channel_server_unread_count(d); - schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001); + schedule_get_channel_difference(dialog_id, 0, MessageId(), 0.001, source); } if (dialog_type == DialogType::Channel && is_removed) { remove_all_dialog_notifications(d, false, source); @@ -37480,7 +37746,7 @@ unique_ptr MessagesManager::parse_dialog(DialogId dialo invalidate_message_indexes(d); // and try to reget it from the server if possible - have_dialog_info_force(dialog_id); + have_dialog_info_force(dialog_id, "parse_dialog"); if (have_input_peer(dialog_id, AccessRights::Read)) { if (dialog_id.get_type() != DialogType::SecretChat) { send_get_dialog_query(dialog_id, Auto(), 0, source); @@ -37697,9 +37963,6 @@ MessagesManager::DialogListView MessagesManager::get_dialog_lists(const Dialog * MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); - if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) { - dialog_list_id = DialogListId(FolderId::main()); - } if (dialog_lists_.count(dialog_list_id) == 0) { LOG(INFO) << "Create " << dialog_list_id; } @@ -37710,9 +37973,6 @@ MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialo MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); - if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) { - dialog_list_id = DialogListId(FolderId::main()); - } auto it = dialog_lists_.find(dialog_list_id); if (it == dialog_lists_.end()) { return nullptr; @@ -37722,9 +37982,6 @@ MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialo const MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) const { CHECK(!td_->auth_manager_->is_bot()); - if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) { - dialog_list_id = DialogListId(FolderId::main()); - } auto it = dialog_lists_.find(dialog_list_id); if (it == dialog_lists_.end()) { return nullptr; @@ -37734,9 +37991,6 @@ const MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) { CHECK(!td_->auth_manager_->is_bot()); - if (folder_id != FolderId::archive()) { - folder_id = FolderId::main(); - } auto it = dialog_folders_.find(folder_id); if (it == dialog_folders_.end()) { return nullptr; @@ -37746,9 +38000,6 @@ MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folde const MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) const { CHECK(!td_->auth_manager_->is_bot()); - if (folder_id != FolderId::archive()) { - folder_id = FolderId::main(); - } auto it = dialog_folders_.find(folder_id); if (it == dialog_folders_.end()) { return nullptr; @@ -37899,6 +38150,7 @@ void MessagesManager::update_expected_channel_pts(DialogId dialog_id, int32 expe if (expected_pts <= 0) { return; } + CHECK(dialog_id.is_valid()); auto &old_pts = expected_channel_pts_[dialog_id]; if (old_pts < expected_pts) { old_pts = expected_pts; @@ -37909,6 +38161,7 @@ void MessagesManager::update_expected_channel_max_message_id(DialogId dialog_id, if (expected_max_message_id == MessageId() || td_->auth_manager_->is_bot()) { return; } + CHECK(dialog_id.is_valid()); auto &old_max_message_id = expected_channel_max_message_id_[dialog_id]; if (old_max_message_id < expected_max_message_id) { old_max_message_id = expected_max_message_id; @@ -37916,8 +38169,9 @@ void MessagesManager::update_expected_channel_max_message_id(DialogId dialog_id, } void MessagesManager::schedule_get_channel_difference(DialogId dialog_id, int32 expected_pts, - MessageId expected_max_message_id, double delay) { - LOG(INFO) << "Schedule getDifference in " << dialog_id; + MessageId expected_max_message_id, double delay, + const char *source) { + LOG(INFO) << "Schedule getDifference in " << dialog_id << " from " << source; update_expected_channel_pts(dialog_id, expected_pts); update_expected_channel_max_message_id(dialog_id, expected_max_message_id); channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), delay); @@ -37987,13 +38241,13 @@ void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, b // can be called multiple times before after_get_channel_difference const Dialog *d = get_dialog(dialog_id); if (d != nullptr && d->notification_info != nullptr) { - if (d->notification_info->message_notification_group_.group_id.is_valid()) { + if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference, - d->notification_info->message_notification_group_.group_id); + d->notification_info->message_notification_group_.get_group_id()); } - if (d->notification_info->mention_notification_group_.group_id.is_valid()) { + if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference, - d->notification_info->mention_notification_group_.group_id); + d->notification_info->mention_notification_group_.get_group_id()); } } @@ -38245,6 +38499,14 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m // offline. It is the best way for gaps support, but it is pretty hard to implement correctly. // It should be also noted that some messages like outgoing live location messages shouldn't be deleted. + if (is_message_unload_enabled()) { + if (d->open_count == 0) { + unload_dialog(dialog_id, 0); + } else { + d->need_unload_on_close = true; + } + } + if (last_message_id > d->last_new_message_id && !td_->auth_manager_->is_bot()) { // TODO properly support last_message_id <= d->last_new_message_id set_dialog_first_database_message_id(d, MessageId(), "on_get_channel_dialog 6"); @@ -38263,6 +38525,7 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m !td_->auth_manager_->is_bot()) { // if last message is really a new message if (!d->last_new_message_id.is_valid() && last_message_id <= d->max_added_message_id) { auto prev_message_id = MessageId(ServerMessageId(last_message_id.get_server_message_id().get() - 1)); + delete_all_dialog_notifications(d, prev_message_id, "on_get_channel_dialog 14"); remove_dialog_newer_messages(d, prev_message_id, "on_get_channel_dialog 15"); } d->last_new_message_id = MessageId(); @@ -38312,9 +38575,22 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m } } -void MessagesManager::on_get_channel_difference( - DialogId dialog_id, int32 request_pts, int32 request_limit, - tl_object_ptr &&difference_ptr) { +void MessagesManager::retry_get_channel_difference_later(DialogId dialog_id) { + auto &delay = channel_get_difference_retry_timeouts_[dialog_id]; + if (delay == 0) { + delay = 1; + } + schedule_get_channel_difference(dialog_id, 0, MessageId(), Random::fast(delay * 800, delay * 1200) * 1e-3, + "retry_get_channel_difference_later"); + delay *= 2; + if (delay > 60) { + delay = Random::fast(60, 80); + } +} + +void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit, + tl_object_ptr &&difference_ptr, + Status &&status) { get_channel_difference_count_--; CHECK(get_channel_difference_count_ >= 0); process_pending_get_channel_differences(); @@ -38326,27 +38602,23 @@ void MessagesManager::on_get_channel_difference( auto d = get_dialog_force(dialog_id, "on_get_channel_difference"); if (difference_ptr == nullptr) { - bool have_access = have_input_peer(dialog_id, AccessRights::Read); + CHECK(status.is_error()); + bool have_access = have_input_peer(dialog_id, AccessRights::Read) && status.message() != "CHANNEL_INVALID"; if (have_access) { if (d == nullptr) { force_create_dialog(dialog_id, "on_get_channel_difference failed"); + d = get_dialog(dialog_id); + if (d == nullptr) { + return after_get_channel_difference(dialog_id, false); + } } - auto &delay = channel_get_difference_retry_timeouts_[dialog_id]; - if (delay == 0) { - delay = 1; - } - schedule_get_channel_difference(dialog_id, 0, MessageId(), Random::fast(delay * 1000, delay * 1500) * 1e-3); - delay *= 2; - if (delay > 60) { - delay = Random::fast(60, 80); - } + retry_get_channel_difference_later(dialog_id); } else { after_get_channel_difference(dialog_id, false); } return; } - - channel_get_difference_retry_timeouts_.erase(dialog_id); + CHECK(status.is_ok()); LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with PTS = " << request_pts << " and limit = " << request_limit << " from " << source << ": " << to_string(difference_ptr); @@ -38356,6 +38628,7 @@ void MessagesManager::on_get_channel_difference( case telegram_api::updates_channelDifferenceEmpty::ID: if (d == nullptr) { // no need to create the dialog + channel_get_difference_retry_timeouts_.erase(dialog_id); after_get_channel_difference(dialog_id, true); return; } @@ -38365,6 +38638,14 @@ void MessagesManager::on_get_channel_difference( have_new_messages = !difference->new_messages_.empty(); td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference"); td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference"); + for (const auto &message : difference->new_messages_) { + if (is_invalid_poll_message(message.get())) { + LOG(ERROR) << "Receive invalid poll message in updates.channelDifference: " << oneline(to_string(message)); + if (channel_get_difference_retry_timeouts_[dialog_id] <= 2) { + return retry_get_channel_difference_later(dialog_id); + } + } + } break; } case telegram_api::updates_channelDifferenceTooLong::ID: { @@ -38378,6 +38659,8 @@ void MessagesManager::on_get_channel_difference( UNREACHABLE(); } + channel_get_difference_retry_timeouts_.erase(dialog_id); + bool need_update_dialog_pos = false; if (d == nullptr) { d = add_dialog_for_new_message(dialog_id, have_new_messages, &need_update_dialog_pos, "on_get_channel_difference"); @@ -38616,13 +38899,13 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ d->is_channel_difference_finished = true; if (d->notification_info != nullptr) { - if (d->notification_info->message_notification_group_.group_id.is_valid()) { + if (d->notification_info->message_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference, - d->notification_info->message_notification_group_.group_id); + d->notification_info->message_notification_group_.get_group_id()); } - if (d->notification_info->mention_notification_group_.group_id.is_valid()) { + if (d->notification_info->mention_notification_group_.is_valid()) { send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference, - d->notification_info->mention_notification_group_.group_id); + d->notification_info->mention_notification_group_.get_group_id()); } } } else { @@ -38653,13 +38936,13 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ if (d != nullptr && !td_->auth_manager_->is_bot() && have_access && !d->last_message_id.is_valid() && !d->is_empty && (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) { - get_history_from_the_end_impl(d, true, false, Auto(), "after_get_channel_difference"); + load_last_dialog_message(d, "after_get_channel_difference"); } auto expected_channel_pts_it = expected_channel_pts_.find(dialog_id); if (expected_channel_pts_it != expected_channel_pts_.end()) { - if (success && expected_channel_pts_it->second > pts) { - schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0); + if (success && d != nullptr && expected_channel_pts_it->second > pts) { + schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0, "after_get_channel_difference"); } expected_channel_pts_.erase(expected_channel_pts_it); } @@ -38667,7 +38950,7 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ auto expected_channel_max_message_id_it = expected_channel_max_message_id_.find(dialog_id); if (expected_channel_max_message_id_it != expected_channel_max_message_id_.end()) { if (success && d != nullptr && expected_channel_max_message_id_it->second > d->last_new_message_id) { - schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0); + schedule_get_channel_difference(dialog_id, 0, MessageId(), 1.0, "after_get_channel_difference 2"); } expected_channel_max_message_id_.erase(expected_channel_max_message_id_it); } @@ -38891,6 +39174,7 @@ void MessagesManager::set_message_reply(const Dialog *d, Message *m, MessageId r } m->reply_in_dialog_id = DialogId(); m->reply_to_message_id = reply_to_message_id; + 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())) { @@ -39047,7 +39331,7 @@ void MessagesManager::on_binlog_events(vector &&events) { dependencies.resolve_force(td_, "SendBotStartMessageLogEvent"); auto bot_user_id = log_event.bot_user_id; - if (!td_->contacts_manager_->have_user_force(bot_user_id)) { + if (!td_->contacts_manager_->have_user_force(bot_user_id, "SendBotStartMessageLogEvent")) { LOG(ERROR) << "Can't find bot " << bot_user_id; binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; @@ -39362,7 +39646,8 @@ void MessagesManager::on_binlog_events(vector &&events) { } auto sender_dialog_id = log_event.sender_dialog_id_; - if (!have_dialog_info_force(sender_dialog_id) || !have_input_peer(sender_dialog_id, AccessRights::Know)) { + if (!have_dialog_info_force(sender_dialog_id, "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; @@ -39581,7 +39866,8 @@ void MessagesManager::on_binlog_events(vector &&events) { 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()) + ? 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)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); @@ -39601,13 +39887,15 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event_parse(log_event, event.get_data()).ensure(); auto dialog_id = log_event.dialog_id_; - if (dialog_id.get_type() == DialogType::SecretChat || !have_dialog_info_force(dialog_id) || + if (dialog_id.get_type() == DialogType::SecretChat || + !have_dialog_info_force(dialog_id, "ToggleDialogIsBlockedOnServerLogEvent") || !have_input_peer(dialog_id, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } - toggle_dialog_is_blocked_on_server(dialog_id, log_event.is_blocked_, event.id_); + toggle_dialog_is_blocked_on_server(dialog_id, log_event.is_blocked_, log_event.is_blocked_for_stories_, + event.id_); break; } case LogEvent::HandlerType::SaveDialogDraftMessageOnServer: { @@ -39709,9 +39997,11 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dependencies dependencies; - dependencies.add_dialog_and_dependencies(dialog_id); + 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 + if (!have_input_peer(dialog_id, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; @@ -39800,12 +40090,7 @@ void MessagesManager::suffix_load_loop(const Dialog *d, SuffixLoadQueries *queri }); queries->suffix_load_has_query_ = true; queries->suffix_load_query_message_id_ = from_message_id; - if (from_message_id.is_valid()) { - get_history_impl(d, from_message_id, -1, 100, true, true, std::move(promise)); - } else { - CHECK(from_message_id == MessageId()); - get_history_from_the_end_impl(d, true, true, std::move(promise), "suffix_load_loop"); - } + get_history_impl(d, from_message_id, -1, MAX_GET_HISTORY, true, true, std::move(promise), "suffix_load_loop"); } void MessagesManager::suffix_load_update_first_message_id(const Dialog *d, SuffixLoadQueries *queries) { @@ -39913,7 +40198,7 @@ void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector>> &&promise) { + Promise> &&promise) { auto m = get_message_force(full_message_id, "get_poll_voters"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); diff --git a/td/telegram/MessagesManager.h b/td/telegram/MessagesManager.h index 9ec961bd62c9..acb2c9101242 100644 --- a/td/telegram/MessagesManager.h +++ b/td/telegram/MessagesManager.h @@ -34,6 +34,7 @@ #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageDb.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/MessageLinkInfo.h" #include "td/telegram/MessageReplyHeader.h" #include "td/telegram/MessageReplyInfo.h" @@ -46,22 +47,26 @@ #include "td/telegram/net/DcId.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/Notification.h" +#include "td/telegram/NotificationGroupFromDatabase.h" #include "td/telegram/NotificationGroupId.h" +#include "td/telegram/NotificationGroupInfo.h" #include "td/telegram/NotificationGroupKey.h" #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationSettingsScope.h" #include "td/telegram/OrderedMessage.h" #include "td/telegram/Photo.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/RecentDialogList.h" #include "td/telegram/ReplyMarkup.h" -#include "td/telegram/ReportReason.h" #include "td/telegram/RestrictionReason.h" #include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/secret_api.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/SecretInputMedia.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/StoryNotificationSettings.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -108,6 +113,7 @@ class DraftMessage; struct InputMessageContent; class MessageContent; struct MessageReactions; +class ReportReason; class Td; class MessagesManager final : public Actor { @@ -139,7 +145,6 @@ class MessagesManager final : public Actor { static constexpr int32 MESSAGE_FLAG_HAS_TTL_PERIOD = 1 << 25; static constexpr int32 MESSAGE_FLAG_NOFORWARDS = 1 << 26; - static constexpr int32 SEND_MESSAGE_FLAG_IS_REPLY = 1 << 0; 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; @@ -164,6 +169,8 @@ class MessagesManager final : public Actor { MessagesManager &operator=(MessagesManager &&) = delete; ~MessagesManager() final; + static bool is_invalid_poll_message(const telegram_api::Message *message); + tl_object_ptr get_input_peer(DialogId dialog_id, AccessRights access_rights) const; static tl_object_ptr get_input_peer_force(DialogId dialog_id); @@ -297,7 +304,7 @@ class MessagesManager final : public Actor { void update_is_translatable(bool new_is_premium); - void on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked); + void on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories); void on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId last_pinned_message_id); @@ -352,8 +359,9 @@ class MessagesManager final : public Actor { void try_reload_message_reactions(DialogId dialog_id, bool is_finished); - void on_get_message_reaction_list(FullMessageId full_message_id, const string &reaction, - FlatHashMap> reactions, int32 total_count); + void on_get_message_reaction_list(FullMessageId full_message_id, const ReactionType &reaction_type, + FlatHashMap, ReactionTypeHash> reaction_types, + int32 total_count); void on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count, bool has_reply_info, @@ -375,7 +383,8 @@ class MessagesManager final : public Actor { void on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id); - void on_update_channel_max_unavailable_message_id(ChannelId channel_id, MessageId max_unavailable_message_id); + void on_update_channel_max_unavailable_message_id(ChannelId channel_id, MessageId max_unavailable_message_id, + const char *source); void on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server); @@ -420,8 +429,13 @@ class MessagesManager final : public Actor { void clear_recently_found_dialogs(); + std::pair> search_recently_found_dialogs(const string &query, int32 limit, + Promise &&promise); + std::pair> get_recently_opened_dialogs(int32 limit, Promise &&promise); + void resolve_dialog(const string &username, ChannelId channel_id, Promise promise); + DialogId resolve_dialog_username(const string &username) const; DialogId search_public_dialog(const string &username_to_search, bool force, Promise &&promise); @@ -441,16 +455,16 @@ class MessagesManager final : public Actor { DialogId get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const; - MessageId get_reply_to_message_id(DialogId dialog_id, MessageId top_thread_message_id, MessageId message_id, - bool for_draft); + MessageInputReplyTo get_message_input_reply_to(DialogId dialog_id, MessageId top_thread_message_id, + td_api::object_ptr &&reply_to, bool for_draft); Result> send_message( - DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id, + 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, MessageId reply_to_message_id, + 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; @@ -459,7 +473,7 @@ class MessagesManager final : public Actor { const string ¶meter) TD_WARN_UNUSED_RESULT; Result send_inline_query_result_message(DialogId dialog_id, MessageId top_thread_message_id, - MessageId reply_to_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; @@ -475,13 +489,11 @@ class MessagesManager final : public Actor { void set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise &&promise); - Status send_screenshot_taken_notification_message(DialogId dialog_id); - void share_dialog_with_bot(FullMessageId full_message_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, - MessageId reply_to_message_id, bool disable_notification, + td_api::object_ptr &&reply_to, bool disable_notification, tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; @@ -540,6 +552,8 @@ class MessagesManager final : public Actor { void after_set_typing_query(DialogId dialog_id, int32 generation); + void get_dialog_filter_dialog_count(td_api::object_ptr filter, Promise &&promise); + void add_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id); void edit_dialog_list_for_dialog_filter(unique_ptr &old_dialog_filter, @@ -559,7 +573,7 @@ class MessagesManager final : public Actor { void set_dialog_description(DialogId dialog_id, const string &description, Promise &&promise); - void set_active_reactions(vector active_reactions); + void set_active_reactions(vector active_reaction_types); void set_dialog_available_reactions(DialogId dialog_id, td_api::object_ptr &&available_reactions_ptr, @@ -585,10 +599,12 @@ class MessagesManager final : public Actor { bool have_dialog_force(DialogId dialog_id, const char *source); bool have_dialog_info(DialogId dialog_id) const; - bool have_dialog_info_force(DialogId dialog_id) const; + bool have_dialog_info_force(DialogId dialog_id, const char *source) const; void reload_dialog_info_full(DialogId dialog_id, const char *source); + void reload_dialog_notification_settings(DialogId dialog_id, Promise &&promise, const char *source); + void on_dialog_info_full_invalidated(DialogId dialog_id); bool load_dialog(DialogId dialog_id, int left_tries, Promise &&promise); @@ -621,7 +637,8 @@ class MessagesManager final : public Actor { bool is_dialog_blocked(DialogId dialog_id) const; - void get_blocked_dialogs(int32 offset, int32 limit, Promise> &&promise); + void get_blocked_dialogs(const td_api::object_ptr &block_list, int32 offset, int32 limit, + Promise> &&promise); void on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count, vector> &&blocked_peers, @@ -702,8 +719,8 @@ class MessagesManager final : public Actor { Status toggle_dialog_is_translatable(DialogId dialog_id, bool is_translatable) TD_WARN_UNUSED_RESULT; - Status toggle_message_sender_is_blocked(const td_api::object_ptr &sender, - bool is_blocked) TD_WARN_UNUSED_RESULT; + Status set_message_sender_block_list(const td_api::object_ptr &sender, + const td_api::object_ptr &block_list) TD_WARN_UNUSED_RESULT; Status toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) TD_WARN_UNUSED_RESULT; @@ -742,6 +759,8 @@ class MessagesManager final : public Actor { void click_animated_emoji_message(FullMessageId full_message_id, Promise> &&promise); + StoryNotificationSettings get_story_notification_settings(DialogId dialog_id); + vector get_dialog_notification_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, bool force, Promise &&promise); @@ -751,6 +770,8 @@ class MessagesManager final : public Actor { void reset_all_notification_settings(); + void update_story_max_reply_media_timestamp_in_replied_messages(StoryFullId story_full_id); + int64 get_chat_id_object(DialogId dialog_id, const char *source) const; vector get_chat_ids_object(const vector &dialog_ids, const char *source) const; @@ -847,10 +868,10 @@ class MessagesManager final : public Actor { Result> get_message_available_reactions(FullMessageId full_message_id, int32 row_size); - void add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, bool add_to_recent, + void add_message_reaction(FullMessageId full_message_id, ReactionType reaction_type, bool is_big, bool add_to_recent, Promise &&promise); - void remove_message_reaction(FullMessageId full_message_id, string reaction, Promise &&promise); + void remove_message_reaction(FullMessageId full_message_id, ReactionType reaction_type, Promise &&promise); void get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit, Promise> &&promise); @@ -885,6 +906,7 @@ class MessagesManager final : public Actor { void on_dialog_photo_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); void on_dialog_default_permissions_updated(DialogId dialog_id); void on_dialog_has_protected_content_updated(DialogId dialog_id); @@ -938,7 +960,7 @@ class MessagesManager final : public Actor { FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, int32 ttl_period, FileId new_file_id, const char *source); - void on_send_message_file_part_missing(int64 random_id, int bad_part); + void on_send_message_file_parts_missing(int64 random_id, vector &&bad_parts); void on_send_message_file_reference_error(int64 random_id); @@ -949,7 +971,7 @@ class MessagesManager final : public Actor { void on_upload_message_media_success(DialogId dialog_id, MessageId message_id, tl_object_ptr &&media); - void on_upload_message_media_file_part_missing(DialogId dialog_id, MessageId message_id, int bad_part); + void on_upload_message_media_file_parts_missing(DialogId dialog_id, MessageId message_id, vector &&bad_parts); void on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error); @@ -959,7 +981,8 @@ class MessagesManager final : public Actor { void on_create_new_dialog_fail(int64 random_id, Status error, Promise &&promise); void on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit, - tl_object_ptr &&difference_ptr); + tl_object_ptr &&difference_ptr, + Status &&status); void try_update_dialog_pos(DialogId dialog_id); @@ -987,13 +1010,7 @@ class MessagesManager final : public Actor { bool is_from_scheduled, bool contains_mention, bool is_pinned, bool is_from_binlog); - struct MessageNotificationGroup { - DialogId dialog_id; - NotificationGroupType type = NotificationGroupType::Calls; - int32 total_count = 0; - vector notifications; - }; - MessageNotificationGroup get_message_notification_group_force(NotificationGroupId group_id); + NotificationGroupFromDatabase get_message_notification_group_force(NotificationGroupId group_id); vector get_message_notification_group_keys_from_database(NotificationGroupKey from_group_key, int32 limit); @@ -1021,7 +1038,7 @@ class MessagesManager final : public Actor { void set_poll_answer(FullMessageId full_message_id, vector &&option_ids, Promise &&promise); void get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit, - Promise>> &&promise); + Promise> &&promise); void stop_poll(FullMessageId full_message_id, td_api::object_ptr &&reply_markup, Promise &&promise); @@ -1163,6 +1180,7 @@ class MessagesManager final : public Actor { int64 reply_to_random_id = 0; // for send_message DialogId reply_in_dialog_id; MessageId top_thread_message_id; + StoryFullId reply_to_story_full_id; MessageId linked_top_thread_message_id; vector local_thread_message_ids; @@ -1271,22 +1289,6 @@ class MessagesManager final : public Actor { ~Message() = default; }; - struct NotificationGroupInfo { - NotificationGroupId group_id; - int32 last_notification_date = 0; // date of last notification in the group - NotificationId last_notification_id; // identifier of last notification in the group - NotificationId max_removed_notification_id; // notification identifier, up to which all notifications are removed - MessageId max_removed_message_id; // message identifier, up to which all notifications are removed - bool is_changed = false; // true, if the group needs to be saved to database - bool try_reuse = false; // true, if the group needs to be deleted from database and tried to be reused - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - struct DialogScheduledMessages { FlatHashMap scheduled_message_date_; @@ -1302,7 +1304,7 @@ class MessagesManager final : public Actor { NotificationGroupInfo mention_notification_group_; NotificationId new_secret_chat_notification_id_; // secret chats only MessageId pinned_message_notification_message_id_; - MessageId max_notification_message_id_; + MessageId max_push_notification_message_id_; vector> pending_new_message_notifications_; vector> pending_new_mention_notifications_; @@ -1394,6 +1396,7 @@ class MessagesManager final : public Actor { bool has_outgoing_messages = false; bool was_opened = false; + bool need_unload_on_close = false; bool need_restore_reply_markup = true; bool need_drop_default_send_message_as_dialog_id = false; @@ -1410,6 +1413,8 @@ class MessagesManager final : public Actor { bool is_marked_as_unread = false; bool is_blocked = false; bool is_is_blocked_inited = false; + bool is_blocked_for_stories = false; + bool is_is_blocked_for_stories_inited = false; bool last_sent_has_scheduled_messages = false; bool has_scheduled_server_messages = false; bool has_scheduled_database_messages = false; @@ -1440,8 +1445,7 @@ class MessagesManager final : public Actor { int32 pending_read_channel_inbox_pts = 0; // for channels only int32 pending_read_channel_inbox_server_unread_count = 0; // for channels only MessageId pending_read_channel_inbox_max_message_id; // for channels only - std::unordered_map> - random_id_to_message_id; // for secret chats and yet unsent messages only + FlatHashMap random_id_to_message_id; // for secret chats and yet unsent messages only MessageId last_assigned_message_id; // identifier of the last local or yet unsent message, assigned after // application start, used to guarantee that all assigned message identifiers @@ -1633,6 +1637,34 @@ class MessagesManager final : public Actor { vector, std::function>> suffix_load_queries_; }; + struct PendingGetHistoryQuery { + DialogId dialog_id_; + MessageId from_message_id_; + MessageId old_last_message_id_; + int32 offset_ = 0; + int32 limit_ = 0; + bool from_database_ = false; + bool only_local_ = false; + + bool operator==(const PendingGetHistoryQuery &other) const { + return dialog_id_ == other.dialog_id_ && from_message_id_ == other.from_message_id_ && + old_last_message_id_ == other.old_last_message_id_ && offset_ == other.offset_ && limit_ == other.limit_ && + from_database_ == other.from_database_ && only_local_ == other.only_local_; + } + }; + struct PendingGetHistoryQueryHash { + uint32 operator()(const PendingGetHistoryQuery &query) const { + uint32 hash = DialogIdHash()(query.dialog_id_); + hash = combine_hashes(hash, MessageIdHash()(query.from_message_id_)); + hash = combine_hashes(hash, MessageIdHash()(query.old_last_message_id_)); + hash = combine_hashes(hash, Hash()(query.offset_)); + hash = combine_hashes(hash, Hash()(query.limit_)); + hash = combine_hashes(hash, static_cast(query.from_database_)); + hash = combine_hashes(hash, static_cast(query.only_local_)); + return hash; + } + }; + class BlockMessageSenderFromRepliesOnServerLogEvent; class DeleteAllCallMessagesOnServerLogEvent; class DeleteAllChannelMessagesFromSenderOnServerLogEvent; @@ -1678,7 +1710,6 @@ 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 int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit static constexpr size_t MIN_DELETED_ASYNCHRONOUSLY_MESSAGES = 10; static constexpr size_t MAX_UNLOADED_MESSAGES = 5000; @@ -1723,7 +1754,7 @@ class MessagesManager final : public Actor { static int32 get_message_date(const tl_object_ptr &message_ptr); - static vector get_message_user_ids(const Message *m); + vector get_message_user_ids(const Message *m) const; static vector get_message_channel_ids(const Message *m); @@ -1792,18 +1823,20 @@ class MessagesManager final : public Actor { static Status can_use_message_send_options(const MessageSendOptions &options, const InputMessageContent &content); - Status can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id); + Status can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id, + const MessageInputReplyTo &input_reply_to); bool is_anonymous_administrator(DialogId dialog_id, string *author_signature) const; int64 generate_new_random_id(const Dialog *d); - unique_ptr create_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, - const MessageSendOptions &options, unique_ptr &&content, - bool suppress_reply_info, unique_ptr forward_info, - bool is_copy, DialogId send_as_dialog_id) const; + 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; - Message *get_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, + 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, unique_ptr forward_info = nullptr, bool is_copy = false, @@ -1841,7 +1874,8 @@ class MessagesManager final : public Actor { static FullMessageId get_replied_message_id(DialogId dialog_id, const Message *m); - MessageId get_reply_to_message_id(Dialog *d, MessageId top_thread_message_id, MessageId message_id, bool for_draft); + MessageInputReplyTo get_message_input_reply_to(Dialog *d, MessageId top_thread_message_id, + 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; @@ -1855,7 +1889,7 @@ class MessagesManager final : public Actor { void on_message_edited(FullMessageId full_message_id, int32 pts, bool had_message); - void delete_messages_from_updates(const vector &message_ids); + void delete_messages_from_updates(const vector &message_ids, bool is_permanent); void delete_dialog_messages(DialogId dialog_id, const vector &message_ids, bool force_update_for_not_found_messages, const char *source); @@ -1892,7 +1926,7 @@ class MessagesManager final : public Actor { struct ForwardedMessages { struct CopiedMessage { unique_ptr content; - MessageId reply_to_message_id; + MessageInputReplyTo input_reply_to; MessageId original_message_id; MessageId original_reply_to_message_id; unique_ptr reply_markup; @@ -1962,6 +1996,8 @@ class MessagesManager final : public Actor { void do_send_inline_query_result_message(DialogId dialog_id, MessageId message_id, int64 query_id, const string &result_id); + void send_screenshot_taken_notification_message(Dialog *d); + static uint64 save_send_screenshot_taken_notification_message_log_event(DialogId dialog_id, const Message *m); void do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 log_event_id); @@ -1975,6 +2011,10 @@ class MessagesManager final : public Actor { bool is_message_unload_enabled() const; + void send_resolve_dialog_username_query(const string &username, Promise &&promise); + + void on_resolve_dialog(const string &username, ChannelId channel_id, Promise &&promise); + int64 generate_new_media_album_id(); static bool can_forward_message(DialogId from_dialog_id, const Message *m); @@ -2021,7 +2061,7 @@ class MessagesManager final : public Actor { double get_next_unload_dialog_delay(Dialog *d) const; - void unload_dialog(DialogId dialog_id); + void unload_dialog(DialogId dialog_id, int32 delay); void clear_dialog_message_list(Dialog *d, bool remove_from_dialog_list, int32 last_message_date); @@ -2113,11 +2153,11 @@ class MessagesManager final : public Actor { bool remove_message_unread_reactions(Dialog *d, Message *m, const char *source); - void read_message_content_from_updates(MessageId message_id); + void read_message_content_from_updates(MessageId message_id, int32 read_date); void read_channel_message_content_from_updates(Dialog *d, MessageId message_id); - bool read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source); + bool read_message_content(Dialog *d, Message *m, bool is_local_read, int32 read_date, const char *source); void read_message_contents_on_server(DialogId dialog_id, vector message_ids, uint64 log_event_id, Promise &&promise, bool skip_log_event = false); @@ -2186,21 +2226,18 @@ class MessagesManager final : public Actor { void preload_older_messages(const Dialog *d, MessageId min_message_id); - void on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, - MessageId old_last_database_message_id, int32 offset, int32 limit, - bool from_the_end, bool only_local, vector &&messages, - Promise &&promise); - - void get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local, Promise &&promise); + void load_last_dialog_message_later(DialogId dialog_id); - void get_history_from_the_end_impl(const Dialog *d, bool from_database, bool only_local, Promise &&promise, - const char *source); + void load_last_dialog_message(const Dialog *d, const char *source); - void get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, bool from_database, - bool only_local, Promise &&promise); + void on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, + MessageId old_last_database_message_id, int32 offset, int32 limit, bool only_local, + vector &&messages, Promise &&promise); void get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, bool from_database, - bool only_local, Promise &&promise); + bool only_local, Promise &&promise, const char *source); + + void on_get_history_finished(const PendingGetHistoryQuery &query, Result &&result); void load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, int left_tries, bool only_local, Promise &&promise); @@ -2287,6 +2324,8 @@ class MessagesManager final : public Actor { void add_message_to_database(const Dialog *d, const Message *m, const char *source); + void delete_all_dialog_notifications(Dialog *d, MessageId max_message_id, const char *source); + void delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id, const char *source); void delete_message_from_database(Dialog *d, MessageId message_id, const Message *m, bool is_permanently_deleted, @@ -2307,6 +2346,10 @@ class MessagesManager final : public Actor { static void delete_notification_id_to_message_id_correspondence(NotificationInfo *notification_info, NotificationId notification_id, MessageId message_id); + static bool is_notification_info_group_id(const NotificationInfo *notification_info, NotificationGroupId group_id); + + static bool is_dialog_notification_group_id(const Dialog *d, NotificationGroupId group_id); + void remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update, bool ignore_pinned_message_notification_removal = false); @@ -2334,6 +2377,8 @@ class MessagesManager final : public Actor { void update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id, MessageId reply_to_message_id); + bool can_register_message_reply(const Message *m) const; + void register_message_reply(DialogId dialog_id, const Message *m); void reregister_message_reply(DialogId dialog_id, const Message *m); @@ -2552,7 +2597,7 @@ class MessagesManager final : public Actor { void set_dialog_is_translatable(Dialog *d, bool is_translatable); - void set_dialog_is_blocked(Dialog *d, bool is_blocked); + void set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_blocked_for_stories); void set_dialog_has_bots(Dialog *d, bool has_bots); @@ -2588,7 +2633,8 @@ class MessagesManager final : public Actor { void toggle_dialog_is_translatable_on_server(DialogId dialog_id, bool is_translatable, uint64 log_event_id); - void toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id); + void toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories, + uint64 log_event_id); void reorder_pinned_dialogs_on_server(FolderId folder_id, const vector &dialog_ids, uint64 log_event_id); @@ -2602,9 +2648,13 @@ class MessagesManager final : public Actor { void remove_dialog_mention_notifications(Dialog *d); - bool set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date, + void set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date, NotificationId last_notification_id, const char *source); + void set_dialog_last_notification_checked(DialogId dialog_id, NotificationGroupInfo &group_info, + int32 last_notification_date, NotificationId last_notification_id, + const char *source); + bool update_dialog_notification_settings(DialogId dialog_id, DialogNotificationSettings *current_settings, DialogNotificationSettings &&new_settings); @@ -2620,7 +2670,7 @@ class MessagesManager final : public Actor { void set_dialog_message_ttl(Dialog *d, MessageTtl message_ttl); ChatReactions get_message_available_reactions(const Dialog *d, const Message *m, - bool dissalow_custom_for_non_premium); + bool disallow_custom_for_non_premium); void set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise &&promise); @@ -2751,6 +2801,7 @@ class MessagesManager final : public Actor { static Message *get_message(Dialog *d, MessageId message_id); static const Message *get_message(const Dialog *d, MessageId message_id); + static const Message *get_message_static(const Dialog *d, MessageId message_id); Message *get_message(FullMessageId full_message_id); const Message *get_message(FullMessageId full_message_id) const; @@ -2826,7 +2877,7 @@ class MessagesManager final : public Actor { void ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id, MessageId till_message_id, double view_date); void ttl_on_view(const Dialog *d, Message *m, double view_date, double now); - bool ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read); + bool ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read, int32 read_date); void ttl_register_message(DialogId dialog_id, const Message *m, double now); void ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source); void ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time); @@ -2857,7 +2908,7 @@ class MessagesManager final : public Actor { void on_restore_missing_message_after_get_difference(FullMessageId full_message_id, MessageId old_message_id, Result result); - void on_get_message_link_dialog(MessageLinkInfo &&info, Promise &&promise); + void on_get_message_link_dialog(MessageLinkInfo &&info, DialogId dialog_id, Promise &&promise); void on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id, Promise &&promise); @@ -3007,6 +3058,8 @@ class MessagesManager final : public Actor { bool running_get_channel_difference(DialogId dialog_id) const; + void retry_get_channel_difference_later(DialogId dialog_id); + void on_channel_get_difference_timeout(DialogId dialog_id); void update_expected_channel_pts(DialogId dialog_id, int32 expected_pts); @@ -3014,7 +3067,7 @@ class MessagesManager final : public Actor { void update_expected_channel_max_message_id(DialogId dialog_id, MessageId expected_max_message_id); void schedule_get_channel_difference(DialogId dialog_id, int32 expected_pts, MessageId expected_max_message_id, - double delay); + double delay, const char *source); void get_channel_difference(DialogId dialog_id, int32 pts, int32 expected_pts, MessageId expected_max_message_id, bool force, const char *source, bool is_old = false); @@ -3107,6 +3160,8 @@ class MessagesManager final : public Actor { Dialog *get_service_notifications_dialog(); + static void extract_authentication_codes(DialogId dialog_id, const Message *m, vector &authentication_codes); + void save_auth_notification_ids(); MessageId get_next_message_id(Dialog *d, MessageType type) const; @@ -3188,7 +3243,8 @@ class MessagesManager final : public Actor { static uint64 save_toggle_dialog_is_translatable_on_server_log_event(DialogId dialog_id, bool is_translatable); - static uint64 save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked); + static uint64 save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked, + bool is_blocked_for_stories); static uint64 save_read_message_contents_on_server_log_event(DialogId dialog_id, const vector &message_ids); @@ -3421,7 +3477,11 @@ class MessagesManager final : public Actor { // full_message_id -> replies with media timestamps FlatHashMap, FullMessageIdHash> - replied_by_media_timestamp_messages_; + message_to_replied_media_timestamp_messages_; + + // story_full_id -> replies with media timestamps + FlatHashMap, StoryFullIdHash> + story_to_replied_media_timestamp_messages_; struct ActiveDialogAction { MessageId top_thread_message_id; @@ -3644,8 +3704,10 @@ class MessagesManager final : public Actor { FlatHashMap pending_read_reactions_; - vector active_reactions_; - FlatHashMap active_reaction_pos_; + vector active_reaction_types_; + FlatHashMap active_reaction_pos_; + + FlatHashMap>, PendingGetHistoryQueryHash> get_history_queries_; uint32 scheduled_messages_sync_generation_ = 1; diff --git a/td/telegram/Notification.h b/td/telegram/Notification.h index 20698114bf38..28ba2ebd2603 100644 --- a/td/telegram/Notification.h +++ b/td/telegram/Notification.h @@ -31,12 +31,12 @@ class Notification { } }; -inline td_api::object_ptr get_notification_object(DialogId dialog_id, +inline td_api::object_ptr get_notification_object(Td *td, DialogId dialog_id, const Notification ¬ification) { CHECK(notification.type != nullptr); return td_api::make_object(notification.notification_id.get(), notification.date, notification.disable_notification, - notification.type->get_notification_type_object(dialog_id)); + notification.type->get_notification_type_object(td, dialog_id)); } inline StringBuilder &operator<<(StringBuilder &sb, const Notification ¬ification) { diff --git a/td/telegram/NotificationGroupFromDatabase.h b/td/telegram/NotificationGroupFromDatabase.h new file mode 100644 index 000000000000..0f748d200f88 --- /dev/null +++ b/td/telegram/NotificationGroupFromDatabase.h @@ -0,0 +1,24 @@ +// +// 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/DialogId.h" +#include "td/telegram/Notification.h" +#include "td/telegram/NotificationGroupType.h" + +#include "td/utils/common.h" + +namespace td { + +struct NotificationGroupFromDatabase { + DialogId dialog_id; + NotificationGroupType type = NotificationGroupType::Calls; + int32 total_count = 0; + vector notifications; +}; + +} // namespace td diff --git a/td/telegram/NotificationGroupInfo.cpp b/td/telegram/NotificationGroupInfo.cpp new file mode 100644 index 000000000000..db2c7e09b691 --- /dev/null +++ b/td/telegram/NotificationGroupInfo.cpp @@ -0,0 +1,133 @@ +// +// 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/NotificationGroupInfo.h" + +#include "td/telegram/NotificationManager.h" + +#include "td/utils/logging.h" + +namespace td { + +bool NotificationGroupInfo::set_last_notification(int32 last_notification_date, NotificationId last_notification_id, + const char *source) { + if (is_removed_notification_id(last_notification_id)) { + last_notification_id = NotificationId(); + last_notification_date = 0; + } + if (last_notification_date_ != last_notification_date || last_notification_id_ != last_notification_id) { + VLOG(notifications) << "Set " << group_id_ << " last notification to " << last_notification_id << " sent at " + << last_notification_date << " from " << source; + if (last_notification_date_ != last_notification_date) { + last_notification_date_ = last_notification_date; + is_key_changed_ = true; + } + last_notification_id_ = last_notification_id; + return true; + } + return false; +} + +bool NotificationGroupInfo::set_max_removed_notification_id(NotificationId max_removed_notification_id, + NotificationObjectId max_removed_object_id, + const char *source) { + if (max_removed_notification_id.get() <= max_removed_notification_id_.get()) { + return false; + } + if (max_removed_object_id > max_removed_object_id_) { + VLOG(notifications) << "Set max_removed_object_id in " << group_id_ << " to " << max_removed_object_id << " from " + << source; + max_removed_object_id_ = max_removed_object_id; + } + + VLOG(notifications) << "Set max_removed_notification_id in " << group_id_ << " to " << max_removed_notification_id + << " from " << source; + max_removed_notification_id_ = max_removed_notification_id; + + if (last_notification_id_.is_valid() && is_removed_notification_id(last_notification_id_)) { + last_notification_id_ = NotificationId(); + last_notification_date_ = 0; + is_key_changed_ = true; + } + + return true; +} + +void NotificationGroupInfo::drop_max_removed_notification_id() { + if (!max_removed_notification_id_.is_valid()) { + return; + } + + VLOG(notifications) << "Drop max_removed_notification_id in " << group_id_; + max_removed_object_id_ = {}; + max_removed_notification_id_ = NotificationId(); +} + +bool NotificationGroupInfo::is_removed_notification(NotificationId notification_id, + NotificationObjectId object_id) const { + return is_removed_notification_id(notification_id) || is_removed_object_id(object_id); +} + +bool NotificationGroupInfo::is_removed_notification_id(NotificationId notification_id) const { + return notification_id.get() <= max_removed_notification_id_.get(); +} + +bool NotificationGroupInfo::is_removed_object_id(NotificationObjectId object_id) const { + return object_id <= max_removed_object_id_; +} + +bool NotificationGroupInfo::is_used_notification_id(NotificationId notification_id) const { + return notification_id.get() <= max_removed_notification_id_.get() || + notification_id.get() <= last_notification_id_.get(); +} + +void NotificationGroupInfo::try_reuse() { + CHECK(is_valid()); + CHECK(last_notification_date_ == 0); + if (!try_reuse_) { + try_reuse_ = true; + is_key_changed_ = true; + } +} + +void NotificationGroupInfo::add_group_key_if_changed(vector &group_keys, DialogId dialog_id) { + if (!is_key_changed_) { + return; + } + is_key_changed_ = false; + + group_keys.emplace_back(group_id_, try_reuse_ ? DialogId() : dialog_id, last_notification_date_); +} + +NotificationGroupId NotificationGroupInfo::get_reused_group_id() { + if (!try_reuse_) { + return {}; + } + if (is_key_changed_) { + LOG(ERROR) << "Failed to reuse changed " << group_id_; + return {}; + } + try_reuse_ = false; + if (!is_valid()) { + LOG(ERROR) << "Failed to reuse invalid " << group_id_; + return {}; + } + CHECK(last_notification_id_ == NotificationId()); + CHECK(last_notification_date_ == 0); + auto result = group_id_; + group_id_ = NotificationGroupId(); + max_removed_notification_id_ = NotificationId(); + max_removed_object_id_ = {}; + return result; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupInfo &group_info) { + return string_builder << group_info.group_id_ << " with last " << group_info.last_notification_id_ << " sent at " + << group_info.last_notification_date_ << ", max removed " + << group_info.max_removed_notification_id_ << '/' << group_info.max_removed_object_id_; +} + +} // namespace td diff --git a/td/telegram/NotificationGroupInfo.h b/td/telegram/NotificationGroupInfo.h new file mode 100644 index 000000000000..1b37e14cdca6 --- /dev/null +++ b/td/telegram/NotificationGroupInfo.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/DialogId.h" +#include "td/telegram/NotificationGroupId.h" +#include "td/telegram/NotificationGroupKey.h" +#include "td/telegram/NotificationId.h" +#include "td/telegram/NotificationObjectId.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class NotificationGroupInfo { + NotificationGroupId group_id_; + int32 last_notification_date_ = 0; // date of the last notification in the group + NotificationId last_notification_id_; // identifier of the last notification in the group + NotificationId max_removed_notification_id_; // notification identifier, up to which all notifications are removed + NotificationObjectId max_removed_object_id_; // object identifier, up to which all notifications are removed + bool is_key_changed_ = false; // true, if the group needs to be saved to database + bool try_reuse_ = false; // true, if the group needs to be deleted from database and tried to be reused + + friend StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupInfo &group_info); + + public: + NotificationGroupInfo() = default; + + explicit NotificationGroupInfo(NotificationGroupId group_id) : group_id_(group_id), is_key_changed_(true) { + } + + bool is_valid() const { + return group_id_.is_valid(); + } + + bool is_active() const { + return is_valid() && !try_reuse_; + } + + NotificationGroupId get_group_id() const { + return group_id_; + } + + bool has_group_id(NotificationGroupId group_id) const { + return group_id_ == group_id; + } + + NotificationId get_last_notification_id() const { + return last_notification_id_; + } + + bool set_last_notification(int32 last_notification_date, NotificationId last_notification_id, const char *source); + + bool set_max_removed_notification_id(NotificationId max_removed_notification_id, + NotificationObjectId max_removed_object_id, const char *source); + + void drop_max_removed_notification_id(); + + bool is_removed_notification(NotificationId notification_id, NotificationObjectId object_id) const; + + bool is_removed_notification_id(NotificationId notification_id) const; + + bool is_removed_object_id(NotificationObjectId object_id) const; + + bool is_used_notification_id(NotificationId notification_id) const; + + void try_reuse(); + + void add_group_key_if_changed(vector &group_keys, DialogId dialog_id); + + NotificationGroupId get_reused_group_id(); + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupInfo &group_info); + +} // namespace td diff --git a/td/telegram/NotificationGroupInfo.hpp b/td/telegram/NotificationGroupInfo.hpp new file mode 100644 index 000000000000..9c682861f257 --- /dev/null +++ b/td/telegram/NotificationGroupInfo.hpp @@ -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) +// +#pragma once + +#include "td/telegram/NotificationGroupInfo.h" +#include "td/telegram/Version.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void NotificationGroupInfo::store(StorerT &storer) const { + using td::store; + store(group_id_, storer); + store(last_notification_date_, storer); + store(last_notification_id_, storer); + store(max_removed_notification_id_, storer); + store(max_removed_object_id_, storer); +} + +template +void NotificationGroupInfo::parse(ParserT &parser) { + using td::parse; + parse(group_id_, parser); + parse(last_notification_date_, parser); + parse(last_notification_id_, parser); + parse(max_removed_notification_id_, parser); + if (parser.version() >= static_cast(Version::AddNotificationGroupInfoMaxRemovedMessageId)) { + parse(max_removed_object_id_, parser); + } +} + +} // namespace td diff --git a/td/telegram/NotificationGroupType.cpp b/td/telegram/NotificationGroupType.cpp new file mode 100644 index 000000000000..efc5eecaa438 --- /dev/null +++ b/td/telegram/NotificationGroupType.cpp @@ -0,0 +1,88 @@ +// +// 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/NotificationGroupType.h" + +namespace td { + +bool is_database_notification_group_type(NotificationGroupType type) { + switch (type) { + case NotificationGroupType::Messages: + case NotificationGroupType::Mentions: + case NotificationGroupType::SecretChat: + return true; + case NotificationGroupType::Calls: + return false; + default: + UNREACHABLE(); + return false; + } +} + +bool is_partial_notification_group_type(NotificationGroupType type) { + switch (type) { + case NotificationGroupType::Messages: + case NotificationGroupType::Mentions: + return true; + case NotificationGroupType::SecretChat: + case NotificationGroupType::Calls: + return false; + default: + UNREACHABLE(); + return false; + } +} + +td_api::object_ptr get_notification_group_type_object(NotificationGroupType type) { + switch (type) { + case NotificationGroupType::Messages: + return td_api::make_object(); + case NotificationGroupType::Mentions: + return td_api::make_object(); + case NotificationGroupType::SecretChat: + return td_api::make_object(); + case NotificationGroupType::Calls: + return td_api::make_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +NotificationGroupType get_notification_group_type(const td_api::object_ptr &type) { + CHECK(type != nullptr); + switch (type->get_id()) { + case td_api::notificationGroupTypeMessages::ID: + return NotificationGroupType::Messages; + case td_api::notificationGroupTypeMentions::ID: + return NotificationGroupType::Mentions; + case td_api::notificationGroupTypeSecretChat::ID: + return NotificationGroupType::SecretChat; + case td_api::notificationGroupTypeCalls::ID: + return NotificationGroupType::Calls; + default: + UNREACHABLE(); + return NotificationGroupType::Calls; + } +} + +StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupType &type) { + switch (type) { + case NotificationGroupType::Messages: + return string_builder << "Messages"; + case NotificationGroupType::Mentions: + return string_builder << "Mentions"; + case NotificationGroupType::SecretChat: + return string_builder << "SecretChat"; + case NotificationGroupType::Calls: + return string_builder << "Calls"; + default: + UNREACHABLE(); + return string_builder; + } +} + +} // namespace td diff --git a/td/telegram/NotificationGroupType.h b/td/telegram/NotificationGroupType.h index 795346c16d67..7b6a81376680 100644 --- a/td/telegram/NotificationGroupType.h +++ b/td/telegram/NotificationGroupType.h @@ -15,55 +15,14 @@ namespace td { enum class NotificationGroupType : int8 { Messages, Mentions, SecretChat, Calls }; -inline td_api::object_ptr get_notification_group_type_object( - NotificationGroupType type) { - switch (type) { - case NotificationGroupType::Messages: - return td_api::make_object(); - case NotificationGroupType::Mentions: - return td_api::make_object(); - case NotificationGroupType::SecretChat: - return td_api::make_object(); - case NotificationGroupType::Calls: - return td_api::make_object(); - default: - UNREACHABLE(); - return nullptr; - } -} +bool is_database_notification_group_type(NotificationGroupType type); -inline NotificationGroupType get_notification_group_type( - const td_api::object_ptr &type) { - CHECK(type != nullptr); - switch (type->get_id()) { - case td_api::notificationGroupTypeMessages::ID: - return NotificationGroupType::Messages; - case td_api::notificationGroupTypeMentions::ID: - return NotificationGroupType::Mentions; - case td_api::notificationGroupTypeSecretChat::ID: - return NotificationGroupType::SecretChat; - case td_api::notificationGroupTypeCalls::ID: - return NotificationGroupType::Calls; - default: - UNREACHABLE(); - return NotificationGroupType::Calls; - } -} +bool is_partial_notification_group_type(NotificationGroupType type); -inline StringBuilder &operator<<(StringBuilder &sb, const NotificationGroupType &type) { - switch (type) { - case NotificationGroupType::Messages: - return sb << "Messages"; - case NotificationGroupType::Mentions: - return sb << "Mentions"; - case NotificationGroupType::SecretChat: - return sb << "SecretChat"; - case NotificationGroupType::Calls: - return sb << "Calls"; - default: - UNREACHABLE(); - return sb; - } -} +td_api::object_ptr get_notification_group_type_object(NotificationGroupType type); + +NotificationGroupType get_notification_group_type(const td_api::object_ptr &type); + +StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupType &type); } // namespace td diff --git a/td/telegram/NotificationManager.cpp b/td/telegram/NotificationManager.cpp index 2bc46e15b661..39b7c909ac02 100644 --- a/td/telegram/NotificationManager.cpp +++ b/td/telegram/NotificationManager.cpp @@ -27,6 +27,8 @@ #include "td/telegram/SecretChatId.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StateManager.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" @@ -342,7 +344,7 @@ td_api::object_ptr NotificationManager::get_u vector> notifications; for (auto ¬ification : reversed(group.second.notifications)) { - auto notification_object = get_notification_object(group.first.dialog_id, notification); + auto notification_object = get_notification_object(td_, group.first.dialog_id, notification); if (notification_object->type_ != nullptr) { notifications.push_back(std::move(notification_object)); } @@ -390,10 +392,18 @@ void NotificationManager::load_group_force(NotificationGroupId group_id) { return; } - auto group_it = get_group_force(group_id, true); + auto group_it = get_group_force(group_id); CHECK(group_it != groups_.end()); } +bool NotificationManager::have_group_force(NotificationGroupId group_id) { + if (is_disabled()) { + return false; + } + + return td::contains(call_notification_group_ids_, group_id) || get_group_force(group_id) != groups_.end(); +} + NotificationManager::NotificationGroups::iterator NotificationManager::get_group_force(NotificationGroupId group_id, bool send_update) { auto group_it = get_group(group_id); @@ -502,61 +512,55 @@ NotificationId NotificationManager::get_last_notification_id(const NotificationG return NotificationId(); } -MessageId NotificationManager::get_first_message_id(const NotificationGroup &group) { - // it's fine to return MessageId() if first notification has no message_id, because - // non-message notification can't be mixed with message notifications +NotificationObjectId NotificationManager::get_first_object_id(const NotificationGroup &group) { + // it's fine to return NotificationObjectId() if first notification has no object_id, because + // object_id is from the same scope within a notification group if (!group.notifications.empty()) { - return group.notifications[0].type->get_message_id(); + return group.notifications[0].type->get_object_id(); } if (!group.pending_notifications.empty()) { - return group.pending_notifications[0].type->get_message_id(); + return group.pending_notifications[0].type->get_object_id(); } - return MessageId(); + return NotificationObjectId(); } -MessageId NotificationManager::get_last_message_id(const NotificationGroup &group) { - // it's fine to return MessageId() if last notification has no message_id, because - // non-message notification can't be mixed with message notifications +NotificationObjectId NotificationManager::get_last_object_id(const NotificationGroup &group) { + // it's fine to return NotificationObjectId() if last notification has no object_id, because + // object_id is from the same scope within a notification group if (!group.pending_notifications.empty()) { - return group.pending_notifications.back().type->get_message_id(); + return group.pending_notifications.back().type->get_object_id(); } if (!group.notifications.empty()) { - return group.notifications.back().type->get_message_id(); + return group.notifications.back().type->get_object_id(); } - return MessageId(); + return NotificationObjectId(); } -MessageId NotificationManager::get_last_message_id_by_notification_id(const NotificationGroup &group, - NotificationId max_notification_id) { +NotificationObjectId NotificationManager::get_last_object_id_by_notification_id(const NotificationGroup &group, + NotificationId max_notification_id) { for (auto ¬ification : reversed(group.pending_notifications)) { if (notification.notification_id.get() <= max_notification_id.get()) { - auto message_id = notification.type->get_message_id(); - if (message_id.is_valid()) { - return message_id; + auto object_id = notification.type->get_object_id(); + if (object_id.is_valid()) { + return object_id; } } } for (auto ¬ification : reversed(group.notifications)) { if (notification.notification_id.get() <= max_notification_id.get()) { - auto message_id = notification.type->get_message_id(); - if (message_id.is_valid()) { - return message_id; + auto object_id = notification.type->get_object_id(); + if (object_id.is_valid()) { + return object_id; } } } - return MessageId(); + return NotificationObjectId(); } -void NotificationManager::load_message_notifications_from_database(const NotificationGroupKey &group_key, - NotificationGroup &group, size_t desired_size) { - if (!G()->use_message_database()) { - return; - } - if (group.is_loaded_from_database || group.is_being_loaded_from_database || - group.type == NotificationGroupType::Calls) { - return; - } - if (group.total_count == 0) { +void NotificationManager::load_notifications_from_database(const NotificationGroupKey &group_key, + NotificationGroup &group, size_t desired_size) { + if (!G()->use_message_database() || group.is_loaded_from_database || group.is_being_loaded_from_database || + group.total_count == 0 || !is_database_notification_group_type(group.type)) { return; } @@ -569,19 +573,32 @@ void NotificationManager::load_message_notifications_from_database(const Notific size_t limit = desired_size - group.notifications.size(); auto first_notification_id = get_first_notification_id(group); auto from_notification_id = first_notification_id.is_valid() ? first_notification_id : NotificationId::max(); - auto first_message_id = get_first_message_id(group); - auto from_message_id = first_message_id.is_valid() ? first_message_id : MessageId::max(); - send_closure(G()->messages_manager(), &MessagesManager::get_message_notifications_from_database, group_key.dialog_id, - group_key.group_id, from_notification_id, from_message_id, static_cast(limit), - PromiseCreator::lambda([actor_id = actor_id(this), group_id = group_key.group_id, - limit](Result> r_notifications) { - send_closure_later(actor_id, &NotificationManager::on_get_message_notifications_from_database, - group_id, limit, std::move(r_notifications)); - })); + auto first_object_id = get_first_object_id(group); + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this), group_id = group_key.group_id, limit](Result> r_notifications) { + send_closure_later(actor_id, &NotificationManager::on_get_notifications_from_database, group_id, limit, + std::move(r_notifications)); + }); + + switch (group.type) { + case NotificationGroupType::SecretChat: + case NotificationGroupType::Messages: + case NotificationGroupType::Mentions: { + auto from_message_id = first_object_id.is_valid() ? MessageId(first_object_id.get()) : MessageId::max(); + send_closure(G()->messages_manager(), &MessagesManager::get_message_notifications_from_database, + group_key.dialog_id, group_key.group_id, from_notification_id, from_message_id, + static_cast(limit), std::move(promise)); + break; + } + case NotificationGroupType::Calls: + default: + UNREACHABLE(); + break; + } } -void NotificationManager::on_get_message_notifications_from_database(NotificationGroupId group_id, size_t limit, - Result> r_notifications) { +void NotificationManager::on_get_notifications_from_database(NotificationGroupId group_id, size_t limit, + Result> r_notifications) { auto group_it = get_group(group_id); CHECK(group_it != groups_.end()); auto &group = group_it->second; @@ -606,9 +623,9 @@ void NotificationManager::on_get_message_notifications_from_database(Notificatio notifications.pop_back(); } } - auto first_message_id = get_first_message_id(group); - if (first_message_id.is_valid()) { - while (!notifications.empty() && notifications.back().type->get_message_id() >= first_message_id) { + auto first_object_id = get_first_object_id(group); + if (first_object_id.is_valid()) { + while (!notifications.empty() && notifications.back().type->get_object_id() >= first_object_id) { // possible if notifications was added after the database request was sent notifications.pop_back(); } @@ -619,7 +636,7 @@ void NotificationManager::on_get_message_notifications_from_database(Notificatio group_it = get_group(group_id); CHECK(group_it != groups_.end()); if (max_notification_group_size_ > group_it->second.notifications.size()) { - load_message_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_); + load_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_); } } @@ -627,8 +644,8 @@ void NotificationManager::add_notifications_to_group_begin(NotificationGroups::i vector notifications) { CHECK(group_it != groups_.end()); - td::remove_if(notifications, [dialog_id = group_it->first.dialog_id](const Notification ¬ification) { - return notification.type->get_notification_type_object(dialog_id) == nullptr; + td::remove_if(notifications, [td = td_, dialog_id = group_it->first.dialog_id](const Notification ¬ification) { + return notification.type->get_notification_type_object(td, dialog_id) == nullptr; }); if (notifications.empty()) { @@ -685,7 +702,7 @@ void NotificationManager::add_notifications_to_group_begin(NotificationGroups::i new_notifications.reserve(notifications.size()); added_notifications.reserve(notifications.size()); for (auto ¬ification : notifications) { - added_notifications.push_back(get_notification_object(group_key.dialog_id, notification)); + added_notifications.push_back(get_notification_object(td_, group_key.dialog_id, notification)); CHECK(added_notifications.back()->type_ != nullptr); new_notifications.push_back(std::move(notification)); } @@ -895,14 +912,17 @@ void NotificationManager::add_notification(NotificationGroupId group_id, Notific on_notification_removed(notification_id); return; } - auto message_id = type->get_message_id(); - if (message_id.is_valid() && message_id <= get_last_message_id(group)) { + auto object_id = type->get_object_id(); + if (object_id.is_valid() && object_id <= get_last_object_id(group)) { LOG(ERROR) << "Failed to add " << notification_id << " of type " << *type << " to " << group_id << " of type " << group_type << " in " << dialog_id << ", because have already added notification about " - << get_last_message_id(group); + << get_last_object_id(group); on_notification_removed(notification_id); return; } + if (notification_settings_dialog_id != dialog_id) { + td_->messages_manager_->force_create_dialog(notification_settings_dialog_id, "add_notification", true); + } PendingNotification notification; notification.date = date; @@ -1002,7 +1022,7 @@ void NotificationManager::add_update_notification_group(td_api::object_ptrtype_ == nullptr) { return; } @@ -1413,7 +1433,7 @@ bool NotificationManager::do_flush_pending_notifications(NotificationGroupKey &g for (auto &pending_notification : pending_notifications) { Notification notification(pending_notification.notification_id, pending_notification.date, pending_notification.disable_notification, std::move(pending_notification.type)); - added_notifications.push_back(get_notification_object(group_key.dialog_id, notification)); + added_notifications.push_back(get_notification_object(td_, group_key.dialog_id, notification)); CHECK(added_notifications.back()->type_ != nullptr); if (!notification.type->can_be_delayed()) { @@ -1491,7 +1511,7 @@ void NotificationManager::send_add_group_update(const NotificationGroupKey &grou vector> added_notifications; added_notifications.reserve(added_size); for (size_t i = total_size - added_size; i < total_size; i++) { - added_notifications.push_back(get_notification_object(group_key.dialog_id, group.notifications[i])); + added_notifications.push_back(get_notification_object(td_, group_key.dialog_id, group.notifications[i])); if (added_notifications.back()->type_ == nullptr) { added_notifications.pop_back(); } @@ -1512,8 +1532,8 @@ void NotificationManager::flush_pending_notifications(NotificationGroupId group_ } td::remove_if(group_it->second.pending_notifications, - [dialog_id = group_it->first.dialog_id](const PendingNotification &pending_notification) { - return pending_notification.type->get_notification_type_object(dialog_id) == nullptr; + [td = td_, dialog_id = group_it->first.dialog_id](const PendingNotification &pending_notification) { + return pending_notification.type->get_notification_type_object(td, dialog_id) == nullptr; }); if (group_it->second.pending_notifications.empty()) { @@ -1587,7 +1607,7 @@ void NotificationManager::flush_pending_notifications(NotificationGroupId group_ on_delayed_notification_update_count_changed(-1, group_id.get(), "flush_pending_notifications"); // if we can delete a lot of notifications simultaneously if (group.notifications.size() > keep_notification_group_size_ + EXTRA_GROUP_SIZE && - group.type != NotificationGroupType::Calls) { + is_database_notification_group_type(group.type)) { // keep only keep_notification_group_size_ last notifications in memory for (auto it = group.notifications.begin(); it != group.notifications.end() - keep_notification_group_size_; ++it) { on_notification_removed(it->notification_id); @@ -1645,7 +1665,7 @@ void NotificationManager::edit_notification(NotificationGroupId group_id, Notifi for (size_t i = 0; i < group.notifications.size(); i++) { auto ¬ification = group.notifications[i]; if (notification.notification_id == notification_id) { - if (notification.type->get_message_id() != type->get_message_id() || + if (notification.type->get_object_id() != type->get_object_id() || notification.type->is_temporary() != type->is_temporary()) { LOG(ERROR) << "Ignore edit of " << notification_id << " with " << *type << ", because previous type is " << *notification.type; @@ -1663,7 +1683,7 @@ void NotificationManager::edit_notification(NotificationGroupId group_id, Notifi } for (auto ¬ification : group.pending_notifications) { if (notification.notification_id == notification_id) { - if (notification.type->get_message_id() != type->get_message_id() || + if (notification.type->get_object_id() != type->get_object_id() || notification.type->is_temporary() != type->is_temporary()) { LOG(ERROR) << "Ignore edit of " << notification_id << " with " << *type << ", because previous type is " << *notification.type; @@ -1709,8 +1729,8 @@ void NotificationManager::on_notification_removed(NotificationId notification_id } temporary_notification_log_event_ids_.erase(add_it); - auto erased_notification_count = temporary_notifications_.erase(temporary_notification_message_ids_[notification_id]); - auto erased_message_id_count = temporary_notification_message_ids_.erase(notification_id); + auto erased_notification_count = temporary_notifications_.erase(temporary_notification_object_ids_[notification_id]); + auto erased_message_id_count = temporary_notification_object_ids_.erase(notification_id); CHECK(erased_notification_count > 0); CHECK(erased_message_id_count > 0); @@ -1861,8 +1881,20 @@ void NotificationManager::remove_notification(NotificationGroupId group_id, Noti return promise.set_value(Unit()); } - if (!is_permanent && group_it->second.type != NotificationGroupType::Calls) { - td_->messages_manager_->remove_message_notification(group_it->first.dialog_id, group_id, notification_id); + if (!is_permanent) { + switch (group_it->second.type) { + case NotificationGroupType::Messages: + case NotificationGroupType::Mentions: + case NotificationGroupType::SecretChat: + td_->messages_manager_->remove_message_notification(group_it->first.dialog_id, group_id, notification_id); + break; + case NotificationGroupType::Calls: + // nothing to do + break; + default: + UNREACHABLE(); + break; + } } for (auto it = group_it->second.pending_notifications.begin(); it != group_it->second.pending_notifications.end(); @@ -1893,8 +1925,7 @@ void NotificationManager::remove_notification(NotificationGroupId group_id, Noti } } - bool have_all_notifications = group_it->second.type == NotificationGroupType::Calls || - group_it->second.type == NotificationGroupType::SecretChat; + bool have_all_notifications = !is_partial_notification_group_type(group_it->second.type); bool is_total_count_changed = false; if ((!have_all_notifications && is_permanent) || (have_all_notifications && is_found)) { if (group_it->second.total_count == 0) { @@ -1917,14 +1948,14 @@ void NotificationManager::remove_notification(NotificationGroupId group_id, Noti removed_notification_ids.push_back(notification_id.get()); if (old_group_size >= max_notification_group_size_ + 1) { added_notifications.push_back( - get_notification_object(group_it->first.dialog_id, + get_notification_object(td_, group_it->first.dialog_id, group_it->second.notifications[old_group_size - max_notification_group_size_ - 1])); if (added_notifications.back()->type_ == nullptr) { added_notifications.pop_back(); } } if (added_notifications.empty() && max_notification_group_size_ > group_it->second.notifications.size()) { - load_message_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_); + load_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_); } } @@ -1941,44 +1972,44 @@ void NotificationManager::remove_notification(NotificationGroupId group_id, Noti promise.set_value(Unit()); } -void NotificationManager::remove_temporary_notification_by_message_id(NotificationGroupId group_id, - MessageId message_id, bool force_update, - const char *source) { +void NotificationManager::remove_temporary_notification_by_object_id(NotificationGroupId group_id, + NotificationObjectId object_id, bool force_update, + const char *source) { if (!group_id.is_valid()) { return; } - VLOG(notifications) << "Remove notification for " << message_id << " in " << group_id << " from " << source; - CHECK(message_id.is_valid()); + VLOG(notifications) << "Remove notification for " << object_id << " in " << group_id << " from " << source; + CHECK(object_id.is_valid()); auto group_it = get_group(group_id); if (group_it == groups_.end()) { return; } - auto remove_notification_by_message_id = [&](auto ¬ifications) { + auto remove_notification_by_object_id = [&](auto ¬ifications) { for (auto ¬ification : notifications) { - if (notification.type->get_message_id() == message_id) { + if (notification.type->get_object_id() == object_id) { for (auto file_id : notification.type->get_file_ids(td_)) { - this->td_->file_manager_->delete_file(file_id, Promise<>(), "remove_temporary_notification_by_message_id"); + this->td_->file_manager_->delete_file(file_id, Promise<>(), "remove_temporary_notification_by_object_id"); } return this->remove_notification(group_id, notification.notification_id, true, force_update, Auto(), - "remove_temporary_notification_by_message_id"); + "remove_temporary_notification_by_object_id"); } } }; - remove_notification_by_message_id(group_it->second.pending_notifications); - remove_notification_by_message_id(group_it->second.notifications); + remove_notification_by_object_id(group_it->second.pending_notifications); + remove_notification_by_object_id(group_it->second.notifications); } void NotificationManager::remove_notification_group(NotificationGroupId group_id, NotificationId max_notification_id, - MessageId max_message_id, int32 new_total_count, bool force_update, - Promise &&promise) { + NotificationObjectId max_object_id, int32 new_total_count, + bool force_update, Promise &&promise) { if (!group_id.is_valid()) { return promise.set_error(Status::Error(400, "Group identifier is invalid")); } - if (!max_notification_id.is_valid() && !max_message_id.is_valid()) { + if (!max_notification_id.is_valid() && !max_object_id.is_valid()) { return promise.set_error(Status::Error(400, "Notification identifier is invalid")); } @@ -1990,7 +2021,7 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id remove_temporary_notifications(group_id, "remove_notification_group"); } - VLOG(notifications) << "Remove " << group_id << " up to " << max_notification_id << " or " << max_message_id + VLOG(notifications) << "Remove " << group_id << " up to " << max_notification_id << " or " << max_object_id << " with new_total_count = " << new_total_count << " and force_update = " << force_update; auto group_it = get_group_force(group_id); @@ -2003,10 +2034,20 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id if (max_notification_id.get() > current_notification_id_.get()) { max_notification_id = current_notification_id_; } - if (group_it->second.type != NotificationGroupType::Calls) { - td_->messages_manager_->remove_message_notifications( - group_it->first.dialog_id, group_id, max_notification_id, - get_last_message_id_by_notification_id(group_it->second, max_notification_id)); + switch (group_it->second.type) { + case NotificationGroupType::Messages: + case NotificationGroupType::Mentions: + case NotificationGroupType::SecretChat: + td_->messages_manager_->remove_message_notifications( + group_it->first.dialog_id, group_id, max_notification_id, + MessageId(get_last_object_id_by_notification_id(group_it->second, max_notification_id).get())); + break; + case NotificationGroupType::Calls: + // nothing to do + break; + default: + UNREACHABLE(); + break; } } @@ -2014,7 +2055,7 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id for (auto it = group_it->second.pending_notifications.begin(); it != group_it->second.pending_notifications.end(); ++it) { if (it->notification_id.get() <= max_notification_id.get() || - (max_message_id.is_valid() && it->type->get_message_id() <= max_message_id)) { + (max_object_id.is_valid() && it->type->get_object_id() <= max_object_id)) { pending_delete_end = it + 1; on_notification_removed(it->notification_id); } @@ -2043,7 +2084,7 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id for (size_t pos = 0; pos < notification_delete_end; pos++) { auto ¬ification = group_it->second.notifications[pos]; if (notification.notification_id.get() > max_notification_id.get() && - (!max_message_id.is_valid() || notification.type->get_message_id() > max_message_id)) { + (!max_object_id.is_valid() || notification.type->get_object_id() > max_object_id)) { notification_delete_end = pos; } else { on_notification_removed(notification.notification_id); @@ -2066,8 +2107,7 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id group_it->second.notifications.erase(group_it->second.notifications.begin(), group_it->second.notifications.begin() + notification_delete_end); } - if (group_it->second.type == NotificationGroupType::Calls || - group_it->second.type == NotificationGroupType::SecretChat) { + if (!is_partial_notification_group_type(group_it->second.type)) { new_total_count = static_cast(group_it->second.notifications.size()); } if (group_it->second.total_count == new_total_count) { @@ -2095,10 +2135,16 @@ void NotificationManager::remove_notification_group(NotificationGroupId group_id }); } else { remove_added_notifications_from_pending_updates( - group_id, [max_message_id](const td_api::object_ptr ¬ification) { - return notification->type_->get_id() == td_api::notificationTypeNewMessage::ID && - static_cast(notification->type_.get())->message_->id_ <= - max_message_id.get(); + group_id, [max_id = max_object_id.get()](const td_api::object_ptr ¬ification) { + const auto *type = notification->type_.get(); + switch (type->get_id()) { + case td_api::notificationTypeNewMessage::ID: + return static_cast(type)->message_->id_ <= max_id; + case td_api::notificationTypeNewPushMessage::ID: + return static_cast(type)->message_id_ <= max_id; + default: + return false; + } }); } @@ -2179,14 +2225,14 @@ void NotificationManager::remove_temporary_notifications(NotificationGroupId gro size_t added_notification_count = 0; for (size_t i = min(old_group_size - max_notification_group_size_, notification_pos); i-- > 0 && added_notification_count++ < removed_notification_ids.size();) { - added_notifications.push_back(get_notification_object(group_it->first.dialog_id, group.notifications[i])); + added_notifications.push_back(get_notification_object(td_, group_it->first.dialog_id, group.notifications[i])); if (added_notifications.back()->type_ == nullptr) { added_notifications.pop_back(); } } if (added_notification_count < removed_notification_ids.size() && max_notification_group_size_ > group.notifications.size()) { - load_message_notifications_from_database(group_it->first, group, keep_notification_group_size_); + load_notifications_from_database(group_it->first, group, keep_notification_group_size_); } std::reverse(added_notifications.begin(), added_notifications.end()); } @@ -2195,10 +2241,16 @@ void NotificationManager::remove_temporary_notifications(NotificationGroupId gro on_notifications_removed(std::move(group_it), std::move(added_notifications), std::move(removed_notification_ids), false); - remove_added_notifications_from_pending_updates( - group_id, [](const td_api::object_ptr ¬ification) { - return notification->get_id() == td_api::notificationTypeNewPushMessage::ID; - }); + remove_added_notifications_from_pending_updates(group_id, + [](const td_api::object_ptr ¬ification) { + const auto *type = notification->type_.get(); + switch (type->get_id()) { + case td_api::notificationTypeNewPushMessage::ID: + return true; + default: + return false; + } + }); } int32 NotificationManager::get_temporary_notification_total_count(const NotificationGroup &group) { @@ -2268,17 +2320,22 @@ vector NotificationManager::get_notification_group_message_ids(Notifi return {}; } + if (group_it->second.type != NotificationGroupType::Mentions && + group_it->second.type != NotificationGroupType::Messages) { + return {}; + } + vector message_ids; for (auto ¬ification : group_it->second.notifications) { - auto message_id = notification.type->get_message_id(); - if (message_id.is_valid()) { - message_ids.push_back(message_id); + auto object_id = notification.type->get_object_id(); + if (object_id.is_valid()) { + message_ids.push_back(MessageId(object_id.get())); } } for (auto ¬ification : group_it->second.pending_notifications) { - auto message_id = notification.type->get_message_id(); - if (message_id.is_valid()) { - message_ids.push_back(message_id); + auto object_id = notification.type->get_object_id(); + if (object_id.is_valid()) { + message_ids.push_back(MessageId(object_id.get())); } } @@ -2340,7 +2397,7 @@ void NotificationManager::add_call_notification(DialogId dialog_id, CallId call_ return; } - G()->td().get_actor_unsafe()->messages_manager_->force_create_dialog(dialog_id, "add_call_notification"); + td_->messages_manager_->force_create_dialog(dialog_id, "add_call_notification"); auto &active_notifications = active_call_notifications_[dialog_id]; if (active_notifications.size() >= MAX_CALL_NOTIFICATIONS) { @@ -2524,7 +2581,7 @@ void NotificationManager::on_notification_group_size_max_changed() { CHECK(!removed_notification_ids.empty()); } else { if (new_max_notification_group_size_size_t > notification_count) { - load_message_notifications_from_database(group_key, group, new_keep_notification_group_size); + load_notifications_from_database(group_key, group, new_keep_notification_group_size); } if (notification_count <= max_notification_group_size_) { VLOG(notifications) << "There is no need to update " << group_key.group_id; @@ -2532,7 +2589,7 @@ void NotificationManager::on_notification_group_size_max_changed() { } for (size_t i = notification_count - min(notification_count, new_max_notification_group_size_size_t); i < notification_count - max_notification_group_size_; i++) { - added_notifications.push_back(get_notification_object(group_key.dialog_id, group.notifications[i])); + added_notifications.push_back(get_notification_object(td_, group_key.dialog_id, group.notifications[i])); if (added_notifications.back()->type_ == nullptr) { added_notifications.pop_back(); } @@ -2928,6 +2985,9 @@ string NotificationManager::convert_loc_key(const string &loc_key) { if (loc_key == "MESSAGE_STICKER") { return "MESSAGE_STICKER"; } + if (loc_key == "MESSAGE_STORY") { + return "MESSAGE_STORY"; + } if (loc_key == "MESSAGE_SUGGEST_PHOTO") { return "MESSAGE_SUGGEST_PHOTO"; } @@ -2942,6 +3002,9 @@ string NotificationManager::convert_loc_key(const string &loc_key) { if (loc_key == "PINNED_STICKER") { return "PINNED_MESSAGE_STICKER"; } + if (loc_key == "PINNED_STORY") { + return "PINNED_MESSAGE_STORY"; + } if (loc_key == "CHAT_PHOTO_EDITED") { return "MESSAGE_CHAT_CHANGE_PHOTO"; } @@ -3006,73 +3069,59 @@ Status NotificationManager::process_push_notification_payload(string payload, bo auto data = std::move(json_value.get_object()); int32 sent_date = G()->unix_time(); - if (has_json_object_field(data, "data")) { - TRY_RESULT(date, get_json_object_int_field(data, "date", true, sent_date)); + if (data.has_field("data")) { + TRY_RESULT(date, data.get_optional_int_field("date", sent_date)); if (sent_date - 28 * 86400 <= date && date <= sent_date + 5) { sent_date = date; } - TRY_RESULT(data_data, get_json_object_field(data, "data", JsonValue::Type::Object, false)); + TRY_RESULT(data_data, data.extract_required_field("data", JsonValue::Type::Object)); data = std::move(data_data.get_object()); } - string loc_key; - JsonObject custom; - string announcement_message_text; + TRY_RESULT(loc_key, data.get_required_string_field("loc_key")); + if (!clean_input_string(loc_key)) { + return Status::Error(PSLICE() << "Receive invalid loc_key " << format::escaped(loc_key)); + } + if (loc_key.empty()) { + return Status::Error("Receive empty loc_key"); + } + vector loc_args; - string sender_name; - for (auto &field_value : data) { - if (field_value.first == "loc_key") { - if (field_value.second.type() != JsonValue::Type::String) { - return Status::Error("Expected loc_key as a String"); - } - loc_key = field_value.second.get_string().str(); - } else if (field_value.first == "loc_args") { - if (field_value.second.type() != JsonValue::Type::Array) { - return Status::Error("Expected loc_args as an Array"); - } - loc_args.reserve(field_value.second.get_array().size()); - for (auto &arg : field_value.second.get_array()) { - if (arg.type() != JsonValue::Type::String) { - return Status::Error("Expected loc_arg as a String"); - } - loc_args.push_back(arg.get_string().str()); - } - } else if (field_value.first == "custom") { - if (field_value.second.type() != JsonValue::Type::Object) { - return Status::Error("Expected custom as an Object"); - } - custom = std::move(field_value.second.get_object()); - } else if (field_value.first == "message") { - if (field_value.second.type() != JsonValue::Type::String) { - return Status::Error("Expected announcement message text as a String"); + TRY_RESULT(loc_args_array, data.extract_optional_field("loc_args", JsonValue::Type::Array)); + if (loc_args_array.type() == JsonValue::Type::Array) { + loc_args.reserve(loc_args_array.get_array().size()); + for (auto &arg : loc_args_array.get_array()) { + if (arg.type() != JsonValue::Type::String) { + return Status::Error("Expected loc_arg as a String"); } - announcement_message_text = field_value.second.get_string().str(); - } else if (field_value.first == "google.sent_time") { - TRY_RESULT(google_sent_time, get_json_object_long_field(data, "google.sent_time")); - google_sent_time /= 1000; - if (sent_date - 28 * 86400 <= google_sent_time && google_sent_time <= sent_date + 5) { - sent_date = narrow_cast(google_sent_time); + auto loc_arg = arg.get_string().str(); + if (!clean_input_string(loc_arg)) { + return Status::Error(PSLICE() << "Receive invalid loc_arg " << format::escaped(loc_arg)); } + loc_args.push_back(std::move(loc_arg)); } } - if (!clean_input_string(loc_key)) { - return Status::Error(PSLICE() << "Receive invalid loc_key " << format::escaped(loc_key)); - } - if (loc_key.empty()) { - return Status::Error("Receive empty loc_key"); + JsonObject custom; + TRY_RESULT(custom_value, data.extract_optional_field("custom", JsonValue::Type::Object)); + if (custom_value.type() == JsonValue::Type::Object) { + custom = std::move(custom_value.get_object()); } - for (auto &loc_arg : loc_args) { - if (!clean_input_string(loc_arg)) { - return Status::Error(PSLICE() << "Receive invalid loc_arg " << format::escaped(loc_arg)); + + TRY_RESULT(google_sent_time, data.get_optional_long_field("google.sent_time")); + if (google_sent_time > 0) { + google_sent_time /= 1000; + if (sent_date - 28 * 86400 <= google_sent_time && google_sent_time <= sent_date + 5) { + sent_date = narrow_cast(google_sent_time); } } + TRY_RESULT(announcement_message_text, data.get_optional_string_field("message")); if (loc_key == "MESSAGE_ANNOUNCEMENT") { if (announcement_message_text.empty()) { return Status::Error("Receive empty announcement message text"); } - TRY_RESULT(announcement_id, get_json_object_int_field(custom, "announcement")); + TRY_RESULT(announcement_id, custom.get_optional_int_field("announcement")); if (announcement_id == 0) { return Status::Error(200, "Receive unsupported announcement ID"); } @@ -3097,13 +3146,13 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } if (loc_key == "DC_UPDATE") { - TRY_RESULT(dc_id, get_json_object_int_field(custom, "dc", false)); - TRY_RESULT(addr, get_json_object_string_field(custom, "addr", false)); + TRY_RESULT(dc_id, custom.get_required_int_field("dc")); + TRY_RESULT(addr, custom.get_required_string_field("addr")); if (!DcId::is_valid(dc_id)) { - return Status::Error("Invalid datacenter ID"); + return Status::Error("Invalid datacenter identifier"); } if (!clean_input_string(addr)) { - return Status::Error(PSLICE() << "Receive invalid addr " << format::escaped(addr)); + return Status::Error(PSLICE() << "Receive invalid datacenter address " << format::escaped(addr)); } send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_update, DcId::internal(dc_id), std::move(addr), std::move(promise)); @@ -3135,35 +3184,35 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } DialogId dialog_id; - if (has_json_object_field(custom, "from_id")) { - TRY_RESULT(user_id_int, get_json_object_long_field(custom, "from_id")); + if (custom.has_field("from_id")) { + TRY_RESULT(user_id_int, custom.get_optional_long_field("from_id")); UserId user_id(user_id_int); if (!user_id.is_valid()) { - return Status::Error("Receive invalid user_id"); + return Status::Error("Receive invalid user identifier"); } dialog_id = DialogId(user_id); } - if (has_json_object_field(custom, "chat_id")) { - TRY_RESULT(chat_id_int, get_json_object_long_field(custom, "chat_id")); + if (custom.has_field("chat_id")) { + TRY_RESULT(chat_id_int, custom.get_optional_long_field("chat_id")); ChatId chat_id(chat_id_int); if (!chat_id.is_valid()) { - return Status::Error("Receive invalid chat_id"); + return Status::Error("Receive invalid basic group identifier"); } dialog_id = DialogId(chat_id); } - if (has_json_object_field(custom, "channel_id")) { - TRY_RESULT(channel_id_int, get_json_object_long_field(custom, "channel_id")); + if (custom.has_field("channel_id")) { + TRY_RESULT(channel_id_int, custom.get_optional_long_field("channel_id")); ChannelId channel_id(channel_id_int); if (!channel_id.is_valid()) { - return Status::Error("Receive invalid channel_id"); + return Status::Error("Receive invalid supergroup identifier"); } dialog_id = DialogId(channel_id); } - if (has_json_object_field(custom, "encryption_id")) { - TRY_RESULT(secret_chat_id_int, get_json_object_int_field(custom, "encryption_id")); + if (custom.has_field("encryption_id")) { + TRY_RESULT(secret_chat_id_int, custom.get_optional_int_field("encryption_id")); SecretChatId secret_chat_id(secret_chat_id_int); if (!secret_chat_id.is_valid()) { - return Status::Error("Receive invalid secret_chat_id"); + return Status::Error("Receive invalid secret chat identifier"); } dialog_id = DialogId(secret_chat_id); } @@ -3171,7 +3220,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo if (loc_key == "ENCRYPTED_MESSAGE" || loc_key == "MESSAGE_MUTED") { return Status::Error(406, "Force loading data from the server"); } - return Status::Error("Can't find dialog_id"); + return Status::Error("Can't find chat identifier"); } if (loc_key == "READ_HISTORY") { @@ -3179,7 +3228,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo return Status::Error("Receive read history in a secret chat"); } - TRY_RESULT(max_id, get_json_object_int_field(custom, "max_id")); + TRY_RESULT(max_id, custom.get_optional_int_field("max_id")); ServerMessageId max_server_message_id(max_id); if (!max_server_message_id.is_valid()) { return Status::Error("Receive invalid max_id"); @@ -3195,14 +3244,14 @@ Status NotificationManager::process_push_notification_payload(string payload, bo if (dialog_id.get_type() == DialogType::SecretChat) { return Status::Error("Receive MESSAGE_DELETED in a secret chat"); } - TRY_RESULT(server_message_ids_str, get_json_object_string_field(custom, "messages", false)); + TRY_RESULT(server_message_ids_str, custom.get_required_string_field("messages")); auto server_message_ids = full_split(server_message_ids_str, ','); vector message_ids; for (const auto &server_message_id_str : server_message_ids) { TRY_RESULT(server_message_id_int, to_integer_safe(server_message_id_str)); ServerMessageId server_message_id(server_message_id_int); if (!server_message_id.is_valid()) { - return Status::Error("Receive invalid message_id"); + return Status::Error("Receive invalid message identifier"); } message_ids.push_back(MessageId(server_message_id)); } @@ -3215,30 +3264,66 @@ Status NotificationManager::process_push_notification_payload(string payload, bo return Status::Error(406, "Notifications about muted messages force loading data from the server"); } - TRY_RESULT(msg_id, get_json_object_int_field(custom, "msg_id")); + if (loc_key == "READ_STORIES") { + if (dialog_id.get_type() == DialogType::SecretChat) { + return Status::Error("Receive READ_STORIES in a secret chat"); + } + + TRY_RESULT(max_id, custom.get_optional_int_field("max_id")); + StoryId max_story_id(max_id); + if (!max_story_id.is_server()) { + return Status::Error("Receive invalid max_id"); + } + + td_->story_manager_->on_update_read_stories(dialog_id, max_story_id); + promise.set_value(Unit()); + return Status::OK(); + } + + if (loc_key == "STORY_DELETED") { + if (dialog_id.get_type() != DialogType::SecretChat) { + return Status::Error("Receive STORY_DELETED in a secret chat"); + } + TRY_RESULT(server_story_ids_str, custom.get_required_string_field("story_id")); + auto server_story_ids = full_split(server_story_ids_str, ','); + vector story_ids; + for (const auto &server_story_id_str : server_story_ids) { + TRY_RESULT(story_id_int, to_integer_safe(server_story_id_str)); + StoryId story_id(story_id_int); + if (!story_id.is_server()) { + return Status::Error("Receive invalid story identifier"); + } + story_ids.push_back(story_id); + } + td_->story_manager_->remove_story_notifications_by_story_ids(dialog_id, story_ids); + promise.set_value(Unit()); + return Status::OK(); + } + + TRY_RESULT(msg_id, custom.get_optional_int_field("msg_id")); ServerMessageId server_message_id(msg_id); if (server_message_id != ServerMessageId() && !server_message_id.is_valid()) { return Status::Error("Receive invalid msg_id"); } - TRY_RESULT(random_id, get_json_object_long_field(custom, "random_id")); + TRY_RESULT(random_id, custom.get_optional_long_field("random_id")); UserId sender_user_id; DialogId sender_dialog_id; - if (has_json_object_field(custom, "chat_from_broadcast_id")) { - TRY_RESULT(sender_channel_id_int, get_json_object_long_field(custom, "chat_from_broadcast_id")); + if (custom.has_field("chat_from_broadcast_id")) { + TRY_RESULT(sender_channel_id_int, custom.get_optional_long_field("chat_from_broadcast_id")); sender_dialog_id = DialogId(ChannelId(sender_channel_id_int)); if (!sender_dialog_id.is_valid()) { return Status::Error("Receive invalid chat_from_broadcast_id"); } - } else if (has_json_object_field(custom, "chat_from_group_id")) { - TRY_RESULT(sender_channel_id_int, get_json_object_long_field(custom, "chat_from_group_id")); + } else if (custom.has_field("chat_from_group_id")) { + TRY_RESULT(sender_channel_id_int, custom.get_optional_long_field("chat_from_group_id")); sender_dialog_id = DialogId(ChannelId(sender_channel_id_int)); if (!sender_dialog_id.is_valid()) { return Status::Error("Receive invalid chat_from_group_id"); } - } else if (has_json_object_field(custom, "chat_from_id")) { - TRY_RESULT(sender_user_id_int, get_json_object_long_field(custom, "chat_from_id")); + } else if (custom.has_field("chat_from_id")) { + TRY_RESULT(sender_user_id_int, custom.get_optional_long_field("chat_from_id")); sender_user_id = UserId(sender_user_id_int); if (!sender_user_id.is_valid()) { return Status::Error("Receive invalid chat_from_id"); @@ -3249,7 +3334,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo sender_dialog_id = dialog_id; } - TRY_RESULT(contains_mention_int, get_json_object_int_field(custom, "mention")); + TRY_RESULT(contains_mention_int, custom.get_optional_int_field("mention")); bool contains_mention = contains_mention_int != 0; if (begins_with(loc_key, "CHANNEL_MESSAGE") || loc_key == "CHANNEL_ALBUM") { @@ -3258,6 +3343,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } loc_key = loc_key.substr(8); } + string sender_name; if (begins_with(loc_key, "CHAT_")) { auto dialog_type = dialog_id.get_type(); if (dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) { @@ -3268,13 +3354,13 @@ Status NotificationManager::process_push_notification_payload(string payload, bo loc_key = loc_key.substr(5); } if (loc_args.empty()) { - return Status::Error("Expect sender name as first argument"); + return Status::Error("Expect sender name as the first argument"); } sender_name = std::move(loc_args[0]); loc_args.erase(loc_args.begin()); } if (begins_with(loc_key, "MESSAGE") && !server_message_id.is_valid()) { - return Status::Error("Receive no message ID"); + return Status::Error("Receive no message identifier"); } if (begins_with(loc_key, "ENCRYPT") || random_id != 0) { if (dialog_id.get_type() != DialogType::SecretChat) { @@ -3282,7 +3368,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } } if (server_message_id.is_valid() && dialog_id.get_type() == DialogType::SecretChat) { - return Status::Error("Receive message ID in secret chat push"); + return Status::Error("Receive message identifier in a secret chat push notification"); } if (begins_with(loc_key, "ENCRYPTION_")) { @@ -3306,7 +3392,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } if (loc_args.empty()) { - return Status::Error("Expected chat name as next argument"); + return Status::Error("Expected chat name as the next argument"); } if (dialog_id.get_type() == DialogType::User) { sender_name = std::move(loc_args[0]); @@ -3340,16 +3426,18 @@ Status NotificationManager::process_push_notification_payload(string payload, bo arg = std::move(loc_args[0]); } - if (sender_user_id.is_valid() && !td_->contacts_manager_->have_user_force(sender_user_id)) { + if (sender_user_id.is_valid() && + !td_->contacts_manager_->have_user_force(sender_user_id, "process_push_notification_payload")) { int64 sender_access_hash = -1; telegram_api::object_ptr sender_photo; - TRY_RESULT(mtpeer, get_json_object_field(custom, "mtpeer", JsonValue::Type::Object)); + TRY_RESULT(mtpeer, custom.extract_optional_field("mtpeer", JsonValue::Type::Object)); if (mtpeer.type() != JsonValue::Type::Null) { - TRY_RESULT(ah, get_json_object_string_field(mtpeer.get_object(), "ah")); + auto &mtpeer_object = mtpeer.get_object(); + TRY_RESULT(ah, mtpeer_object.get_optional_string_field("ah")); if (!ah.empty()) { TRY_RESULT_ASSIGN(sender_access_hash, to_integer_safe(ah)); } - TRY_RESULT(ph, get_json_object_field(mtpeer.get_object(), "ph", JsonValue::Type::Object)); + TRY_RESULT(ph, mtpeer_object.extract_optional_field("ph", JsonValue::Type::Object)); if (ph.type() != JsonValue::Type::Null) { // TODO parse sender photo } @@ -3365,16 +3453,17 @@ Status NotificationManager::process_push_notification_payload(string payload, bo flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, 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>()); + 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); td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload"); } Photo attached_photo; Document attached_document; - if (has_json_object_field(custom, "attachb64")) { - TRY_RESULT(attachb64, get_json_object_string_field(custom, "attachb64", false)); + if (custom.has_field("attachb64")) { + TRY_RESULT(attachb64, custom.get_required_string_field("attachb64")); TRY_RESULT(attach, base64url_decode(attachb64)); TlParser gzip_parser(attach); @@ -3492,11 +3581,11 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } } - if (has_json_object_field(custom, "edit_date")) { + if (custom.has_field("edit_date")) { if (random_id != 0) { return Status::Error("Receive edit of secret message"); } - TRY_RESULT(edit_date, get_json_object_int_field(custom, "edit_date")); + TRY_RESULT(edit_date, custom.get_optional_int_field("edit_date")); if (edit_date <= 0) { return Status::Error("Receive wrong edit date"); } @@ -3504,14 +3593,14 @@ Status NotificationManager::process_push_notification_payload(string payload, bo std::move(arg), std::move(attached_photo), std::move(attached_document), 0, std::move(promise)); } else { - bool is_from_scheduled = has_json_object_field(custom, "schedule"); + bool is_from_scheduled = custom.has_field("schedule"); bool disable_notification = false; int64 ringtone_id = -1; - if (has_json_object_field(custom, "silent")) { + if (custom.has_field("silent")) { disable_notification = true; ringtone_id = 0; - } else if (has_json_object_field(custom, "ringtone")) { - TRY_RESULT_ASSIGN(ringtone_id, get_json_object_long_field(custom, "ringtone")); + } else if (custom.has_field("ringtone")) { + TRY_RESULT_ASSIGN(ringtone_id, custom.get_optional_long_field("ringtone")); } add_message_push_notification(dialog_id, MessageId(server_message_id), random_id, sender_user_id, sender_dialog_id, std::move(sender_name), sent_date, is_from_scheduled, contains_mention, @@ -3716,16 +3805,17 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess CHECK(log_event_id != 0); } - if (sender_user_id.is_valid() && !td_->contacts_manager_->have_user_force(sender_user_id)) { + if (sender_user_id.is_valid() && + !td_->contacts_manager_->have_user_force(sender_user_id, "add_message_push_notification")) { int32 flags = USER_FLAG_IS_INACCESSIBLE; auto user_name = sender_user_id.get() == 136817688 ? "Channel" : sender_name; auto user = telegram_api::make_object( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, false /*ignored*/, sender_user_id.get(), 0, - user_name, string(), string(), string(), nullptr, nullptr, 0, Auto(), string(), string(), nullptr, - vector>()); + 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); td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification"); } @@ -3757,10 +3847,11 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess sender_user_id.is_valid() ? td_->contacts_manager_->get_my_id() == sender_user_id : is_from_scheduled; if (log_event_id != 0) { VLOG(notifications) << "Register temporary " << notification_id << " with log event " << log_event_id; + NotificationObjectFullId object_full_id(dialog_id, message_id); temporary_notification_log_event_ids_[notification_id] = log_event_id; - temporary_notifications_[FullMessageId(dialog_id, message_id)] = {group_id, notification_id, sender_user_id, - sender_dialog_id, sender_name, is_outgoing}; - temporary_notification_message_ids_[notification_id] = FullMessageId(dialog_id, message_id); + temporary_notifications_[object_full_id] = {group_id, notification_id, sender_user_id, + sender_dialog_id, sender_name, is_outgoing}; + temporary_notification_object_ids_[notification_id] = object_full_id; } push_notification_promises_[notification_id].push_back(std::move(promise)); @@ -3856,7 +3947,7 @@ void NotificationManager::edit_message_push_notification(DialogId dialog_id, Mes return promise.set_error(Status::Error(200, "Immediate success")); } - auto it = temporary_notifications_.find(FullMessageId(dialog_id, message_id)); + auto it = temporary_notifications_.find({dialog_id, message_id}); if (it == temporary_notifications_.end()) { VLOG(notifications) << "Ignore edit of message push notification for " << message_id << " in " << dialog_id << " edited at " << edit_date; @@ -3915,8 +4006,8 @@ Result NotificationManager::get_push_receiver_id(string payload) { } auto data = std::move(json_value.get_object()); - if (has_json_object_field(data, "data")) { - auto r_data_data = get_json_object_field(data, "data", JsonValue::Type::Object, false); + if (data.has_field("data")) { + auto r_data_data = data.extract_required_field("data", JsonValue::Type::Object); if (r_data_data.is_error()) { return Status::Error(400, r_data_data.error().message()); } @@ -3924,38 +4015,24 @@ Result NotificationManager::get_push_receiver_id(string payload) { data = std::move(data_data.get_object()); } - for (auto &field_value : data) { - if (field_value.first == "p") { - auto encrypted_payload = std::move(field_value.second); - if (encrypted_payload.type() != JsonValue::Type::String) { - return Status::Error(400, "Expected encrypted payload as a String"); - } - Slice encrypted_data = encrypted_payload.get_string(); - if (encrypted_data.size() < 12) { - return Status::Error(400, "Encrypted payload is too small"); - } - auto r_decoded = base64url_decode(encrypted_data.substr(0, 12)); - if (r_decoded.is_error()) { - return Status::Error(400, "Failed to base64url-decode payload"); - } - CHECK(r_decoded.ok().size() == 9); - return as(r_decoded.ok().c_str()); + if (data.has_field("p")) { + TRY_RESULT(encrypted_data, data.get_required_string_field("p")); + if (encrypted_data.size() < 12) { + return Status::Error(400, "Encrypted payload is too small"); } - if (field_value.first == "user_id") { - auto user_id = std::move(field_value.second); - if (user_id.type() != JsonValue::Type::String && user_id.type() != JsonValue::Type::Number) { - return Status::Error(400, "Expected user_id as a String or a Number"); - } - Slice user_id_str = user_id.type() == JsonValue::Type::String ? user_id.get_string() : user_id.get_number(); - auto r_user_id = to_integer_safe(user_id_str); - if (r_user_id.is_error()) { - return Status::Error(400, PSLICE() << "Failed to get user_id from " << user_id_str); - } - if (r_user_id.ok() <= 0) { - return Status::Error(400, PSLICE() << "Receive wrong user_id " << user_id_str); - } - return r_user_id.ok(); + auto r_decoded = base64url_decode(encrypted_data.substr(0, 12)); + if (r_decoded.is_error()) { + return Status::Error(400, "Failed to base64url-decode payload"); + } + CHECK(r_decoded.ok().size() == 9); + return as(r_decoded.ok().c_str()); + } + if (data.has_field("user_id")) { + TRY_RESULT(user_id, data.get_required_long_field("user_id")); + if (user_id <= 0) { + return Status::Error(400, PSLICE() << "Receive wrong user_id " << user_id); } + return user_id; } return static_cast(0); @@ -3972,24 +4049,16 @@ Result NotificationManager::decrypt_push(int64 encryption_key_id, string return Status::Error(400, "Expected JSON object"); } - for (auto &field_value : json_value.get_object()) { - if (field_value.first == "p") { - auto encrypted_payload = std::move(field_value.second); - if (encrypted_payload.type() != JsonValue::Type::String) { - return Status::Error(400, "Expected encrypted payload as a String"); - } - Slice encrypted_data = encrypted_payload.get_string(); - if (encrypted_data.size() < 12) { - return Status::Error(400, "Encrypted payload is too small"); - } - auto r_decoded = base64url_decode(encrypted_data); - if (r_decoded.is_error()) { - return Status::Error(400, "Failed to base64url-decode payload"); - } - return decrypt_push_payload(encryption_key_id, std::move(encryption_key), r_decoded.move_as_ok()); - } + const auto &object = json_value.get_object(); + TRY_RESULT(encrypted_data, object.get_required_string_field("p")); + if (encrypted_data.size() < 12) { + return Status::Error(400, "Encrypted payload is too small"); + } + auto r_decoded = base64url_decode(encrypted_data); + if (r_decoded.is_error()) { + return Status::Error(400, "Failed to base64url-decode payload"); } - return Status::Error(400, "No 'p'(payload) field found in push"); + return decrypt_push_payload(encryption_key_id, std::move(encryption_key), r_decoded.move_as_ok()); } Result NotificationManager::decrypt_push_payload(int64 encryption_key_id, string encryption_key, diff --git a/td/telegram/NotificationManager.h b/td/telegram/NotificationManager.h index 16aab4e86671..e46980401d43 100644 --- a/td/telegram/NotificationManager.h +++ b/td/telegram/NotificationManager.h @@ -9,13 +9,14 @@ #include "td/telegram/CallId.h" #include "td/telegram/DialogId.h" #include "td/telegram/Document.h" -#include "td/telegram/FullMessageId.h" #include "td/telegram/MessageId.h" #include "td/telegram/Notification.h" #include "td/telegram/NotificationGroupId.h" #include "td/telegram/NotificationGroupKey.h" #include "td/telegram/NotificationGroupType.h" #include "td/telegram/NotificationId.h" +#include "td/telegram/NotificationObjectFullId.h" +#include "td/telegram/NotificationObjectId.h" #include "td/telegram/NotificationType.h" #include "td/telegram/Photo.h" #include "td/telegram/td_api.h" @@ -67,6 +68,8 @@ class NotificationManager final : public Actor { void load_group_force(NotificationGroupId group_id); + bool have_group_force(NotificationGroupId group_id); + void add_notification(NotificationGroupId group_id, NotificationGroupType group_type, DialogId dialog_id, int32 date, DialogId notification_settings_dialog_id, bool disable_notification, int64 ringtone_id, int32 min_delay_ms, NotificationId notification_id, unique_ptr type, @@ -80,11 +83,11 @@ class NotificationManager final : public Actor { void remove_temporary_notifications(NotificationGroupId group_id, const char *source); - void remove_temporary_notification_by_message_id(NotificationGroupId group_id, MessageId message_id, - bool force_update, const char *source); + void remove_temporary_notification_by_object_id(NotificationGroupId group_id, NotificationObjectId object_id, + bool force_update, const char *source); void remove_notification_group(NotificationGroupId group_id, NotificationId max_notification_id, - MessageId max_message_id, int32 new_total_count, bool force_update, + NotificationObjectId max_object_id, int32 new_total_count, bool force_update, Promise &&promise); void set_notification_total_count(NotificationGroupId group_id, int32 new_total_count); @@ -236,22 +239,22 @@ class NotificationManager final : public Actor { static NotificationId get_last_notification_id(const NotificationGroup &group); - static MessageId get_first_message_id(const NotificationGroup &group); + static NotificationObjectId get_first_object_id(const NotificationGroup &group); - static MessageId get_last_message_id(const NotificationGroup &group); + static NotificationObjectId get_last_object_id(const NotificationGroup &group); - static MessageId get_last_message_id_by_notification_id(const NotificationGroup &group, - NotificationId max_notification_id); + static NotificationObjectId get_last_object_id_by_notification_id(const NotificationGroup &group, + NotificationId max_notification_id); static int32 get_temporary_notification_total_count(const NotificationGroup &group); int32 load_message_notification_groups_from_database(int32 limit, bool send_update); - void load_message_notifications_from_database(const NotificationGroupKey &group_key, NotificationGroup &group, - size_t desired_size); + void load_notifications_from_database(const NotificationGroupKey &group_key, NotificationGroup &group, + size_t desired_size); - void on_get_message_notifications_from_database(NotificationGroupId group_id, size_t limit, - Result> r_notifications); + void on_get_notifications_from_database(NotificationGroupId group_id, size_t limit, + Result> r_notifications); void add_notifications_to_group_begin(NotificationGroups::iterator group_it, vector notifications); @@ -398,8 +401,8 @@ class NotificationManager final : public Actor { string sender_name; bool is_outgoing; }; - FlatHashMap temporary_notifications_; - FlatHashMap temporary_notification_message_ids_; + FlatHashMap temporary_notifications_; + FlatHashMap temporary_notification_object_ids_; FlatHashMap>, NotificationIdHash> push_notification_promises_; struct ActiveCallNotification { diff --git a/td/telegram/NotificationObjectFullId.h b/td/telegram/NotificationObjectFullId.h new file mode 100644 index 000000000000..2aa8062497b9 --- /dev/null +++ b/td/telegram/NotificationObjectFullId.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/DialogId.h" +#include "td/telegram/NotificationObjectId.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +struct NotificationObjectFullId { + private: + DialogId dialog_id; + NotificationObjectId notification_object_id; + + public: + NotificationObjectFullId() : dialog_id(), notification_object_id() { + } + + NotificationObjectFullId(DialogId dialog_id, NotificationObjectId notification_object_id) + : dialog_id(dialog_id), notification_object_id(notification_object_id) { + } + + bool operator==(const NotificationObjectFullId &other) const { + return dialog_id == other.dialog_id && notification_object_id == other.notification_object_id; + } + + bool operator!=(const NotificationObjectFullId &other) const { + return !(*this == other); + } + + DialogId get_dialog_id() const { + return dialog_id; + } + + NotificationObjectId get_notification_object_id() const { + return notification_object_id; + } +}; + +struct NotificationObjectFullIdHash { + uint32 operator()(NotificationObjectFullId full_notification_object_id) const { + return combine_hashes(DialogIdHash()(full_notification_object_id.get_dialog_id()), + NotificationObjectIdHash()(full_notification_object_id.get_notification_object_id())); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, NotificationObjectFullId full_notification_object_id) { + return string_builder << full_notification_object_id.get_notification_object_id() << " in " + << full_notification_object_id.get_dialog_id(); +} + +} // namespace td diff --git a/td/telegram/NotificationObjectId.h b/td/telegram/NotificationObjectId.h new file mode 100644 index 000000000000..786b4c813912 --- /dev/null +++ b/td/telegram/NotificationObjectId.h @@ -0,0 +1,83 @@ +// +// 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/MessageId.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class NotificationObjectId { + int64 id = 0; + + public: + NotificationObjectId() = default; + + NotificationObjectId(MessageId message_id) : id(message_id.get()) { + } + + static NotificationObjectId max() { + return NotificationObjectId(MessageId::max()); + } + + int64 get() const { + return id; + } + + bool is_valid() const { + return id > 0; + } + + bool operator==(const NotificationObjectId &other) const { + return id == other.id; + } + + bool operator!=(const NotificationObjectId &other) const { + return id != other.id; + } + + friend bool operator<(const NotificationObjectId &lhs, const NotificationObjectId &rhs) { + return lhs.id < rhs.id; + } + + friend bool operator>(const NotificationObjectId &lhs, const NotificationObjectId &rhs) { + return lhs.id > rhs.id; + } + + friend bool operator<=(const NotificationObjectId &lhs, const NotificationObjectId &rhs) { + return lhs.id <= rhs.id; + } + + friend bool operator>=(const NotificationObjectId &lhs, const NotificationObjectId &rhs) { + return lhs.id >= rhs.id; + } + + template + void store(StorerT &storer) const { + storer.store_long(id); + } + + template + void parse(ParserT &parser) { + id = parser.fetch_long(); + } +}; + +struct NotificationObjectIdHash { + uint32 operator()(NotificationObjectId notification_object_id) const { + return Hash()(notification_object_id.get()); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, NotificationObjectId notification_object_id) { + return string_builder << "notification object " << notification_object_id.get(); +} + +} // namespace td diff --git a/td/telegram/NotificationSettingsManager.cpp b/td/telegram/NotificationSettingsManager.cpp index ac6a26a0e4d3..3801c1eda41f 100644 --- a/td/telegram/NotificationSettingsManager.cpp +++ b/td/telegram/NotificationSettingsManager.cpp @@ -42,7 +42,6 @@ #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/Random.h" -#include "td/utils/SliceBuilder.h" #include "td/utils/tl_helpers.h" #include @@ -84,8 +83,9 @@ class UploadRingtoneQuery final : public Td::ResultHandler { if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error " << status; } - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - // TODO support FILE_PART_*_MISSING + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + // TODO reupload the file } td_->file_manager_->delete_partial_remote_location(file_id_); @@ -238,8 +238,8 @@ class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler { if (compare_sound) { flags |= telegram_api::account_getNotifyExceptions::COMPARE_SOUND_MASK; } - send_query(G()->net_query_creator().create( - telegram_api::account_getNotifyExceptions(flags, false /*ignored*/, std::move(input_notify_peer)))); + send_query(G()->net_query_creator().create(telegram_api::account_getNotifyExceptions( + flags, false /*ignored*/, false /*ignored*/, std::move(input_notify_peer)))); } void on_result(BufferSlice packet) final { @@ -283,6 +283,64 @@ class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler { } }; +class GetStoryNotifySettingsExceptionsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStoryNotifySettingsExceptionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + int32 flags = telegram_api::account_getNotifyExceptions::COMPARE_STORIES_MASK; + send_query(G()->net_query_creator().create( + telegram_api::account_getNotifyExceptions(flags, false /*ignored*/, false /*ignored*/, nullptr))); + } + + 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 updates_ptr = result_ptr.move_as_ok(); + auto dialog_ids = UpdatesManager::get_update_notify_settings_dialog_ids(updates_ptr.get()); + vector> users; + vector> chats; + switch (updates_ptr->get_id()) { + case telegram_api::updatesCombined::ID: { + auto updates = static_cast(updates_ptr.get()); + users = std::move(updates->users_); + chats = std::move(updates->chats_); + reset_to_empty(updates->users_); + reset_to_empty(updates->chats_); + break; + } + case telegram_api::updates::ID: { + auto updates = static_cast(updates_ptr.get()); + users = std::move(updates->users_); + chats = std::move(updates->chats_); + reset_to_empty(updates->users_); + reset_to_empty(updates->chats_); + break; + } + } + td_->contacts_manager_->on_get_users(std::move(users), "GetStoryNotifySettingsExceptionsQuery"); + td_->contacts_manager_->on_get_chats(std::move(chats), "GetStoryNotifySettingsExceptionsQuery"); + for (auto &dialog_id : dialog_ids) { + td_->messages_manager_->force_create_dialog(dialog_id, "GetStoryNotifySettingsExceptionsQuery"); + } + auto chat_ids = td_->messages_manager_->get_chats_object(-1, dialog_ids, "GetStoryNotifySettingsExceptionsQuery"); + auto promise = PromiseCreator::lambda([promise = std::move(promise_), chat_ids = std::move(chat_ids)]( + Result) mutable { promise.set_value(std::move(chat_ids)); }); + td_->updates_manager_->on_get_updates(std::move(updates_ptr), std::move(promise)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetScopeNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; NotificationSettingsScope scope_; @@ -334,23 +392,8 @@ class UpdateDialogNotifySettingsQuery final : public Td::ResultHandler { return on_error(Status::Error(500, "Can't update chat notification settings")); } - int32 flags = 0; - if (!new_settings.use_default_mute_until) { - flags |= telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK; - } - if (new_settings.sound != nullptr) { - flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK; - } - if (!new_settings.use_default_show_preview) { - flags |= telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK; - } - if (new_settings.silent_send_message) { - flags |= telegram_api::inputPeerNotifySettings::SILENT_MASK; - } send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings( - std::move(input_notify_peer), make_tl_object( - flags, new_settings.show_preview, new_settings.silent_send_message, - new_settings.mute_until, get_input_notification_sound(new_settings.sound))))); + std::move(input_notify_peer), new_settings.get_input_peer_notify_settings()))); } void on_result(BufferSlice packet) final { @@ -394,15 +437,8 @@ class UpdateScopeNotifySettingsQuery final : public Td::ResultHandler { void send(NotificationSettingsScope scope, const ScopeNotificationSettings &new_settings) { auto input_notify_peer = get_input_notify_peer(scope); CHECK(input_notify_peer != nullptr); - int32 flags = telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK | - telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK; - if (new_settings.sound != nullptr) { - flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK; - } send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings( - std::move(input_notify_peer), make_tl_object( - flags, new_settings.show_preview, false, new_settings.mute_until, - get_input_notification_sound(new_settings.sound))))); + std::move(input_notify_peer), new_settings.get_input_peer_notify_settings()))); scope_ = scope; } @@ -562,7 +598,8 @@ void NotificationSettingsManager::init() { if (!channels_notification_settings_.is_synchronized && is_authorized) { channels_notification_settings_ = ScopeNotificationSettings( chats_notification_settings_.mute_until, dup_notification_sound(chats_notification_settings_.sound), - chats_notification_settings_.show_preview, false, false); + chats_notification_settings_.show_preview, chats_notification_settings_.use_default_mute_stories, + chats_notification_settings_.mute_stories, nullptr, false, false, false); channels_notification_settings_.is_synchronized = false; send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>()); } @@ -591,15 +628,29 @@ int32 NotificationSettingsManager::get_scope_mute_until(NotificationSettingsScop return get_scope_notification_settings(scope)->mute_until; } +std::pair NotificationSettingsManager::get_scope_mute_stories(NotificationSettingsScope scope) const { + auto *settings = get_scope_notification_settings(scope); + return {settings->use_default_mute_stories, settings->mute_stories}; +} + const unique_ptr &NotificationSettingsManager::get_scope_notification_sound( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->sound; } +const unique_ptr &NotificationSettingsManager::get_scope_story_notification_sound( + NotificationSettingsScope scope) const { + return get_scope_notification_settings(scope)->story_sound; +} + bool NotificationSettingsManager::get_scope_show_preview(NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->show_preview; } +bool NotificationSettingsManager::get_scope_hide_story_sender(NotificationSettingsScope scope) const { + return get_scope_notification_settings(scope)->hide_story_sender; +} + bool NotificationSettingsManager::get_scope_disable_pinned_message_notifications( NotificationSettingsScope scope) const { return get_scope_notification_settings(scope)->disable_pinned_message_notifications; @@ -1476,6 +1527,11 @@ void NotificationSettingsManager::get_notify_settings_exceptions(NotificationSet td_->create_handler(std::move(promise))->send(scope, filter_scope, compare_sound); } +void NotificationSettingsManager::get_story_notification_settings_exceptions( + Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); +} + void NotificationSettingsManager::on_binlog_events(vector &&events) { if (G()->close_flag()) { return; diff --git a/td/telegram/NotificationSettingsManager.h b/td/telegram/NotificationSettingsManager.h index 95d2c7728ff3..137c370e8492 100644 --- a/td/telegram/NotificationSettingsManager.h +++ b/td/telegram/NotificationSettingsManager.h @@ -26,6 +26,7 @@ #include "td/utils/Status.h" #include +#include namespace td { @@ -46,10 +47,16 @@ class NotificationSettingsManager final : public Actor { int32 get_scope_mute_until(NotificationSettingsScope scope) const; + std::pair get_scope_mute_stories(NotificationSettingsScope scope) const; + const unique_ptr &get_scope_notification_sound(NotificationSettingsScope scope) const; + const unique_ptr &get_scope_story_notification_sound(NotificationSettingsScope scope) const; + bool get_scope_show_preview(NotificationSettingsScope scope) const; + bool get_scope_hide_story_sender(NotificationSettingsScope scope) const; + bool get_scope_disable_pinned_message_notifications(NotificationSettingsScope scope) const; bool get_scope_disable_mention_notifications(NotificationSettingsScope scope) const; @@ -102,6 +109,8 @@ class NotificationSettingsManager final : public Actor { void get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, Promise &&promise); + void get_story_notification_settings_exceptions(Promise> &&promise); + void init(); void on_binlog_events(vector &&events); diff --git a/td/telegram/NotificationSound.cpp b/td/telegram/NotificationSound.cpp index 38cc8f53ae6f..8f6d9d46507f 100644 --- a/td/telegram/NotificationSound.cpp +++ b/td/telegram/NotificationSound.cpp @@ -251,15 +251,15 @@ static unique_ptr get_notification_sound(telegram_api::Notifi } } -unique_ptr get_notification_sound(telegram_api::peerNotifySettings *settings) { +unique_ptr get_notification_sound(telegram_api::peerNotifySettings *settings, bool for_stories) { CHECK(settings != nullptr); telegram_api::NotificationSound *sound = #if TD_ANDROID - settings->android_sound_.get(); + for_stories ? settings->stories_android_sound_.get() : settings->android_sound_.get(); #elif TD_DARWIN_IOS || TD_DARWIN_TV_OS || TD_DARWIN_WATCH_OS - settings->ios_sound_.get(); + for_stories ? settings->stories_ios_sound_.get() : settings->ios_sound_.get(); #else - settings->other_sound_.get(); + for_stories ? settings->stories_other_sound_.get() : settings->other_sound_.get(); #endif return get_notification_sound(sound); } diff --git a/td/telegram/NotificationSound.h b/td/telegram/NotificationSound.h index accfb141dc48..64ae2043556c 100644 --- a/td/telegram/NotificationSound.h +++ b/td/telegram/NotificationSound.h @@ -57,7 +57,7 @@ unique_ptr get_legacy_notification_sound(const string &sound) unique_ptr get_notification_sound(bool use_default_sound, int64 ringtone_id); -unique_ptr get_notification_sound(telegram_api::peerNotifySettings *settings); +unique_ptr get_notification_sound(telegram_api::peerNotifySettings *settings, bool for_stories); telegram_api::object_ptr get_input_notification_sound( const unique_ptr ¬ification_sound); diff --git a/td/telegram/NotificationType.cpp b/td/telegram/NotificationType.cpp index 947123e90d6f..09e85a66b38f 100644 --- a/td/telegram/NotificationType.cpp +++ b/td/telegram/NotificationType.cpp @@ -9,7 +9,6 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/DocumentsManager.h" -#include "td/telegram/Global.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/PhotoFormat.h" @@ -35,7 +34,7 @@ class NotificationTypeMessage final : public NotificationType { return false; } - MessageId get_message_id() const final { + NotificationObjectId get_object_id() const final { return message_id_; } @@ -43,9 +42,9 @@ class NotificationTypeMessage final : public NotificationType { return {}; } - td_api::object_ptr get_notification_type_object(DialogId dialog_id) const final { - auto message_object = G()->td().get_actor_unsafe()->messages_manager_->get_message_object( - {dialog_id, message_id_}, "get_notification_type_object"); + td_api::object_ptr get_notification_type_object(Td *td, DialogId dialog_id) const final { + auto message_object = + td->messages_manager_->get_message_object({dialog_id, message_id_}, "get_notification_type_object"); if (message_object == nullptr) { return nullptr; } @@ -74,15 +73,15 @@ class NotificationTypeSecretChat final : public NotificationType { return false; } - MessageId get_message_id() const final { - return MessageId(); + NotificationObjectId get_object_id() const final { + return NotificationObjectId(); } vector get_file_ids(const Td *td) const final { return {}; } - td_api::object_ptr get_notification_type_object(DialogId dialog_id) const final { + td_api::object_ptr get_notification_type_object(Td *, DialogId) const final { return td_api::make_object(); } @@ -104,15 +103,15 @@ class NotificationTypeCall final : public NotificationType { return false; } - MessageId get_message_id() const final { - return MessageId::max(); + NotificationObjectId get_object_id() const final { + return NotificationObjectId::max(); } vector get_file_ids(const Td *td) const final { return {}; } - td_api::object_ptr get_notification_type_object(DialogId dialog_id) const final { + td_api::object_ptr get_notification_type_object(Td *, DialogId) const final { return td_api::make_object(call_id_.get()); } @@ -136,7 +135,7 @@ class NotificationTypePushMessage final : public NotificationType { return true; } - MessageId get_message_id() const final { + NotificationObjectId get_object_id() const final { return message_id_; } @@ -148,7 +147,8 @@ class NotificationTypePushMessage final : public NotificationType { return photo_get_file_ids(photo_); } - static td_api::object_ptr get_push_message_content_object(Slice key, const string &arg, + static td_api::object_ptr get_push_message_content_object(Td *td, Slice key, + const string &arg, const Photo &photo, const Document &document) { bool is_pinned = false; @@ -167,14 +167,12 @@ class NotificationTypePushMessage final : public NotificationType { switch (key[8]) { case 'A': if (key == "MESSAGE_ANIMATION") { - auto animations_manager = G()->td().get_actor_unsafe()->animations_manager_.get(); return td_api::make_object( - animations_manager->get_animation_object(document.file_id), arg, is_pinned); + td->animations_manager_->get_animation_object(document.file_id), arg, is_pinned); } if (key == "MESSAGE_AUDIO") { - auto audios_manager = G()->td().get_actor_unsafe()->audios_manager_.get(); return td_api::make_object( - audios_manager->get_audio_object(document.file_id), is_pinned); + td->audios_manager_->get_audio_object(document.file_id), is_pinned); } if (key == "MESSAGE_AUDIOS") { return td_api::make_object(to_integer(arg), false, false, true, @@ -229,9 +227,8 @@ class NotificationTypePushMessage final : public NotificationType { break; case 'D': if (key == "MESSAGE_DOCUMENT") { - auto documents_manager = G()->td().get_actor_unsafe()->documents_manager_.get(); return td_api::make_object( - documents_manager->get_document_object(document.file_id, PhotoFormat::Jpeg), is_pinned); + td->documents_manager_->get_document_object(document.file_id, PhotoFormat::Jpeg), is_pinned); } if (key == "MESSAGE_DOCUMENTS") { return td_api::make_object(to_integer(arg), false, false, false, @@ -273,9 +270,8 @@ class NotificationTypePushMessage final : public NotificationType { break; case 'P': if (key == "MESSAGE_PHOTO") { - auto file_manager = G()->td().get_actor_unsafe()->file_manager_.get(); - return td_api::make_object(get_photo_object(file_manager, photo), arg, false, - is_pinned); + return td_api::make_object(get_photo_object(td->file_manager_.get(), photo), + arg, false, is_pinned); } if (key == "MESSAGE_PHOTOS") { return td_api::make_object(to_integer(arg), true, false, false, @@ -309,9 +305,11 @@ class NotificationTypePushMessage final : public NotificationType { return td_api::make_object(nullptr, arg, true, false); } if (key == "MESSAGE_STICKER") { - auto stickers_manager = G()->td().get_actor_unsafe()->stickers_manager_.get(); return td_api::make_object( - stickers_manager->get_sticker_object(document.file_id), trim(arg), is_pinned); + td->stickers_manager_->get_sticker_object(document.file_id), trim(arg), is_pinned); + } + if (key == "MESSAGE_STORY") { + return td_api::make_object(is_pinned); } if (key == "MESSAGE_SUGGEST_PHOTO") { return td_api::make_object(); @@ -324,23 +322,20 @@ class NotificationTypePushMessage final : public NotificationType { break; case 'V': if (key == "MESSAGE_VIDEO") { - auto videos_manager = G()->td().get_actor_unsafe()->videos_manager_.get(); return td_api::make_object( - videos_manager->get_video_object(document.file_id), arg, false, is_pinned); + td->videos_manager_->get_video_object(document.file_id), arg, false, is_pinned); } if (key == "MESSAGE_VIDEO_NOTE") { - auto video_notes_manager = G()->td().get_actor_unsafe()->video_notes_manager_.get(); return td_api::make_object( - video_notes_manager->get_video_note_object(document.file_id), is_pinned); + td->video_notes_manager_->get_video_note_object(document.file_id), is_pinned); } if (key == "MESSAGE_VIDEOS") { return td_api::make_object(to_integer(arg), false, true, false, false); } if (key == "MESSAGE_VOICE_NOTE") { - auto voice_notes_manager = G()->td().get_actor_unsafe()->voice_notes_manager_.get(); return td_api::make_object( - voice_notes_manager->get_voice_note_object(document.file_id), is_pinned); + td->voice_notes_manager_->get_voice_note_object(document.file_id), is_pinned); } break; case 'W': @@ -354,12 +349,11 @@ class NotificationTypePushMessage final : public NotificationType { UNREACHABLE(); } - td_api::object_ptr get_notification_type_object(DialogId dialog_id) const final { - auto sender = get_message_sender_object(G()->td().get_actor_unsafe(), sender_user_id_, sender_dialog_id_, - "get_notification_type_object"); + td_api::object_ptr get_notification_type_object(Td *td, DialogId) const final { + auto sender = get_message_sender_object(td, sender_user_id_, sender_dialog_id_, "get_notification_type_object"); return td_api::make_object( message_id_.get(), std::move(sender), sender_name_, is_outgoing_, - get_push_message_content_object(key_, arg_, photo_, document_)); + get_push_message_content_object(td, key_, arg_, photo_, document_)); } StringBuilder &to_string_builder(StringBuilder &string_builder) const final { diff --git a/td/telegram/NotificationType.h b/td/telegram/NotificationType.h index ce2091a75f25..df7e784637f0 100644 --- a/td/telegram/NotificationType.h +++ b/td/telegram/NotificationType.h @@ -11,6 +11,7 @@ #include "td/telegram/Document.h" #include "td/telegram/files/FileId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/NotificationObjectId.h" #include "td/telegram/Photo.h" #include "td/telegram/td_api.h" #include "td/telegram/UserId.h" @@ -33,11 +34,12 @@ class NotificationType { virtual bool is_temporary() const = 0; - virtual MessageId get_message_id() const = 0; + virtual NotificationObjectId get_object_id() const = 0; virtual vector get_file_ids(const Td *td) const = 0; - virtual td_api::object_ptr get_notification_type_object(DialogId dialog_id) const = 0; + virtual td_api::object_ptr get_notification_type_object(Td *td, + DialogId dialog_id) const = 0; virtual StringBuilder &to_string_builder(StringBuilder &string_builder) const = 0; }; diff --git a/td/telegram/OptionManager.cpp b/td/telegram/OptionManager.cpp index b3454ad04ce1..970a8f40fa98 100644 --- a/td/telegram/OptionManager.cpp +++ b/td/telegram/OptionManager.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/OptionManager.h" +#include "td/telegram/AccountManager.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AttachMenuManager.h" #include "td/telegram/AuthManager.h" @@ -17,17 +18,19 @@ #include "td/telegram/Global.h" #include "td/telegram/JsonValue.h" #include "td/telegram/LanguagePackManager.h" -#include "td/telegram/MessageReaction.h" #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StorageManager.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/TopDialogManager.h" +#include "td/telegram/UpdatesManager.h" #include "td/db/KeyValueSyncInterface.h" #include "td/db/TsSeqKeyValue.h" @@ -58,6 +61,7 @@ OptionManager::OptionManager(Td *td) all_options["utc_time_offset"] = PSTRING() << 'I' << Clocks::tz_offset(); for (const auto &name_value : all_options) { const string &name = name_value.first; + CHECK(!name.empty()); options_->set(name, name_value.second); if (!is_internal_option(name)) { send_closure(G()->td(), &Td::send_update, @@ -76,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("story_caption_length_max")) { + set_option_integer("story_caption_length_max", 200); + } if (!have_option("bio_length_max")) { set_option_integer("bio_length_max", 70); } @@ -109,7 +116,20 @@ OptionManager::OptionManager(Td *td) if (!have_option("pinned_forum_topic_count_max")) { set_option_integer("pinned_forum_topic_count_max", G()->is_test_dc() ? 3 : 5); } + if (!have_option("archive_all_stories")) { + // set_option_boolean("archive_all_stories", false); + } + if (!have_option("story_stealth_mode_past_period")) { + set_option_integer("story_stealth_mode_past_period", 300); + } + if (!have_option("story_stealth_mode_future_period")) { + set_option_integer("story_stealth_mode_future_period", 1500); + } + if (!have_option("story_stealth_mode_cooldown_period")) { + set_option_integer("story_stealth_mode_cooldown_period", 3600); + } + set_option_empty("archive_and_mute_new_chats_from_unknown_users"); set_option_empty("chat_filter_count_max"); set_option_empty("chat_filter_chosen_chat_count_max"); set_option_empty("forum_member_count_min"); @@ -244,7 +264,8 @@ bool OptionManager::is_internal_option(Slice name) { case 'a': return name == "about_length_limit_default" || name == "about_length_limit_premium" || name == "aggressive_anti_spam_supergroup_member_count_min" || name == "animated_emoji_zoom" || - name == "animation_search_emojis" || name == "animation_search_provider"; + name == "animation_search_emojis" || name == "animation_search_provider" || + name == "authorization_autoconfirm_period"; case 'b': return name == "base_language_pack_version"; case 'c': @@ -253,7 +274,9 @@ bool OptionManager::is_internal_option(Slice name) { name == "channels_limit_default" || name == "channels_limit_premium" || name == "channels_public_limit_default" || name == "channels_public_limit_premium" || name == "channels_read_media_period" || name == "chat_read_mark_expire_period" || - name == "chat_read_mark_size_threshold"; + name == "chat_read_mark_size_threshold" || name == "chatlist_invites_limit_default" || + name == "chatlist_invites_limit_premium" || name == "chatlists_joined_limit_default" || + name == "chatlists_joined_limit_premium"; case 'd': return name == "dc_txt_domain_name" || name == "default_reaction" || name == "default_reaction_needs_sync" || name == "dialog_filters_chats_limit_default" || name == "dialog_filters_chats_limit_premium" || @@ -274,20 +297,27 @@ bool OptionManager::is_internal_option(Slice name) { case 'm': return name == "my_phone_number"; case 'n': - return name == "notification_cloud_delay_ms" || name == "notification_default_delay_ms"; + return name == "need_premium_for_story_caption_entities" || name == "need_synchronize_archive_all_stories" || + name == "notification_cloud_delay_ms" || name == "notification_default_delay_ms"; case 'o': return name == "online_cloud_timeout_ms" || name == "online_update_period_ms" || name == "otherwise_relogin_days"; case 'p': return name == "premium_bot_username" || name == "premium_features" || name == "premium_invoice_slug"; case 'r': return name == "rating_e_decay" || name == "reactions_uniq_max" || name == "reactions_user_max_default" || - name == "reactions_user_max_premium" || name == "recent_stickers_limit" || name == "revoke_pm_inbox" || - name == "revoke_time_limit" || name == "revoke_pm_time_limit"; + name == "reactions_user_max_premium" || name == "recent_stickers_limit" || + name == "restriction_add_platforms" || name == "revoke_pm_inbox" || name == "revoke_time_limit" || + name == "revoke_pm_time_limit"; case 's': return name == "saved_animations_limit" || name == "saved_gifs_limit_default" || name == "saved_gifs_limit_premium" || name == "session_count" || name == "since_last_open" || name == "stickers_faved_limit_default" || name == "stickers_faved_limit_premium" || - name == "stickers_normal_by_emoji_per_premium_num" || name == "stickers_premium_by_emoji_num"; + name == "stickers_normal_by_emoji_per_premium_num" || name == "stickers_premium_by_emoji_num" || + name == "stories_changelog_user_id" || name == "stories_sent_monthly_limit_default" || + name == "stories_sent_monthly_limit_premium" || name == "stories_sent_weekly_limit_default" || + name == "stories_sent_weekly_limit_premium" || name == "story_caption_length_limit_default" || + name == "story_caption_length_limit_premium" || name == "story_expiring_limit_default" || + name == "story_expiring_limit_premium"; case 'v': return name == "video_note_size_max"; case 'w': @@ -299,7 +329,7 @@ bool OptionManager::is_internal_option(Slice name) { td_api::object_ptr OptionManager::get_internal_option_update(Slice name) const { if (name == "default_reaction") { - return get_update_default_reaction_type(get_option_string(name)); + return ReactionType(get_option_string(name)).get_update_default_reaction_type(); } if (name == "otherwise_relogin_days") { auto days = narrow_cast(get_option_integer(name)); @@ -332,6 +362,9 @@ void OptionManager::on_option_updated(Slice name) { if (name == "animation_search_provider") { td_->animations_manager_->on_update_animation_search_provider(); } + if (name == "authorization_autoconfirm_period") { + td_->account_manager_->update_unconfirmed_authorization_timeout(true); + } break; case 'b': if (name == "base_language_pack_version") { @@ -385,6 +418,11 @@ void OptionManager::on_option_updated(Slice name) { G()->net_query_dispatcher().update_mtproto_header(); } } + if (name == "is_premium") { + set_option_boolean( + "can_use_text_entities_in_story_caption", + !get_option_boolean("need_premium_for_story_caption_entities") || get_option_boolean("is_premium")); + } break; case 'l': if (name == "language_pack_id") { @@ -405,6 +443,14 @@ void OptionManager::on_option_updated(Slice name) { } break; case 'n': + if (name == "need_premium_for_story_caption_entities") { + set_option_boolean( + "can_use_text_entities_in_story_caption", + !get_option_boolean("need_premium_for_story_caption_entities") || get_option_boolean("is_premium")); + } + if (name == "need_synchronize_archive_all_stories") { + send_closure(td_->story_manager_actor_, &StoryManager::try_synchronize_archive_all_stories); + } if (name == "notification_cloud_delay_ms") { send_closure(td_->notification_manager_actor_, &NotificationManager::on_notification_cloud_delay_changed); } @@ -438,6 +484,7 @@ void OptionManager::on_option_updated(Slice name) { } if (name == "session_count") { G()->net_query_dispatcher().update_session_count(); + td_->updates_manager_->init_sessions(false); } break; case 'u': @@ -468,11 +515,6 @@ void OptionManager::get_option(const string &name, Promiseconfig_manager_, &ConfigManager::get_global_privacy_settings, wrap_promise()); - } - break; case 'c': if (!is_bot && name == "can_ignore_sensitive_content_restrictions") { return send_closure_later(td_->config_manager_, &ConfigManager::get_content_settings, wrap_promise()); @@ -526,7 +568,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.14"); + return td_api::make_object("1.8.18"); } break; } @@ -616,19 +658,12 @@ void OptionManager::set_option(const string &name, td_api::object_ptr(value.get())->value_; - send_closure_later(td_->config_manager_, &ConfigManager::set_archive_and_mute, archive_and_mute, - std::move(promise)); + /* + if (!is_bot && set_boolean_option("archive_all_stories")) { + set_option_boolean("need_synchronize_archive_all_stories", true); return; } + */ break; case 'c': if (!is_bot && set_string_option("connection_parameters", [](Slice value) { diff --git a/td/telegram/OrderInfo.cpp b/td/telegram/OrderInfo.cpp index 310b24966bee..905935e86daf 100644 --- a/td/telegram/OrderInfo.cpp +++ b/td/telegram/OrderInfo.cpp @@ -132,12 +132,12 @@ Result
address_from_json(Slice json) { } auto &object = value.get_object(); - TRY_RESULT(country_code, get_json_object_string_field(object, "country_code")); - TRY_RESULT(state, get_json_object_string_field(object, "state")); - TRY_RESULT(city, get_json_object_string_field(object, "city")); - TRY_RESULT(street_line1, get_json_object_string_field(object, "street_line1")); - TRY_RESULT(street_line2, get_json_object_string_field(object, "street_line2")); - TRY_RESULT(postal_code, get_json_object_string_field(object, "post_code")); + TRY_RESULT(country_code, object.get_optional_string_field("country_code")); + TRY_RESULT(state, object.get_optional_string_field("state")); + TRY_RESULT(city, object.get_optional_string_field("city")); + TRY_RESULT(street_line1, object.get_optional_string_field("street_line1")); + TRY_RESULT(street_line2, object.get_optional_string_field("street_line2")); + TRY_RESULT(postal_code, object.get_optional_string_field("post_code")); TRY_STATUS(check_country_code(country_code)); TRY_STATUS(check_state(state)); diff --git a/td/telegram/PasswordManager.cpp b/td/telegram/PasswordManager.cpp index 7450738c7bf3..cffe24c63d42 100644 --- a/td/telegram/PasswordManager.cpp +++ b/td/telegram/PasswordManager.cpp @@ -14,6 +14,7 @@ #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/mtproto/DhHandshake.h" @@ -243,8 +244,8 @@ void PasswordManager::do_get_secure_secret(bool allow_recursive, string password return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID")); } get_full_state( - password, PromiseCreator::lambda([password, allow_recursive, promise = std::move(promise), - actor_id = actor_id(this)](Result r_state) mutable { + password, PromiseCreator::lambda([actor_id = actor_id(this), password, allow_recursive, + promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } @@ -261,7 +262,7 @@ void PasswordManager::do_get_secure_secret(bool allow_recursive, string password } auto new_promise = - PromiseCreator::lambda([password, promise = std::move(promise), actor_id](Result r_ok) mutable { + PromiseCreator::lambda([actor_id, password, promise = std::move(promise)](Result r_ok) mutable { if (r_ok.is_error()) { return promise.set_error(r_ok.move_as_error()); } @@ -301,8 +302,8 @@ void PasswordManager::create_temp_password(string password, int32 timeout, Promi send_closure(actor_id, &PasswordManager::on_finish_create_temp_password, std::move(result), false); }); - do_get_state(PromiseCreator::lambda([password = std::move(password), timeout, promise = std::move(new_promise), - actor_id = actor_id(this)](Result r_state) mutable { + do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), password = std::move(password), timeout, + promise = std::move(new_promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } @@ -349,8 +350,8 @@ void PasswordManager::get_full_state(string password, Promise send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action, SuggestedAction{SuggestedAction::Type::CheckPassword}); - do_get_state(PromiseCreator::lambda([password = std::move(password), promise = std::move(promise), - actor_id = actor_id(this)](Result r_state) mutable { + do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), password = std::move(password), + promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } diff --git a/td/telegram/Payments.cpp b/td/telegram/Payments.cpp index cbc7f4e1e3ed..968dbfdff943 100644 --- a/td/telegram/Payments.cpp +++ b/td/telegram/Payments.cpp @@ -220,13 +220,13 @@ static tl_object_ptr convert_payment_provider( return nullptr; } - auto r_public_token = get_json_object_string_field(value.get_object(), "public_token", false); - + const auto &object = value.get_object(); + auto r_public_token = object.get_required_string_field("public_token"); if (r_public_token.is_error()) { LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; return nullptr; } - if (value.get_object().size() != 1) { + if (object.field_count() != 1) { LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; } @@ -246,10 +246,11 @@ static tl_object_ptr convert_payment_provider( return nullptr; } - auto r_need_country = get_json_object_bool_field(value.get_object(), "need_country", false); - auto r_need_postal_code = get_json_object_bool_field(value.get_object(), "need_zip", false); - auto r_need_cardholder_name = get_json_object_bool_field(value.get_object(), "need_cardholder_name", false); - auto r_publishable_key = get_json_object_string_field(value.get_object(), "publishable_key", false); + const auto &object = value.get_object(); + auto r_need_country = object.get_required_bool_field("need_country"); + auto r_need_postal_code = object.get_required_bool_field("need_zip"); + auto r_need_cardholder_name = object.get_required_bool_field("need_cardholder_name"); + auto r_publishable_key = object.get_required_string_field("publishable_key"); // TODO support "gpay_parameters":{"gateway":"stripe","stripe:publishableKey":"...","stripe:version":"..."} if (r_need_country.is_error() || r_need_postal_code.is_error() || r_need_cardholder_name.is_error() || @@ -257,7 +258,7 @@ static tl_object_ptr convert_payment_provider( LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; return nullptr; } - if (value.get_object().size() != 5) { + if (object.field_count() != 5) { LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"'; } diff --git a/td/telegram/PhoneNumberManager.cpp b/td/telegram/PhoneNumberManager.cpp index 650523b7599b..112eb505cb6d 100644 --- a/td/telegram/PhoneNumberManager.cpp +++ b/td/telegram/PhoneNumberManager.cpp @@ -16,7 +16,6 @@ #include "td/telegram/telegram_api.h" #include "td/utils/logging.h" -#include "td/utils/ScopeGuard.h" namespace td { @@ -83,7 +82,7 @@ void PhoneNumberManager::set_phone_number_and_hash(uint64 query_id, string hash, void PhoneNumberManager::resend_authentication_code(uint64 query_id) { if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(400, "resendAuthenticationCode unexpected")); + return on_query_error(query_id, Status::Error(400, "Can't resend code")); } auto r_resend_code = send_code_helper_.resend_code(); @@ -102,7 +101,7 @@ void PhoneNumberManager::send_new_check_code_query(const telegram_api::Function void PhoneNumberManager::check_code(uint64 query_id, string code) { if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(400, "checkAuthenticationCode unexpected")); + return on_query_error(query_id, Status::Error(400, "Can't check code")); } on_new_query(query_id); @@ -124,7 +123,7 @@ void PhoneNumberManager::check_code(uint64 query_id, string code) { void PhoneNumberManager::on_new_query(uint64 query_id) { if (query_id_ != 0) { - on_query_error(Status::Error(400, "Another authorization query has started")); + on_current_query_error(Status::Error(400, "Another query has started")); } net_query_id_ = 0; net_query_type_ = NetQueryType::None; @@ -132,8 +131,10 @@ void PhoneNumberManager::on_new_query(uint64 query_id) { // TODO: cancel older net_query } -void PhoneNumberManager::on_query_error(Status status) { - CHECK(query_id_ != 0); +void PhoneNumberManager::on_current_query_error(Status status) { + if (query_id_ == 0) { + return; + } auto id = query_id_; query_id_ = 0; net_query_id_ = 0; @@ -145,8 +146,10 @@ void PhoneNumberManager::on_query_error(uint64 id, Status status) { send_closure(G()->td(), &Td::send_error, id, std::move(status)); } -void PhoneNumberManager::on_query_ok() { - CHECK(query_id_ != 0); +void PhoneNumberManager::on_current_query_ok() { + if (query_id_ == 0) { + return; + } auto id = query_id_; net_query_id_ = 0; net_query_type_ = NetQueryType::None; @@ -163,57 +166,57 @@ void PhoneNumberManager::start_net_query(NetQueryType net_query_type, NetQueryPt void PhoneNumberManager::process_check_code_result(Result> &&result) { if (result.is_error()) { - return on_query_error(result.move_as_error()); + return on_current_query_error(result.move_as_error()); } - send_closure(G()->contacts_manager(), &ContactsManager::on_get_user, result.move_as_ok(), "process_check_code_result", - true); + send_closure(G()->contacts_manager(), &ContactsManager::on_get_user, result.move_as_ok(), + "process_check_code_result"); state_ = State::Ok; - on_query_ok(); + on_current_query_ok(); } void PhoneNumberManager::process_check_code_result(Result &&result) { if (result.is_error()) { - return on_query_error(result.move_as_error()); + return on_current_query_error(result.move_as_error()); } state_ = State::Ok; - on_query_ok(); + on_current_query_ok(); } -void PhoneNumberManager::on_check_code_result(NetQueryPtr &result) { +void PhoneNumberManager::on_check_code_result(NetQueryPtr &&net_query) { switch (type_) { case Type::ChangePhone: - return process_check_code_result(fetch_result(result->ok())); + return process_check_code_result(fetch_result(std::move(net_query))); case Type::VerifyPhone: - return process_check_code_result(fetch_result(result->ok())); + return process_check_code_result(fetch_result(std::move(net_query))); case Type::ConfirmPhone: - return process_check_code_result(fetch_result(result->ok())); + return process_check_code_result(fetch_result(std::move(net_query))); default: UNREACHABLE(); } } -void PhoneNumberManager::on_send_code_result(NetQueryPtr &result) { +void PhoneNumberManager::on_send_code_result(NetQueryPtr &&net_query) { auto r_sent_code = [&] { switch (type_) { case Type::ChangePhone: - return fetch_result(result->ok()); + return fetch_result(std::move(net_query)); case Type::VerifyPhone: - return fetch_result(result->ok()); + return fetch_result(std::move(net_query)); case Type::ConfirmPhone: - return fetch_result(result->ok()); + return fetch_result(std::move(net_query)); default: UNREACHABLE(); - return fetch_result(result->ok()); + return fetch_result(std::move(net_query)); } }(); if (r_sent_code.is_error()) { - return on_query_error(r_sent_code.move_as_error()); + return on_current_query_error(r_sent_code.move_as_error()); } auto sent_code_ptr = r_sent_code.move_as_ok(); auto sent_code_id = sent_code_ptr->get_id(); if (sent_code_id != telegram_api::auth_sentCode::ID) { CHECK(sent_code_id == telegram_api::auth_sentCodeSuccess::ID); - return on_query_error(Status::Error(500, "Receive invalid response")); + return on_current_query_error(Status::Error(500, "Receive invalid response")); } auto sent_code = telegram_api::move_object_as(sent_code_ptr); @@ -222,7 +225,7 @@ void PhoneNumberManager::on_send_code_result(NetQueryPtr &result) { switch (sent_code->type_->get_id()) { case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID: case telegram_api::auth_sentCodeTypeEmailCode::ID: - return on_query_error(Status::Error(500, "Receive incorrect response")); + return on_current_query_error(Status::Error(500, "Receive incorrect response")); default: break; } @@ -230,35 +233,28 @@ void PhoneNumberManager::on_send_code_result(NetQueryPtr &result) { send_code_helper_.on_sent_code(std::move(sent_code)); state_ = State::WaitCode; - on_query_ok(); + on_current_query_ok(); } -void PhoneNumberManager::on_result(NetQueryPtr result) { - SCOPE_EXIT { - result->clear(); - }; +void PhoneNumberManager::on_result(NetQueryPtr net_query) { NetQueryType type = NetQueryType::None; - if (result->id() == net_query_id_) { + if (net_query->id() == net_query_id_) { net_query_id_ = 0; type = net_query_type_; net_query_type_ = NetQueryType::None; - if (result->is_error()) { - if (query_id_ != 0) { - on_query_error(std::move(result->error())); - } - return; - } } switch (type) { case NetQueryType::None: - result->ignore(); + net_query->clear(); break; case NetQueryType::SendCode: - on_send_code_result(result); + on_send_code_result(std::move(net_query)); break; case NetQueryType::CheckCode: - on_check_code_result(result); + on_check_code_result(std::move(net_query)); break; + default: + UNREACHABLE(); } } diff --git a/td/telegram/PhoneNumberManager.h b/td/telegram/PhoneNumberManager.h index b9456ca82e29..3257496e6c3e 100644 --- a/td/telegram/PhoneNumberManager.h +++ b/td/telegram/PhoneNumberManager.h @@ -48,9 +48,9 @@ class PhoneNumberManager final : public NetActor { void on_new_query(uint64 query_id); - void on_query_ok(); + void on_current_query_ok(); - void on_query_error(Status status); + void on_current_query_error(Status status); static void on_query_error(uint64 id, Status status); @@ -64,11 +64,11 @@ class PhoneNumberManager final : public NetActor { void process_check_code_result(Result &&result); - void on_result(NetQueryPtr result) final; + void on_result(NetQueryPtr net_query) final; - void on_send_code_result(NetQueryPtr &result); + void on_send_code_result(NetQueryPtr &&net_query); - void on_check_code_result(NetQueryPtr &result); + void on_check_code_result(NetQueryPtr &&net_query); void tear_down() final; }; diff --git a/td/telegram/Photo.cpp b/td/telegram/Photo.cpp index 37daed2e6401..e2cbb11fb0e2 100644 --- a/td/telegram/Photo.cpp +++ b/td/telegram/Photo.cpp @@ -16,6 +16,7 @@ #include "td/telegram/PhotoFormat.h" #include "td/telegram/PhotoSizeSource.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/common.h" @@ -297,15 +298,15 @@ Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr &&photo, DialogId owner_dialog_id) { +Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id, FileType file_type) { if (photo == nullptr || photo->get_id() == telegram_api::photoEmpty::ID) { return Photo(); } CHECK(photo->get_id() == telegram_api::photo::ID); - return get_photo(td, move_tl_object_as(photo), owner_dialog_id); + return get_photo(td, move_tl_object_as(photo), owner_dialog_id, file_type); } -Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id) { +Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id, FileType file_type) { CHECK(photo != nullptr); Photo res; @@ -320,8 +321,8 @@ Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId own DcId dc_id = DcId::create(photo->dc_id_); for (auto &size_ptr : photo->sizes_) { - auto photo_size = get_photo_size(td->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Photo, 0), - photo->id_, photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, + auto photo_size = get_photo_size(td->file_manager_.get(), PhotoSizeSource::thumbnail(file_type, 0), photo->id_, + photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, std::move(size_ptr), PhotoFormat::Jpeg); if (photo_size.get_offset() == 0) { PhotoSize &size = photo_size.get<0>(); @@ -338,7 +339,7 @@ Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId own for (auto &size_ptr : photo->video_sizes_) { auto animation = - process_video_size(td, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_, photo->access_hash_, + process_video_size(td, PhotoSizeSource::thumbnail(file_type, 0), photo->id_, photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, std::move(size_ptr)); if (animation.empty()) { continue; diff --git a/td/telegram/Photo.h b/td/telegram/Photo.h index f3c2470bbc32..4de7923a1c4a 100644 --- a/td/telegram/Photo.h +++ b/td/telegram/Photo.h @@ -9,6 +9,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/EncryptedFile.h" #include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileType.h" #include "td/telegram/PhotoSize.h" #include "td/telegram/secret_api.h" #include "td/telegram/SecretInputMedia.h" @@ -84,6 +85,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &pro DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash, tl_object_ptr &&chat_photo_ptr); + tl_object_ptr get_chat_photo_info_object(FileManager *file_manager, const DialogPhoto *dialog_photo); @@ -104,9 +106,11 @@ bool need_update_dialog_photo(const DialogPhoto &from, const DialogPhoto &to); StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo); -Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id); +Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id, + FileType file_type = FileType::Photo); -Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id); +Photo get_photo(Td *td, tl_object_ptr &&photo, DialogId owner_dialog_id, + FileType file_type = FileType::Photo); Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr &&file, tl_object_ptr &&photo, DialogId owner_dialog_id); diff --git a/td/telegram/PhotoSize.cpp b/td/telegram/PhotoSize.cpp index 5dc463441fe5..5a37ada77b98 100644 --- a/td/telegram/PhotoSize.cpp +++ b/td/telegram/PhotoSize.cpp @@ -9,6 +9,7 @@ #include "td/telegram/files/FileLocation.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/base64.h" #include "td/utils/HttpUrl.h" @@ -410,25 +411,25 @@ PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_t Result get_input_photo_size(FileManager *file_manager, FileId file_id, int32 width, int32 height) { if (width < 0 || width > 10000) { - return Status::Error(400, "Wrong photo width"); + return Status::Error(400, "Width of the photo is too big"); } if (height < 0 || height > 10000) { - return Status::Error(400, "Wrong photo height"); + return Status::Error(400, "Height of the photo is too big"); } if (width + height > 10000) { - return Status::Error(400, "Photo dimensions are too big"); + return Status::Error(400, "Dimensions of the photo are too big"); } auto file_view = file_manager->get_file_view(file_id); auto file_size = file_view.size(); if (file_size < 0 || file_size >= 1000000000) { - return Status::Error(400, "Photo is too big"); + return Status::Error(400, "Size of the photo is too big"); } int32 type = 'i'; if (file_view.has_remote_location() && !file_view.remote_location().is_web()) { auto photo_size_source = file_view.remote_location().get_source(); - if (photo_size_source.get_type("create_input_message_content") == PhotoSizeSource::Type::Thumbnail) { + if (photo_size_source.get_type("get_input_photo_size") == PhotoSizeSource::Type::Thumbnail) { auto old_type = photo_size_source.thumbnail().thumbnail_type; if (old_type != 't') { type = old_type; diff --git a/td/telegram/PhotoSizeSource.cpp b/td/telegram/PhotoSizeSource.cpp index 9218af27d9ce..64f5a1640030 100644 --- a/td/telegram/PhotoSizeSource.cpp +++ b/td/telegram/PhotoSizeSource.cpp @@ -65,67 +65,121 @@ FileType PhotoSizeSource::get_file_type(const char *source) const { } string PhotoSizeSource::get_unique(const char *source) const { + auto compare_type = get_compare_type(source); + if (compare_type != 2 && compare_type != 3) { + return string(1, static_cast(compare_type)); + } + auto ptr = StackAllocator::alloc(16); MutableSlice data = ptr.as_slice(); TlStorerUnsafe storer(data.ubegin()); + if (compare_type == 2) { + storer.store_slice(Slice("\x02")); + } + td::store(get_compare_volume_id(), storer); + td::store(get_compare_local_id(), storer); + auto size = storer.get_buf() - data.ubegin(); + CHECK(size <= 13); + return string(data.begin(), size); +} + +int32 PhotoSizeSource::get_compare_type(const char *source) const { switch (get_type(source)) { case Type::Legacy: - UNREACHABLE(); break; case Type::Thumbnail: { auto type = thumbnail().thumbnail_type; CHECK(0 <= type && type <= 127); if (type == 'a') { - type = 0; - } else if (type == 'c') { - type = 1; - } else { - type += 5; + return 0; } - return string(1, static_cast(type)); + if (type == 'c') { + return 1; + } + return type + 5; } case Type::DialogPhotoSmall: - // it doesn't matter to which Dialog the photo belongs - return string(1, '\x00'); + return 0; case Type::DialogPhotoBig: - // it doesn't matter to which Dialog the photo belongs - return string(1, '\x01'); + return 1; case Type::StickerSetThumbnail: - UNREACHABLE(); break; - case Type::FullLegacy: { - auto &legacy = full_legacy(); - td::store(legacy.volume_id, storer); - td::store(legacy.local_id, storer); - break; - } + case Type::FullLegacy: case Type::DialogPhotoSmallLegacy: - case Type::DialogPhotoBigLegacy: { - auto &legacy = dialog_photo_legacy(); - td::store(legacy.volume_id, storer); - td::store(legacy.local_id, storer); - break; - } - case Type::StickerSetThumbnailLegacy: { - auto &legacy = sticker_set_thumbnail_legacy(); - td::store(legacy.volume_id, storer); - td::store(legacy.local_id, storer); - break; - } - case Type::StickerSetThumbnailVersion: { - auto &thumbnail = sticker_set_thumbnail_version(); - storer.store_slice(Slice("\x02")); - td::store(thumbnail.sticker_set_id, storer); - td::store(thumbnail.version, storer); + case Type::DialogPhotoBigLegacy: + case Type::StickerSetThumbnailLegacy: + return 3; + case Type::StickerSetThumbnailVersion: + return 2; + default: break; - } + } + UNREACHABLE(); + return -1; +} + +int64 PhotoSizeSource::get_compare_volume_id() const { + switch (get_type("get_compare_volume_id")) { + case Type::FullLegacy: + return full_legacy().volume_id; + case Type::DialogPhotoSmallLegacy: + case Type::DialogPhotoBigLegacy: + return dialog_photo_legacy().volume_id; + case Type::StickerSetThumbnailLegacy: + return sticker_set_thumbnail_legacy().volume_id; + case Type::StickerSetThumbnailVersion: + return sticker_set_thumbnail_version().sticker_set_id; default: UNREACHABLE(); - break; + return 0; } - auto size = storer.get_buf() - data.ubegin(); - CHECK(size <= 13); - return string(data.begin(), size); +} + +int32 PhotoSizeSource::get_compare_local_id() const { + switch (get_type("get_compare_volume_id")) { + case Type::FullLegacy: + return full_legacy().local_id; + case Type::DialogPhotoSmallLegacy: + case Type::DialogPhotoBigLegacy: + return dialog_photo_legacy().local_id; + case Type::StickerSetThumbnailLegacy: + return sticker_set_thumbnail_legacy().local_id; + case Type::StickerSetThumbnailVersion: + return sticker_set_thumbnail_version().version; + default: + UNREACHABLE(); + return 0; + } +} + +bool PhotoSizeSource::unique_less(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) { + auto lhs_compare_type = lhs.get_compare_type("unique_less"); + auto rhs_compare_type = rhs.get_compare_type("unique_less"); + if (lhs_compare_type != rhs_compare_type) { + return lhs_compare_type < rhs_compare_type; + } + if (lhs_compare_type != 2 && lhs_compare_type != 3) { + return false; + } + auto lhs_volume_id = lhs.get_compare_volume_id(); + auto rhs_volume_id = rhs.get_compare_volume_id(); + if (lhs_volume_id != rhs_volume_id) { + return lhs_volume_id < rhs_volume_id; + } + return lhs.get_compare_local_id() < rhs.get_compare_local_id(); +} + +bool PhotoSizeSource::unique_equal(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) { + auto lhs_compare_type = lhs.get_compare_type("unique_equal"); + auto rhs_compare_type = rhs.get_compare_type("unique_equal"); + if (lhs_compare_type != rhs_compare_type) { + return false; + } + if (lhs_compare_type != 2 && lhs_compare_type != 3) { + return true; + } + return lhs.get_compare_volume_id() == rhs.get_compare_volume_id() && + lhs.get_compare_local_id() == rhs.get_compare_local_id(); } string PhotoSizeSource::get_unique_name(int64 photo_id, const char *source) const { @@ -233,18 +287,18 @@ StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSizeSource & return string_builder << "PhotoSizeSourceThumbnail[" << source.thumbnail().file_type << ", type = " << source.thumbnail().thumbnail_type << ']'; case PhotoSizeSource::Type::DialogPhotoSmall: - return string_builder << "PhotoSizeSourceChatPhotoSmall[" << source.dialog_photo().dialog_id << ']'; + return string_builder << "PhotoSizeSourceChatPhotoSmall[]"; case PhotoSizeSource::Type::DialogPhotoBig: - return string_builder << "PhotoSizeSourceChatPhotoBig[" << source.dialog_photo().dialog_id << ']'; + return string_builder << "PhotoSizeSourceChatPhotoBig[]"; case PhotoSizeSource::Type::StickerSetThumbnail: return string_builder << "PhotoSizeSourceStickerSetThumbnail[" << source.sticker_set_thumbnail().sticker_set_id << ']'; case PhotoSizeSource::Type::FullLegacy: return string_builder << "PhotoSizeSourceFullLegacy[]"; case PhotoSizeSource::Type::DialogPhotoSmallLegacy: - return string_builder << "PhotoSizeSourceChatPhotoSmallLegacy[" << source.dialog_photo().dialog_id << ']'; + return string_builder << "PhotoSizeSourceChatPhotoSmallLegacy[]"; case PhotoSizeSource::Type::DialogPhotoBigLegacy: - return string_builder << "PhotoSizeSourceChatPhotoBigLegacy[" << source.dialog_photo().dialog_id << ']'; + return string_builder << "PhotoSizeSourceChatPhotoBigLegacy[]"; case PhotoSizeSource::Type::StickerSetThumbnailLegacy: return string_builder << "PhotoSizeSourceStickerSetThumbnailLegacy[" << source.sticker_set_thumbnail().sticker_set_id << ']'; diff --git a/td/telegram/PhotoSizeSource.h b/td/telegram/PhotoSizeSource.h index d3dd2d227a18..af9ea5855193 100644 --- a/td/telegram/PhotoSizeSource.h +++ b/td/telegram/PhotoSizeSource.h @@ -152,10 +152,6 @@ struct PhotoSizeSource { } } - static PhotoSizeSource sticker_set_thumbnail(int64 sticker_set_id, int64 sticker_set_access_hash) { - return PhotoSizeSource(StickerSetThumbnail(sticker_set_id, sticker_set_access_hash)); - } - static PhotoSizeSource full_legacy(int64 volume_id, int32 local_id, int64 secret) { return PhotoSizeSource(FullLegacy(volume_id, local_id, secret)); } @@ -178,6 +174,10 @@ struct PhotoSizeSource { return PhotoSizeSource(StickerSetThumbnailVersion(sticker_set_id, sticker_set_access_hash, version)); } + static bool unique_less(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs); + + static bool unique_equal(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs); + Type get_type(const char *source) const { auto offset = variant_.get_offset(); LOG_CHECK(offset >= 0) << offset << ' ' << source; @@ -262,6 +262,12 @@ struct PhotoSizeSource { template explicit PhotoSizeSource(const T &variant) : variant_(variant) { } + + int32 get_compare_type(const char *source) const; + + int64 get_compare_volume_id() const; + + int32 get_compare_local_id() const; }; bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs); diff --git a/td/telegram/PhotoSizeSource.hpp b/td/telegram/PhotoSizeSource.hpp index 51563d191209..9d2ca6d6a12c 100644 --- a/td/telegram/PhotoSizeSource.hpp +++ b/td/telegram/PhotoSizeSource.hpp @@ -7,6 +7,7 @@ #pragma once #include "td/telegram/PhotoSizeSource.h" +#include "td/telegram/Version.h" #include "td/utils/SliceBuilder.h" #include "td/utils/tl_helpers.h" @@ -186,6 +187,17 @@ void PhotoSizeSource::store(StorerT &storer) const { template void PhotoSizeSource::parse(ParserT &parser) { td::parse(variant_, parser); + if (parser.get_error() == nullptr && parser.version() >= static_cast(Version::RemovePhotoVolumeAndLocalId)) { + switch (get_type("PhotoSizeSource::parse")) { + case Type::Legacy: + case Type::StickerSetThumbnail: + parser.set_error("Invalid photo size source stored"); + break; + default: + // ok + break; + } + } } } // namespace td diff --git a/td/telegram/PollManager.cpp b/td/telegram/PollManager.cpp index a2b170de6687..cf3fc3836051 100644 --- a/td/telegram/PollManager.cpp +++ b/td/telegram/PollManager.cpp @@ -15,6 +15,7 @@ #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/PollId.hpp" @@ -22,7 +23,7 @@ #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/db/binlog/BinlogEvent.h" @@ -380,8 +381,17 @@ void PollManager::on_load_poll_from_database(PollId poll_id, string value) { if (log_event_parse(*poll, value).is_error()) { return; } - for (auto &user_id : poll->recent_voter_user_ids_) { - td_->contacts_manager_->have_user_force(user_id); + for (const auto &recent_voter_min_channel : poll->recent_voter_min_channels_) { + LOG(INFO) << "Add min voted " << recent_voter_min_channel.first; + td_->contacts_manager_->add_min_channel(recent_voter_min_channel.first, recent_voter_min_channel.second); + } + Dependencies dependencies; + for (auto dialog_id : poll->recent_voter_dialog_ids_) { + dependencies.add_message_sender_dependencies(dialog_id); + } + if (!dependencies.resolve_force(td_, "on_load_poll_from_database")) { + poll->recent_voter_dialog_ids_.clear(); + poll->recent_voter_min_channels_.clear(); } if (!poll->is_closed_ && poll->close_date_ != 0) { if (poll->close_date_ <= G()->server_time()) { @@ -537,10 +547,11 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co vector> poll_options; auto it = pending_answers_.find(poll_id); int32 voter_count_diff = 0; - if (it == pending_answers_.end()) { + if (it == pending_answers_.end() || (it->second.is_finished_ && poll->was_saved_)) { poll_options = transform(poll->options_, get_poll_option_object); } else { - auto &chosen_options = it->second.options_; + const auto &chosen_options = it->second.options_; + LOG(INFO) << "Have pending chosen options " << chosen_options << " in " << poll_id; for (auto &poll_option : poll->options_) { auto is_being_chosen = td::contains(chosen_options, poll_option.data_); if (poll_option.is_chosen_) { @@ -613,10 +624,17 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co open_period = 0; close_date = 0; } - return td_api::make_object( - poll_id.get(), poll->question_, std::move(poll_options), total_voter_count, - td_->contacts_manager_->get_user_ids_object(poll->recent_voter_user_ids_, "get_poll_object"), poll->is_anonymous_, - std::move(poll_type), open_period, close_date, poll->is_closed_); + + vector> recent_voters; + for (auto dialog_id : poll->recent_voter_dialog_ids_) { + auto recent_voter = get_min_message_sender_object(td_, dialog_id, "get_poll_object"); + if (recent_voter != nullptr) { + recent_voters.push_back(std::move(recent_voter)); + } + } + return td_api::make_object(poll_id.get(), poll->question_, std::move(poll_options), total_voter_count, + std::move(recent_voters), poll->is_anonymous_, std::move(poll_type), + open_period, close_date, poll->is_closed_); } telegram_api::object_ptr PollManager::get_input_poll_option(const PollOption &poll_option) { @@ -898,11 +916,12 @@ void PollManager::do_set_poll_answer(PollId poll_id, FullMessageId full_message_ pending_answer.promises_.push_back(std::move(promise)); pending_answer.generation_ = generation; pending_answer.log_event_id_ = log_event_id; + pending_answer.is_finished_ = false; notify_on_poll_update(poll_id); auto query_promise = PromiseCreator::lambda( - [poll_id, generation, actor_id = actor_id(this)](Result> &&result) { + [actor_id = actor_id(this), poll_id, generation](Result> &&result) { send_closure(actor_id, &PollManager::on_set_poll_answer, poll_id, generation, std::move(result)); }); td_->create_handler(std::move(query_promise)) @@ -930,10 +949,10 @@ void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation, if (pending_answer.log_event_id_ != 0) { LOG(INFO) << "Delete set poll answer log event " << pending_answer.log_event_id_; binlog_erase(G()->td_db()->get_binlog(), pending_answer.log_event_id_); + pending_answer.log_event_id_ = 0; } - auto promises = std::move(pending_answer.promises_); - pending_answers_.erase(it); + pending_answer.is_finished_ = true; auto poll = get_poll(poll_id); if (poll != nullptr) { @@ -941,16 +960,31 @@ void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation, } if (result.is_ok()) { td_->updates_manager_->on_get_updates( - result.move_as_ok(), PromiseCreator::lambda([actor_id = actor_id(this), poll_id, - promises = std::move(promises)](Result &&result) mutable { - send_closure(actor_id, &PollManager::on_set_poll_answer_finished, poll_id, Unit(), std::move(promises)); + result.move_as_ok(), + PromiseCreator::lambda([actor_id = actor_id(this), poll_id, generation](Result &&result) mutable { + send_closure(actor_id, &PollManager::on_set_poll_answer_finished, poll_id, Unit(), generation); })); } else { - on_set_poll_answer_finished(poll_id, result.move_as_error(), std::move(promises)); + on_set_poll_answer_finished(poll_id, result.move_as_error(), generation); } } -void PollManager::on_set_poll_answer_finished(PollId poll_id, Result &&result, vector> &&promises) { +void PollManager::on_set_poll_answer_finished(PollId poll_id, Result &&result, uint64 generation) { + auto it = pending_answers_.find(poll_id); + if (it == pending_answers_.end()) { + return; + } + + auto &pending_answer = it->second; + CHECK(!pending_answer.promises_.empty()); + if (pending_answer.generation_ != generation) { + return; + } + CHECK(pending_answer.is_finished_); + + auto promises = std::move(pending_answer.promises_); + pending_answers_.erase(it); + if (!G()->close_flag()) { auto poll = get_poll(poll_id); if (poll != nullptr && !poll->was_saved_) { @@ -966,6 +1000,8 @@ void PollManager::on_set_poll_answer_finished(PollId poll_id, Result &&res } } + LOG(INFO) << "Finish to set answer for " << poll_id; + if (result.is_ok()) { set_promises(promises); } else { @@ -1014,8 +1050,21 @@ PollManager::PollOptionVoters &PollManager::get_poll_option_voters(const Poll *p return poll_voters[index]; } +td_api::object_ptr PollManager::get_poll_voters_object( + int32 total_count, const vector &voter_dialog_ids) const { + auto result = td_api::make_object(); + result->total_count_ = total_count; + for (auto dialog_id : voter_dialog_ids) { + auto message_sender = get_min_message_sender_object(td_, dialog_id, "get_poll_voters_object"); + if (message_sender != nullptr) { + result->senders_.push_back(std::move(message_sender)); + } + } + return result; +} + void PollManager::get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset, - int32 limit, Promise>> &&promise) { + int32 limit, Promise> &&promise) { if (is_local_poll_id(poll_id)) { return promise.set_error(Status::Error(400, "Poll results can't be received")); } @@ -1040,26 +1089,27 @@ void PollManager::get_poll_voters(PollId poll_id, FullMessageId full_message_id, auto &voters = get_poll_option_voters(poll, poll_id, option_id); if (voters.pending_queries_.empty() && voters.was_invalidated_ && offset == 0) { - voters.voter_user_ids_.clear(); + voters.voter_dialog_ids_.clear(); voters.next_offset_.clear(); voters.was_invalidated_ = false; } - auto cur_offset = narrow_cast(voters.voter_user_ids_.size()); + auto cur_offset = narrow_cast(voters.voter_dialog_ids_.size()); if (offset > cur_offset) { return promise.set_error(Status::Error(400, "Too big offset specified; voters can be received only consequently")); } if (offset < cur_offset) { - vector result; + vector result; for (int32 i = offset; i != cur_offset && i - offset < limit; i++) { - result.push_back(voters.voter_user_ids_[i]); + result.push_back(voters.voter_dialog_ids_[i]); } - return promise.set_value({max(poll->options_[option_id].voter_count_, cur_offset), std::move(result)}); + return promise.set_value( + get_poll_voters_object(max(poll->options_[option_id].voter_count_, cur_offset), std::move(result))); } if (poll->options_[option_id].voter_count_ == 0 || (voters.next_offset_.empty() && cur_offset > 0)) { - return promise.set_value({0, vector()}); + return promise.set_value(get_poll_voters_object(0, vector())); } voters.pending_queries_.push_back(std::move(promise)); @@ -1114,6 +1164,7 @@ void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id, string off auto vote_list = result.move_as_ok(); td_->contacts_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters"); + td_->contacts_manager_->on_get_chats(std::move(vote_list->chats_), "on_get_poll_voters"); voters.next_offset_ = std::move(vote_list->next_offset_); if (poll->options_[option_id].voter_count_ != vote_list->count_) { @@ -1121,54 +1172,54 @@ void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id, string off update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0); } - vector user_ids; - for (auto &user_vote : vote_list->votes_) { - UserId user_id; - switch (user_vote->get_id()) { - case telegram_api::messageUserVote::ID: { - auto voter = telegram_api::move_object_as(user_vote); + vector dialog_ids; + for (auto &peer_vote : vote_list->votes_) { + DialogId dialog_id; + switch (peer_vote->get_id()) { + case telegram_api::messagePeerVote::ID: { + auto voter = telegram_api::move_object_as(peer_vote); if (voter->option_ != poll->options_[option_id].data_) { continue; } - user_id = UserId(voter->user_id_); + dialog_id = DialogId(voter->peer_); break; } - case telegram_api::messageUserVoteInputOption::ID: { - auto voter = telegram_api::move_object_as(user_vote); - user_id = UserId(voter->user_id_); + case telegram_api::messagePeerVoteInputOption::ID: { + auto voter = telegram_api::move_object_as(peer_vote); + dialog_id = DialogId(voter->peer_); break; } - case telegram_api::messageUserVoteMultiple::ID: { - auto voter = telegram_api::move_object_as(user_vote); + case telegram_api::messagePeerVoteMultiple::ID: { + auto voter = telegram_api::move_object_as(peer_vote); if (!td::contains(voter->options_, poll->options_[option_id].data_)) { continue; } - user_id = UserId(voter->user_id_); + dialog_id = DialogId(voter->peer_); break; } default: UNREACHABLE(); } - if (user_id.is_valid()) { - user_ids.push_back(user_id); + if (dialog_id.is_valid()) { + dialog_ids.push_back(dialog_id); } else { - LOG(ERROR) << "Receive " << user_id << " as voter in " << poll_id; + LOG(ERROR) << "Receive " << dialog_id << " as voter in " << poll_id; } } - append(voters.voter_user_ids_, user_ids); - if (static_cast(user_ids.size()) > limit) { - user_ids.resize(limit); + append(voters.voter_dialog_ids_, dialog_ids); + if (static_cast(dialog_ids.size()) > limit) { + dialog_ids.resize(limit); } - auto known_voter_count = narrow_cast(voters.voter_user_ids_.size()); + auto known_voter_count = narrow_cast(voters.voter_dialog_ids_.size()); if (voters.next_offset_.empty() && known_voter_count != vote_list->count_) { // invalidate_poll_option_voters(poll, poll_id, option_id); voters.was_invalidated_ = true; } for (auto &promise : promises) { - promise.set_value({max(vote_list->count_, known_voter_count), vector(user_ids)}); + promise.set_value(get_poll_voters_object(max(vote_list->count_, known_voter_count), vector(dialog_ids))); } } @@ -1190,8 +1241,8 @@ void PollManager::stop_poll(PollId poll_id, FullMessageId full_message_id, uniqu ++current_generation_; poll->is_closed_ = true; - notify_on_poll_update(poll_id); save_poll(poll, poll_id); + notify_on_poll_update(poll_id); do_stop_poll(poll_id, full_message_id, std::move(reply_markup), 0, std::move(promise)); } @@ -1295,7 +1346,7 @@ void PollManager::on_update_poll_timeout(PollId poll_id) { auto full_message_id = server_poll_messages_[poll_id].get_random(); LOG(INFO) << "Fetching results of " << poll_id << " from " << full_message_id; - auto query_promise = PromiseCreator::lambda([poll_id, generation = current_generation_, actor_id = actor_id(this)]( + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), poll_id, generation = current_generation_]( Result> &&result) { send_closure(actor_id, &PollManager::on_get_poll_results, poll_id, generation, std::move(result)); }); @@ -1316,8 +1367,8 @@ void PollManager::on_close_poll_timeout(PollId poll_id) { LOG(INFO) << "Trying to close " << poll_id << " by timer"; if (poll->close_date_ <= G()->server_time()) { poll->is_closed_ = true; - notify_on_poll_update(poll_id); save_poll(poll, poll_id); + notify_on_poll_update(poll_id); // don't send updatePoll for bots, because there is no way to guarantee it @@ -1541,7 +1592,11 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr(); poll = p.get(); polls_.set(poll_id, std::move(p)); + } else if (poll_results != nullptr && poll_results->min_ && pending_answers_.count(poll_id) != 0) { + LOG(INFO) << "Ignore being answered min-" << poll_id; + return poll_id; } + CHECK(poll != nullptr); bool poll_server_is_closed = false; @@ -1698,7 +1753,8 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptrvoters_ = 0; } if (option.is_chosen_ && poll_result->voters_ == 0) { - LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id << " from " << source; + LOG(ERROR) << "Receive 0 voters for the chosen option " << option_index << " in " << poll_id << " from " + << source; poll_result->voters_ = 1; } if (poll_result->voters_ > poll->total_voter_count_) { @@ -1769,24 +1825,24 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr recent_voter_user_ids; + vector recent_voter_dialog_ids; if (!is_bot) { - for (auto &user_id_int : poll_results->recent_voters_) { - UserId user_id(user_id_int); - if (user_id.is_valid()) { - recent_voter_user_ids.push_back(user_id); + for (auto &peer_id : poll_results->recent_voters_) { + DialogId dialog_id(peer_id); + if (dialog_id.is_valid()) { + recent_voter_dialog_ids.push_back(dialog_id); } else { - LOG(ERROR) << "Receive " << user_id << " as recent voter in " << poll_id << " from " << source; + LOG(ERROR) << "Receive " << dialog_id << " as recent voter in " << poll_id << " from " << source; } } } - if (poll->is_anonymous_ && !recent_voter_user_ids.empty()) { - LOG(ERROR) << "Receive anonymous " << poll_id << " with recent voters " << recent_voter_user_ids << " from " + if (poll->is_anonymous_ && !recent_voter_dialog_ids.empty()) { + LOG(ERROR) << "Receive anonymous " << poll_id << " with recent voters " << recent_voter_dialog_ids << " from " << source; - recent_voter_user_ids.clear(); + recent_voter_dialog_ids.clear(); } - if (recent_voter_user_ids != poll->recent_voter_user_ids_) { - poll->recent_voter_user_ids_ = std::move(recent_voter_user_ids); + if (recent_voter_dialog_ids != poll->recent_voter_dialog_ids_) { + poll->recent_voter_dialog_ids_ = std::move(recent_voter_dialog_ids); invalidate_poll_voters(poll, poll_id); is_changed = true; } @@ -1796,12 +1852,12 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptris_closed_ && being_closed_polls_.erase(poll_id) != 0))) { send_closure(G()->td(), &Td::send_update, td_api::make_object(get_poll_object(poll_id, poll))); @@ -1810,13 +1866,13 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr &&options) { +void PollManager::on_get_poll_vote(PollId poll_id, DialogId dialog_id, vector &&options) { if (!poll_id.is_valid()) { LOG(ERROR) << "Receive updateMessagePollVote about invalid " << poll_id; return; } - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive updateMessagePollVote from invalid " << user_id; + if (!dialog_id.is_valid()) { + LOG(ERROR) << "Receive updateMessagePollVote from invalid " << dialog_id; return; } if (!td_->auth_manager_->is_bot()) { @@ -1833,10 +1889,10 @@ void PollManager::on_get_poll_vote(PollId poll_id, UserId user_id, vector(slice[0] - '0')); } - send_closure(G()->td(), &Td::send_update, - td_api::make_object( - poll_id.get(), td_->contacts_manager_->get_user_id_object(user_id, "on_get_poll_vote"), - std::move(option_ids))); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + poll_id.get(), get_message_sender_object(td_, dialog_id, "on_get_poll_vote"), std::move(option_ids))); } void PollManager::on_binlog_events(vector &&events) { diff --git a/td/telegram/PollManager.h b/td/telegram/PollManager.h index 7d6a8bc9250f..60f70cc80a76 100644 --- a/td/telegram/PollManager.h +++ b/td/telegram/PollManager.h @@ -6,14 +6,16 @@ // #pragma once +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" #include "td/telegram/FullMessageId.h" #include "td/telegram/MessageEntity.h" +#include "td/telegram/MinChannel.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/PollId.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" -#include "td/telegram/UserId.h" #include "td/actor/actor.h" #include "td/actor/MultiTimeout.h" @@ -65,7 +67,7 @@ class PollManager final : public Actor { Promise &&promise); void get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit, - Promise>> &&promise); + Promise> &&promise); void stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr &&reply_markup, Promise &&promise); @@ -81,7 +83,7 @@ class PollManager final : public Actor { PollId on_get_poll(PollId poll_id, tl_object_ptr &&poll_server, tl_object_ptr &&poll_results, const char *source); - void on_get_poll_vote(PollId poll_id, UserId user_id, vector &&options); + void on_get_poll_vote(PollId poll_id, DialogId dialog_id, vector &&options); td_api::object_ptr get_poll_object(PollId poll_id) const; @@ -111,7 +113,8 @@ class PollManager final : public Actor { struct Poll { string question_; vector options_; - vector recent_voter_user_ids_; + vector recent_voter_dialog_ids_; + vector> recent_voter_min_channels_; FormattedText explanation_; int32 total_voter_count_ = 0; int32 correct_option_id_ = -1; @@ -131,9 +134,9 @@ class PollManager final : public Actor { }; struct PollOptionVoters { - vector voter_user_ids_; + vector voter_dialog_ids_; string next_offset_; - vector>>> pending_queries_; + vector>> pending_queries_; bool was_invalidated_ = false; // the list needs to be invalidated when voters are changed }; @@ -201,7 +204,7 @@ class PollManager final : public Actor { void on_set_poll_answer(PollId poll_id, uint64 generation, Result> &&result); - void on_set_poll_answer_finished(PollId poll_id, Result &&result, vector> &&promises); + void on_set_poll_answer_finished(PollId poll_id, Result &&result, uint64 generation); void invalidate_poll_voters(const Poll *poll, PollId poll_id); @@ -209,6 +212,9 @@ class PollManager final : public Actor { PollOptionVoters &get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id); + td_api::object_ptr get_poll_voters_object(int32 total_count, + const vector &voter_dialog_ids) const; + void on_get_poll_voters(PollId poll_id, int32 option_id, string offset, int32 limit, Result> &&result); @@ -224,8 +230,6 @@ class PollManager final : public Actor { MultiTimeout close_poll_timeout_{"ClosePollTimeout"}; MultiTimeout unload_poll_timeout_{"UnloadPollTimeout"}; - Td *td_; - ActorShared<> parent_; WaitFreeHashMap, PollIdHash> polls_; WaitFreeHashMap, PollIdHash> server_poll_messages_; @@ -237,6 +241,7 @@ class PollManager final : public Actor { uint64 generation_ = 0; uint64 log_event_id_ = 0; NetQueryRef query_ref_; + bool is_finished_ = false; }; FlatHashMap pending_answers_; @@ -249,6 +254,9 @@ class PollManager final : public Actor { FlatHashSet loaded_from_database_polls_; FlatHashSet being_closed_polls_; + + Td *td_; + ActorShared<> parent_; }; } // namespace td diff --git a/td/telegram/PollManager.hpp b/td/telegram/PollManager.hpp index 85cb4ae3cacc..f7bc1b6fc5d2 100644 --- a/td/telegram/PollManager.hpp +++ b/td/telegram/PollManager.hpp @@ -6,7 +6,9 @@ // #pragma once +#include "td/telegram/MinChannel.hpp" #include "td/telegram/PollManager.h" +#include "td/telegram/UserId.h" #include "td/telegram/Version.h" #include "td/utils/algorithm.h" @@ -43,20 +45,23 @@ template void PollManager::Poll::store(StorerT &storer) const { using ::td::store; bool is_public = !is_anonymous_; - bool has_recent_voters = !recent_voter_user_ids_.empty(); bool has_open_period = open_period_ != 0; bool has_close_date = close_date_ != 0; bool has_explanation = !explanation_.text.empty(); + bool has_recent_voter_dialog_ids = !recent_voter_dialog_ids_.empty(); + bool has_recent_voter_min_channels = !recent_voter_min_channels_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_closed_); STORE_FLAG(is_public); STORE_FLAG(allow_multiple_answers_); STORE_FLAG(is_quiz_); - STORE_FLAG(has_recent_voters); + STORE_FLAG(false); STORE_FLAG(has_open_period); STORE_FLAG(has_close_date); STORE_FLAG(has_explanation); STORE_FLAG(is_updated_after_close_); + STORE_FLAG(has_recent_voter_dialog_ids); + STORE_FLAG(has_recent_voter_min_channels); END_STORE_FLAGS(); store(question_, storer); @@ -65,9 +70,6 @@ void PollManager::Poll::store(StorerT &storer) const { if (is_quiz_) { store(correct_option_id_, storer); } - if (has_recent_voters) { - store(recent_voter_user_ids_, storer); - } if (has_open_period) { store(open_period_, storer); } @@ -77,26 +79,36 @@ void PollManager::Poll::store(StorerT &storer) const { if (has_explanation) { store(explanation_, storer); } + if (has_recent_voter_dialog_ids) { + store(recent_voter_dialog_ids_, storer); + } + if (has_recent_voter_min_channels) { + store(recent_voter_min_channels_, storer); + } } template void PollManager::Poll::parse(ParserT &parser) { using ::td::parse; bool is_public; - bool has_recent_voters; + bool has_recent_voter_user_ids; bool has_open_period; bool has_close_date; bool has_explanation; + bool has_recent_voter_dialog_ids; + bool has_recent_voter_min_channels; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_closed_); PARSE_FLAG(is_public); PARSE_FLAG(allow_multiple_answers_); PARSE_FLAG(is_quiz_); - PARSE_FLAG(has_recent_voters); + PARSE_FLAG(has_recent_voter_user_ids); PARSE_FLAG(has_open_period); PARSE_FLAG(has_close_date); PARSE_FLAG(has_explanation); PARSE_FLAG(is_updated_after_close_); + PARSE_FLAG(has_recent_voter_dialog_ids); + PARSE_FLAG(has_recent_voter_min_channels); END_PARSE_FLAGS(); is_anonymous_ = !is_public; @@ -109,8 +121,10 @@ void PollManager::Poll::parse(ParserT &parser) { parser.set_error("Wrong correct_option_id"); } } - if (has_recent_voters) { - parse(recent_voter_user_ids_, parser); + if (has_recent_voter_user_ids) { + vector recent_voter_user_ids; + parse(recent_voter_user_ids, parser); + recent_voter_dialog_ids_ = transform(recent_voter_user_ids, [](UserId user_id) { return DialogId(user_id); }); } if (has_open_period) { parse(open_period_, parser); @@ -121,6 +135,12 @@ void PollManager::Poll::parse(ParserT &parser) { if (has_explanation) { parse(explanation_, parser); } + if (has_recent_voter_dialog_ids) { + parse(recent_voter_dialog_ids_, parser); + } + if (has_recent_voter_min_channels) { + parse(recent_voter_min_channels_, parser); + } } template diff --git a/td/telegram/Premium.cpp b/td/telegram/Premium.cpp index ef67bcf82923..c8e92c78f6e3 100644 --- a/td/telegram/Premium.cpp +++ b/td/telegram/Premium.cpp @@ -78,6 +78,9 @@ static td_api::object_ptr get_premium_feature_object(Sli if (premium_feature == "translations") { return td_api::make_object(); } + if (premium_feature == "stories") { + return td_api::make_object(); + } return nullptr; } @@ -302,7 +305,11 @@ const vector &get_premium_limit_keys() { "caption_length", "about_length", "chatlist_invites", - "chatlists_joined"}; + "chatlists_joined", + "story_expiring", + "story_caption_length", + "stories_sent_weekly", + "stories_sent_monthly"}; return limit_keys; } @@ -333,6 +340,14 @@ static Slice get_limit_type_key(const td_api::PremiumLimitType *limit_type) { return Slice("chatlist_invites"); case td_api::premiumLimitTypeShareableChatFolderCount::ID: return Slice("chatlists_joined"); + case td_api::premiumLimitTypeActiveStoryCount::ID: + return Slice("story_expiring"); + case td_api::premiumLimitTypeStoryCaptionLength::ID: + return Slice("story_caption_length"); + case td_api::premiumLimitTypeWeeklySentStoryCount::ID: + return Slice("stories_sent_weekly"); + case td_api::premiumLimitTypeMonthlySentStoryCount::ID: + return Slice("stories_sent_monthly"); default: UNREACHABLE(); return Slice(); @@ -383,12 +398,38 @@ static string get_premium_source(const td_api::PremiumFeature *feature) { return "app_icons"; case td_api::premiumFeatureRealTimeChatTranslation::ID: return "translations"; + case td_api::premiumFeatureUpgradedStories::ID: + return "stories"; default: UNREACHABLE(); } return string(); } +static string get_premium_source(const td_api::PremiumStoryFeature *feature) { + if (feature == nullptr) { + return string(); + } + + switch (feature->get_id()) { + case td_api::premiumStoryFeaturePriorityOrder::ID: + return "stories__priority_order"; + case td_api::premiumStoryFeatureStealthMode::ID: + return "stories__stealth_mode"; + case td_api::premiumStoryFeaturePermanentViewsHistory::ID: + return "stories__permanent_views_history"; + case td_api::premiumStoryFeatureCustomExpirationDuration::ID: + return "stories__expiration_durations"; + case td_api::premiumStoryFeatureSaveStories::ID: + return "stories__save_stories_to_gallery"; + case td_api::premiumStoryFeatureLinksAndFormatting::ID: + return "stories__links_and_formatting"; + default: + UNREACHABLE(); + return string(); + } +} + static string get_premium_source(const td_api::object_ptr &source) { if (source == nullptr) { return string(); @@ -402,6 +443,10 @@ static string get_premium_source(const td_api::object_ptr auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); } + case td_api::premiumSourceStoryFeature::ID: { + auto *feature = static_cast(source.get())->feature_.get(); + return get_premium_source(feature); + } case td_api::premiumSourceLink::ID: { auto &referrer = static_cast(source.get())->referrer_; if (referrer.empty()) { @@ -460,6 +505,18 @@ static td_api::object_ptr get_premium_limit_object(Slice k if (key == "chatlists_joined") { return td_api::make_object(); } + if (key == "story_expiring") { + return td_api::make_object(); + } + if (key == "story_caption_length") { + return td_api::make_object(); + } + if (key == "stories_sent_weekly") { + return td_api::make_object(); + } + if (key == "stories_sent_monthly") { + return td_api::make_object(); + } UNREACHABLE(); return nullptr; }(); @@ -477,13 +534,12 @@ void get_premium_limit(const td_api::object_ptr &limit void get_premium_features(Td *td, const td_api::object_ptr &source, Promise> &&promise) { - auto premium_features = full_split( - G()->get_option_string( - "premium_features", - "double_limits,more_upload,faster_download,voice_to_text,no_ads,infinite_reactions,premium_stickers," - "animated_emoji,advanced_chat_management,profile_badge,emoji_status,animated_userpics,app_icons," - "translations"), - ','); + auto premium_features = + full_split(G()->get_option_string("premium_features", + "double_limits,stories,more_upload,faster_download,voice_to_text,no_ads," + "infinite_reactions,premium_stickers,animated_emoji,advanced_chat_management," + "profile_badge,emoji_status,animated_userpics,app_icons,translations"), + ','); vector> features; for (const auto &premium_feature : premium_features) { auto feature = get_premium_feature_object(premium_feature); @@ -557,6 +613,7 @@ void assign_app_store_transaction(Td *td, const string &receipt, if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise()); + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise()); } td->create_handler(std::move(promise))->send(receipt, std::move(purpose)); } @@ -568,6 +625,7 @@ void assign_play_market_transaction(Td *td, const string &package_name, const st if (purpose != nullptr && purpose->get_id() == td_api::storePaymentPurposePremiumSubscription::ID) { dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::UpgradePremium}, Promise()); dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SubscribeToAnnualPremium}, Promise()); + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::RestorePremium}, Promise()); } td->create_handler(std::move(promise)) ->send(package_name, store_product_id, purchase_token, std::move(purpose)); diff --git a/td/telegram/PrivacyManager.cpp b/td/telegram/PrivacyManager.cpp index 4556e6f19a98..d13354a53464 100644 --- a/td/telegram/PrivacyManager.cpp +++ b/td/telegram/PrivacyManager.cpp @@ -6,17 +6,16 @@ // #include "td/telegram/PrivacyManager.h" -#include "td/telegram/ChannelId.h" -#include "td/telegram/ChatId.h" #include "td/telegram/ContactsManager.h" -#include "td/telegram/DialogId.h" #include "td/telegram/Global.h" -#include "td/telegram/MessagesManager.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" #include "td/utils/algorithm.h" +#include "td/utils/buffer.h" #include "td/utils/logging.h" #include @@ -24,403 +23,67 @@ namespace td { -Result PrivacyManager::UserPrivacySetting::get_user_privacy_setting( - tl_object_ptr key) { - if (key == nullptr) { - return Status::Error(400, "UserPrivacySetting must be non-empty"); - } - return UserPrivacySetting(*key); -} - -PrivacyManager::UserPrivacySetting::UserPrivacySetting(const telegram_api::PrivacyKey &key) { - switch (key.get_id()) { - case telegram_api::privacyKeyStatusTimestamp::ID: - type_ = Type::UserStatus; - break; - case telegram_api::privacyKeyChatInvite::ID: - type_ = Type::ChatInvite; - break; - case telegram_api::privacyKeyPhoneCall::ID: - type_ = Type::Call; - break; - case telegram_api::privacyKeyPhoneP2P::ID: - type_ = Type::PeerToPeerCall; - break; - case telegram_api::privacyKeyForwards::ID: - type_ = Type::LinkInForwardedMessages; - break; - case telegram_api::privacyKeyProfilePhoto::ID: - type_ = Type::UserProfilePhoto; - break; - case telegram_api::privacyKeyPhoneNumber::ID: - type_ = Type::UserPhoneNumber; - break; - case telegram_api::privacyKeyAddedByPhone::ID: - type_ = Type::FindByPhoneNumber; - break; - case telegram_api::privacyKeyVoiceMessages::ID: - type_ = Type::VoiceMessages; - break; - default: - UNREACHABLE(); - type_ = Type::UserStatus; - } -} +class GetPrivacyQuery final : public Td::ResultHandler { + Promise promise_; -tl_object_ptr PrivacyManager::UserPrivacySetting::get_user_privacy_setting_object() const { - switch (type_) { - case Type::UserStatus: - return make_tl_object(); - case Type::ChatInvite: - return make_tl_object(); - case Type::Call: - return make_tl_object(); - case Type::PeerToPeerCall: - return make_tl_object(); - case Type::LinkInForwardedMessages: - return make_tl_object(); - case Type::UserProfilePhoto: - return make_tl_object(); - case Type::UserPhoneNumber: - return make_tl_object(); - case Type::FindByPhoneNumber: - return make_tl_object(); - case Type::VoiceMessages: - return make_tl_object(); - default: - UNREACHABLE(); - return nullptr; + public: + explicit GetPrivacyQuery(Promise &&promise) : promise_(std::move(promise)) { } -} -tl_object_ptr PrivacyManager::UserPrivacySetting::get_input_privacy_key() const { - switch (type_) { - case Type::UserStatus: - return make_tl_object(); - case Type::ChatInvite: - return make_tl_object(); - case Type::Call: - return make_tl_object(); - case Type::PeerToPeerCall: - return make_tl_object(); - case Type::LinkInForwardedMessages: - return make_tl_object(); - case Type::UserProfilePhoto: - return make_tl_object(); - case Type::UserPhoneNumber: - return make_tl_object(); - case Type::FindByPhoneNumber: - return make_tl_object(); - case Type::VoiceMessages: - return make_tl_object(); - default: - UNREACHABLE(); - return nullptr; - } -} -PrivacyManager::UserPrivacySetting::UserPrivacySetting(const td_api::UserPrivacySetting &key) { - switch (key.get_id()) { - case td_api::userPrivacySettingShowStatus::ID: - type_ = Type::UserStatus; - break; - case td_api::userPrivacySettingAllowChatInvites::ID: - type_ = Type::ChatInvite; - break; - case td_api::userPrivacySettingAllowCalls::ID: - type_ = Type::Call; - break; - case td_api::userPrivacySettingAllowPeerToPeerCalls::ID: - type_ = Type::PeerToPeerCall; - break; - case td_api::userPrivacySettingShowLinkInForwardedMessages::ID: - type_ = Type::LinkInForwardedMessages; - break; - case td_api::userPrivacySettingShowProfilePhoto::ID: - type_ = Type::UserProfilePhoto; - break; - case td_api::userPrivacySettingShowPhoneNumber::ID: - type_ = Type::UserPhoneNumber; - break; - case td_api::userPrivacySettingAllowFindingByPhoneNumber::ID: - type_ = Type::FindByPhoneNumber; - break; - case td_api::userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages::ID: - type_ = Type::VoiceMessages; - break; - default: - UNREACHABLE(); - type_ = Type::UserStatus; + void send(UserPrivacySetting user_privacy_setting) { + send_query(G()->net_query_creator().create( + telegram_api::account_getPrivacy(user_privacy_setting.get_input_privacy_key()))); } -} -void PrivacyManager::UserPrivacySettingRule::set_chat_ids(const vector &dialog_ids) { - chat_ids_.clear(); - auto td = G()->td().get_actor_unsafe(); - for (auto dialog_id_int : dialog_ids) { - DialogId dialog_id(dialog_id_int); - if (!td->messages_manager_->have_dialog_force(dialog_id, "UserPrivacySettingRule::set_chat_ids")) { - LOG(ERROR) << "Ignore not found " << dialog_id; - continue; + 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()); } - switch (dialog_id.get_type()) { - case DialogType::Chat: - chat_ids_.push_back(dialog_id.get_chat_id().get()); - break; - case DialogType::Channel: { - auto channel_id = dialog_id.get_channel_id(); - if (!td->contacts_manager_->is_megagroup_channel(channel_id)) { - LOG(ERROR) << "Ignore broadcast " << channel_id; - break; - } - chat_ids_.push_back(channel_id.get()); - break; - } - default: - LOG(ERROR) << "Ignore " << dialog_id; - } + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetPrivacyQuery: " << to_string(ptr); + promise_.set_value(UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(ptr))); } -} -PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const td_api::UserPrivacySettingRule &rule) { - switch (rule.get_id()) { - case td_api::userPrivacySettingRuleAllowContacts::ID: - type_ = Type::AllowContacts; - break; - case td_api::userPrivacySettingRuleAllowAll::ID: - type_ = Type::AllowAll; - break; - case td_api::userPrivacySettingRuleAllowUsers::ID: - type_ = Type::AllowUsers; - user_ids_ = UserId::get_user_ids(static_cast(rule).user_ids_); - break; - case td_api::userPrivacySettingRuleAllowChatMembers::ID: - type_ = Type::AllowChatParticipants; - set_chat_ids(static_cast(rule).chat_ids_); - break; - case td_api::userPrivacySettingRuleRestrictContacts::ID: - type_ = Type::RestrictContacts; - break; - case td_api::userPrivacySettingRuleRestrictAll::ID: - type_ = Type::RestrictAll; - break; - case td_api::userPrivacySettingRuleRestrictUsers::ID: - type_ = Type::RestrictUsers; - user_ids_ = - UserId::get_user_ids(static_cast(rule).user_ids_); - break; - case td_api::userPrivacySettingRuleRestrictChatMembers::ID: - type_ = Type::RestrictChatParticipants; - set_chat_ids(static_cast(rule).chat_ids_); - break; - default: - UNREACHABLE(); + void on_error(Status status) final { + promise_.set_error(std::move(status)); } -} +}; -PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const telegram_api::PrivacyRule &rule) { - switch (rule.get_id()) { - case telegram_api::privacyValueAllowContacts::ID: - type_ = Type::AllowContacts; - break; - case telegram_api::privacyValueAllowAll::ID: - type_ = Type::AllowAll; - break; - case telegram_api::privacyValueAllowUsers::ID: - type_ = Type::AllowUsers; - user_ids_ = UserId::get_user_ids(static_cast(rule).users_); - break; - case telegram_api::privacyValueAllowChatParticipants::ID: - type_ = Type::AllowChatParticipants; - chat_ids_ = static_cast(rule).chats_; - break; - case telegram_api::privacyValueDisallowContacts::ID: - type_ = Type::RestrictContacts; - break; - case telegram_api::privacyValueDisallowAll::ID: - type_ = Type::RestrictAll; - break; - case telegram_api::privacyValueDisallowUsers::ID: - type_ = Type::RestrictUsers; - user_ids_ = UserId::get_user_ids(static_cast(rule).users_); - break; - case telegram_api::privacyValueDisallowChatParticipants::ID: - type_ = Type::RestrictChatParticipants; - chat_ids_ = static_cast(rule).chats_; - break; - default: - UNREACHABLE(); - } -} +class SetPrivacyQuery final : public Td::ResultHandler { + Promise promise_; -tl_object_ptr -PrivacyManager::UserPrivacySettingRule::get_user_privacy_setting_rule_object() const { - switch (type_) { - case Type::AllowContacts: - return make_tl_object(); - case Type::AllowAll: - return make_tl_object(); - case Type::AllowUsers: - return make_tl_object(UserId::get_input_user_ids(user_ids_)); - case Type::AllowChatParticipants: - return make_tl_object(chat_ids_as_dialog_ids()); - case Type::RestrictContacts: - return make_tl_object(); - case Type::RestrictAll: - return make_tl_object(); - case Type::RestrictUsers: - return make_tl_object(UserId::get_input_user_ids(user_ids_)); - case Type::RestrictChatParticipants: - return make_tl_object(chat_ids_as_dialog_ids()); - default: - UNREACHABLE(); + public: + explicit SetPrivacyQuery(Promise &&promise) : promise_(std::move(promise)) { } -} -tl_object_ptr PrivacyManager::UserPrivacySettingRule::get_input_privacy_rule() const { - switch (type_) { - case Type::AllowContacts: - return make_tl_object(); - case Type::AllowAll: - return make_tl_object(); - case Type::AllowUsers: - return make_tl_object(get_input_users()); - case Type::AllowChatParticipants: - return make_tl_object(vector{chat_ids_}); - case Type::RestrictContacts: - return make_tl_object(); - case Type::RestrictAll: - return make_tl_object(); - case Type::RestrictUsers: - return make_tl_object(get_input_users()); - case Type::RestrictChatParticipants: - return make_tl_object(vector{chat_ids_}); - default: - UNREACHABLE(); + void send(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules) { + send_query(G()->net_query_creator().create(telegram_api::account_setPrivacy( + user_privacy_setting.get_input_privacy_key(), privacy_rules.get_input_privacy_rules(td_)))); } -} -Result PrivacyManager::UserPrivacySettingRule::get_user_privacy_setting_rule( - tl_object_ptr rule) { - CHECK(rule != nullptr); - UserPrivacySettingRule result(*rule); - auto td = G()->td().get_actor_unsafe(); - for (auto user_id : result.user_ids_) { - if (!td->contacts_manager_->have_user(user_id)) { - return Status::Error(500, "Receive inaccessible user from the server"); + 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()); } - } - for (auto chat_id_int : result.chat_ids_) { - ChatId chat_id(chat_id_int); - DialogId dialog_id(chat_id); - if (!td->contacts_manager_->have_chat(chat_id)) { - ChannelId channel_id(chat_id_int); - dialog_id = DialogId(channel_id); - if (!td->contacts_manager_->have_channel(channel_id)) { - return Status::Error(500, "Receive inaccessible chat from the server"); - } - } - td->messages_manager_->force_create_dialog(dialog_id, "UserPrivacySettingRule"); - } - return result; -} - -vector> PrivacyManager::UserPrivacySettingRule::get_input_users() const { - vector> result; - for (auto user_id : user_ids_) { - auto r_input_user = G()->td().get_actor_unsafe()->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_ok()) { - result.push_back(r_input_user.move_as_ok()); - } else { - LOG(ERROR) << "Have no access to " << user_id; - } - } - return result; -} - -vector PrivacyManager::UserPrivacySettingRule::chat_ids_as_dialog_ids() const { - vector result; - auto td = G()->td().get_actor_unsafe(); - for (auto chat_id_int : chat_ids_) { - ChatId chat_id(chat_id_int); - DialogId dialog_id(chat_id); - if (!td->contacts_manager_->have_chat(chat_id)) { - ChannelId channel_id(chat_id_int); - dialog_id = DialogId(channel_id); - CHECK(td->contacts_manager_->have_channel(channel_id)); - } - CHECK(td->messages_manager_->have_dialog(dialog_id)); - result.push_back(td->messages_manager_->get_chat_id_object(dialog_id, "UserPrivacySettingRule")); - } - return result; -} - -vector PrivacyManager::UserPrivacySettingRule::get_restricted_user_ids() const { - if (type_ == Type::RestrictUsers) { - return user_ids_; - } - return {}; -} - -Result PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules( - tl_object_ptr rules) { - G()->td().get_actor_unsafe()->contacts_manager_->on_get_users(std::move(rules->users_), "on get privacy rules"); - G()->td().get_actor_unsafe()->contacts_manager_->on_get_chats(std::move(rules->chats_), "on get privacy rules"); - return get_user_privacy_setting_rules(std::move(rules->rules_)); -} -Result PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules( - vector> rules) { - UserPrivacySettingRules result; - for (auto &rule : rules) { - TRY_RESULT(new_rule, UserPrivacySettingRule::get_user_privacy_setting_rule(std::move(rule))); - result.rules_.push_back(new_rule); - } - if (!result.rules_.empty() && result.rules_.back().get_user_privacy_setting_rule_object()->get_id() == - td_api::userPrivacySettingRuleRestrictAll::ID) { - result.rules_.pop_back(); + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SetPrivacyQuery: " << to_string(ptr); + promise_.set_value(UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(ptr))); } - return result; -} -Result PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules( - tl_object_ptr rules) { - if (rules == nullptr) { - return Status::Error(400, "UserPrivacySettingRules must be non-empty"); - } - UserPrivacySettingRules result; - for (auto &rule : rules->rules_) { - if (rule == nullptr) { - return Status::Error(400, "UserPrivacySettingRule must be non-empty"); - } - result.rules_.emplace_back(*rule); + void on_error(Status status) final { + promise_.set_error(std::move(status)); } - return result; -} +}; -tl_object_ptr -PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules_object() const { - return make_tl_object( - transform(rules_, [](const auto &rule) { return rule.get_user_privacy_setting_rule_object(); })); +PrivacyManager::PrivacyManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { } -vector> PrivacyManager::UserPrivacySettingRules::get_input_privacy_rules() - const { - auto result = transform(rules_, [](const auto &rule) { return rule.get_input_privacy_rule(); }); - if (!result.empty() && result.back()->get_id() == telegram_api::inputPrivacyValueDisallowAll::ID) { - result.pop_back(); - } - return result; -} - -vector PrivacyManager::UserPrivacySettingRules::get_restricted_user_ids() const { - vector result; - for (auto &rule : rules_) { - combine(result, rule.get_restricted_user_ids()); - } - std::sort(result.begin(), result.end(), [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); }); - result.erase(std::unique(result.begin(), result.end()), result.end()); - return result; +void PrivacyManager::tear_down() { + parent_.reset(); } void PrivacyManager::get_privacy(tl_object_ptr key, @@ -431,81 +94,69 @@ void PrivacyManager::get_privacy(tl_object_ptr key, } auto user_privacy_setting = r_user_privacy_setting.move_as_ok(); auto &info = get_info(user_privacy_setting); - if (info.is_synchronized) { - return promise.set_value(info.rules.get_user_privacy_setting_rules_object()); + if (info.is_synchronized_) { + return promise.set_value(info.rules_.get_user_privacy_setting_rules_object(td_)); } - info.get_promises.push_back(std::move(promise)); - if (info.get_promises.size() > 1u) { + info.get_promises_.push_back(std::move(promise)); + if (info.get_promises_.size() > 1u) { // query has already been sent, just wait for the result return; } - auto net_query = - G()->net_query_creator().create(telegram_api::account_getPrivacy(user_privacy_setting.get_input_privacy_key())); - - send_with_promise(std::move(net_query), - PromiseCreator::lambda([this, user_privacy_setting](Result x_net_query) { - on_get_result(user_privacy_setting, [&]() -> Result { - TRY_RESULT(net_query, std::move(x_net_query)); - TRY_RESULT(rules, fetch_result(std::move(net_query))); - LOG(INFO) << "Receive " << to_string(rules); - return UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules)); - }()); - })); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), user_privacy_setting](Result r_privacy_rules) { + send_closure(actor_id, &PrivacyManager::on_get_user_privacy_settings, user_privacy_setting, + std::move(r_privacy_rules)); + }); + td_->create_handler(std::move(query_promise))->send(user_privacy_setting); } void PrivacyManager::set_privacy(tl_object_ptr key, tl_object_ptr rules, Promise promise) { TRY_RESULT_PROMISE(promise, user_privacy_setting, UserPrivacySetting::get_user_privacy_setting(std::move(key))); - TRY_RESULT_PROMISE(promise, privacy_rules, UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules))); + TRY_RESULT_PROMISE(promise, privacy_rules, + UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(rules))); auto &info = get_info(user_privacy_setting); - if (info.has_set_query) { - // TODO cancel previous query - return promise.set_error(Status::Error(400, "Another set_privacy query is active")); + if (info.has_set_query_) { + info.pending_rules_ = std::move(privacy_rules); + info.set_promises_.push_back(std::move(promise)); + return; } - auto net_query = G()->net_query_creator().create(telegram_api::account_setPrivacy( - user_privacy_setting.get_input_privacy_key(), privacy_rules.get_input_privacy_rules())); - - info.has_set_query = true; - send_with_promise( - std::move(net_query), PromiseCreator::lambda([this, user_privacy_setting, promise = std::move(promise)]( - Result x_net_query) mutable { - promise.set_result([&]() -> Result { - get_info(user_privacy_setting).has_set_query = false; - TRY_RESULT(net_query, std::move(x_net_query)); - TRY_RESULT(rules, fetch_result(std::move(net_query))); - LOG(INFO) << "Receive " << to_string(rules); - TRY_RESULT(privacy_rules, UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules))); - do_update_privacy(user_privacy_setting, std::move(privacy_rules), true); - return Unit(); - }()); - })); + info.has_set_query_ = true; + + set_privacy_impl(user_privacy_setting, std::move(privacy_rules), std::move(promise)); } -void PrivacyManager::update_privacy(tl_object_ptr update) { +void PrivacyManager::set_privacy_impl(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules, + Promise &&promise) { + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), user_privacy_setting, + promise = std::move(promise)](Result r_privacy_rules) mutable { + send_closure(actor_id, &PrivacyManager::on_set_user_privacy_settings, user_privacy_setting, + std::move(r_privacy_rules), std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(user_privacy_setting, std::move(privacy_rules)); +} + +void PrivacyManager::on_update_privacy(tl_object_ptr update) { CHECK(update != nullptr); CHECK(update->key_ != nullptr); UserPrivacySetting user_privacy_setting(*update->key_); - auto r_privacy_rules = UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(update->rules_)); - if (r_privacy_rules.is_error()) { - LOG(INFO) << "Skip updatePrivacy: " << r_privacy_rules.error().message(); - auto &info = get_info(user_privacy_setting); - info.is_synchronized = false; - } else { - do_update_privacy(user_privacy_setting, r_privacy_rules.move_as_ok(), true); - } + auto privacy_rules = UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(update->rules_)); + do_update_privacy(user_privacy_setting, std::move(privacy_rules), true); } -void PrivacyManager::on_get_result(UserPrivacySetting user_privacy_setting, - Result r_privacy_rules) { +void PrivacyManager::on_get_user_privacy_settings(UserPrivacySetting user_privacy_setting, + Result r_privacy_rules) { + G()->ignore_result_if_closing(r_privacy_rules); auto &info = get_info(user_privacy_setting); - auto promises = std::move(info.get_promises); - reset_to_empty(info.get_promises); + auto promises = std::move(info.get_promises_); + reset_to_empty(info.get_promises_); for (auto &promise : promises) { if (r_privacy_rules.is_error()) { promise.set_error(r_privacy_rules.error().clone()); } else { - promise.set_value(r_privacy_rules.ok().get_user_privacy_setting_rules_object()); + promise.set_value(r_privacy_rules.ok().get_user_privacy_setting_rules_object(td_)); } } if (r_privacy_rules.is_ok()) { @@ -513,19 +164,56 @@ void PrivacyManager::on_get_result(UserPrivacySetting user_privacy_setting, } } +void PrivacyManager::on_set_user_privacy_settings(UserPrivacySetting user_privacy_setting, + Result r_privacy_rules, + Promise &&promise) { + if (G()->close_flag()) { + auto &info = get_info(user_privacy_setting); + CHECK(info.has_set_query_); + info.has_set_query_ = false; + auto promises = std::move(info.set_promises_); + fail_promises(promises, Global::request_aborted_error()); + promise.set_error(Global::request_aborted_error()); + return; + } + + auto &info = get_info(user_privacy_setting); + CHECK(info.has_set_query_); + info.has_set_query_ = false; + if (r_privacy_rules.is_error()) { + promise.set_error(r_privacy_rules.move_as_error()); + } else { + do_update_privacy(user_privacy_setting, r_privacy_rules.move_as_ok(), true); + promise.set_value(Unit()); + } + if (!info.set_promises_.empty()) { + info.has_set_query_ = true; + auto join_promise = + PromiseCreator::lambda([promises = std::move(info.set_promises_)](Result &&result) mutable { + if (result.is_ok()) { + set_promises(promises); + } else { + fail_promises(promises, result.move_as_error()); + } + }); + reset_to_empty(info.set_promises_); + set_privacy_impl(user_privacy_setting, std::move(info.pending_rules_), std::move(join_promise)); + } +} + void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules, bool from_update) { auto &info = get_info(user_privacy_setting); - bool was_synchronized = info.is_synchronized; - info.is_synchronized = true; + bool was_synchronized = info.is_synchronized_; + info.is_synchronized_ = true; - if (!(info.rules == privacy_rules)) { + if (!(info.rules_ == privacy_rules)) { if ((from_update || was_synchronized) && !G()->close_flag()) { switch (user_privacy_setting.type()) { case UserPrivacySetting::Type::UserStatus: { send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_online_status_privacy); - auto old_restricted = info.rules.get_restricted_user_ids(); + auto old_restricted = info.rules_.get_restricted_user_ids(); auto new_restricted = privacy_rules.get_restricted_user_ids(); if (old_restricted != new_restricted) { // if a user was unrestricted, it is not received from the server anymore @@ -548,28 +236,12 @@ void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting, } } - info.rules = std::move(privacy_rules); + info.rules_ = std::move(privacy_rules); send_closure( G()->td(), &Td::send_update, make_tl_object(user_privacy_setting.get_user_privacy_setting_object(), - info.rules.get_user_privacy_setting_rules_object())); + info.rules_.get_user_privacy_setting_rules_object(td_))); } } -void PrivacyManager::on_result(NetQueryPtr query) { - auto token = get_link_token(); - container_.extract(token).set_value(std::move(query)); -} - -void PrivacyManager::send_with_promise(NetQueryPtr query, Promise promise) { - auto id = container_.create(std::move(promise)); - G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id)); -} - -void PrivacyManager::hangup() { - container_.for_each( - [](auto id, Promise &promise) { promise.set_error(Global::request_aborted_error()); }); - stop(); -} - } // namespace td diff --git a/td/telegram/PrivacyManager.h b/td/telegram/PrivacyManager.h index c0bda4646658..a57c39c94575 100644 --- a/td/telegram/PrivacyManager.h +++ b/td/telegram/PrivacyManager.h @@ -6,15 +6,14 @@ // #pragma once -#include "td/telegram/net/NetQuery.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" -#include "td/telegram/UserId.h" +#include "td/telegram/UserPrivacySetting.h" +#include "td/telegram/UserPrivacySettingRule.h" #include "td/actor/actor.h" #include "td/utils/common.h" -#include "td/utils/Container.h" #include "td/utils/Promise.h" #include "td/utils/Status.h" @@ -22,10 +21,11 @@ namespace td { -class PrivacyManager final : public NetQueryCallback { +class Td; + +class PrivacyManager final : public Actor { public: - explicit PrivacyManager(ActorShared<> parent) : parent_(std::move(parent)) { - } + PrivacyManager(Td *td, ActorShared<> parent); void get_privacy(tl_object_ptr key, Promise> promise); @@ -33,134 +33,40 @@ class PrivacyManager final : public NetQueryCallback { void set_privacy(tl_object_ptr key, tl_object_ptr rules, Promise promise); - void update_privacy(tl_object_ptr update); + void on_update_privacy(tl_object_ptr update); private: - class UserPrivacySetting { - public: - enum class Type : int32 { - UserStatus, - ChatInvite, - Call, - PeerToPeerCall, - LinkInForwardedMessages, - UserProfilePhoto, - UserPhoneNumber, - FindByPhoneNumber, - VoiceMessages, - Size - }; - - explicit UserPrivacySetting(const telegram_api::PrivacyKey &key); - - static Result get_user_privacy_setting(tl_object_ptr key); - - tl_object_ptr get_user_privacy_setting_object() const; - - tl_object_ptr get_input_privacy_key() const; - - Type type() const { - return type_; - } - - private: - Type type_; - - explicit UserPrivacySetting(const td_api::UserPrivacySetting &key); - }; - - class UserPrivacySettingRule { - public: - UserPrivacySettingRule() = default; - - explicit UserPrivacySettingRule(const td_api::UserPrivacySettingRule &rule); - - static Result get_user_privacy_setting_rule(tl_object_ptr rule); - - tl_object_ptr get_user_privacy_setting_rule_object() const; - - tl_object_ptr get_input_privacy_rule() const; - - bool operator==(const UserPrivacySettingRule &other) const { - return type_ == other.type_ && user_ids_ == other.user_ids_ && chat_ids_ == other.chat_ids_; - } - - vector get_restricted_user_ids() const; - - private: - enum class Type : int32 { - AllowContacts, - AllowAll, - AllowUsers, - AllowChatParticipants, - RestrictContacts, - RestrictAll, - RestrictUsers, - RestrictChatParticipants - } type_ = Type::RestrictAll; - - vector user_ids_; - vector chat_ids_; - - vector> get_input_users() const; - - void set_chat_ids(const vector &dialog_ids); - - vector chat_ids_as_dialog_ids() const; - - explicit UserPrivacySettingRule(const telegram_api::PrivacyRule &rule); - }; - - class UserPrivacySettingRules { - public: - UserPrivacySettingRules() = default; - - static Result get_user_privacy_setting_rules( - tl_object_ptr rules); - - static Result get_user_privacy_setting_rules( - vector> rules); - - static Result get_user_privacy_setting_rules( - tl_object_ptr rules); - - tl_object_ptr get_user_privacy_setting_rules_object() const; - - vector> get_input_privacy_rules() const; - - bool operator==(const UserPrivacySettingRules &other) const { - return rules_ == other.rules_; - } - - vector get_restricted_user_ids() const; - - private: - vector rules_; - }; - - ActorShared<> parent_; - struct PrivacyInfo { - UserPrivacySettingRules rules; - vector>> get_promises; - bool has_set_query = false; - bool is_synchronized = false; + UserPrivacySettingRules rules_; + UserPrivacySettingRules pending_rules_; + vector>> get_promises_; + vector> set_promises_; + bool has_set_query_ = false; + bool is_synchronized_ = false; }; - std::array(UserPrivacySetting::Type::Size)> info_; + + void tear_down() final; PrivacyInfo &get_info(UserPrivacySetting key) { return info_[static_cast(key.type())]; } - void on_get_result(UserPrivacySetting user_privacy_setting, Result r_privacy_rules); + void on_get_user_privacy_settings(UserPrivacySetting user_privacy_setting, + Result r_privacy_rules); + + void set_privacy_impl(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules, + Promise &&promise); + + void on_set_user_privacy_settings(UserPrivacySetting user_privacy_setting, + Result r_privacy_rules, Promise &&promise); + void do_update_privacy(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules, bool from_update); - void on_result(NetQueryPtr query) final; - Container> container_; - void send_with_promise(NetQueryPtr query, Promise promise); + std::array(UserPrivacySetting::Type::Size)> info_; - void hangup() final; + Td *td_; + ActorShared<> parent_; }; } // namespace td diff --git a/td/telegram/ReactionManager.cpp b/td/telegram/ReactionManager.cpp new file mode 100644 index 000000000000..2c9089948738 --- /dev/null +++ b/td/telegram/ReactionManager.cpp @@ -0,0 +1,732 @@ +// +// 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/ReactionManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/ConfigManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/ReactionManager.hpp" +#include "td/telegram/StickerFormat.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/logging.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/Status.h" + +#include + +namespace td { + +class GetAvailableReactionsQuery final : public Td::ResultHandler { + public: + void send(int32 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableReactions(hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetAvailableReactionsQuery: " << to_string(ptr); + td_->reaction_manager_->on_get_available_reactions(std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetAvailableReactionsQuery: " << status; + td_->reaction_manager_->on_get_available_reactions(nullptr); + } +}; + +class GetRecentReactionsQuery final : public Td::ResultHandler { + public: + void send(int32 limit, int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getRecentReactions(limit, hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetRecentReactionsQuery: " << to_string(ptr); + td_->reaction_manager_->on_get_recent_reactions(std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetRecentReactionsQuery: " << status; + td_->reaction_manager_->on_get_recent_reactions(nullptr); + } +}; + +class GetTopReactionsQuery final : public Td::ResultHandler { + public: + void send(int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getTopReactions(50, hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetTopReactionsQuery: " << to_string(ptr); + td_->reaction_manager_->on_get_top_reactions(std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetTopReactionsQuery: " << status; + td_->reaction_manager_->on_get_top_reactions(nullptr); + } +}; + +class ClearRecentReactionsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ClearRecentReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::messages_clearRecentReactions())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->reaction_manager_->reload_recent_reactions(); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (!G()->is_expected_error(status)) { + LOG(ERROR) << "Receive error for clear recent reactions: " << status; + } + td_->reaction_manager_->reload_recent_reactions(); + promise_.set_error(std::move(status)); + } +}; + +class SetDefaultReactionQuery final : public Td::ResultHandler { + ReactionType reaction_type_; + + public: + void send(const ReactionType &reaction_type) { + reaction_type_ = reaction_type; + send_query( + G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(reaction_type.get_input_reaction()))); + } + + 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()); + } + + if (!result_ptr.ok()) { + return on_error(Status::Error(400, "Receive false")); + } + + auto default_reaction = td_->option_manager_->get_option_string("default_reaction", "-"); + if (default_reaction != reaction_type_.get_string()) { + td_->reaction_manager_->send_set_default_reaction_query(); + } else { + td_->option_manager_->set_option_empty("default_reaction_needs_sync"); + } + } + + void on_error(Status status) final { + if (G()->close_flag()) { + return; + } + + LOG(INFO) << "Receive error for SetDefaultReactionQuery: " << status; + td_->option_manager_->set_option_empty("default_reaction_needs_sync"); + send_closure(G()->config_manager(), &ConfigManager::request_config, false); + } +}; + +ReactionManager::ReactionManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +ReactionManager::~ReactionManager() = default; + +void ReactionManager::start_up() { + init(); +} + +void ReactionManager::tear_down() { + parent_.reset(); +} + +void ReactionManager::init() { + if (G()->close_flag()) { + return; + } + if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + is_inited_ = true; + + td_->stickers_manager_->init(); + + load_active_reactions(); + + if (td_->option_manager_->get_option_boolean("default_reaction_needs_sync")) { + send_set_default_reaction_query(); + } +} + +td_api::object_ptr ReactionManager::get_emoji_reaction_object(const string &emoji) const { + for (auto &reaction : reactions_.reactions_) { + if (reaction.reaction_type_.get_string() == emoji) { + return td_api::make_object( + reaction.reaction_type_.get_string(), reaction.title_, reaction.is_active_, + td_->stickers_manager_->get_sticker_object(reaction.static_icon_), + td_->stickers_manager_->get_sticker_object(reaction.appear_animation_), + td_->stickers_manager_->get_sticker_object(reaction.select_animation_), + td_->stickers_manager_->get_sticker_object(reaction.activate_animation_), + td_->stickers_manager_->get_sticker_object(reaction.effect_animation_), + td_->stickers_manager_->get_sticker_object(reaction.around_animation_), + td_->stickers_manager_->get_sticker_object(reaction.center_animation_)); + } + } + return nullptr; +} + +void ReactionManager::get_emoji_reaction(const string &emoji, + Promise> &&promise) { + load_reactions(); + if (reactions_.reactions_.empty() && reactions_.are_being_reloaded_) { + pending_get_emoji_reaction_queries_.emplace_back(emoji, std::move(promise)); + return; + } + promise.set_value(get_emoji_reaction_object(emoji)); +} + +td_api::object_ptr ReactionManager::get_sorted_available_reactions( + ChatReactions available_reactions, ChatReactions active_reactions, int32 row_size) { + load_recent_reactions(); + load_top_reactions(); + + if (row_size < 5 || row_size > 25) { + row_size = 8; + } + + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); + bool show_premium = is_premium; + const auto &recent_reactions = recent_reactions_.reaction_types_; + auto top_reactions = top_reactions_.reaction_types_; + LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions + << " and recent reactions " << recent_reactions; + if (active_reactions.allow_custom_ && active_reactions.allow_all_) { + for (auto &reaction_type : recent_reactions) { + if (reaction_type.is_custom_reaction()) { + show_premium = true; + } + } + for (auto &reaction_type : top_reactions) { + if (reaction_type.is_custom_reaction()) { + show_premium = true; + } + } + } + + FlatHashSet all_available_reaction_types; + for (const auto &reaction_type : available_reactions.reaction_types_) { + CHECK(!reaction_type.is_empty()); + all_available_reaction_types.insert(reaction_type); + } + + vector> top_reaction_objects; + vector> recent_reaction_objects; + vector> popular_reaction_objects; + vector> last_reaction_objects; + + FlatHashSet added_custom_reaction_types; + auto add_reactions = [&](vector> &reaction_objects, + const vector &reaction_types) { + for (auto &reaction_type : reaction_types) { + if (all_available_reaction_types.erase(reaction_type) != 0) { + // add available reaction + if (reaction_type.is_custom_reaction()) { + added_custom_reaction_types.insert(reaction_type); + } + reaction_objects.push_back( + td_api::make_object(reaction_type.get_reaction_type_object(), false)); + } else if (reaction_type.is_custom_reaction() && available_reactions.allow_custom_ && + added_custom_reaction_types.insert(reaction_type).second) { + // add implicitly available custom reaction + reaction_objects.push_back( + td_api::make_object(reaction_type.get_reaction_type_object(), !is_premium)); + } else { + // skip the reaction + } + } + }; + if (show_premium) { + if (top_reactions.size() > 2 * static_cast(row_size)) { + top_reactions.resize(2 * static_cast(row_size)); + } + add_reactions(top_reaction_objects, top_reactions); + + if (!recent_reactions.empty()) { + add_reactions(recent_reaction_objects, recent_reactions); + } + } else { + add_reactions(top_reaction_objects, top_reactions); + } + add_reactions(last_reaction_objects, active_reaction_types_); + add_reactions(last_reaction_objects, available_reactions.reaction_types_); + + if (show_premium) { + if (recent_reactions.empty()) { + popular_reaction_objects = std::move(last_reaction_objects); + } else { + auto max_objects = 10 * static_cast(row_size); + if (recent_reaction_objects.size() + last_reaction_objects.size() > max_objects) { + if (last_reaction_objects.size() < max_objects) { + recent_reaction_objects.resize(max_objects - last_reaction_objects.size()); + } else { + recent_reaction_objects.clear(); + } + } + append(recent_reaction_objects, std::move(last_reaction_objects)); + } + } else { + append(top_reaction_objects, std::move(last_reaction_objects)); + } + + CHECK(all_available_reaction_types.empty()); + + return td_api::make_object( + std::move(top_reaction_objects), std::move(recent_reaction_objects), std::move(popular_reaction_objects), + available_reactions.allow_custom_); +} + +td_api::object_ptr ReactionManager::get_available_reactions(int32 row_size) { + ChatReactions available_reactions; + available_reactions.reaction_types_ = active_reaction_types_; + available_reactions.allow_custom_ = true; + return get_sorted_available_reactions(std::move(available_reactions), ChatReactions(true, true), row_size); +} + +void ReactionManager::add_recent_reaction(const ReactionType &reaction_type) { + load_recent_reactions(); + + auto &reactions = recent_reactions_.reaction_types_; + if (!reactions.empty() && reactions[0] == 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); + + recent_reactions_.hash_ = get_reaction_types_hash(reactions); +} + +void ReactionManager::clear_recent_reactions(Promise &&promise) { + load_recent_reactions(); + + if (recent_reactions_.reaction_types_.empty()) { + return promise.set_value(Unit()); + } + + recent_reactions_.hash_ = 0; + recent_reactions_.reaction_types_.clear(); + + td_->create_handler(std::move(promise))->send(); +} + +void ReactionManager::reload_reactions() { + if (G()->close_flag() || reactions_.are_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + reactions_.are_being_reloaded_ = true; + load_reactions(); // must be after are_being_reloaded_ is set to true to avoid recursion + td_->create_handler()->send(reactions_.hash_); +} + +void ReactionManager::reload_recent_reactions() { + if (G()->close_flag() || recent_reactions_.is_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + recent_reactions_.is_being_reloaded_ = true; + load_recent_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion + td_->create_handler()->send(MAX_RECENT_REACTIONS, recent_reactions_.hash_); +} + +void ReactionManager::reload_top_reactions() { + if (G()->close_flag() || top_reactions_.is_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + top_reactions_.is_being_reloaded_ = true; + load_top_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion + td_->create_handler()->send(top_reactions_.hash_); +} + +td_api::object_ptr ReactionManager::get_update_active_emoji_reactions_object() + const { + return td_api::make_object( + transform(active_reaction_types_, [](const ReactionType &reaction_type) { return reaction_type.get_string(); })); +} + +void ReactionManager::save_active_reactions() { + LOG(INFO) << "Save " << active_reaction_types_.size() << " active reactions"; + G()->td_db()->get_binlog_pmc()->set("active_reactions", log_event_store(active_reaction_types_).as_slice().str()); +} + +void ReactionManager::save_reactions() { + LOG(INFO) << "Save " << reactions_.reactions_.size() << " available reactions"; + are_reactions_loaded_from_database_ = true; + G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str()); +} + +void ReactionManager::save_recent_reactions() { + LOG(INFO) << "Save " << recent_reactions_.reaction_types_.size() << " recent reactions"; + are_recent_reactions_loaded_from_database_ = true; + G()->td_db()->get_binlog_pmc()->set("recent_reactions", log_event_store(recent_reactions_).as_slice().str()); +} + +void ReactionManager::save_top_reactions() { + LOG(INFO) << "Save " << top_reactions_.reaction_types_.size() << " top reactions"; + are_top_reactions_loaded_from_database_ = true; + G()->td_db()->get_binlog_pmc()->set("top_reactions", log_event_store(top_reactions_).as_slice().str()); +} + +void ReactionManager::load_active_reactions() { + LOG(INFO) << "Loading active reactions"; + string active_reaction_types = G()->td_db()->get_binlog_pmc()->get("active_reactions"); + if (active_reaction_types.empty()) { + return reload_reactions(); + } + + auto status = log_event_parse(active_reaction_types_, active_reaction_types); + if (status.is_error()) { + LOG(ERROR) << "Can't load active reactions: " << status; + active_reaction_types_ = {}; + return reload_reactions(); + } + + LOG(INFO) << "Successfully loaded " << active_reaction_types_.size() << " active reactions"; + + td_->messages_manager_->set_active_reactions(active_reaction_types_); + + send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); +} + +void ReactionManager::load_reactions() { + if (are_reactions_loaded_from_database_) { + return; + } + are_reactions_loaded_from_database_ = true; + + LOG(INFO) << "Loading available reactions"; + string reactions = G()->td_db()->get_binlog_pmc()->get("reactions"); + if (reactions.empty()) { + return reload_reactions(); + } + + auto new_reactions = reactions_; + auto status = log_event_parse(new_reactions, reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load available reactions: " << status; + return reload_reactions(); + } + for (auto &reaction_type : new_reactions.reactions_) { + if (!reaction_type.is_valid()) { + LOG(ERROR) << "Loaded invalid reaction"; + return reload_reactions(); + } + } + reactions_ = std::move(new_reactions); + + LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions"; + + update_active_reactions(); +} + +void ReactionManager::load_recent_reactions() { + if (are_recent_reactions_loaded_from_database_) { + return; + } + are_recent_reactions_loaded_from_database_ = true; + + LOG(INFO) << "Loading recent reactions"; + string recent_reactions = G()->td_db()->get_binlog_pmc()->get("recent_reactions"); + if (recent_reactions.empty()) { + return reload_recent_reactions(); + } + + auto status = log_event_parse(recent_reactions_, recent_reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load recent reactions: " << status; + recent_reactions_ = {}; + return reload_recent_reactions(); + } + + LOG(INFO) << "Successfully loaded " << recent_reactions_.reaction_types_.size() << " recent reactions"; +} + +void ReactionManager::load_top_reactions() { + if (are_top_reactions_loaded_from_database_) { + return; + } + are_top_reactions_loaded_from_database_ = true; + + LOG(INFO) << "Loading top reactions"; + string top_reactions = G()->td_db()->get_binlog_pmc()->get("top_reactions"); + if (top_reactions.empty()) { + return reload_top_reactions(); + } + + auto status = log_event_parse(top_reactions_, top_reactions); + if (status.is_error()) { + LOG(ERROR) << "Can't load top reactions: " << status; + top_reactions_ = {}; + return reload_top_reactions(); + } + + LOG(INFO) << "Successfully loaded " << top_reactions_.reaction_types_.size() << " top reactions"; +} + +void ReactionManager::update_active_reactions() { + vector active_reaction_types; + for (auto &reaction : reactions_.reactions_) { + if (reaction.is_active_) { + active_reaction_types.emplace_back(reaction.reaction_type_); + } + } + if (active_reaction_types == active_reaction_types_) { + return; + } + active_reaction_types_ = active_reaction_types; + + save_active_reactions(); + + send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); + + td_->messages_manager_->set_active_reactions(std::move(active_reaction_types)); +} + +void ReactionManager::on_get_available_reactions( + tl_object_ptr &&available_reactions_ptr) { + CHECK(reactions_.are_being_reloaded_); + reactions_.are_being_reloaded_ = false; + + auto get_emoji_reaction_queries = std::move(pending_get_emoji_reaction_queries_); + pending_get_emoji_reaction_queries_.clear(); + SCOPE_EXIT { + for (auto &query : get_emoji_reaction_queries) { + query.second.set_value(get_emoji_reaction_object(query.first)); + } + }; + + if (available_reactions_ptr == nullptr) { + // failed to get available reactions + return; + } + + int32 constructor_id = available_reactions_ptr->get_id(); + if (constructor_id == telegram_api::messages_availableReactionsNotModified::ID) { + LOG(INFO) << "Available reactions are not modified"; + return; + } + + CHECK(constructor_id == telegram_api::messages_availableReactions::ID); + auto available_reactions = move_tl_object_as(available_reactions_ptr); + vector new_reactions; + for (auto &available_reaction : available_reactions->reactions_) { + Reaction reaction; + reaction.is_active_ = !available_reaction->inactive_; + reaction.is_premium_ = available_reaction->premium_; + reaction.reaction_type_ = ReactionType(std::move(available_reaction->reaction_)); + reaction.title_ = std::move(available_reaction->title_); + reaction.static_icon_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->static_icon_), StickerFormat::Webp) + .second; + reaction.appear_animation_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->appear_animation_), StickerFormat::Tgs) + .second; + reaction.select_animation_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->select_animation_), StickerFormat::Tgs) + .second; + reaction.activate_animation_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->activate_animation_), StickerFormat::Tgs) + .second; + reaction.effect_animation_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->effect_animation_), StickerFormat::Tgs) + .second; + reaction.around_animation_ = + td_->stickers_manager_ + ->on_get_sticker_document(std::move(available_reaction->around_animation_), StickerFormat::Tgs) + .second; + reaction.center_animation_ = + td_->stickers_manager_->on_get_sticker_document(std::move(available_reaction->center_icon_), StickerFormat::Tgs) + .second; + + if (!reaction.is_valid()) { + LOG(ERROR) << "Receive invalid " << reaction.reaction_type_; + continue; + } + if (reaction.is_premium_) { + LOG(ERROR) << "Receive premium " << reaction.reaction_type_; + continue; + } + + new_reactions.push_back(std::move(reaction)); + } + reactions_.reactions_ = std::move(new_reactions); + reactions_.hash_ = available_reactions->hash_; + + save_reactions(); + + update_active_reactions(); +} + +void ReactionManager::on_get_recent_reactions(tl_object_ptr &&reactions_ptr) { + CHECK(recent_reactions_.is_being_reloaded_); + recent_reactions_.is_being_reloaded_ = false; + + if (reactions_ptr == nullptr) { + // failed to get recent reactions + return; + } + + int32 constructor_id = reactions_ptr->get_id(); + if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + + CHECK(constructor_id == telegram_api::messages_reactions::ID); + auto reactions = move_tl_object_as(reactions_ptr); + auto new_reaction_types = transform( + reactions->reactions_, + [](const telegram_api::object_ptr &reaction) { return ReactionType(reaction); }); + if (new_reaction_types == recent_reactions_.reaction_types_ && recent_reactions_.hash_ == reactions->hash_) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + recent_reactions_.reaction_types_ = std::move(new_reaction_types); + recent_reactions_.hash_ = reactions->hash_; + + auto expected_hash = get_reaction_types_hash(recent_reactions_.reaction_types_); + if (recent_reactions_.hash_ != expected_hash) { + LOG(ERROR) << "Receive hash " << recent_reactions_.hash_ << " instead of " << expected_hash << " for reactions " + << recent_reactions_.reaction_types_; + } + + save_recent_reactions(); +} + +void ReactionManager::on_get_top_reactions(tl_object_ptr &&reactions_ptr) { + CHECK(top_reactions_.is_being_reloaded_); + top_reactions_.is_being_reloaded_ = false; + + if (reactions_ptr == nullptr) { + // failed to get top reactions + return; + } + + int32 constructor_id = reactions_ptr->get_id(); + if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + + CHECK(constructor_id == telegram_api::messages_reactions::ID); + auto reactions = move_tl_object_as(reactions_ptr); + auto new_reaction_types = transform( + reactions->reactions_, + [](const telegram_api::object_ptr &reaction) { return ReactionType(reaction); }); + if (new_reaction_types == top_reactions_.reaction_types_ && top_reactions_.hash_ == reactions->hash_) { + LOG(INFO) << "Top reactions are not modified"; + return; + } + top_reactions_.reaction_types_ = std::move(new_reaction_types); + top_reactions_.hash_ = reactions->hash_; + + save_top_reactions(); +} + +bool ReactionManager::is_active_reaction(const ReactionType &reaction_type) const { + for (auto &supported_reaction : reactions_.reactions_) { + if (supported_reaction.reaction_type_ == reaction_type) { + return supported_reaction.is_active_; + } + } + return false; +} + +void ReactionManager::set_default_reaction(ReactionType reaction_type, Promise &&promise) { + if (reaction_type.is_empty()) { + return promise.set_error(Status::Error(400, "Default reaction must be non-empty")); + } + if (!reaction_type.is_custom_reaction() && !is_active_reaction(reaction_type)) { + return promise.set_error(Status::Error(400, "Can't set incative reaction as default")); + } + + if (td_->option_manager_->get_option_string("default_reaction", "-") != reaction_type.get_string()) { + td_->option_manager_->set_option_string("default_reaction", reaction_type.get_string()); + if (!td_->option_manager_->get_option_boolean("default_reaction_needs_sync")) { + td_->option_manager_->set_option_boolean("default_reaction_needs_sync", true); + send_set_default_reaction_query(); + } + } + promise.set_value(Unit()); +} + +void ReactionManager::send_set_default_reaction_query() { + td_->create_handler()->send( + ReactionType(td_->option_manager_->get_option_string("default_reaction"))); +} + +void ReactionManager::get_current_state(vector> &updates) const { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (!active_reaction_types_.empty()) { + updates.push_back(get_update_active_emoji_reactions_object()); + } +} + +} // namespace td diff --git a/td/telegram/ReactionManager.h b/td/telegram/ReactionManager.h new file mode 100644 index 000000000000..1345f05007ac --- /dev/null +++ b/td/telegram/ReactionManager.h @@ -0,0 +1,165 @@ +// +// 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/ChatReactions.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/ReactionType.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" + +#include + +namespace td { + +class Td; + +class ReactionManager final : public Actor { + public: + ReactionManager(Td *td, ActorShared<> parent); + ReactionManager(const ReactionManager &) = delete; + ReactionManager &operator=(const ReactionManager &) = delete; + ReactionManager(ReactionManager &&) = delete; + ReactionManager &operator=(ReactionManager &&) = delete; + ~ReactionManager() final; + + void init(); + + bool is_active_reaction(const ReactionType &reaction_type) const; + + void get_emoji_reaction(const string &emoji, Promise> &&promise); + + td_api::object_ptr get_sorted_available_reactions(ChatReactions available_reactions, + ChatReactions active_reactions, + int32 row_size); + + td_api::object_ptr get_available_reactions(int32 row_size); + + void add_recent_reaction(const ReactionType &reaction_type); + + void clear_recent_reactions(Promise &&promise); + + void reload_reactions(); + + void reload_recent_reactions(); + + void reload_top_reactions(); + + void on_get_available_reactions(tl_object_ptr &&available_reactions_ptr); + + void on_get_recent_reactions(tl_object_ptr &&reactions_ptr); + + void on_get_top_reactions(tl_object_ptr &&reactions_ptr); + + void set_default_reaction(ReactionType reaction_type, Promise &&promise); + + void send_set_default_reaction_query(); + + void get_current_state(vector> &updates) const; + + private: + static constexpr int32 MAX_RECENT_REACTIONS = 100; // some reasonable value + + struct Reaction { + ReactionType reaction_type_; + string title_; + bool is_active_ = false; + bool is_premium_ = false; + FileId static_icon_; + FileId appear_animation_; + FileId select_animation_; + FileId activate_animation_; + FileId effect_animation_; + FileId around_animation_; + FileId center_animation_; + + bool is_valid() const { + return static_icon_.is_valid() && appear_animation_.is_valid() && select_animation_.is_valid() && + activate_animation_.is_valid() && effect_animation_.is_valid() && !reaction_type_.is_empty(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct Reactions { + int32 hash_ = 0; + bool are_being_reloaded_ = false; + vector reactions_; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct ReactionList { + int64 hash_ = 0; + bool is_being_reloaded_ = false; + vector reaction_types_; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + td_api::object_ptr get_emoji_reaction_object(const string &emoji) const; + + void start_up() final; + + void tear_down() final; + + void save_active_reactions(); + + void save_reactions(); + + void save_recent_reactions(); + + void save_top_reactions(); + + void load_active_reactions(); + + void load_reactions(); + + void load_recent_reactions(); + + void load_top_reactions(); + + void update_active_reactions(); + + td_api::object_ptr get_update_active_emoji_reactions_object() const; + + Td *td_; + ActorShared<> parent_; + + bool is_inited_ = false; + + vector>>> pending_get_emoji_reaction_queries_; + + Reactions reactions_; + vector active_reaction_types_; + + ReactionList recent_reactions_; + ReactionList top_reactions_; + + bool are_reactions_loaded_from_database_ = false; + bool are_recent_reactions_loaded_from_database_ = false; + bool are_top_reactions_loaded_from_database_ = false; +}; + +} // namespace td diff --git a/td/telegram/ReactionManager.hpp b/td/telegram/ReactionManager.hpp new file mode 100644 index 000000000000..85e2625adf9f --- /dev/null +++ b/td/telegram/ReactionManager.hpp @@ -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/ReactionManager.h" + +#include "td/telegram/ReactionType.hpp" +#include "td/telegram/StickersManager.h" +#include "td/telegram/StickersManager.hpp" +#include "td/telegram/Td.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void ReactionManager::Reaction::store(StorerT &storer) const { + StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); + bool has_around_animation = !around_animation_.empty(); + bool has_center_animation = !center_animation_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_active_); + STORE_FLAG(has_around_animation); + STORE_FLAG(has_center_animation); + STORE_FLAG(is_premium_); + END_STORE_FLAGS(); + td::store(reaction_type_, storer); + td::store(title_, storer); + stickers_manager->store_sticker(static_icon_, false, storer, "Reaction"); + stickers_manager->store_sticker(appear_animation_, false, storer, "Reaction"); + stickers_manager->store_sticker(select_animation_, false, storer, "Reaction"); + stickers_manager->store_sticker(activate_animation_, false, storer, "Reaction"); + stickers_manager->store_sticker(effect_animation_, false, storer, "Reaction"); + if (has_around_animation) { + stickers_manager->store_sticker(around_animation_, false, storer, "Reaction"); + } + if (has_center_animation) { + stickers_manager->store_sticker(center_animation_, false, storer, "Reaction"); + } +} + +template +void ReactionManager::Reaction::parse(ParserT &parser) { + StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); + bool has_around_animation; + bool has_center_animation; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_active_); + PARSE_FLAG(has_around_animation); + PARSE_FLAG(has_center_animation); + PARSE_FLAG(is_premium_); + END_PARSE_FLAGS(); + td::parse(reaction_type_, parser); + td::parse(title_, parser); + static_icon_ = stickers_manager->parse_sticker(false, parser); + appear_animation_ = stickers_manager->parse_sticker(false, parser); + select_animation_ = stickers_manager->parse_sticker(false, parser); + activate_animation_ = stickers_manager->parse_sticker(false, parser); + effect_animation_ = stickers_manager->parse_sticker(false, parser); + if (has_around_animation) { + around_animation_ = stickers_manager->parse_sticker(false, parser); + } + if (has_center_animation) { + center_animation_ = stickers_manager->parse_sticker(false, parser); + } + + is_premium_ = false; +} + +template +void ReactionManager::Reactions::store(StorerT &storer) const { + bool has_reactions = !reactions_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_reactions); + END_STORE_FLAGS(); + if (has_reactions) { + td::store(reactions_, storer); + td::store(hash_, storer); + } +} + +template +void ReactionManager::Reactions::parse(ParserT &parser) { + bool has_reactions; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_reactions); + END_PARSE_FLAGS(); + if (has_reactions) { + td::parse(reactions_, parser); + td::parse(hash_, parser); + } +} + +template +void ReactionManager::ReactionList::store(StorerT &storer) const { + bool has_reaction_types = !reaction_types_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_reaction_types); + END_STORE_FLAGS(); + if (has_reaction_types) { + td::store(reaction_types_, storer); + td::store(hash_, storer); + } +} + +template +void ReactionManager::ReactionList::parse(ParserT &parser) { + bool has_reaction_types; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_reaction_types); + END_PARSE_FLAGS(); + if (has_reaction_types) { + td::parse(reaction_types_, parser); + td::parse(hash_, parser); + } +} + +} // namespace td diff --git a/td/telegram/ReactionType.cpp b/td/telegram/ReactionType.cpp new file mode 100644 index 000000000000..c273f9c0b59e --- /dev/null +++ b/td/telegram/ReactionType.cpp @@ -0,0 +1,163 @@ +// +// 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/ReactionType.h" + +#include "td/telegram/misc.h" + +#include "td/utils/as.h" +#include "td/utils/base64.h" +#include "td/utils/crypto.h" +#include "td/utils/emoji.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/utf8.h" + +namespace td { + +static int64 get_custom_emoji_id(const string &reaction) { + auto r_decoded = base64_decode(Slice(&reaction[1], reaction.size() - 1)); + CHECK(r_decoded.is_ok()); + CHECK(r_decoded.ok().size() == 8); + return as(r_decoded.ok().c_str()); +} + +static string get_custom_emoji_string(int64 custom_emoji_id) { + char s[8]; + as(&s) = custom_emoji_id; + return PSTRING() << '#' << base64_encode(Slice(s, 8)); +} + +ReactionType::ReactionType(string &&emoji) : reaction_(std::move(emoji)) { +} + +ReactionType::ReactionType(const telegram_api::object_ptr &reaction) { + if (reaction == nullptr) { + return; + } + switch (reaction->get_id()) { + case telegram_api::reactionEmpty::ID: + break; + case telegram_api::reactionEmoji::ID: + reaction_ = static_cast(reaction.get())->emoticon_; + if (is_custom_reaction()) { + reaction_ = string(); + } + break; + case telegram_api::reactionCustomEmoji::ID: + reaction_ = + get_custom_emoji_string(static_cast(reaction.get())->document_id_); + break; + default: + UNREACHABLE(); + break; + } +} + +ReactionType::ReactionType(const td_api::object_ptr &type) { + if (type == nullptr) { + return; + } + switch (type->get_id()) { + case td_api::reactionTypeEmoji::ID: { + const string &emoji = static_cast(type.get())->emoji_; + if (!check_utf8(emoji)) { + break; + } + reaction_ = emoji; + if (is_custom_reaction()) { + reaction_ = string(); + break; + } + break; + } + case td_api::reactionTypeCustomEmoji::ID: + reaction_ = + get_custom_emoji_string(static_cast(type.get())->custom_emoji_id_); + break; + default: + UNREACHABLE(); + break; + } +} + +telegram_api::object_ptr ReactionType::get_input_reaction() const { + if (is_empty()) { + return telegram_api::make_object(); + } + if (is_custom_reaction()) { + return telegram_api::make_object(get_custom_emoji_id(reaction_)); + } + return telegram_api::make_object(reaction_); +} + +td_api::object_ptr ReactionType::get_reaction_type_object() const { + if (is_empty()) { + return nullptr; + } + if (is_custom_reaction()) { + return td_api::make_object(get_custom_emoji_id(reaction_)); + } + return td_api::make_object(reaction_); +} + +td_api::object_ptr ReactionType::get_update_default_reaction_type() const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(get_reaction_type_object()); +} + +bool ReactionType::is_custom_reaction() const { + return reaction_[0] == '#'; +} + +bool ReactionType::is_active_reaction( + const FlatHashMap &active_reaction_pos) const { + return !is_empty() && (is_custom_reaction() || active_reaction_pos.count(*this) > 0); +} + +bool operator<(const ReactionType &lhs, const ReactionType &rhs) { + return lhs.reaction_ < rhs.reaction_; +} + +bool operator==(const ReactionType &lhs, const ReactionType &rhs) { + return lhs.reaction_ == rhs.reaction_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionType &reaction_type) { + if (reaction_type.is_empty()) { + return string_builder << "empty reaction"; + } + if (reaction_type.is_custom_reaction()) { + return string_builder << "custom reaction " << get_custom_emoji_id(reaction_type.reaction_); + } + return string_builder << "reaction " << reaction_type.reaction_; +} + +int64 get_reaction_types_hash(const vector &reaction_types) { + vector numbers; + for (auto &reaction_type : reaction_types) { + if (reaction_type.is_custom_reaction()) { + auto custom_emoji_id = static_cast(get_custom_emoji_id(reaction_type.get_string())); + numbers.push_back(custom_emoji_id >> 32); + numbers.push_back(custom_emoji_id & 0xFFFFFFFF); + } else { + auto emoji = remove_emoji_selectors(reaction_type.get_string()); + unsigned char hash[16]; + md5(emoji, {hash, sizeof(hash)}); + auto get = [hash](int num) { + return static_cast(hash[num]); + }; + + numbers.push_back(0); + numbers.push_back(static_cast((get(0) << 24) + (get(1) << 16) + (get(2) << 8) + get(3))); + } + } + return get_vector_hash(numbers); +} + +} // namespace td diff --git a/td/telegram/ReactionType.h b/td/telegram/ReactionType.h new file mode 100644 index 000000000000..d78b27926288 --- /dev/null +++ b/td/telegram/ReactionType.h @@ -0,0 +1,84 @@ +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +struct ReactionTypeHash; + +class ReactionType { + string reaction_; + + friend bool operator<(const ReactionType &lhs, const ReactionType &rhs); + + friend bool operator==(const ReactionType &lhs, const ReactionType &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const ReactionType &reaction_type); + + friend struct ReactionTypeHash; + + public: + ReactionType() = default; + + explicit ReactionType(string &&emoji); + + explicit ReactionType(const telegram_api::object_ptr &reaction); + + explicit ReactionType(const td_api::object_ptr &type); + + telegram_api::object_ptr get_input_reaction() const; + + td_api::object_ptr get_reaction_type_object() const; + + td_api::object_ptr get_update_default_reaction_type() const; + + bool is_custom_reaction() const; + + bool is_active_reaction(const FlatHashMap &active_reaction_pos) const; + + bool is_empty() const { + return reaction_.empty(); + } + + const string &get_string() const { + return reaction_; + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +struct ReactionTypeHash { + uint32 operator()(const ReactionType &reaction_type) const { + return Hash()(reaction_type.reaction_); + } +}; + +bool operator<(const ReactionType &lhs, const ReactionType &rhs); + +bool operator==(const ReactionType &lhs, const ReactionType &rhs); + +inline bool operator!=(const ReactionType &lhs, const ReactionType &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionType &reaction_type); + +int64 get_reaction_types_hash(const vector &reaction_types); + +} // namespace td diff --git a/td/telegram/ReactionType.hpp b/td/telegram/ReactionType.hpp new file mode 100644 index 000000000000..fe37340713eb --- /dev/null +++ b/td/telegram/ReactionType.hpp @@ -0,0 +1,27 @@ +// +// 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/ReactionType.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void ReactionType::store(StorerT &storer) const { + CHECK(!is_empty()); + td::store(reaction_, storer); +} + +template +void ReactionType::parse(ParserT &parser) { + td::parse(reaction_, parser); +} + +} // namespace td diff --git a/td/telegram/ReplyMarkup.cpp b/td/telegram/ReplyMarkup.cpp index c5d186e0c308..cbb960ae6323 100644 --- a/td/telegram/ReplyMarkup.cpp +++ b/td/telegram/ReplyMarkup.cpp @@ -882,7 +882,7 @@ static tl_object_ptr get_input_keyboard_button( case InlineKeyboardButton::Type::Url: return make_tl_object(keyboard_button.text, keyboard_button.data); case InlineKeyboardButton::Type::Callback: - return make_tl_object(0, false /*ignored*/, keyboard_button.text, + return make_tl_object(0, false, keyboard_button.text, BufferSlice(keyboard_button.data)); case InlineKeyboardButton::Type::CallbackGame: return make_tl_object(keyboard_button.text); @@ -906,13 +906,13 @@ static tl_object_ptr get_input_keyboard_button( peer_types.push_back(telegram_api::make_object()); } } - return make_tl_object(flags, false /*ignored*/, keyboard_button.text, + return make_tl_object(flags, false, keyboard_button.text, keyboard_button.data, std::move(peer_types)); } case InlineKeyboardButton::Type::SwitchInlineCurrentDialog: return make_tl_object( - telegram_api::keyboardButtonSwitchInline::SAME_PEER_MASK, false /*ignored*/, keyboard_button.text, - keyboard_button.data, vector>()); + telegram_api::keyboardButtonSwitchInline::SAME_PEER_MASK, true, keyboard_button.text, keyboard_button.data, + vector>()); case InlineKeyboardButton::Type::Buy: return make_tl_object(keyboard_button.text); case InlineKeyboardButton::Type::UrlAuth: { diff --git a/td/telegram/ReportReason.cpp b/td/telegram/ReportReason.cpp index 36e629b60781..9ba8d34130e9 100644 --- a/td/telegram/ReportReason.cpp +++ b/td/telegram/ReportReason.cpp @@ -10,7 +10,7 @@ namespace td { -Result ReportReason::get_report_reason(td_api::object_ptr reason, +Result ReportReason::get_report_reason(td_api::object_ptr reason, string &&message) { if (reason == nullptr) { return Status::Error(400, "Chat report reason must be non-empty"); @@ -21,25 +21,25 @@ Result ReportReason::get_report_reason(td_api::object_ptrget_id()) { - case td_api::chatReportReasonSpam::ID: + case td_api::reportReasonSpam::ID: return ReportReason::Type::Spam; - case td_api::chatReportReasonViolence::ID: + case td_api::reportReasonViolence::ID: return ReportReason::Type::Violence; - case td_api::chatReportReasonPornography::ID: + case td_api::reportReasonPornography::ID: return ReportReason::Type::Pornography; - case td_api::chatReportReasonChildAbuse::ID: + case td_api::reportReasonChildAbuse::ID: return ReportReason::Type::ChildAbuse; - case td_api::chatReportReasonCopyright::ID: + case td_api::reportReasonCopyright::ID: return ReportReason::Type::Copyright; - case td_api::chatReportReasonUnrelatedLocation::ID: + case td_api::reportReasonUnrelatedLocation::ID: return ReportReason::Type::UnrelatedLocation; - case td_api::chatReportReasonFake::ID: + case td_api::reportReasonFake::ID: return ReportReason::Type::Fake; - case td_api::chatReportReasonIllegalDrugs::ID: + case td_api::reportReasonIllegalDrugs::ID: return ReportReason::Type::IllegalDrugs; - case td_api::chatReportReasonPersonalDetails::ID: + case td_api::reportReasonPersonalDetails::ID: return ReportReason::Type::PersonalDetails; - case td_api::chatReportReasonCustom::ID: + case td_api::reportReasonCustom::ID: return ReportReason::Type::Custom; default: UNREACHABLE(); diff --git a/td/telegram/ReportReason.h b/td/telegram/ReportReason.h index 3ea27d70b2b6..6368cdedf95d 100644 --- a/td/telegram/ReportReason.h +++ b/td/telegram/ReportReason.h @@ -39,7 +39,7 @@ class ReportReason { public: ReportReason() = default; - static Result get_report_reason(td_api::object_ptr reason, string &&message); + static Result get_report_reason(td_api::object_ptr reason, string &&message); tl_object_ptr get_input_report_reason() const; diff --git a/td/telegram/ScopeNotificationSettings.cpp b/td/telegram/ScopeNotificationSettings.cpp index 2e121613334b..6c14f2ab5b29 100644 --- a/td/telegram/ScopeNotificationSettings.cpp +++ b/td/telegram/ScopeNotificationSettings.cpp @@ -12,9 +12,31 @@ namespace td { +telegram_api::object_ptr +ScopeNotificationSettings::get_input_peer_notify_settings() const { + int32 flags = telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK | + telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK | + telegram_api::inputPeerNotifySettings::STORIES_HIDE_SENDER_MASK; + if (sound != nullptr) { + flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK; + } + if (story_sound != nullptr) { + flags |= telegram_api::inputPeerNotifySettings::STORIES_SOUND_MASK; + } + if (!use_default_mute_stories) { + flags |= telegram_api::inputPeerNotifySettings::STORIES_MUTED_MASK; + } + return telegram_api::make_object( + flags, show_preview, false, mute_until, get_input_notification_sound(sound), mute_stories, hide_story_sender, + get_input_notification_sound(story_sound)); +} + StringBuilder &operator<<(StringBuilder &string_builder, const ScopeNotificationSettings ¬ification_settings) { return string_builder << "[" << notification_settings.mute_until << ", " << notification_settings.sound << ", " - << notification_settings.show_preview << ", " << notification_settings.is_synchronized << ", " + << notification_settings.show_preview << ", " << notification_settings.use_default_mute_stories + << ", " << notification_settings.mute_stories << ", " << notification_settings.story_sound + << ", " << notification_settings.hide_story_sender << ", " + << notification_settings.is_synchronized << ", " << notification_settings.disable_pinned_message_notifications << ", " << notification_settings.disable_mention_notifications << "]"; } @@ -25,6 +47,8 @@ td_api::object_ptr get_scope_notification_set return td_api::make_object( max(0, notification_settings->mute_until - G()->unix_time()), get_notification_sound_ringtone_id(notification_settings->sound), notification_settings->show_preview, + notification_settings->use_default_mute_stories, notification_settings->mute_stories, + get_notification_sound_ringtone_id(notification_settings->story_sound), !notification_settings->hide_story_sender, notification_settings->disable_pinned_message_notifications, notification_settings->disable_mention_notifications); } @@ -49,10 +73,12 @@ Result get_scope_notification_settings( } auto mute_until = get_mute_until(notification_settings->mute_for_); - return ScopeNotificationSettings(mute_until, get_notification_sound(false, notification_settings->sound_id_), - notification_settings->show_preview_, - notification_settings->disable_pinned_message_notifications_, - notification_settings->disable_mention_notifications_); + return ScopeNotificationSettings( + mute_until, get_notification_sound(false, notification_settings->sound_id_), notification_settings->show_preview_, + notification_settings->use_default_mute_stories_, notification_settings->mute_stories_, + get_notification_sound(false, notification_settings->story_sound_id_), !notification_settings->show_story_sender_, + notification_settings->disable_pinned_message_notifications_, + notification_settings->disable_mention_notifications_); } ScopeNotificationSettings get_scope_notification_settings(tl_object_ptr &&settings, @@ -66,7 +92,17 @@ ScopeNotificationSettings get_scope_notification_settings(tl_object_ptrshow_previews_; - return {mute_until, get_notification_sound(settings.get()), show_preview, old_disable_pinned_message_notifications, + auto use_default_mute_stories = (settings->flags_ & telegram_api::peerNotifySettings::STORIES_MUTED_MASK) == 0; + auto mute_stories = settings->stories_muted_; + auto hide_story_sender = settings->stories_hide_sender_; + return {mute_until, + get_notification_sound(settings.get(), false), + show_preview, + use_default_mute_stories, + mute_stories, + get_notification_sound(settings.get(), true), + hide_story_sender, + old_disable_pinned_message_notifications, old_disable_mention_notifications}; } diff --git a/td/telegram/ScopeNotificationSettings.h b/td/telegram/ScopeNotificationSettings.h index f6a59caeaba0..bc510e44795f 100644 --- a/td/telegram/ScopeNotificationSettings.h +++ b/td/telegram/ScopeNotificationSettings.h @@ -20,7 +20,11 @@ class ScopeNotificationSettings { public: int32 mute_until = 0; unique_ptr sound; + unique_ptr story_sound; bool show_preview = true; + bool use_default_mute_stories = true; + bool mute_stories = false; + bool hide_story_sender = false; bool is_synchronized = false; // local settings @@ -30,14 +34,22 @@ class ScopeNotificationSettings { ScopeNotificationSettings() = default; ScopeNotificationSettings(int32 mute_until, unique_ptr &&sound, bool show_preview, + bool use_default_mute_stories, bool mute_stories, + unique_ptr &&story_sound, bool hide_story_sender, bool disable_pinned_message_notifications, bool disable_mention_notifications) : mute_until(mute_until) , sound(std::move(sound)) + , story_sound(std::move(story_sound)) , show_preview(show_preview) + , use_default_mute_stories(use_default_mute_stories) + , mute_stories(mute_stories) + , hide_story_sender(hide_story_sender) , is_synchronized(true) , disable_pinned_message_notifications(disable_pinned_message_notifications) , disable_mention_notifications(disable_mention_notifications) { } + + telegram_api::object_ptr get_input_peer_notify_settings() const; }; StringBuilder &operator<<(StringBuilder &string_builder, const ScopeNotificationSettings ¬ification_settings); diff --git a/td/telegram/ScopeNotificationSettings.hpp b/td/telegram/ScopeNotificationSettings.hpp index 25f2f3ce32af..b83fa1649903 100644 --- a/td/telegram/ScopeNotificationSettings.hpp +++ b/td/telegram/ScopeNotificationSettings.hpp @@ -19,6 +19,8 @@ void store(const ScopeNotificationSettings ¬ification_settings, StorerT &stor bool is_muted = notification_settings.mute_until != 0 && notification_settings.mute_until > G()->unix_time(); bool has_sound = notification_settings.sound != nullptr; bool has_ringtone_support = true; + bool has_story_sound = notification_settings.story_sound != nullptr; + bool use_mute_stories = !notification_settings.use_default_mute_stories; BEGIN_STORE_FLAGS(); STORE_FLAG(is_muted); STORE_FLAG(has_sound); @@ -28,6 +30,10 @@ void store(const ScopeNotificationSettings ¬ification_settings, StorerT &stor STORE_FLAG(notification_settings.disable_pinned_message_notifications); STORE_FLAG(notification_settings.disable_mention_notifications); STORE_FLAG(has_ringtone_support); + STORE_FLAG(notification_settings.mute_stories); + STORE_FLAG(has_story_sound); + STORE_FLAG(notification_settings.hide_story_sender); + STORE_FLAG(use_mute_stories); END_STORE_FLAGS(); if (is_muted) { store(notification_settings.mute_until, storer); @@ -35,6 +41,9 @@ void store(const ScopeNotificationSettings ¬ification_settings, StorerT &stor if (has_sound) { store(notification_settings.sound, storer); } + if (has_story_sound) { + store(notification_settings.story_sound, storer); + } } template @@ -43,6 +52,8 @@ void parse(ScopeNotificationSettings ¬ification_settings, ParserT &parser) { bool has_sound; bool silent_send_message_ignored; bool has_ringtone_support; + bool has_story_sound; + bool use_mute_stories; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_muted); PARSE_FLAG(has_sound); @@ -52,6 +63,10 @@ void parse(ScopeNotificationSettings ¬ification_settings, ParserT &parser) { PARSE_FLAG(notification_settings.disable_pinned_message_notifications); PARSE_FLAG(notification_settings.disable_mention_notifications); PARSE_FLAG(has_ringtone_support); + PARSE_FLAG(notification_settings.mute_stories); + PARSE_FLAG(has_story_sound); + PARSE_FLAG(notification_settings.hide_story_sender); + PARSE_FLAG(use_mute_stories); END_PARSE_FLAGS(); (void)silent_send_message_ignored; if (is_muted) { @@ -66,6 +81,10 @@ void parse(ScopeNotificationSettings ¬ification_settings, ParserT &parser) { notification_settings.sound = get_legacy_notification_sound(sound); } } + if (has_story_sound) { + parse_notification_sound(notification_settings.story_sound, parser); + } + notification_settings.use_default_mute_stories = !use_mute_stories; } } // namespace td diff --git a/td/telegram/SecretChatActor.cpp b/td/telegram/SecretChatActor.cpp index 76ab0aa241e9..d95d2ab5bca3 100644 --- a/td/telegram/SecretChatActor.cpp +++ b/td/telegram/SecretChatActor.cpp @@ -10,6 +10,7 @@ #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/secret_api.hpp" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/UniqueId.h" @@ -222,12 +223,11 @@ Result SecretChatActor::create_encrypted_message(int32 my_in_seq_no LOG(INFO) << "Create message " << to_string(message_with_layer); auto storer = create_storer(*message_with_layer); auto new_storer = mtproto::PacketStorer(storer); - mtproto::PacketInfo info; - info.type = mtproto::PacketInfo::EndToEnd; - info.version = 2; - info.is_creator = auth_state_.x == 0; - auto packet_writer = BufferWriter{mtproto::Transport::write(new_storer, *auth_key, &info), 0, 0}; - mtproto::Transport::write(new_storer, *auth_key, &info, packet_writer.as_mutable_slice()); + mtproto::PacketInfo packet_info; + packet_info.type = mtproto::PacketInfo::EndToEnd; + packet_info.version = 2; + packet_info.is_creator = auth_state_.x == 0; + auto packet_writer = mtproto::Transport::write(new_storer, *auth_key, &packet_info); message = std::move(message_with_layer->message_); return packet_writer.as_buffer_slice(); } @@ -792,12 +792,12 @@ Result> SecretChatActor::decrypt(BufferSl data = encrypted_message_copy.as_mutable_slice(); CHECK(is_aligned_pointer<4>(data.data())); - mtproto::PacketInfo info; - info.type = mtproto::PacketInfo::EndToEnd; + mtproto::PacketInfo packet_info; + packet_info.type = mtproto::PacketInfo::EndToEnd; mtproto_version = versions[i]; - info.version = mtproto_version; - info.is_creator = auth_state_.x == 0; - r_read_result = mtproto::Transport::read(data, *auth_key, &info); + packet_info.version = mtproto_version; + packet_info.is_creator = auth_state_.x == 0; + r_read_result = mtproto::Transport::read(data, *auth_key, &packet_info); if (i + 1 != versions.size() && r_read_result.is_error()) { if (config_state_.his_layer >= static_cast(SecretChatLayer::Mtproto2)) { LOG(WARNING) << tag("mtproto", mtproto_version) << " decryption failed " << r_read_result.error(); @@ -1776,9 +1776,7 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChatRequested &upd auth_state_.date = context_->unix_time(); TRY_STATUS(save_common_info(update)); auth_state_.handshake.set_g_a(update.g_a_.as_slice()); - if ((update.flags_ & telegram_api::encryptedChatRequested::FOLDER_ID_MASK) != 0) { - auth_state_.initial_folder_id = FolderId(update.folder_id_); - } + auth_state_.initial_folder_id = FolderId(update.folder_id_); send_update_secret_chat(); return Status::OK(); diff --git a/td/telegram/SecretChatsManager.cpp b/td/telegram/SecretChatsManager.cpp index 47e7911c1427..fc103c3bb227 100644 --- a/td/telegram/SecretChatsManager.cpp +++ b/td/telegram/SecretChatsManager.cpp @@ -15,10 +15,11 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/net/NetQueryDispatcher.h" +#include "td/telegram/SecretChatDb.h" #include "td/telegram/SequenceDispatcher.h" #include "td/telegram/StateManager.h" #include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/mtproto/DhCallback.h" @@ -181,12 +182,24 @@ void SecretChatsManager::on_update_chat(tl_object_ptr update) { - int32 id = 0; - downcast_call(*update->chat_, [&](auto &x) { id = x.id_; }); - - send_closure( - update->chat_->get_id() == telegram_api::encryptedChatDiscarded::ID ? get_chat_actor(id) : create_chat_actor(id), - &SecretChatActor::update_chat, std::move(update->chat_)); + auto actor_id = [this, chat = update->chat_.get()] { + switch (chat->get_id()) { + case telegram_api::encryptedChatEmpty::ID: + return create_chat_actor(static_cast(chat)->id_); + case telegram_api::encryptedChatWaiting::ID: + return create_chat_actor(static_cast(chat)->id_); + case telegram_api::encryptedChatRequested::ID: + return create_chat_actor(static_cast(chat)->id_); + case telegram_api::encryptedChat::ID: + return create_chat_actor(static_cast(chat)->id_); + case telegram_api::encryptedChatDiscarded::ID: + return get_chat_actor(static_cast(chat)->id_); + default: + UNREACHABLE(); + return ActorId(); + } + }(); + send_closure(actor_id, &SecretChatActor::update_chat, std::move(update->chat_)); } void SecretChatsManager::on_new_message(tl_object_ptr &&message_ptr, @@ -198,14 +211,24 @@ void SecretChatsManager::on_new_message(tl_object_ptr(); event->promise = std::move(promise); - downcast_call(*message_ptr, [&](auto &x) { - event->chat_id = x.chat_id_; - event->date = x.date_; - event->encrypted_message = std::move(x.bytes_); - }); - if (message_ptr->get_id() == telegram_api::encryptedMessage::ID) { - auto message = move_tl_object_as(message_ptr); - event->file = EncryptedFile::get_encrypted_file(std::move(message->file_)); + switch (message_ptr->get_id()) { + case telegram_api::encryptedMessage::ID: { + auto message = telegram_api::move_object_as(message_ptr); + event->chat_id = message->chat_id_; + event->date = message->date_; + event->encrypted_message = std::move(message->bytes_); + event->file = EncryptedFile::get_encrypted_file(std::move(message->file_)); + break; + } + case telegram_api::encryptedMessageService::ID: { + auto message = telegram_api::move_object_as(message_ptr); + event->chat_id = message->chat_id_; + event->date = message->date_; + event->encrypted_message = std::move(message->bytes_); + break; + } + default: + UNREACHABLE(); } add_inbound_message(std::move(event)); } diff --git a/td/telegram/SecureManager.cpp b/td/telegram/SecureManager.cpp index 69dae9aaaf2f..c348c93d82b9 100644 --- a/td/telegram/SecureManager.cpp +++ b/td/telegram/SecureManager.cpp @@ -16,6 +16,7 @@ #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" diff --git a/td/telegram/SecureValue.cpp b/td/telegram/SecureValue.cpp index 402a4101e03c..7c27da837111 100644 --- a/td/telegram/SecureValue.cpp +++ b/td/telegram/SecureValue.cpp @@ -15,7 +15,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" #include "td/telegram/OrderInfo.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/base64.h" @@ -24,7 +24,6 @@ #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" -#include "td/utils/overloaded.h" #include "td/utils/SliceBuilder.h" #include "td/utils/utf8.h" @@ -399,12 +398,10 @@ telegram_api::object_ptr get_input_secure_file_ob if (res == nullptr) { return file_manager->get_file_view(file.file.file_id).remote_location().as_input_secure_file(); } - telegram_api::downcast_call(*res, overloaded( - [&](telegram_api::inputSecureFileUploaded &uploaded) { - uploaded.secret_ = BufferSlice(file.encrypted_secret); - uploaded.file_hash_ = BufferSlice(file.file_hash); - }, - [&](telegram_api::inputSecureFile &) { UNREACHABLE(); })); + CHECK(res->get_id() == telegram_api::inputSecureFileUploaded::ID); + auto uploaded = static_cast(res.get()); + uploaded->secret_ = BufferSlice(file.encrypted_secret); + uploaded->file_hash_ = BufferSlice(file.file_hash); return res; } @@ -802,19 +799,19 @@ static Result> get_personal_details_ } auto &object = value.get_object(); - TRY_RESULT(first_name, get_json_object_string_field(object, "first_name")); - TRY_RESULT(middle_name, get_json_object_string_field(object, "middle_name")); - TRY_RESULT(last_name, get_json_object_string_field(object, "last_name")); - TRY_RESULT(native_first_name, get_json_object_string_field(object, "first_name_native")); - TRY_RESULT(native_middle_name, get_json_object_string_field(object, "middle_name_native")); - TRY_RESULT(native_last_name, get_json_object_string_field(object, "last_name_native")); - TRY_RESULT(birthdate, get_json_object_string_field(object, "birth_date")); + TRY_RESULT(first_name, object.get_optional_string_field("first_name")); + TRY_RESULT(middle_name, object.get_optional_string_field("middle_name")); + TRY_RESULT(last_name, object.get_optional_string_field("last_name")); + TRY_RESULT(native_first_name, object.get_optional_string_field("first_name_native")); + TRY_RESULT(native_middle_name, object.get_optional_string_field("middle_name_native")); + TRY_RESULT(native_last_name, object.get_optional_string_field("last_name_native")); + TRY_RESULT(birthdate, object.get_optional_string_field("birth_date")); if (birthdate.empty()) { return Status::Error(400, "Birthdate must be non-empty"); } - TRY_RESULT(gender, get_json_object_string_field(object, "gender")); - TRY_RESULT(country_code, get_json_object_string_field(object, "country_code")); - TRY_RESULT(residence_country_code, get_json_object_string_field(object, "residence_country_code")); + TRY_RESULT(gender, object.get_optional_string_field("gender")); + TRY_RESULT(country_code, object.get_optional_string_field("country_code")); + TRY_RESULT(residence_country_code, object.get_optional_string_field("residence_country_code")); TRY_STATUS(check_name(first_name)); TRY_STATUS(check_name(middle_name)); @@ -872,7 +869,7 @@ static Result get_identity_document(SecureValueType type, FileManag return Status::Error(400, "Identity document must be non-empty"); } TRY_STATUS(check_document_number(identity_document->number_)); - TRY_RESULT(date, get_date(std::move(identity_document->expiry_date_))); + TRY_RESULT(date, get_date(std::move(identity_document->expiration_date_))); SecureValue res; res.type = type; @@ -936,8 +933,8 @@ static Result> get_identity_documen } auto &object = json_value.get_object(); - TRY_RESULT(number, get_json_object_string_field(object, "document_no")); - TRY_RESULT(expiry_date, get_json_object_string_field(object, "expiry_date")); + TRY_RESULT(number, object.get_optional_string_field("document_no")); + TRY_RESULT(expiry_date, object.get_optional_string_field("expiry_date")); TRY_STATUS(check_document_number(number)); TRY_RESULT(date, get_date_object(expiry_date)); diff --git a/td/telegram/SponsoredMessageManager.cpp b/td/telegram/SponsoredMessageManager.cpp index 6770d97fb01e..ecbd7c37eb88 100644 --- a/td/telegram/SponsoredMessageManager.cpp +++ b/td/telegram/SponsoredMessageManager.cpp @@ -15,6 +15,7 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/OptionManager.h" +#include "td/telegram/Photo.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" @@ -53,7 +54,9 @@ class GetSponsoredMessagesQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - promise_.set_value(result_ptr.move_as_ok()); + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetSponsoredMessagesQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr)); } void on_error(Status status) final { @@ -88,6 +91,38 @@ class ViewSponsoredMessageQuery final : public Td::ResultHandler { } }; +class ClickSponsoredMessageQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ClickSponsoredMessageQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, const string &message_id) { + channel_id_ = channel_id; + auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return promise_.set_value(Unit()); + } + send_query(G()->net_query_creator().create( + telegram_api::channels_clickSponsoredMessage(std::move(input_channel), BufferSlice(message_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_->contacts_manager_->on_get_channel_error(channel_id_, status, "ClickSponsoredMessageQuery"); + promise_.set_error(std::move(status)); + } +}; + struct SponsoredMessageManager::SponsoredMessage { int64 local_id = 0; bool is_recommended = false; @@ -99,10 +134,14 @@ struct SponsoredMessageManager::SponsoredMessage { unique_ptr content; string sponsor_info; string additional_info; + string site_url; + string site_name; + DialogPhoto site_photo; SponsoredMessage(int64 local_id, bool is_recommended, bool show_dialog_photo, DialogId sponsor_dialog_id, ServerMessageId server_message_id, string start_param, string invite_hash, - unique_ptr content, string sponsor_info, string additional_info) + unique_ptr content, string sponsor_info, string additional_info, string site_url, + string site_name, DialogPhoto site_photo) : local_id(local_id) , is_recommended(is_recommended) , show_dialog_photo(show_dialog_photo) @@ -112,14 +151,23 @@ struct SponsoredMessageManager::SponsoredMessage { , invite_hash(std::move(invite_hash)) , content(std::move(content)) , sponsor_info(std::move(sponsor_info)) - , additional_info(std::move(additional_info)) { + , additional_info(std::move(additional_info)) + , site_url(std::move(site_url)) + , site_name(std::move(site_name)) + , site_photo(std::move(site_photo)) { } }; +struct SponsoredMessageManager::SponsoredMessageInfo { + string random_id_; + bool is_viewed_ = false; + bool is_clicked_ = false; +}; + struct SponsoredMessageManager::DialogSponsoredMessages { vector>> promises; vector messages; - FlatHashMap message_random_ids; + FlatHashMap message_infos; int32 messages_between = 0; bool is_premium = false; }; @@ -157,10 +205,10 @@ void SponsoredMessageManager::delete_cached_sponsored_messages(DialogId dialog_i } } -td_api::object_ptr SponsoredMessageManager::get_sponsored_message_object( - DialogId dialog_id, const SponsoredMessage &sponsored_message) const { - td_api::object_ptr chat_invite_link_info; - td_api::object_ptr link; +td_api::object_ptr SponsoredMessageManager::get_message_sponsor_object( + const SponsoredMessage &sponsored_message) const { + td_api::object_ptr type; + td_api::object_ptr photo; switch (sponsored_message.sponsor_dialog_id.get_type()) { case DialogType::User: { auto user_id = sponsored_message.sponsor_dialog_id.get_user_id(); @@ -173,7 +221,13 @@ td_api::object_ptr SponsoredMessageManager::get_sponso LOG(ERROR) << "Sponsor " << user_id << " has no username"; return nullptr; } - link = td_api::make_object(bot_username, sponsored_message.start_param, false); + type = td_api::make_object( + td_->contacts_manager_->get_user_id_object(user_id, "messageSponsorTypeBot"), + td_api::make_object(bot_username, sponsored_message.start_param, false)); + if (sponsored_message.show_dialog_photo) { + photo = get_chat_photo_info_object(td_->file_manager_.get(), + td_->contacts_manager_->get_user_dialog_photo(user_id)); + } break; } case DialogType::Channel: { @@ -182,38 +236,63 @@ td_api::object_ptr SponsoredMessageManager::get_sponso LOG(ERROR) << "Sponsor " << channel_id << " is not a channel"; return nullptr; } + td_api::object_ptr link; if (sponsored_message.server_message_id.is_valid()) { link = td_api::make_object( PSTRING() << LinkManager::get_t_me_url() << "c/" << channel_id.get() << '/' << sponsored_message.server_message_id.get()); } + type = td_api::make_object( + td_->messages_manager_->get_chat_id_object(sponsored_message.sponsor_dialog_id, "sponsoredMessage"), + std::move(link)); + if (sponsored_message.show_dialog_photo) { + photo = get_chat_photo_info_object(td_->file_manager_.get(), + td_->contacts_manager_->get_channel_dialog_photo(channel_id)); + } break; } case DialogType::None: { - CHECK(!sponsored_message.invite_hash.empty()); + if (sponsored_message.invite_hash.empty()) { + CHECK(!sponsored_message.site_url.empty()); + type = td_api::make_object(sponsored_message.site_url, + sponsored_message.site_name); + if (sponsored_message.show_dialog_photo) { + photo = get_chat_photo_info_object(td_->file_manager_.get(), &sponsored_message.site_photo); + } + break; + } auto invite_link = LinkManager::get_dialog_invite_link(sponsored_message.invite_hash, false); - chat_invite_link_info = td_->contacts_manager_->get_chat_invite_link_info_object(invite_link); + auto chat_invite_link_info = td_->contacts_manager_->get_chat_invite_link_info_object(invite_link); if (chat_invite_link_info == nullptr) { LOG(ERROR) << "Failed to get invite link info for " << invite_link; return nullptr; } - if (chat_invite_link_info->type_->get_id() != td_api::chatTypeSupergroup::ID || - !static_cast(chat_invite_link_info->type_.get())->is_channel_) { + if (chat_invite_link_info->type_->get_id() != td_api::inviteLinkChatTypeChannel::ID) { LOG(ERROR) << "Receive sponsor chat of a wrong type " << to_string(chat_invite_link_info->type_); return nullptr; } - link = td_api::make_object( - LinkManager::get_dialog_invite_link(sponsored_message.invite_hash, true)); + type = td_api::make_object(chat_invite_link_info->title_, invite_link); + if (sponsored_message.show_dialog_photo) { + photo = std::move(chat_invite_link_info->photo_); + } + break; } default: break; } + return td_api::make_object(std::move(type), std::move(photo), sponsored_message.sponsor_info); +} + +td_api::object_ptr SponsoredMessageManager::get_sponsored_message_object( + DialogId dialog_id, const SponsoredMessage &sponsored_message) const { + auto sponsor = get_message_sponsor_object(sponsored_message); + if (sponsor == nullptr) { + return nullptr; + } return td_api::make_object( sponsored_message.local_id, sponsored_message.is_recommended, - td_->messages_manager_->get_chat_id_object(sponsored_message.sponsor_dialog_id, "sponsoredMessage"), - std::move(chat_invite_link_info), sponsored_message.show_dialog_photo, std::move(link), get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1), - sponsored_message.sponsor_info, sponsored_message.additional_info); + std::move(sponsor), sponsored_message.additional_info); } td_api::object_ptr SponsoredMessageManager::get_sponsored_messages_object( @@ -270,7 +349,7 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( auto promises = std::move(messages->promises); reset_to_empty(messages->promises); CHECK(messages->messages.empty()); - CHECK(messages->message_random_ids.empty()); + CHECK(messages->message_infos.empty()); if (result.is_error()) { dialog_sponsored_messages_.erase(dialog_id); @@ -291,9 +370,13 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( DialogId sponsor_dialog_id; ServerMessageId server_message_id; string invite_hash; + string site_url; + string site_name; + DialogPhoto site_photo; if (sponsored_message->from_id_ != nullptr) { sponsor_dialog_id = DialogId(sponsored_message->from_id_); - if (!sponsor_dialog_id.is_valid() || !td_->messages_manager_->have_dialog_info_force(sponsor_dialog_id)) { + if (!sponsor_dialog_id.is_valid() || + !td_->messages_manager_->have_dialog_info_force(sponsor_dialog_id, "on_get_dialog_sponsored_messages")) { LOG(ERROR) << "Receive unknown sponsor " << sponsor_dialog_id; continue; } @@ -319,6 +402,13 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( continue; } invite_hash = std::move(sponsored_message->chat_invite_hash_); + } else if (sponsored_message->webpage_ != nullptr && !sponsored_message->webpage_->url_.empty()) { + site_url = std::move(sponsored_message->webpage_->url_); + site_name = std::move(sponsored_message->webpage_->site_name_); + if (sponsored_message->webpage_->photo_ != nullptr) { + auto photo = get_photo(td_, std::move(sponsored_message->webpage_->photo_), DialogId()); + site_photo = as_fake_dialog_photo(photo, DialogId(), false); + } } else { LOG(ERROR) << "Receive " << to_string(sponsored_message); continue; @@ -339,20 +429,22 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( current_sponsored_message_id_ = current_sponsored_message_id_.get_next_message_id(MessageType::Local); if (!current_sponsored_message_id_.is_valid_sponsored()) { - LOG(ERROR) << "Sponsored message ID overflowed"; + LOG(ERROR) << "Sponsored message identifier overflowed"; current_sponsored_message_id_ = MessageId::max().get_next_message_id(MessageType::Local); CHECK(current_sponsored_message_id_.is_valid_sponsored()); } auto local_id = current_sponsored_message_id_.get(); CHECK(!current_sponsored_message_id_.is_valid()); CHECK(!current_sponsored_message_id_.is_scheduled()); - auto is_inserted = - messages->message_random_ids.emplace(local_id, sponsored_message->random_id_.as_slice().str()).second; + SponsoredMessageInfo message_info; + message_info.random_id_ = sponsored_message->random_id_.as_slice().str(); + auto is_inserted = messages->message_infos.emplace(local_id, std::move(message_info)).second; CHECK(is_inserted); messages->messages.emplace_back( local_id, sponsored_message->recommended_, sponsored_message->show_peer_photo_, sponsor_dialog_id, server_message_id, std::move(sponsored_message->start_param_), std::move(invite_hash), std::move(content), - std::move(sponsored_message->sponsor_info_), std::move(sponsored_message->additional_info_)); + std::move(sponsored_message->sponsor_info_), std::move(sponsored_message->additional_info_), + std::move(site_url), std::move(site_name), std::move(site_photo)); } messages->messages_between = sponsored_messages->posts_between_; break; @@ -375,14 +467,32 @@ void SponsoredMessageManager::view_sponsored_message(DialogId dialog_id, Message if (it == dialog_sponsored_messages_.end()) { return; } - auto random_id_it = it->second->message_random_ids.find(sponsored_message_id.get()); - if (random_id_it == it->second->message_random_ids.end()) { + auto random_id_it = it->second->message_infos.find(sponsored_message_id.get()); + if (random_id_it == it->second->message_infos.end() || random_id_it->second.is_viewed_) { return; } - auto random_id = std::move(random_id_it->second); - it->second->message_random_ids.erase(random_id_it); - td_->create_handler()->send(dialog_id.get_channel_id(), random_id); + random_id_it->second.is_viewed_ = true; + td_->create_handler()->send(dialog_id.get_channel_id(), random_id_it->second.random_id_); +} + +void SponsoredMessageManager::click_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id, + Promise &&promise) { + if (!dialog_id.is_valid() || !sponsored_message_id.is_valid_sponsored()) { + return promise.set_error(Status::Error(400, "Invalid message specified")); + } + auto it = dialog_sponsored_messages_.find(dialog_id); + if (it == dialog_sponsored_messages_.end()) { + return promise.set_value(Unit()); + } + auto random_id_it = it->second->message_infos.find(sponsored_message_id.get()); + if (random_id_it == it->second->message_infos.end() || random_id_it->second.is_clicked_) { + return promise.set_value(Unit()); + } + + random_id_it->second.is_clicked_ = true; + td_->create_handler(std::move(promise)) + ->send(dialog_id.get_channel_id(), random_id_it->second.random_id_); } } // namespace td diff --git a/td/telegram/SponsoredMessageManager.h b/td/telegram/SponsoredMessageManager.h index ceda66e6ea1c..34fdc5f533dd 100644 --- a/td/telegram/SponsoredMessageManager.h +++ b/td/telegram/SponsoredMessageManager.h @@ -37,8 +37,11 @@ class SponsoredMessageManager final : public Actor { void view_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id); + void click_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id, Promise &&promise); + private: struct SponsoredMessage; + struct SponsoredMessageInfo; struct DialogSponsoredMessages; void tear_down() final; @@ -48,6 +51,9 @@ class SponsoredMessageManager final : public Actor { void delete_cached_sponsored_messages(DialogId dialog_id); + td_api::object_ptr get_message_sponsor_object( + const SponsoredMessage &sponsored_message) const; + td_api::object_ptr get_sponsored_message_object( DialogId dialog_id, const SponsoredMessage &sponsored_message) const; diff --git a/td/telegram/StickersManager.cpp b/td/telegram/StickersManager.cpp index 50e6f83c2b22..9c853dc5c718 100644 --- a/td/telegram/StickersManager.cpp +++ b/td/telegram/StickersManager.cpp @@ -22,7 +22,6 @@ #include "td/telegram/LanguagePackManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" -#include "td/telegram/MessageReaction.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/net/DcId.h" @@ -54,7 +53,6 @@ #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/Random.h" -#include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/StackAllocator.h" @@ -70,105 +68,6 @@ namespace td { -class GetAvailableReactionsQuery final : public Td::ResultHandler { - public: - void send(int32 hash) { - send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableReactions(hash))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetAvailableReactionsQuery: " << to_string(ptr); - td_->stickers_manager_->on_get_available_reactions(std::move(ptr)); - } - - void on_error(Status status) final { - LOG(INFO) << "Receive error for GetAvailableReactionsQuery: " << status; - td_->stickers_manager_->on_get_available_reactions(nullptr); - } -}; - -class GetRecentReactionsQuery final : public Td::ResultHandler { - public: - void send(int32 limit, int64 hash) { - send_query(G()->net_query_creator().create(telegram_api::messages_getRecentReactions(limit, hash))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetRecentReactionsQuery: " << to_string(ptr); - td_->stickers_manager_->on_get_recent_reactions(std::move(ptr)); - } - - void on_error(Status status) final { - LOG(INFO) << "Receive error for GetRecentReactionsQuery: " << status; - td_->stickers_manager_->on_get_recent_reactions(nullptr); - } -}; - -class GetTopReactionsQuery final : public Td::ResultHandler { - public: - void send(int64 hash) { - send_query(G()->net_query_creator().create(telegram_api::messages_getTopReactions(50, hash))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetTopReactionsQuery: " << to_string(ptr); - td_->stickers_manager_->on_get_top_reactions(std::move(ptr)); - } - - void on_error(Status status) final { - LOG(INFO) << "Receive error for GetTopReactionsQuery: " << status; - td_->stickers_manager_->on_get_top_reactions(nullptr); - } -}; - -class ClearRecentReactionsQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit ClearRecentReactionsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::messages_clearRecentReactions())); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->stickers_manager_->reload_recent_reactions(); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (!G()->is_expected_error(status)) { - LOG(ERROR) << "Receive error for clear recent reactions: " << status; - } - td_->stickers_manager_->reload_recent_reactions(); - promise_.set_error(std::move(status)); - } -}; - class GetAllStickersQuery final : public Td::ResultHandler { StickerType sticker_type_; @@ -1057,8 +956,9 @@ class UploadStickerFileQuery final : public Td::ResultHandler { void on_error(Status status) final { if (was_uploaded_) { CHECK(file_id_.is_valid()); - if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) { - // TODO td_->stickers_manager_->on_upload_sticker_file_part_missing(file_id_, to_integer(status.message().substr(10))); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + // TODO td_->stickers_manager_->on_upload_sticker_file_parts_missing(file_id_, std::move(bad_parts)); // return; } else { if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { @@ -1733,7 +1633,10 @@ void StickersManager::start_up() { } void StickersManager::init() { - if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || G()->close_flag()) { + if (G()->close_flag()) { + return; + } + if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } LOG(INFO) << "Init StickersManager"; @@ -1777,8 +1680,6 @@ void StickersManager::init() { } send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object()); - load_active_reactions(); - on_update_dice_success_values(); on_update_dice_emojis(); @@ -1809,104 +1710,6 @@ void StickersManager::init() { td_->option_manager_->set_option_empty("animated_emoji_sticker_set_name"); // legacy } -td_api::object_ptr StickersManager::get_emoji_reaction_object(const string &emoji) const { - for (auto &reaction : reactions_.reactions_) { - if (reaction.reaction_ == emoji) { - return td_api::make_object( - reaction.reaction_, reaction.title_, reaction.is_active_, get_sticker_object(reaction.static_icon_), - get_sticker_object(reaction.appear_animation_), get_sticker_object(reaction.select_animation_), - get_sticker_object(reaction.activate_animation_), get_sticker_object(reaction.effect_animation_), - get_sticker_object(reaction.around_animation_), get_sticker_object(reaction.center_animation_)); - } - } - return nullptr; -} - -void StickersManager::get_emoji_reaction(const string &emoji, - Promise> &&promise) { - load_reactions(); - if (reactions_.reactions_.empty() && reactions_.are_being_reloaded_) { - pending_get_emoji_reaction_queries_.emplace_back(emoji, std::move(promise)); - return; - } - promise.set_value(get_emoji_reaction_object(emoji)); -} - -vector StickersManager::get_recent_reactions() { - load_recent_reactions(); - return recent_reactions_.reactions_; -} - -vector StickersManager::get_top_reactions() { - load_top_reactions(); - return top_reactions_.reactions_; -} - -void StickersManager::add_recent_reaction(const string &reaction) { - load_recent_reactions(); - - auto &reactions = recent_reactions_.reactions_; - if (!reactions.empty() && reactions[0] == reaction) { - return; - } - - auto it = std::find(reactions.begin(), reactions.end(), reaction); - if (it == reactions.end()) { - if (static_cast(reactions.size()) == MAX_RECENT_REACTIONS) { - reactions.back() = reaction; - } else { - reactions.push_back(reaction); - } - it = reactions.end() - 1; - } - std::rotate(reactions.begin(), it, it + 1); - - recent_reactions_.hash_ = get_reactions_hash(reactions); -} - -void StickersManager::clear_recent_reactions(Promise &&promise) { - load_recent_reactions(); - - if (recent_reactions_.reactions_.empty()) { - return promise.set_value(Unit()); - } - - recent_reactions_.hash_ = 0; - recent_reactions_.reactions_.clear(); - - td_->create_handler(std::move(promise))->send(); -} - -void StickersManager::reload_reactions() { - if (G()->close_flag() || reactions_.are_being_reloaded_) { - return; - } - CHECK(!td_->auth_manager_->is_bot()); - reactions_.are_being_reloaded_ = true; - load_reactions(); // must be after are_being_reloaded_ is set to true to avoid recursion - td_->create_handler()->send(reactions_.hash_); -} - -void StickersManager::reload_recent_reactions() { - if (G()->close_flag() || recent_reactions_.is_being_reloaded_) { - return; - } - CHECK(!td_->auth_manager_->is_bot()); - recent_reactions_.is_being_reloaded_ = true; - load_recent_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion - td_->create_handler()->send(MAX_RECENT_REACTIONS, recent_reactions_.hash_); -} - -void StickersManager::reload_top_reactions() { - if (G()->close_flag() || top_reactions_.is_being_reloaded_) { - return; - } - CHECK(!td_->auth_manager_->is_bot()); - top_reactions_.is_being_reloaded_ = true; - load_top_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion - td_->create_handler()->send(top_reactions_.hash_); -} - StickersManager::SpecialStickerSet &StickersManager::add_special_sticker_set(const SpecialStickerSetType &type) { CHECK(!type.is_empty()); auto &result_ptr = special_sticker_sets_[type]; @@ -2496,6 +2299,34 @@ tl_object_ptr StickersManager::get_stickers_object(const vecto transform(sticker_ids, [&](FileId sticker_id) { return get_sticker_object(sticker_id); })); } +td_api::object_ptr StickersManager::get_sticker_emojis_object(const vector &sticker_ids, + bool return_only_main_emoji) { + auto emojis = td_api::make_object(); + FlatHashSet added_emojis; + auto add_emoji = [&](const string &emoji) { + if (!emoji.empty() && added_emojis.insert(emoji).second) { + emojis->emojis_.push_back(emoji); + } + }; + for (auto sticker_id : sticker_ids) { + auto sticker = get_sticker(sticker_id); + CHECK(sticker != nullptr); + add_emoji(sticker->alt_); + if (!return_only_main_emoji && sticker->set_id_.is_valid()) { + const StickerSet *sticker_set = get_sticker_set(sticker->set_id_); + if (sticker_set != nullptr) { + auto it = sticker_set->sticker_emojis_map_.find(sticker_id); + if (it != sticker_set->sticker_emojis_map_.end()) { + for (auto emoji : it->second) { + add_emoji(emoji); + } + } + } + } + } + return emojis; +} + tl_object_ptr StickersManager::get_dice_stickers_object(const string &emoji, int32 value) const { if (td_->auth_manager_->is_bot()) { return nullptr; @@ -4136,284 +3967,6 @@ void StickersManager::on_get_special_sticker_set(const SpecialStickerSetType &ty on_load_special_sticker_set(type, Status::OK()); } -td_api::object_ptr StickersManager::get_update_active_emoji_reactions_object() - const { - return td_api::make_object(vector(active_reactions_)); -} - -void StickersManager::save_active_reactions() { - LOG(INFO) << "Save " << active_reactions_.size() << " active reactions"; - G()->td_db()->get_binlog_pmc()->set("active_reactions", log_event_store(active_reactions_).as_slice().str()); -} - -void StickersManager::save_reactions() { - LOG(INFO) << "Save " << reactions_.reactions_.size() << " available reactions"; - are_reactions_loaded_from_database_ = true; - G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str()); -} - -void StickersManager::save_recent_reactions() { - LOG(INFO) << "Save " << recent_reactions_.reactions_.size() << " recent reactions"; - are_recent_reactions_loaded_from_database_ = true; - G()->td_db()->get_binlog_pmc()->set("recent_reactions", log_event_store(recent_reactions_).as_slice().str()); -} - -void StickersManager::save_top_reactions() { - LOG(INFO) << "Save " << top_reactions_.reactions_.size() << " top reactions"; - are_top_reactions_loaded_from_database_ = true; - G()->td_db()->get_binlog_pmc()->set("top_reactions", log_event_store(top_reactions_).as_slice().str()); -} - -void StickersManager::load_active_reactions() { - LOG(INFO) << "Loading active reactions"; - string active_reactions = G()->td_db()->get_binlog_pmc()->get("active_reactions"); - if (active_reactions.empty()) { - return reload_reactions(); - } - - auto status = log_event_parse(active_reactions_, active_reactions); - if (status.is_error()) { - LOG(ERROR) << "Can't load active reactions: " << status; - active_reactions_ = {}; - return reload_reactions(); - } - - LOG(INFO) << "Successfully loaded " << active_reactions_.size() << " active reactions"; - - td_->messages_manager_->set_active_reactions(vector(active_reactions_)); - - send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); -} - -void StickersManager::load_reactions() { - if (are_reactions_loaded_from_database_) { - return; - } - are_reactions_loaded_from_database_ = true; - - LOG(INFO) << "Loading available reactions"; - string reactions = G()->td_db()->get_binlog_pmc()->get("reactions"); - if (reactions.empty()) { - return reload_reactions(); - } - - auto new_reactions = reactions_; - auto status = log_event_parse(new_reactions, reactions); - if (status.is_error()) { - LOG(ERROR) << "Can't load available reactions: " << status; - return reload_reactions(); - } - for (auto &reaction : new_reactions.reactions_) { - if (!reaction.is_valid()) { - LOG(ERROR) << "Loaded invalid reaction"; - return reload_reactions(); - } - } - reactions_ = std::move(new_reactions); - - LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions"; - - update_active_reactions(); -} - -void StickersManager::load_recent_reactions() { - if (are_recent_reactions_loaded_from_database_) { - return; - } - are_recent_reactions_loaded_from_database_ = true; - - LOG(INFO) << "Loading recent reactions"; - string recent_reactions = G()->td_db()->get_binlog_pmc()->get("recent_reactions"); - if (recent_reactions.empty()) { - return reload_recent_reactions(); - } - - auto status = log_event_parse(recent_reactions_, recent_reactions); - if (status.is_error()) { - LOG(ERROR) << "Can't load recent reactions: " << status; - recent_reactions_ = {}; - return reload_recent_reactions(); - } - - LOG(INFO) << "Successfully loaded " << recent_reactions_.reactions_.size() << " recent reactions"; -} - -void StickersManager::load_top_reactions() { - if (are_top_reactions_loaded_from_database_) { - return; - } - are_top_reactions_loaded_from_database_ = true; - - LOG(INFO) << "Loading top reactions"; - string top_reactions = G()->td_db()->get_binlog_pmc()->get("top_reactions"); - if (top_reactions.empty()) { - return reload_top_reactions(); - } - - auto status = log_event_parse(top_reactions_, top_reactions); - if (status.is_error()) { - LOG(ERROR) << "Can't load top reactions: " << status; - top_reactions_ = {}; - return reload_top_reactions(); - } - - LOG(INFO) << "Successfully loaded " << top_reactions_.reactions_.size() << " top reactions"; -} - -void StickersManager::update_active_reactions() { - vector active_reactions; - for (auto &reaction : reactions_.reactions_) { - if (reaction.is_active_) { - active_reactions.emplace_back(reaction.reaction_); - } - } - if (active_reactions == active_reactions_) { - return; - } - active_reactions_ = active_reactions; - - save_active_reactions(); - - send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object()); - - td_->messages_manager_->set_active_reactions(std::move(active_reactions)); -} - -void StickersManager::on_get_available_reactions( - tl_object_ptr &&available_reactions_ptr) { - CHECK(reactions_.are_being_reloaded_); - reactions_.are_being_reloaded_ = false; - - auto get_emoji_reaction_queries = std::move(pending_get_emoji_reaction_queries_); - pending_get_emoji_reaction_queries_.clear(); - SCOPE_EXIT { - for (auto &query : get_emoji_reaction_queries) { - query.second.set_value(get_emoji_reaction_object(query.first)); - } - }; - - if (available_reactions_ptr == nullptr) { - // failed to get available reactions - return; - } - - int32 constructor_id = available_reactions_ptr->get_id(); - if (constructor_id == telegram_api::messages_availableReactionsNotModified::ID) { - LOG(INFO) << "Available reactions are not modified"; - return; - } - - CHECK(constructor_id == telegram_api::messages_availableReactions::ID); - auto available_reactions = move_tl_object_as(available_reactions_ptr); - vector new_reactions; - for (auto &available_reaction : available_reactions->reactions_) { - Reaction reaction; - reaction.is_active_ = !available_reaction->inactive_; - reaction.is_premium_ = available_reaction->premium_; - reaction.reaction_ = std::move(available_reaction->reaction_); - reaction.title_ = std::move(available_reaction->title_); - reaction.static_icon_ = - on_get_sticker_document(std::move(available_reaction->static_icon_), StickerFormat::Webp).second; - reaction.appear_animation_ = - on_get_sticker_document(std::move(available_reaction->appear_animation_), StickerFormat::Tgs).second; - reaction.select_animation_ = - on_get_sticker_document(std::move(available_reaction->select_animation_), StickerFormat::Tgs).second; - reaction.activate_animation_ = - on_get_sticker_document(std::move(available_reaction->activate_animation_), StickerFormat::Tgs).second; - reaction.effect_animation_ = - on_get_sticker_document(std::move(available_reaction->effect_animation_), StickerFormat::Tgs).second; - reaction.around_animation_ = - on_get_sticker_document(std::move(available_reaction->around_animation_), StickerFormat::Tgs).second; - reaction.center_animation_ = - on_get_sticker_document(std::move(available_reaction->center_icon_), StickerFormat::Tgs).second; - - if (!reaction.is_valid()) { - LOG(ERROR) << "Receive invalid reaction " << reaction.reaction_; - continue; - } - if (reaction.is_premium_) { - LOG(ERROR) << "Receive premium reaction " << reaction.reaction_; - continue; - } - - new_reactions.push_back(std::move(reaction)); - } - reactions_.reactions_ = std::move(new_reactions); - reactions_.hash_ = available_reactions->hash_; - - save_reactions(); - - update_active_reactions(); -} - -void StickersManager::on_get_recent_reactions(tl_object_ptr &&reactions_ptr) { - CHECK(recent_reactions_.is_being_reloaded_); - recent_reactions_.is_being_reloaded_ = false; - - if (reactions_ptr == nullptr) { - // failed to get recent reactions - return; - } - - int32 constructor_id = reactions_ptr->get_id(); - if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { - LOG(INFO) << "Top reactions are not modified"; - return; - } - - CHECK(constructor_id == telegram_api::messages_reactions::ID); - auto reactions = move_tl_object_as(reactions_ptr); - auto new_reactions = - transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { - return get_message_reaction_string(reaction); - }); - if (new_reactions == recent_reactions_.reactions_ && recent_reactions_.hash_ == reactions->hash_) { - LOG(INFO) << "Top reactions are not modified"; - return; - } - recent_reactions_.reactions_ = std::move(new_reactions); - recent_reactions_.hash_ = reactions->hash_; - - auto expected_hash = get_reactions_hash(recent_reactions_.reactions_); - if (recent_reactions_.hash_ != expected_hash) { - LOG(ERROR) << "Receive hash " << recent_reactions_.hash_ << " instead of " << expected_hash << " for reactions " - << recent_reactions_.reactions_; - } - - save_recent_reactions(); -} - -void StickersManager::on_get_top_reactions(tl_object_ptr &&reactions_ptr) { - CHECK(top_reactions_.is_being_reloaded_); - top_reactions_.is_being_reloaded_ = false; - - if (reactions_ptr == nullptr) { - // failed to get top reactions - return; - } - - int32 constructor_id = reactions_ptr->get_id(); - if (constructor_id == telegram_api::messages_reactionsNotModified::ID) { - LOG(INFO) << "Top reactions are not modified"; - return; - } - - CHECK(constructor_id == telegram_api::messages_reactions::ID); - auto reactions = move_tl_object_as(reactions_ptr); - auto new_reactions = - transform(reactions->reactions_, [](const telegram_api::object_ptr &reaction) { - return get_message_reaction_string(reaction); - }); - if (new_reactions == top_reactions_.reactions_ && top_reactions_.hash_ == reactions->hash_) { - LOG(INFO) << "Top reactions are not modified"; - return; - } - top_reactions_.reactions_ = std::move(new_reactions); - top_reactions_.hash_ = reactions->hash_; - - save_top_reactions(); -} - void StickersManager::on_get_installed_sticker_sets(StickerType sticker_type, tl_object_ptr &&stickers_ptr) { auto type = static_cast(sticker_type); @@ -6377,19 +5930,19 @@ void StickersManager::get_default_emoji_statuses(bool is_recursive, return; } - vector> statuses; + vector custom_emoji_ids; for (auto sticker_id : sticker_set->sticker_ids_) { auto custom_emoji_id = get_custom_emoji_id(sticker_id); if (!custom_emoji_id.is_valid()) { LOG(ERROR) << "Ignore wrong sticker " << sticker_id; continue; } - statuses.push_back(td_api::make_object(custom_emoji_id.get())); - if (statuses.size() >= 8) { + custom_emoji_ids.push_back(custom_emoji_id.get()); + if (custom_emoji_ids.size() >= 8) { break; } } - promise.set_value(td_api::make_object(std::move(statuses))); + promise.set_value(td_api::make_object(std::move(custom_emoji_ids))); } bool StickersManager::is_default_emoji_status(CustomEmojiId custom_emoji_id) { @@ -7098,11 +6651,11 @@ Status StickersManager::on_animated_emoji_message_clicked(string &&emoji, FullMe return Status::Error("Expected an object"); } auto &object = value.get_object(); - TRY_RESULT(version, get_json_object_int_field(object, "v", false)); + TRY_RESULT(version, object.get_required_int_field("v")); if (version != 1) { return Status::OK(); } - TRY_RESULT(array_value, get_json_object_field(object, "a", JsonValue::Type::Array, false)); + TRY_RESULT(array_value, object.extract_required_field("a", JsonValue::Type::Array)); auto &array = array_value.get_array(); if (array.size() > 20) { return Status::Error("Click array is too big"); @@ -7115,11 +6668,11 @@ Status StickersManager::on_animated_emoji_message_clicked(string &&emoji, FullMe return Status::Error("Expected clicks as JSON objects"); } auto &click_object = click.get_object(); - TRY_RESULT(index, get_json_object_int_field(click_object, "i", false)); + TRY_RESULT(index, click_object.get_required_int_field("i")); if (index <= 0 || index > 9) { return Status::Error("Wrong index"); } - TRY_RESULT(start_time, get_json_object_double_field(click_object, "t", false)); + TRY_RESULT(start_time, click_object.get_required_double_field("t")); if (!std::isfinite(start_time)) { return Status::Error("Receive invalid start time"); } @@ -7228,15 +6781,6 @@ void StickersManager::send_update_animated_emoji_clicked(FullMessageId full_mess full_message_id.get_message_id().get(), get_sticker_object(sticker_id, false, true))); } -bool StickersManager::is_active_reaction(const string &reaction) const { - for (auto &supported_reaction : reactions_.reactions_) { - if (supported_reaction.reaction_ == reaction) { - return supported_reaction.is_active_; - } - } - return false; -} - void StickersManager::view_featured_sticker_sets(const vector &sticker_set_ids) { for (auto sticker_set_id : sticker_set_ids) { auto set = get_sticker_set(sticker_set_id); @@ -8340,8 +7884,6 @@ void StickersManager::on_upload_sticker_file_error(FileId file_id, Status status being_uploaded_files_.erase(it); - // TODO FILE_PART_X_MISSING support - promise.set_error(Status::Error(status.code() > 0 ? status.code() : 500, status.message())); // TODO CHECK that status has always a code } @@ -10413,9 +9955,6 @@ void StickersManager::get_current_state(vector(type))); diff --git a/td/telegram/StickersManager.h b/td/telegram/StickersManager.h index 6a82fc00c4f5..71d105a3f5e0 100644 --- a/td/telegram/StickersManager.h +++ b/td/telegram/StickersManager.h @@ -81,6 +81,9 @@ class StickersManager final : public Actor { tl_object_ptr get_stickers_object(const vector &sticker_ids) const; + td_api::object_ptr get_sticker_emojis_object(const vector &sticker_ids, + bool return_only_main_emoji); + td_api::object_ptr get_custom_emoji_sticker_object(CustomEmojiId custom_emoji_id); tl_object_ptr get_dice_stickers_object(const string &emoji, int32 value) const; @@ -146,8 +149,6 @@ class StickersManager final : public Actor { Status on_animated_emoji_message_clicked(string &&emoji, FullMessageId full_message_id, string data); - bool is_active_reaction(const string &reaction) const; - void create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail, PhotoSize thumbnail, Dimensions dimensions, tl_object_ptr sticker, tl_object_ptr custom_emoji, @@ -193,29 +194,10 @@ class StickersManager final : public Actor { void view_featured_sticker_sets(const vector &sticker_set_ids); - void get_emoji_reaction(const string &emoji, Promise> &&promise); - - vector get_recent_reactions(); - - vector get_top_reactions(); - - void add_recent_reaction(const string &reaction); - - void clear_recent_reactions(Promise &&promise); - - void reload_reactions(); - - void reload_recent_reactions(); - - void reload_top_reactions(); - void reload_special_sticker_set_by_type(SpecialStickerSetType type, bool is_recursive = false); - void on_get_available_reactions(tl_object_ptr &&available_reactions_ptr); - - void on_get_recent_reactions(tl_object_ptr &&reactions_ptr); - - void on_get_top_reactions(tl_object_ptr &&reactions_ptr); + std::pair on_get_sticker_document(tl_object_ptr &&document_ptr, + StickerFormat expected_format); void on_get_installed_sticker_sets(StickerType sticker_type, tl_object_ptr &&stickers_ptr); @@ -457,8 +439,6 @@ class StickersManager final : public Actor { static constexpr int32 EMOJI_KEYWORDS_UPDATE_DELAY = 3600; static constexpr double MIN_ANIMATED_EMOJI_CLICK_DELAY = 0.2; - static constexpr int32 MAX_RECENT_REACTIONS = 100; // some reasonable value - class Sticker { public: StickerSetId set_id_; @@ -583,55 +563,6 @@ class StickersManager final : public Actor { void parse(ParserT &parser); }; - struct Reaction { - string reaction_; - string title_; - bool is_active_ = false; - bool is_premium_ = false; - FileId static_icon_; - FileId appear_animation_; - FileId select_animation_; - FileId activate_animation_; - FileId effect_animation_; - FileId around_animation_; - FileId center_animation_; - - bool is_valid() const { - return static_icon_.is_valid() && appear_animation_.is_valid() && select_animation_.is_valid() && - activate_animation_.is_valid() && effect_animation_.is_valid() && !reaction_.empty(); - } - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct Reactions { - int32 hash_ = 0; - bool are_being_reloaded_ = false; - vector reactions_; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct ReactionList { - int64 hash_ = 0; - bool is_being_reloaded_ = false; - vector reactions_; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - class CustomEmojiLogEvent; class CustomEmojiIdsLogEvent; class StickerListLogEvent; @@ -654,8 +585,6 @@ class StickersManager final : public Actor { tl_object_ptr get_sticker_set_info_object(StickerSetId sticker_set_id, size_t covers_limit, bool prefer_premium) const; - td_api::object_ptr get_emoji_reaction_object(const string &emoji) const; - Sticker *get_sticker(FileId file_id); const Sticker *get_sticker(FileId file_id) const; @@ -708,9 +637,6 @@ class StickersManager final : public Actor { static PhotoFormat get_sticker_set_thumbnail_format(StickerFormat sticker_format); - std::pair on_get_sticker_document(tl_object_ptr &&document_ptr, - StickerFormat expected_format); - static tl_object_ptr get_input_sticker_set(const StickerSet *set); StickerSetId on_get_input_sticker_set(FileId sticker_file_id, tl_object_ptr &&set_ptr, @@ -946,26 +872,6 @@ class StickersManager final : public Actor { void tear_down() final; - void save_active_reactions(); - - void save_reactions(); - - void save_recent_reactions(); - - void save_top_reactions(); - - void load_active_reactions(); - - void load_reactions(); - - void load_recent_reactions(); - - void load_top_reactions(); - - void update_active_reactions(); - - td_api::object_ptr get_update_active_emoji_reactions_object() const; - SpecialStickerSet &add_special_sticker_set(const SpecialStickerSetType &type); static void init_special_sticker_set(SpecialStickerSet &sticker_set, int64 sticker_set_id, int64 access_hash, @@ -1053,7 +959,7 @@ class StickersManager final : public Actor { bool is_inited_ = false; - WaitFreeHashMap, FileIdHash> stickers_; // file_id -> Sticker + WaitFreeHashMap, FileIdHash> stickers_; WaitFreeHashMap, StickerSetIdHash> sticker_sets_; // sticker_set_id -> StickerSet WaitFreeHashMap short_name_to_sticker_set_id_; @@ -1155,8 +1061,6 @@ class StickersManager final : public Actor { vector> pending_get_default_statuses_queries_; vector> pending_get_default_topic_icons_queries_; - vector>>> pending_get_emoji_reaction_queries_; - double next_click_animated_emoji_message_time_ = 0; double next_update_animated_emoji_clicked_time_ = 0; vector pending_get_animated_emoji_click_stickers_; @@ -1177,16 +1081,6 @@ class StickersManager final : public Actor { FlatHashMap>, FileIdHash> being_uploaded_files_; - Reactions reactions_; - vector active_reactions_; - - ReactionList recent_reactions_; - ReactionList top_reactions_; - - bool are_reactions_loaded_from_database_ = false; - bool are_recent_reactions_loaded_from_database_ = false; - bool are_top_reactions_loaded_from_database_ = false; - FlatHashMap> emoji_language_codes_; FlatHashMap emoji_language_code_versions_; FlatHashMap emoji_language_code_last_difference_times_; diff --git a/td/telegram/StickersManager.hpp b/td/telegram/StickersManager.hpp index 674f2e4e5cb8..ad64127db304 100644 --- a/td/telegram/StickersManager.hpp +++ b/td/telegram/StickersManager.hpp @@ -13,8 +13,6 @@ #include "td/telegram/PhotoSize.hpp" #include "td/telegram/StickerFormat.h" #include "td/telegram/StickerMaskPosition.hpp" -#include "td/telegram/StickersManager.h" -#include "td/telegram/Td.h" #include "td/utils/emoji.h" #include "td/utils/logging.h" @@ -465,106 +463,4 @@ void StickersManager::parse_sticker_set_id(StickerSetId &sticker_set_id, ParserT add_sticker_set(sticker_set_id, sticker_set_access_hash); } -template -void StickersManager::Reaction::store(StorerT &storer) const { - StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); - bool has_around_animation = !around_animation_.empty(); - bool has_center_animation = !center_animation_.empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(is_active_); - STORE_FLAG(has_around_animation); - STORE_FLAG(has_center_animation); - STORE_FLAG(is_premium_); - END_STORE_FLAGS(); - td::store(reaction_, storer); - td::store(title_, storer); - stickers_manager->store_sticker(static_icon_, false, storer, "Reaction"); - stickers_manager->store_sticker(appear_animation_, false, storer, "Reaction"); - stickers_manager->store_sticker(select_animation_, false, storer, "Reaction"); - stickers_manager->store_sticker(activate_animation_, false, storer, "Reaction"); - stickers_manager->store_sticker(effect_animation_, false, storer, "Reaction"); - if (has_around_animation) { - stickers_manager->store_sticker(around_animation_, false, storer, "Reaction"); - } - if (has_center_animation) { - stickers_manager->store_sticker(center_animation_, false, storer, "Reaction"); - } -} - -template -void StickersManager::Reaction::parse(ParserT &parser) { - StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); - bool has_around_animation; - bool has_center_animation; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(is_active_); - PARSE_FLAG(has_around_animation); - PARSE_FLAG(has_center_animation); - PARSE_FLAG(is_premium_); - END_PARSE_FLAGS(); - td::parse(reaction_, parser); - td::parse(title_, parser); - static_icon_ = stickers_manager->parse_sticker(false, parser); - appear_animation_ = stickers_manager->parse_sticker(false, parser); - select_animation_ = stickers_manager->parse_sticker(false, parser); - activate_animation_ = stickers_manager->parse_sticker(false, parser); - effect_animation_ = stickers_manager->parse_sticker(false, parser); - if (has_around_animation) { - around_animation_ = stickers_manager->parse_sticker(false, parser); - } - if (has_center_animation) { - center_animation_ = stickers_manager->parse_sticker(false, parser); - } - - is_premium_ = false; -} - -template -void StickersManager::Reactions::store(StorerT &storer) const { - bool has_reactions = !reactions_.empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_reactions); - END_STORE_FLAGS(); - if (has_reactions) { - td::store(reactions_, storer); - td::store(hash_, storer); - } -} - -template -void StickersManager::Reactions::parse(ParserT &parser) { - bool has_reactions; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_reactions); - END_PARSE_FLAGS(); - if (has_reactions) { - td::parse(reactions_, parser); - td::parse(hash_, parser); - } -} - -template -void StickersManager::ReactionList::store(StorerT &storer) const { - bool has_reactions = !reactions_.empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_reactions); - END_STORE_FLAGS(); - if (has_reactions) { - td::store(reactions_, storer); - td::store(hash_, storer); - } -} - -template -void StickersManager::ReactionList::parse(ParserT &parser) { - bool has_reactions; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_reactions); - END_PARSE_FLAGS(); - if (has_reactions) { - td::parse(reactions_, parser); - td::parse(hash_, parser); - } -} - } // namespace td diff --git a/td/telegram/StoryContent.cpp b/td/telegram/StoryContent.cpp new file mode 100644 index 000000000000..f95db21f35e2 --- /dev/null +++ b/td/telegram/StoryContent.cpp @@ -0,0 +1,538 @@ +// +// 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/StoryContent.h" + +#include "td/telegram/Dependencies.h" +#include "td/telegram/Dimensions.h" +#include "td/telegram/Document.h" +#include "td/telegram/DocumentsManager.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" +#include "td/telegram/Photo.h" +#include "td/telegram/Photo.hpp" +#include "td/telegram/PhotoSize.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/VideosManager.h" + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/tl_helpers.h" + +#include + +namespace td { + +class StoryContentPhoto final : public StoryContent { + public: + Photo photo_; + + StoryContentPhoto() = default; + explicit StoryContentPhoto(Photo &&photo) : photo_(std::move(photo)) { + } + + StoryContentType get_type() const final { + return StoryContentType::Photo; + } +}; + +class StoryContentVideo final : public StoryContent { + public: + FileId file_id_; + FileId alt_file_id_; + + StoryContentVideo() = default; + StoryContentVideo(FileId file_id, FileId alt_file_id) : file_id_(file_id), alt_file_id_(alt_file_id) { + } + + StoryContentType get_type() const final { + return StoryContentType::Video; + } +}; + +class StoryContentUnsupported final : public StoryContent { + public: + static constexpr int32 CURRENT_VERSION = 1; + int32 version_ = CURRENT_VERSION; + + StoryContentUnsupported() = default; + explicit StoryContentUnsupported(int32 version) : version_(version) { + } + + StoryContentType get_type() const final { + return StoryContentType::Unsupported; + } +}; + +template +static void store(const StoryContent *content, StorerT &storer) { + CHECK(content != nullptr); + + Td *td = storer.context()->td().get_actor_unsafe(); + CHECK(td != nullptr); + + auto content_type = content->get_type(); + store(content_type, storer); + + switch (content_type) { + case StoryContentType::Photo: { + const auto *story_content = static_cast(content); + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + store(story_content->photo_, storer); + break; + } + case StoryContentType::Video: { + const auto *story_content = static_cast(content); + bool has_alt_file_id = story_content->alt_file_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_alt_file_id); + END_STORE_FLAGS(); + td->videos_manager_->store_video(story_content->file_id_, storer); + if (has_alt_file_id) { + td->videos_manager_->store_video(story_content->alt_file_id_, storer); + } + break; + } + case StoryContentType::Unsupported: { + const auto *story_content = static_cast(content); + store(story_content->version_, storer); + break; + } + default: + UNREACHABLE(); + } +} + +template +static void parse(unique_ptr &content, ParserT &parser) { + Td *td = parser.context()->td().get_actor_unsafe(); + CHECK(td != nullptr); + + StoryContentType content_type; + parse(content_type, parser); + + bool is_bad = false; + switch (content_type) { + case StoryContentType::Photo: { + auto story_content = make_unique(); + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + parse(story_content->photo_, parser); + is_bad |= story_content->photo_.is_bad(); + content = std::move(story_content); + break; + } + case StoryContentType::Video: { + auto story_content = make_unique(); + bool has_alt_file_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_alt_file_id); + END_PARSE_FLAGS(); + story_content->file_id_ = td->videos_manager_->parse_video(parser); + if (has_alt_file_id) { + story_content->alt_file_id_ = td->videos_manager_->parse_video(parser); + if (!story_content->alt_file_id_.is_valid()) { + LOG(ERROR) << "Failed to parse alternative video"; + } + } + content = std::move(story_content); + break; + } + case StoryContentType::Unsupported: { + auto story_content = make_unique(); + parse(story_content->version_, parser); + content = std::move(story_content); + break; + } + default: + is_bad = true; + } + if (is_bad) { + LOG(ERROR) << "Load a story with an invalid content of type " << content_type; + content = make_unique(0); + } +} + +void store_story_content(const StoryContent *content, LogEventStorerCalcLength &storer) { + store(content, storer); +} + +void store_story_content(const StoryContent *content, LogEventStorerUnsafe &storer) { + store(content, storer); +} + +void parse_story_content(unique_ptr &content, LogEventParser &parser) { + parse(content, parser); +} + +void add_story_content_dependencies(Dependencies &dependencies, const StoryContent *story_content) { + switch (story_content->get_type()) { + case StoryContentType::Photo: + break; + case StoryContentType::Video: + break; + case StoryContentType::Unsupported: + break; + default: + UNREACHABLE(); + break; + } +} + +unique_ptr get_story_content(Td *td, tl_object_ptr &&media_ptr, + DialogId owner_dialog_id) { + CHECK(media_ptr != nullptr); + int32 constructor_id = media_ptr->get_id(); + switch (constructor_id) { + case telegram_api::messageMediaPhoto::ID: { + auto media = move_tl_object_as(media_ptr); + if (media->photo_ == nullptr || (media->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) != 0 || + media->spoiler_) { + LOG(ERROR) << "Receive a story with content " << to_string(media); + break; + } + + auto photo = get_photo(td, std::move(media->photo_), owner_dialog_id, FileType::PhotoStory); + if (photo.is_empty()) { + LOG(ERROR) << "Receive a story with empty photo"; + break; + } + return make_unique(std::move(photo)); + } + case telegram_api::messageMediaDocument::ID: { + auto media = move_tl_object_as(media_ptr); + if (media->document_ == nullptr || (media->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) != 0 || + media->spoiler_) { + LOG(ERROR) << "Receive a story with content " << to_string(media); + break; + } + + auto document_ptr = std::move(media->document_); + int32 document_id = document_ptr->get_id(); + if (document_id == telegram_api::documentEmpty::ID) { + LOG(ERROR) << "Receive a story with empty document"; + break; + } + CHECK(document_id == telegram_api::document::ID); + auto parsed_document = td->documents_manager_->on_get_document( + move_tl_object_as(document_ptr), owner_dialog_id, nullptr, Document::Type::Video, + DocumentsManager::Subtype::Story); + if (parsed_document.empty() || parsed_document.type != Document::Type::Video) { + LOG(ERROR) << "Receive a story with " << parsed_document; + break; + } + CHECK(parsed_document.file_id.is_valid()); + + FileId alt_file_id; + if (media->alt_document_ != nullptr) { + auto alt_document_ptr = std::move(media->alt_document_); + int32 alt_document_id = alt_document_ptr->get_id(); + if (alt_document_id == telegram_api::documentEmpty::ID) { + LOG(ERROR) << "Receive alternative " << to_string(alt_document_ptr); + } else { + CHECK(alt_document_id == telegram_api::document::ID); + auto parsed_alt_document = td->documents_manager_->on_get_document( + move_tl_object_as(alt_document_ptr), owner_dialog_id, nullptr, + Document::Type::Video, DocumentsManager::Subtype::Story); + if (parsed_alt_document.empty() || parsed_alt_document.type != Document::Type::Video) { + LOG(ERROR) << "Receive alternative " << to_string(alt_document_ptr); + } else { + alt_file_id = parsed_alt_document.file_id; + } + } + } + + return make_unique(parsed_document.file_id, alt_file_id); + } + case telegram_api::messageMediaUnsupported::ID: + return make_unique(); + default: + break; + } + return nullptr; +} + +Result> get_input_story_content( + Td *td, td_api::object_ptr &&input_story_content, DialogId owner_dialog_id) { + LOG(INFO) << "Get input story content from " << to_string(input_story_content); + if (input_story_content == nullptr) { + return Status::Error(400, "Input story content must be non-empty"); + } + + switch (input_story_content->get_id()) { + case td_api::inputStoryContentPhoto::ID: { + auto input_story = static_cast(input_story_content.get()); + TRY_RESULT(file_id, td->file_manager_->get_input_file_id(FileType::Photo, input_story->photo_, owner_dialog_id, + false, false)); + file_id = + td->file_manager_->copy_file_id(file_id, FileType::PhotoStory, owner_dialog_id, "get_input_story_content"); + auto sticker_file_ids = + td->stickers_manager_->get_attached_sticker_file_ids(input_story->added_sticker_file_ids_); + TRY_RESULT(photo, + create_photo(td->file_manager_.get(), file_id, PhotoSize(), 720, 1280, std::move(sticker_file_ids))); + return make_unique(std::move(photo)); + } + case td_api::inputStoryContentVideo::ID: { + auto input_story = static_cast(input_story_content.get()); + TRY_RESULT(file_id, td->file_manager_->get_input_file_id(FileType::Video, input_story->video_, owner_dialog_id, + false, false)); + file_id = + td->file_manager_->copy_file_id(file_id, FileType::VideoStory, owner_dialog_id, "get_input_story_content"); + if (input_story->duration_ < 0 || input_story->duration_ > 60.0) { + return Status::Error(400, "Invalid video duration specified"); + } + auto sticker_file_ids = + td->stickers_manager_->get_attached_sticker_file_ids(input_story->added_sticker_file_ids_); + bool has_stickers = !sticker_file_ids.empty(); + td->videos_manager_->create_video(file_id, string(), PhotoSize(), AnimationSize(), has_stickers, + std::move(sticker_file_ids), "story.mp4", "video/mp4", + static_cast(std::ceil(input_story->duration_)), input_story->duration_, + get_dimensions(720, 1280, nullptr), true, input_story->is_animation_, 0, false); + + return make_unique(file_id, FileId()); + } + default: + UNREACHABLE(); + return nullptr; + } +} + +telegram_api::object_ptr get_story_content_input_media( + Td *td, const StoryContent *content, telegram_api::object_ptr input_file) { + switch (content->get_type()) { + case StoryContentType::Photo: { + const auto *story_content = static_cast(content); + return photo_get_input_media(td->file_manager_.get(), story_content->photo_, std::move(input_file), 0, false); + } + case StoryContentType::Video: { + const auto *story_content = static_cast(content); + return td->videos_manager_->get_input_media(story_content->file_id_, std::move(input_file), nullptr, 0, false); + } + case StoryContentType::Unsupported: + default: + UNREACHABLE(); + return nullptr; + } +} + +void compare_story_contents(const StoryContent *old_content, const StoryContent *new_content, bool &is_content_changed, + bool &need_update) { + StoryContentType content_type = new_content->get_type(); + if (old_content->get_type() != content_type) { + need_update = true; + return; + } + + switch (content_type) { + case StoryContentType::Photo: { + const auto *old_ = static_cast(old_content); + const auto *new_ = static_cast(new_content); + if (old_->photo_ != new_->photo_) { + need_update = true; + } + break; + } + case StoryContentType::Video: { + const auto *old_ = static_cast(old_content); + const auto *new_ = static_cast(new_content); + if (old_->file_id_ != new_->file_id_ || old_->alt_file_id_ != new_->alt_file_id_) { + need_update = true; + } + break; + } + case StoryContentType::Unsupported: { + const auto *old_ = static_cast(old_content); + const auto *new_ = static_cast(new_content); + if (old_->version_ != new_->version_) { + is_content_changed = true; + } + break; + } + default: + UNREACHABLE(); + break; + } +} + +void merge_story_contents(Td *td, const StoryContent *old_content, StoryContent *new_content, DialogId dialog_id, + bool &is_content_changed, bool &need_update) { + StoryContentType content_type = new_content->get_type(); + CHECK(old_content->get_type() == content_type); + + switch (content_type) { + case StoryContentType::Photo: { + const auto *old_ = static_cast(old_content); + auto *new_ = static_cast(new_content); + merge_photos(td, &old_->photo_, &new_->photo_, dialog_id, false, is_content_changed, need_update); + break; + } + case StoryContentType::Video: { + const auto *old_ = static_cast(old_content); + const auto *new_ = static_cast(new_content); + if (old_->file_id_ != new_->file_id_ || old_->alt_file_id_ != new_->alt_file_id_) { + need_update = true; + } + break; + } + case StoryContentType::Unsupported: { + const auto *old_ = static_cast(old_content); + const auto *new_ = static_cast(new_content); + if (old_->version_ != new_->version_) { + is_content_changed = true; + } + break; + } + default: + UNREACHABLE(); + break; + } +} + +unique_ptr copy_story_content(const StoryContent *content) { + if (content == nullptr) { + return nullptr; + } + switch (content->get_type()) { + case StoryContentType::Photo: { + const auto *story_content = static_cast(content); + return make_unique(Photo(story_content->photo_)); + } + case StoryContentType::Video: { + const auto *story_content = static_cast(content); + return make_unique(story_content->file_id_, story_content->alt_file_id_); + } + case StoryContentType::Unsupported: { + const auto *story_content = static_cast(content); + return make_unique(story_content->version_); + } + default: + UNREACHABLE(); + return nullptr; + } +} + +unique_ptr dup_story_content(Td *td, const StoryContent *content) { + if (content == nullptr) { + return nullptr; + } + + auto fix_file_id = [file_manager = td->file_manager_.get()](FileId file_id) { + return file_manager->dup_file_id(file_id, "dup_story_content"); + }; + + switch (content->get_type()) { + case StoryContentType::Photo: { + const auto *old_content = static_cast(content); + // Find 'i' or largest + PhotoSize photo_size; + for (const auto &size : old_content->photo_.photos) { + if (size.type == 'i') { + photo_size = size; + } + } + if (photo_size.type == 0) { + for (const auto &size : old_content->photo_.photos) { + if (photo_size.type == 0 || photo_size < size) { + photo_size = size; + } + } + } + photo_size.type = 'i'; + photo_size.file_id = fix_file_id(photo_size.file_id); + + auto result = make_unique(Photo(old_content->photo_)); + + result->photo_.photos.clear(); + result->photo_.animations.clear(); + result->photo_.sticker_photo_size = nullptr; + + result->photo_.photos.push_back(std::move(photo_size)); + return std::move(result); + } + case StoryContentType::Video: { + const auto *old_content = static_cast(content); + return make_unique( + td->videos_manager_->dup_video(fix_file_id(old_content->file_id_), old_content->file_id_), FileId()); + } + case StoryContentType::Unsupported: + return nullptr; + default: + UNREACHABLE(); + return nullptr; + } +} + +td_api::object_ptr get_story_content_object(Td *td, const StoryContent *content) { + CHECK(content != nullptr); + switch (content->get_type()) { + case StoryContentType::Photo: { + const auto *s = static_cast(content); + auto photo = get_photo_object(td->file_manager_.get(), s->photo_); + if (photo == nullptr) { + return td_api::make_object(); + } + return td_api::make_object(std::move(photo)); + } + case StoryContentType::Video: { + const auto *s = static_cast(content); + return td_api::make_object( + td->videos_manager_->get_story_video_object(s->file_id_), + td->videos_manager_->get_story_video_object(s->alt_file_id_)); + } + case StoryContentType::Unsupported: + return td_api::make_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +FileId get_story_content_any_file_id(const Td *td, const StoryContent *content) { + switch (content->get_type()) { + case StoryContentType::Photo: + return get_photo_any_file_id(static_cast(content)->photo_); + case StoryContentType::Video: + return static_cast(content)->file_id_; + case StoryContentType::Unsupported: + default: + return {}; + } +} + +vector get_story_content_file_ids(const Td *td, const StoryContent *content) { + switch (content->get_type()) { + case StoryContentType::Photo: + return photo_get_file_ids(static_cast(content)->photo_); + case StoryContentType::Video: { + vector result; + const auto *s = static_cast(content); + Document(Document::Type::Video, s->file_id_).append_file_ids(td, result); + Document(Document::Type::Video, s->alt_file_id_).append_file_ids(td, result); + return result; + } + case StoryContentType::Unsupported: + default: + return {}; + } +} + +int32 get_story_content_duration(const Td *td, const StoryContent *content) { + CHECK(content != nullptr); + switch (content->get_type()) { + case StoryContentType::Video: { + auto file_id = static_cast(content)->file_id_; + return td->videos_manager_->get_video_duration(file_id); + } + default: + return -1; + } +} + +} // namespace td diff --git a/td/telegram/StoryContent.h b/td/telegram/StoryContent.h new file mode 100644 index 000000000000..244369d1dc1d --- /dev/null +++ b/td/telegram/StoryContent.h @@ -0,0 +1,71 @@ +// +// 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/DialogId.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/StoryContentType.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 StoryContent { + public: + StoryContent() = default; + StoryContent(const StoryContent &) = default; + StoryContent &operator=(const StoryContent &) = default; + StoryContent(StoryContent &&) = default; + StoryContent &operator=(StoryContent &&) = default; + + virtual StoryContentType get_type() const = 0; + virtual ~StoryContent() = default; +}; + +void store_story_content(const StoryContent *content, LogEventStorerCalcLength &storer); + +void store_story_content(const StoryContent *content, LogEventStorerUnsafe &storer); + +void parse_story_content(unique_ptr &content, LogEventParser &parser); + +void add_story_content_dependencies(Dependencies &dependencies, const StoryContent *story_content); + +unique_ptr get_story_content(Td *td, telegram_api::object_ptr &&media_ptr, + DialogId owner_dialog_id); + +Result> get_input_story_content( + Td *td, td_api::object_ptr &&input_story_content, DialogId owner_dialog_id); + +telegram_api::object_ptr get_story_content_input_media( + Td *td, const StoryContent *content, telegram_api::object_ptr input_file); + +void compare_story_contents(Td *td, const StoryContent *old_content, const StoryContent *new_content, + bool &is_content_changed, bool &need_update); + +void merge_story_contents(Td *td, const StoryContent *old_content, StoryContent *new_content, DialogId dialog_id, + bool &is_content_changed, bool &need_update); + +unique_ptr copy_story_content(const StoryContent *content); + +unique_ptr dup_story_content(Td *td, const StoryContent *content); + +td_api::object_ptr get_story_content_object(Td *td, const StoryContent *content); + +FileId get_story_content_any_file_id(const Td *td, const StoryContent *content); + +vector get_story_content_file_ids(const Td *td, const StoryContent *content); + +int32 get_story_content_duration(const Td *td, const StoryContent *content); + +} // namespace td diff --git a/td/telegram/StoryContentType.cpp b/td/telegram/StoryContentType.cpp new file mode 100644 index 000000000000..f467c2663efc --- /dev/null +++ b/td/telegram/StoryContentType.cpp @@ -0,0 +1,24 @@ +// +// 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/StoryContentType.h" + +namespace td { + +StringBuilder &operator<<(StringBuilder &string_builder, StoryContentType content_type) { + switch (content_type) { + case StoryContentType::Photo: + return string_builder << "Photo"; + case StoryContentType::Video: + return string_builder << "Video"; + case StoryContentType::Unsupported: + return string_builder << "Unsupported"; + default: + return string_builder << "Invalid type " << static_cast(content_type); + } +} + +} // namespace td diff --git a/td/telegram/StoryContentType.h b/td/telegram/StoryContentType.h new file mode 100644 index 000000000000..ef34a32c29ee --- /dev/null +++ b/td/telegram/StoryContentType.h @@ -0,0 +1,26 @@ +// +// 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/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +// increase StoryContentUnsupported::CURRENT_VERSION each time a new Story content type is added +enum class StoryContentType : int32 { Photo, Video, Unsupported }; + +StringBuilder &operator<<(StringBuilder &string_builder, StoryContentType content_type); + +struct StoryContentTypeHash { + uint32 operator()(StoryContentType content_type) const { + return Hash()(static_cast(content_type)); + } +}; + +} // namespace td diff --git a/td/telegram/StoryDb.cpp b/td/telegram/StoryDb.cpp new file mode 100644 index 000000000000..5d878f24a3b1 --- /dev/null +++ b/td/telegram/StoryDb.cpp @@ -0,0 +1,569 @@ +// +// 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/StoryDb.h" + +#include "td/telegram/StoryId.h" +#include "td/telegram/Version.h" + +#include "td/db/SqliteConnectionSafe.h" +#include "td/db/SqliteDb.h" +#include "td/db/SqliteStatement.h" + +#include "td/actor/actor.h" +#include "td/actor/SchedulerLocalStorage.h" + +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" + +#include + +namespace td { + +// NB: must happen inside a transaction +Status init_story_db(SqliteDb &db, int32 version) { + LOG(INFO) << "Init story database " << tag("version", version); + + // Check if database exists + TRY_RESULT(has_stories_table, db.has_table("stories")); + TRY_RESULT(has_active_stories_table, db.has_table("active_stories")); + TRY_RESULT(has_story_list_table, db.has_table("active_story_lists")); + if ((!has_stories_table || !has_active_stories_table || !has_story_list_table) || version > current_db_version()) { + TRY_STATUS(drop_story_db(db, version)); + version = 0; + } + + if (version == 0) { + LOG(INFO) << "Create new story database"; + TRY_STATUS( + db.exec("CREATE TABLE IF NOT EXISTS stories (dialog_id INT8, story_id INT4, expires_at INT4, notification_id " + "INT4, data BLOB, PRIMARY KEY (dialog_id, story_id))")); + + TRY_STATUS(db.exec("CREATE INDEX IF NOT EXISTS story_by_ttl ON stories (expires_at) WHERE expires_at IS NOT NULL")); + + TRY_STATUS( + db.exec("CREATE INDEX IF NOT EXISTS story_by_notification_id ON stories (dialog_id, notification_id) WHERE " + "notification_id IS NOT NULL")); + + TRY_STATUS( + db.exec("CREATE TABLE IF NOT EXISTS active_stories (dialog_id INT8 PRIMARY KEY, story_list_id INT4, " + "dialog_order INT8, data BLOB)")); + + TRY_STATUS( + db.exec("CREATE INDEX IF NOT EXISTS active_stories_by_order ON active_stories (story_list_id, dialog_order, " + "dialog_id) WHERE story_list_id IS NOT NULL")); + + TRY_STATUS(db.exec("CREATE TABLE IF NOT EXISTS active_story_lists (story_list_id INT4 PRIMARY KEY, data BLOB)")); + + version = current_db_version(); + } + return Status::OK(); +} + +// NB: must happen inside a transaction +Status drop_story_db(SqliteDb &db, int32 version) { + if (version != 0) { + LOG(WARNING) << "Drop story database " << tag("version", version) + << tag("current_db_version", current_db_version()); + } + auto status = db.exec("DROP TABLE IF EXISTS stories"); + TRY_STATUS(db.exec("DROP TABLE IF EXISTS active_stories")); + TRY_STATUS(db.exec("DROP TABLE IF EXISTS active_story_lists")); + return status; +} + +class StoryDbImpl final : public StoryDbSyncInterface { + public: + explicit StoryDbImpl(SqliteDb db) : db_(std::move(db)) { + init().ensure(); + } + + Status init() { + TRY_RESULT_ASSIGN(add_story_stmt_, db_.get_statement("INSERT OR REPLACE INTO stories VALUES(?1, ?2, ?3, ?4, ?5)")); + + TRY_RESULT_ASSIGN(delete_story_stmt_, + db_.get_statement("DELETE FROM stories WHERE dialog_id = ?1 AND story_id = ?2")); + + TRY_RESULT_ASSIGN(get_story_stmt_, + db_.get_statement("SELECT data FROM stories WHERE dialog_id = ?1 AND story_id = ?2")); + + TRY_RESULT_ASSIGN( + get_expiring_stories_stmt_, + db_.get_statement("SELECT dialog_id, story_id, data FROM stories WHERE expires_at <= ?1 LIMIT ?2")); + + TRY_RESULT_ASSIGN(get_stories_from_notification_id_stmt_, + db_.get_statement("SELECT story_id, data FROM stories WHERE dialog_id = ?1 AND " + "notification_id < ?2 ORDER BY notification_id DESC LIMIT ?3")); + + TRY_RESULT_ASSIGN(add_active_stories_stmt_, + db_.get_statement("INSERT OR REPLACE INTO active_stories VALUES(?1, ?2, ?3, ?4)")); + + TRY_RESULT_ASSIGN(delete_active_stories_stmt_, + db_.get_statement("DELETE FROM active_stories WHERE dialog_id = ?1")); + + TRY_RESULT_ASSIGN( + get_active_story_list_stmt_, + db_.get_statement("SELECT data, dialog_id, dialog_order FROM active_stories WHERE " + "story_list_id = ?1 AND (dialog_order < ?2 OR (dialog_order = ?2 AND dialog_id < ?3)) ORDER " + "BY dialog_order DESC, dialog_id DESC LIMIT ?4")); + + TRY_RESULT_ASSIGN(get_active_stories_stmt_, + db_.get_statement("SELECT data FROM active_stories WHERE dialog_id = ?1")); + + TRY_RESULT_ASSIGN(add_active_story_list_state_stmt_, + db_.get_statement("INSERT OR REPLACE INTO active_story_lists VALUES(?1, ?2)")); + + TRY_RESULT_ASSIGN(get_active_story_list_state_stmt_, + db_.get_statement("SELECT data FROM active_story_lists WHERE story_list_id = ?1")); + + return Status::OK(); + } + + void add_story(StoryFullId story_full_id, int32 expires_at, NotificationId notification_id, BufferSlice data) final { + LOG(INFO) << "Add " << story_full_id << " to database"; + CHECK(story_full_id.is_server()); + auto dialog_id = story_full_id.get_dialog_id(); + auto story_id = story_full_id.get_story_id(); + SCOPE_EXIT { + add_story_stmt_.reset(); + }; + + add_story_stmt_.bind_int64(1, dialog_id.get()).ensure(); + add_story_stmt_.bind_int32(2, story_id.get()).ensure(); + if (expires_at != 0) { + add_story_stmt_.bind_int32(3, expires_at).ensure(); + } else { + add_story_stmt_.bind_null(3).ensure(); + } + if (notification_id.is_valid()) { + add_story_stmt_.bind_int32(4, notification_id.get()).ensure(); + } else { + add_story_stmt_.bind_null(4).ensure(); + } + add_story_stmt_.bind_blob(5, data.as_slice()).ensure(); + + add_story_stmt_.step().ensure(); + } + + void delete_story(StoryFullId story_full_id) final { + LOG(INFO) << "Delete " << story_full_id << " from database"; + CHECK(story_full_id.is_valid()); + auto dialog_id = story_full_id.get_dialog_id(); + auto story_id = story_full_id.get_story_id(); + SCOPE_EXIT { + delete_story_stmt_.reset(); + }; + delete_story_stmt_.bind_int64(1, dialog_id.get()).ensure(); + delete_story_stmt_.bind_int32(2, story_id.get()).ensure(); + delete_story_stmt_.step().ensure(); + } + + Result get_story(StoryFullId story_full_id) final { + CHECK(story_full_id.is_server()); + auto dialog_id = story_full_id.get_dialog_id(); + auto story_id = story_full_id.get_story_id(); + SCOPE_EXIT { + get_story_stmt_.reset(); + }; + + get_story_stmt_.bind_int64(1, dialog_id.get()).ensure(); + get_story_stmt_.bind_int32(2, story_id.get()).ensure(); + get_story_stmt_.step().ensure(); + if (!get_story_stmt_.has_row()) { + return Status::Error("Not found"); + } + return BufferSlice(get_story_stmt_.view_blob(0)); + } + + vector get_expiring_stories(int32 expires_till, int32 limit) final { + auto &stmt = get_expiring_stories_stmt_; + SCOPE_EXIT { + stmt.reset(); + }; + + stmt.bind_int32(1, expires_till).ensure(); + stmt.bind_int32(2, limit).ensure(); + stmt.step().ensure(); + + vector stories; + while (stmt.has_row()) { + DialogId dialog_id(stmt.view_int64(0)); + StoryId story_id(stmt.view_int32(1)); + BufferSlice data(stmt.view_blob(2)); + stories.emplace_back(StoryFullId{dialog_id, story_id}, std::move(data)); + stmt.step().ensure(); + } + + return stories; + } + + vector get_stories_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, + int32 limit) final { + auto &stmt = get_stories_from_notification_id_stmt_; + SCOPE_EXIT { + stmt.reset(); + }; + stmt.bind_int64(1, dialog_id.get()).ensure(); + stmt.bind_int32(2, from_notification_id.get()).ensure(); + stmt.bind_int32(3, limit).ensure(); + stmt.step().ensure(); + + vector stories; + while (stmt.has_row()) { + StoryId story_id(stmt.view_int32(0)); + BufferSlice data(stmt.view_blob(1)); + stories.emplace_back(StoryFullId{dialog_id, story_id}, std::move(data)); + stmt.step().ensure(); + } + return stories; + } + + void add_active_stories(DialogId dialog_id, StoryListId story_list_id, int64 dialog_order, BufferSlice data) final { + SCOPE_EXIT { + add_active_stories_stmt_.reset(); + }; + add_active_stories_stmt_.bind_int64(1, dialog_id.get()).ensure(); + if (story_list_id.is_valid()) { + add_active_stories_stmt_.bind_int32(2, story_list_id == StoryListId::archive() ? 1 : 0).ensure(); + } else { + add_active_stories_stmt_.bind_null(2).ensure(); + } + add_active_stories_stmt_.bind_int64(3, dialog_order).ensure(); + add_active_stories_stmt_.bind_blob(4, data.as_slice()).ensure(); + add_active_stories_stmt_.step().ensure(); + } + + void delete_active_stories(DialogId dialog_id) final { + SCOPE_EXIT { + delete_active_stories_stmt_.reset(); + }; + delete_active_stories_stmt_.bind_int64(1, dialog_id.get()).ensure(); + delete_active_stories_stmt_.step().ensure(); + } + + Result get_active_stories(DialogId dialog_id) final { + SCOPE_EXIT { + get_active_stories_stmt_.reset(); + }; + + get_active_stories_stmt_.bind_int64(1, dialog_id.get()).ensure(); + get_active_stories_stmt_.step().ensure(); + if (!get_active_stories_stmt_.has_row()) { + return Status::Error("Not found"); + } + return BufferSlice(get_active_stories_stmt_.view_blob(0)); + } + + StoryDbGetActiveStoryListResult get_active_story_list(StoryListId story_list_id, int64 order, DialogId dialog_id, + int32 limit) final { + SCOPE_EXIT { + get_active_story_list_stmt_.reset(); + }; + + get_active_story_list_stmt_.bind_int32(1, story_list_id == StoryListId::archive() ? 1 : 0).ensure(); + get_active_story_list_stmt_.bind_int64(2, order).ensure(); + get_active_story_list_stmt_.bind_int64(3, dialog_id.get()).ensure(); + get_active_story_list_stmt_.bind_int32(4, limit).ensure(); + + StoryDbGetActiveStoryListResult result; + result.next_dialog_id_ = dialog_id; + result.next_order_ = order; + get_active_story_list_stmt_.step().ensure(); + while (get_active_story_list_stmt_.has_row()) { + BufferSlice data(get_active_story_list_stmt_.view_blob(0)); + result.next_dialog_id_ = DialogId(get_active_story_list_stmt_.view_int64(1)); + result.next_order_ = get_active_story_list_stmt_.view_int64(2); + LOG(INFO) << "Load active stories in " << result.next_dialog_id_ << " with order " << result.next_order_; + result.active_stories_.emplace_back(result.next_dialog_id_, std::move(data)); + get_active_story_list_stmt_.step().ensure(); + } + + return result; + } + + void add_active_story_list_state(StoryListId story_list_id, BufferSlice data) final { + SCOPE_EXIT { + add_active_story_list_state_stmt_.reset(); + }; + add_active_story_list_state_stmt_.bind_int32(1, story_list_id == StoryListId::archive() ? 1 : 0).ensure(); + add_active_story_list_state_stmt_.bind_blob(2, data.as_slice()).ensure(); + add_active_story_list_state_stmt_.step().ensure(); + } + + Result get_active_story_list_state(StoryListId story_list_id) final { + SCOPE_EXIT { + get_active_story_list_state_stmt_.reset(); + }; + + get_active_story_list_state_stmt_.bind_int64(1, story_list_id == StoryListId::archive() ? 1 : 0).ensure(); + get_active_story_list_state_stmt_.step().ensure(); + if (!get_active_story_list_state_stmt_.has_row()) { + return Status::Error("Not found"); + } + return BufferSlice(get_active_story_list_state_stmt_.view_blob(0)); + } + + Status begin_write_transaction() final { + return db_.begin_write_transaction(); + } + Status commit_transaction() final { + return db_.commit_transaction(); + } + + private: + SqliteDb db_; + + SqliteStatement add_story_stmt_; + SqliteStatement delete_story_stmt_; + SqliteStatement get_story_stmt_; + SqliteStatement get_expiring_stories_stmt_; + SqliteStatement get_stories_from_notification_id_stmt_; + + SqliteStatement add_active_stories_stmt_; + SqliteStatement delete_active_stories_stmt_; + SqliteStatement get_active_stories_stmt_; + SqliteStatement get_active_story_list_stmt_; + + SqliteStatement add_active_story_list_state_stmt_; + SqliteStatement get_active_story_list_state_stmt_; +}; + +std::shared_ptr create_story_db_sync( + std::shared_ptr sqlite_connection) { + class StoryDbSyncSafe final : public StoryDbSyncSafeInterface { + public: + explicit StoryDbSyncSafe(std::shared_ptr sqlite_connection) + : lsls_db_([safe_connection = std::move(sqlite_connection)] { + return make_unique(safe_connection->get().clone()); + }) { + } + StoryDbSyncInterface &get() final { + return *lsls_db_.get(); + } + + private: + LazySchedulerLocalStorage> lsls_db_; + }; + return std::make_shared(std::move(sqlite_connection)); +} + +class StoryDbAsync final : public StoryDbAsyncInterface { + public: + StoryDbAsync(std::shared_ptr sync_db, int32 scheduler_id) { + impl_ = create_actor_on_scheduler("StoryDbActor", scheduler_id, std::move(sync_db)); + } + + void add_story(StoryFullId story_full_id, int32 expires_at, NotificationId notification_id, BufferSlice data, + Promise promise) final { + send_closure_later(impl_, &Impl::add_story, story_full_id, expires_at, notification_id, std::move(data), + std::move(promise)); + } + + void delete_story(StoryFullId story_full_id, Promise promise) final { + send_closure_later(impl_, &Impl::delete_story, story_full_id, std::move(promise)); + } + + void get_story(StoryFullId story_full_id, Promise promise) final { + send_closure_later(impl_, &Impl::get_story, story_full_id, std::move(promise)); + } + + void get_expiring_stories(int32 expires_till, int32 limit, Promise> promise) final { + send_closure_later(impl_, &Impl::get_expiring_stories, expires_till, limit, std::move(promise)); + } + + void get_stories_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit, + Promise> promise) final { + send_closure_later(impl_, &Impl::get_stories_from_notification_id, dialog_id, from_notification_id, limit, + std::move(promise)); + } + + void add_active_stories(DialogId dialog_id, StoryListId story_list_id, int64 dialog_order, BufferSlice data, + Promise promise) final { + send_closure_later(impl_, &Impl::add_active_stories, dialog_id, story_list_id, dialog_order, std::move(data), + std::move(promise)); + } + + void delete_active_stories(DialogId dialog_id, Promise promise) final { + send_closure_later(impl_, &Impl::delete_active_stories, dialog_id, std::move(promise)); + } + + void get_active_stories(DialogId dialog_id, Promise promise) final { + send_closure_later(impl_, &Impl::get_active_stories, dialog_id, std::move(promise)); + } + + void get_active_story_list(StoryListId story_list_id, int64 order, DialogId dialog_id, int32 limit, + Promise promise) final { + send_closure_later(impl_, &Impl::get_active_story_list, story_list_id, order, dialog_id, limit, std::move(promise)); + } + + void add_active_story_list_state(StoryListId story_list_id, BufferSlice data, Promise promise) final { + send_closure_later(impl_, &Impl::add_active_story_list_state, story_list_id, std::move(data), std::move(promise)); + } + + void get_active_story_list_state(StoryListId story_list_id, Promise promise) final { + send_closure_later(impl_, &Impl::get_active_story_list_state, story_list_id, std::move(promise)); + } + + void close(Promise promise) final { + send_closure_later(impl_, &Impl::close, std::move(promise)); + } + + void force_flush() final { + send_closure_later(impl_, &Impl::force_flush); + } + + private: + class Impl final : public Actor { + public: + explicit Impl(std::shared_ptr sync_db_safe) : sync_db_safe_(std::move(sync_db_safe)) { + } + void add_story(StoryFullId story_full_id, int32 expires_at, NotificationId notification_id, BufferSlice data, + Promise promise) { + add_write_query([this, story_full_id, expires_at, notification_id, data = std::move(data), + promise = std::move(promise)](Unit) mutable { + sync_db_->add_story(story_full_id, expires_at, notification_id, std::move(data)); + on_write_result(std::move(promise)); + }); + } + + void delete_story(StoryFullId story_full_id, Promise promise) { + add_write_query([this, story_full_id, promise = std::move(promise)](Unit) mutable { + sync_db_->delete_story(story_full_id); + on_write_result(std::move(promise)); + }); + } + + void on_write_result(Promise &&promise) { + // We are inside a transaction and don't know how to handle errors + finished_writes_.push_back(std::move(promise)); + } + + void get_story(StoryFullId story_full_id, Promise promise) { + add_read_query(); + promise.set_result(sync_db_->get_story(story_full_id)); + } + + void get_expiring_stories(int32 expires_till, int32 limit, Promise> promise) { + add_read_query(); + promise.set_value(sync_db_->get_expiring_stories(expires_till, limit)); + } + + void get_stories_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit, + Promise> promise) { + add_read_query(); + promise.set_value(sync_db_->get_stories_from_notification_id(dialog_id, from_notification_id, limit)); + } + + void add_active_stories(DialogId dialog_id, StoryListId story_list_id, int64 dialog_order, BufferSlice data, + Promise promise) { + add_write_query([this, dialog_id, story_list_id, dialog_order, data = std::move(data), + promise = std::move(promise)](Unit) mutable { + sync_db_->add_active_stories(dialog_id, story_list_id, dialog_order, std::move(data)); + on_write_result(std::move(promise)); + }); + } + + void delete_active_stories(DialogId dialog_id, Promise promise) { + add_write_query([this, dialog_id, promise = std::move(promise)](Unit) mutable { + sync_db_->delete_active_stories(dialog_id); + on_write_result(std::move(promise)); + }); + } + + void get_active_stories(DialogId dialog_id, Promise promise) { + add_read_query(); + promise.set_result(sync_db_->get_active_stories(dialog_id)); + } + + void get_active_story_list(StoryListId story_list_id, int64 order, DialogId dialog_id, int32 limit, + Promise promise) { + add_read_query(); + promise.set_value(sync_db_->get_active_story_list(story_list_id, order, dialog_id, limit)); + } + + void add_active_story_list_state(StoryListId story_list_id, BufferSlice data, Promise promise) { + add_write_query([this, story_list_id, data = std::move(data), promise = std::move(promise)](Unit) mutable { + sync_db_->add_active_story_list_state(story_list_id, std::move(data)); + on_write_result(std::move(promise)); + }); + } + + void get_active_story_list_state(StoryListId story_list_id, Promise promise) { + add_read_query(); + promise.set_result(sync_db_->get_active_story_list_state(story_list_id)); + } + + void close(Promise promise) { + do_flush(); + sync_db_safe_.reset(); + sync_db_ = nullptr; + promise.set_value(Unit()); + stop(); + } + + void force_flush() { + do_flush(); + LOG(INFO) << "StoryDb flushed"; + } + + private: + std::shared_ptr sync_db_safe_; + StoryDbSyncInterface *sync_db_ = nullptr; + + static constexpr size_t MAX_PENDING_QUERIES_COUNT{50}; + static constexpr double MAX_PENDING_QUERIES_DELAY{0.01}; + + //NB: order is important, destructor of pending_writes_ will change finished_writes_ + vector> finished_writes_; + vector> pending_writes_; // TODO use Action + double wakeup_at_ = 0; + + template + void add_write_query(F &&f) { + pending_writes_.push_back(PromiseCreator::lambda(std::forward(f))); + if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) { + do_flush(); + wakeup_at_ = 0; + } else if (wakeup_at_ == 0) { + wakeup_at_ = Time::now_cached() + MAX_PENDING_QUERIES_DELAY; + } + if (wakeup_at_ != 0) { + set_timeout_at(wakeup_at_); + } + } + void add_read_query() { + do_flush(); + } + void do_flush() { + if (pending_writes_.empty()) { + return; + } + sync_db_->begin_write_transaction().ensure(); + set_promises(pending_writes_); + sync_db_->commit_transaction().ensure(); + set_promises(finished_writes_); + cancel_timeout(); + } + void timeout_expired() final { + do_flush(); + } + + void start_up() final { + sync_db_ = &sync_db_safe_->get(); + } + }; + ActorOwn impl_; +}; + +std::shared_ptr create_story_db_async(std::shared_ptr sync_db, + int32 scheduler_id) { + return std::make_shared(std::move(sync_db), scheduler_id); +} + +} // namespace td diff --git a/td/telegram/StoryDb.h b/td/telegram/StoryDb.h new file mode 100644 index 000000000000..43275f2efff4 --- /dev/null +++ b/td/telegram/StoryDb.h @@ -0,0 +1,133 @@ +// +// 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/DialogId.h" +#include "td/telegram/NotificationId.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/StoryListId.h" + +#include "td/utils/buffer.h" +#include "td/utils/common.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +#include +#include + +namespace td { + +class SqliteConnectionSafe; +class SqliteDb; + +struct StoryDbStory { + StoryFullId story_full_id_; + BufferSlice data_; + + StoryDbStory(StoryFullId story_full_id, BufferSlice &&data) : story_full_id_(story_full_id), data_(std::move(data)) { + } +}; + +struct StoryDbGetActiveStoryListResult { + vector> active_stories_; + int64 next_order_ = 0; + DialogId next_dialog_id_; +}; + +class StoryDbSyncInterface { + public: + StoryDbSyncInterface() = default; + StoryDbSyncInterface(const StoryDbSyncInterface &) = delete; + StoryDbSyncInterface &operator=(const StoryDbSyncInterface &) = delete; + virtual ~StoryDbSyncInterface() = default; + + virtual void add_story(StoryFullId story_full_id, int32 expires_at, NotificationId notification_id, + BufferSlice data) = 0; + + virtual void delete_story(StoryFullId story_full_id) = 0; + + virtual Result get_story(StoryFullId story_full_id) = 0; + + virtual vector get_expiring_stories(int32 expires_till, int32 limit) = 0; + + virtual vector get_stories_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, + int32 limit) = 0; + + virtual void add_active_stories(DialogId dialog_id, StoryListId story_list_id, int64 dialog_order, + BufferSlice data) = 0; + + virtual void delete_active_stories(DialogId dialog_id) = 0; + + virtual Result get_active_stories(DialogId dialog_id) = 0; + + virtual StoryDbGetActiveStoryListResult get_active_story_list(StoryListId story_list_id, int64 order, + DialogId dialog_id, int32 limit) = 0; + + virtual void add_active_story_list_state(StoryListId story_list_id, BufferSlice data) = 0; + + virtual Result get_active_story_list_state(StoryListId story_list_id) = 0; + + virtual Status begin_write_transaction() = 0; + virtual Status commit_transaction() = 0; +}; + +class StoryDbSyncSafeInterface { + public: + StoryDbSyncSafeInterface() = default; + StoryDbSyncSafeInterface(const StoryDbSyncSafeInterface &) = delete; + StoryDbSyncSafeInterface &operator=(const StoryDbSyncSafeInterface &) = delete; + virtual ~StoryDbSyncSafeInterface() = default; + + virtual StoryDbSyncInterface &get() = 0; +}; + +class StoryDbAsyncInterface { + public: + StoryDbAsyncInterface() = default; + StoryDbAsyncInterface(const StoryDbAsyncInterface &) = delete; + StoryDbAsyncInterface &operator=(const StoryDbAsyncInterface &) = delete; + virtual ~StoryDbAsyncInterface() = default; + + virtual void add_story(StoryFullId story_full_id, int32 expires_at, NotificationId notification_id, BufferSlice data, + Promise promise) = 0; + + virtual void delete_story(StoryFullId story_full_id, Promise promise) = 0; + + virtual void get_story(StoryFullId story_full_id, Promise promise) = 0; + + virtual void get_expiring_stories(int32 expires_till, int32 limit, Promise> promise) = 0; + + virtual void get_stories_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit, + Promise> promise) = 0; + + virtual void add_active_stories(DialogId dialog_id, StoryListId story_list_id, int64 dialog_order, BufferSlice data, + Promise promise) = 0; + + virtual void delete_active_stories(DialogId dialog_id, Promise promise) = 0; + + virtual void get_active_stories(DialogId dialog_id, Promise promise) = 0; + + virtual void get_active_story_list(StoryListId story_list_id, int64 order, DialogId dialog_id, int32 limit, + Promise promise) = 0; + + virtual void add_active_story_list_state(StoryListId story_list_id, BufferSlice data, Promise promise) = 0; + + virtual void get_active_story_list_state(StoryListId story_list_id, Promise promise) = 0; + + virtual void close(Promise promise) = 0; + virtual void force_flush() = 0; +}; + +Status init_story_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT; +Status drop_story_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT; + +std::shared_ptr create_story_db_sync(std::shared_ptr sqlite_connection); + +std::shared_ptr create_story_db_async(std::shared_ptr sync_db, + int32 scheduler_id = -1); + +} // namespace td diff --git a/td/telegram/StoryFullId.h b/td/telegram/StoryFullId.h new file mode 100644 index 000000000000..acf3d45af058 --- /dev/null +++ b/td/telegram/StoryFullId.h @@ -0,0 +1,77 @@ +// +// 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/DialogId.h" +#include "td/telegram/StoryId.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +struct StoryFullId { + private: + DialogId dialog_id; + StoryId story_id; + + public: + StoryFullId() : dialog_id(), story_id() { + } + + StoryFullId(DialogId dialog_id, StoryId story_id) : dialog_id(dialog_id), story_id(story_id) { + } + + bool operator==(const StoryFullId &other) const { + return dialog_id == other.dialog_id && story_id == other.story_id; + } + + bool operator!=(const StoryFullId &other) const { + return !(*this == other); + } + + DialogId get_dialog_id() const { + return dialog_id; + } + + StoryId get_story_id() const { + return story_id; + } + + bool is_valid() const { + return dialog_id.is_valid() && story_id.is_valid(); + } + + bool is_server() const { + return dialog_id.is_valid() && story_id.is_server(); + } + + template + void store(StorerT &storer) const { + dialog_id.store(storer); + story_id.store(storer); + } + + template + void parse(ParserT &parser) { + dialog_id.parse(parser); + story_id.parse(parser); + } +}; + +struct StoryFullIdHash { + uint32 operator()(StoryFullId story_full_id) const { + return combine_hashes(DialogIdHash()(story_full_id.get_dialog_id()), StoryIdHash()(story_full_id.get_story_id())); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, StoryFullId story_full_id) { + return string_builder << story_full_id.get_story_id() << " in " << story_full_id.get_dialog_id(); +} + +} // namespace td diff --git a/td/telegram/StoryId.h b/td/telegram/StoryId.h new file mode 100644 index 000000000000..ceeacbd3938f --- /dev/null +++ b/td/telegram/StoryId.h @@ -0,0 +1,81 @@ +// +// 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/HashTableUtils.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tl_helpers.h" + +#include + +namespace td { + +class StoryId { + int32 id = 0; + + public: + static constexpr int32 MAX_SERVER_STORY_ID = 1999999999; + + StoryId() = default; + + explicit constexpr StoryId(int32 story_id) : id(story_id) { + } + template ::value>> + StoryId(T story_id) = delete; + + static vector get_input_story_ids(const vector &story_ids) { + vector input_story_ids; + input_story_ids.reserve(story_ids.size()); + for (auto &story_id : story_ids) { + input_story_ids.emplace_back(story_id.get()); + } + return input_story_ids; + } + + int32 get() const { + return id; + } + + bool operator==(const StoryId &other) const { + return id == other.id; + } + + bool operator!=(const StoryId &other) const { + return id != other.id; + } + + bool is_valid() const { + return id > 0; + } + + bool is_server() const { + return id > 0 && id <= MAX_SERVER_STORY_ID; + } + + template + void store(StorerT &storer) const { + td::store(id, storer); + } + + template + void parse(ParserT &parser) { + td::parse(id, parser); + } +}; + +struct StoryIdHash { + uint32 operator()(StoryId story_id) const { + return Hash()(story_id.get()); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, StoryId story_id) { + return string_builder << "story " << story_id.get(); +} + +} // namespace td diff --git a/td/telegram/StoryInteractionInfo.cpp b/td/telegram/StoryInteractionInfo.cpp new file mode 100644 index 000000000000..6f1e620e389f --- /dev/null +++ b/td/telegram/StoryInteractionInfo.cpp @@ -0,0 +1,92 @@ +// +// 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/StoryInteractionInfo.h" + +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/Td.h" + +#include "td/utils/algorithm.h" +#include "td/utils/logging.h" + +namespace td { + +StoryInteractionInfo::StoryInteractionInfo(Td *td, telegram_api::object_ptr &&story_views) { + if (story_views == nullptr) { + return; + } + for (auto &viewer_id : story_views->recent_viewers_) { + UserId user_id(viewer_id); + if (user_id.is_valid() && td->contacts_manager_->have_min_user(user_id)) { + if (recent_viewer_user_ids_.size() == MAX_RECENT_VIEWERS) { + LOG(ERROR) << "Receive too many recent story viewers: " << story_views->recent_viewers_; + break; + } + recent_viewer_user_ids_.push_back(user_id); + } else { + LOG(ERROR) << "Receive " << user_id << " as recent viewer"; + } + } + view_count_ = story_views->views_count_; + if (view_count_ < 0) { + LOG(ERROR) << "Receive " << view_count_ << " story views"; + view_count_ = 0; + } + reaction_count_ = story_views->reactions_count_; + if (reaction_count_ < 0) { + LOG(ERROR) << "Receive " << reaction_count_ << " story reactions"; + reaction_count_ = 0; + } + has_viewers_ = story_views->has_viewers_; +} + +void StoryInteractionInfo::add_dependencies(Dependencies &dependencies) const { + for (auto user_id : recent_viewer_user_ids_) { + dependencies.add(user_id); + } +} + +bool StoryInteractionInfo::set_recent_viewer_user_ids(vector &&user_ids) { + if (recent_viewer_user_ids_.empty() && view_count_ > 0) { + // don't update recent viewers for stories with expired viewers + return false; + } + if (user_ids.size() > MAX_RECENT_VIEWERS) { + user_ids.resize(MAX_RECENT_VIEWERS); + } + if (recent_viewer_user_ids_ != user_ids) { + recent_viewer_user_ids_ = std::move(user_ids); + return true; + } + return false; +} + +bool StoryInteractionInfo::definitely_has_no_user(UserId user_id) const { + return !is_empty() && view_count_ == static_cast(recent_viewer_user_ids_.size()) && + !contains(recent_viewer_user_ids_, user_id); +} + +td_api::object_ptr StoryInteractionInfo::get_story_interaction_info_object(Td *td) const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object( + view_count_, reaction_count_, + td->contacts_manager_->get_user_ids_object(recent_viewer_user_ids_, "get_story_interaction_info_object")); +} + +bool operator==(const StoryInteractionInfo &lhs, const StoryInteractionInfo &rhs) { + return lhs.recent_viewer_user_ids_ == rhs.recent_viewer_user_ids_ && lhs.view_count_ == rhs.view_count_ && + lhs.reaction_count_ == rhs.reaction_count_ && lhs.has_viewers_ == rhs.has_viewers_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryInteractionInfo &info) { + return string_builder << info.view_count_ << " views with " << info.reaction_count_ << " reactions by " + << info.recent_viewer_user_ids_; +} + +} // namespace td diff --git a/td/telegram/StoryInteractionInfo.h b/td/telegram/StoryInteractionInfo.h new file mode 100644 index 000000000000..abf8529a6137 --- /dev/null +++ b/td/telegram/StoryInteractionInfo.h @@ -0,0 +1,86 @@ +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Dependencies; +class Td; + +class StoryInteractionInfo { + vector recent_viewer_user_ids_; + int32 view_count_ = -1; + int32 reaction_count_ = 0; + bool has_viewers_ = false; + + static constexpr size_t MAX_RECENT_VIEWERS = 3; + + friend bool operator==(const StoryInteractionInfo &lhs, const StoryInteractionInfo &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StoryInteractionInfo &info); + + public: + StoryInteractionInfo() = default; + + StoryInteractionInfo(Td *td, telegram_api::object_ptr &&story_views); + + bool is_empty() const { + return view_count_ < 0; + } + + bool has_hidden_viewers() const { + return view_count_ < 0 || !has_viewers_; + } + + void add_dependencies(Dependencies &dependencies) const; + + bool set_counts(int32 view_count, int32 reaction_count) { + if (view_count != view_count_ || reaction_count != reaction_count_) { + view_count = view_count_; + reaction_count = reaction_count_; + return true; + } + return false; + } + + int32 get_view_count() const { + return view_count_; + } + + int32 get_reaction_count() const { + return reaction_count_; + } + + bool definitely_has_no_user(UserId user_id) const; + + bool set_recent_viewer_user_ids(vector &&user_ids); + + td_api::object_ptr get_story_interaction_info_object(Td *td) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const StoryInteractionInfo &lhs, const StoryInteractionInfo &rhs); + +inline bool operator!=(const StoryInteractionInfo &lhs, const StoryInteractionInfo &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryInteractionInfo &info); + +} // namespace td diff --git a/td/telegram/StoryInteractionInfo.hpp b/td/telegram/StoryInteractionInfo.hpp new file mode 100644 index 000000000000..112010298d4b --- /dev/null +++ b/td/telegram/StoryInteractionInfo.hpp @@ -0,0 +1,61 @@ +// +// 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/StoryInteractionInfo.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void StoryInteractionInfo::store(StorerT &storer) const { + using td::store; + bool has_recent_viewer_user_ids = !recent_viewer_user_ids_.empty(); + bool has_reaction_count = reaction_count_ > 0; + bool know_has_viewers = true; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_recent_viewer_user_ids); + STORE_FLAG(has_reaction_count); + STORE_FLAG(know_has_viewers); + STORE_FLAG(has_viewers_); + END_STORE_FLAGS(); + store(view_count_, storer); + if (has_recent_viewer_user_ids) { + store(recent_viewer_user_ids_, storer); + } + if (has_reaction_count) { + store(reaction_count_, storer); + } +} + +template +void StoryInteractionInfo::parse(ParserT &parser) { + using td::parse; + bool has_recent_viewer_user_ids; + bool has_reaction_count; + bool know_has_viewers; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_recent_viewer_user_ids); + PARSE_FLAG(has_reaction_count); + PARSE_FLAG(know_has_viewers); + PARSE_FLAG(has_viewers_); + END_PARSE_FLAGS(); + parse(view_count_, parser); + if (has_recent_viewer_user_ids) { + parse(recent_viewer_user_ids_, parser); + } + if (has_reaction_count) { + parse(reaction_count_, parser); + } + if (!know_has_viewers) { + has_viewers_ = (view_count_ > 0 && !has_recent_viewer_user_ids) || reaction_count_ > 0; + } +} + +} // namespace td diff --git a/td/telegram/StoryListId.h b/td/telegram/StoryListId.h new file mode 100644 index 000000000000..77d6bf314290 --- /dev/null +++ b/td/telegram/StoryListId.h @@ -0,0 +1,95 @@ +// +// 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/td_api.h" + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class StoryListId { + enum class Type : int32 { None = -1, Main, Archive }; + Type type_ = Type::None; + + friend struct StoryListIdHash; + + explicit StoryListId(Type type) : type_(type) { + } + + public: + StoryListId() = default; + + explicit StoryListId(const td_api::object_ptr &story_list) { + if (story_list == nullptr) { + return; + } + switch (story_list->get_id()) { + case td_api::storyListMain::ID: + type_ = Type::Main; + break; + case td_api::storyListArchive::ID: + type_ = Type::Archive; + break; + default: + UNREACHABLE(); + } + } + + static StoryListId main() { + return StoryListId(Type::Main); + } + + static StoryListId archive() { + return StoryListId(Type::Archive); + } + + td_api::object_ptr get_story_list_object() const { + switch (type_) { + case Type::None: + return nullptr; + case Type::Main: + return td_api::make_object(); + case Type::Archive: + return td_api::make_object(); + default: + UNREACHABLE(); + } + } + + bool is_valid() const { + return type_ == Type::Main || type_ == Type::Archive; + } + + bool operator==(const StoryListId &other) const { + return type_ == other.type_; + } + + bool operator!=(const StoryListId &other) const { + return type_ != other.type_; + } +}; + +struct StoryListIdHash { + uint32 operator()(StoryListId story_list_id) const { + return Hash()(static_cast(story_list_id.type_)); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, StoryListId story_list_id) { + if (story_list_id == StoryListId::main()) { + return string_builder << "MainStoryList"; + } + if (story_list_id == StoryListId::archive()) { + return string_builder << "ArchiveStoryList"; + } + return string_builder << "InvalidStoryList"; +} + +} // namespace td diff --git a/td/telegram/StoryManager.cpp b/td/telegram/StoryManager.cpp new file mode 100644 index 000000000000..cfdec3f884db --- /dev/null +++ b/td/telegram/StoryManager.cpp @@ -0,0 +1,4830 @@ +// +// 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/StoryManager.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/ConfigManager.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/FileReferenceManager.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/MediaArea.hpp" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/NotificationId.h" +#include "td/telegram/NotificationManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/ReactionManager.h" +#include "td/telegram/ReactionType.hpp" +#include "td/telegram/ReportReason.h" +#include "td/telegram/StoryContent.h" +#include "td/telegram/StoryContentType.h" +#include "td/telegram/StoryInteractionInfo.hpp" +#include "td/telegram/StoryStealthMode.hpp" +#include "td/telegram/StoryViewer.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserId.h" +#include "td/telegram/WebPagesManager.h" + +#include "td/db/binlog/BinlogEvent.h" +#include "td/db/binlog/BinlogHelper.h" + +#include "td/actor/MultiPromise.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/Random.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" + +#include + +namespace td { + +static td_api::object_ptr get_can_send_story_result_object(const Status &error, + bool force = false) { + CHECK(error.is_error()); + if (error.message() == "PREMIUM_ACCOUNT_REQUIRED") { + return td_api::make_object(); + } + if (error.message() == "STORIES_TOO_MUCH") { + return td_api::make_object(); + } + if (begins_with(error.message(), "STORY_SEND_FLOOD_WEEKLY_")) { + auto r_next_date = to_integer_safe(error.message().substr(Slice("STORY_SEND_FLOOD_WEEKLY_").size())); + if (r_next_date.is_ok() && r_next_date.ok() > 0) { + auto retry_after = r_next_date.ok() - G()->unix_time(); + if (retry_after > 0 || force) { + return td_api::make_object(max(retry_after, 0)); + } else { + return td_api::make_object(); + } + } + } + if (begins_with(error.message(), "STORY_SEND_FLOOD_MONTHLY_")) { + auto r_next_date = to_integer_safe(error.message().substr(Slice("STORY_SEND_FLOOD_MONTHLY_").size())); + if (r_next_date.is_ok() && r_next_date.ok() > 0) { + auto retry_after = r_next_date.ok() - G()->unix_time(); + if (retry_after > 0 || force) { + return td_api::make_object(max(retry_after, 0)); + } else { + return td_api::make_object(); + } + } + } + return nullptr; +} + +class GetAllStoriesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetAllStoriesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(StoryListId story_list_id, bool is_next, const string &state) { + int32 flags = 0; + if (!state.empty()) { + flags |= telegram_api::stories_getAllStories::STATE_MASK; + } + if (is_next) { + flags |= telegram_api::stories_getAllStories::NEXT_MASK; + } + if (story_list_id == StoryListId::archive()) { + flags |= telegram_api::stories_getAllStories::HIDDEN_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::stories_getAllStories(flags, false /*ignored*/, false /*ignored*/, state))); + } + + 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 GetAllStoriesQuery: " << to_string(result); + promise_.set_value(std::move(result)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ToggleStoriesHiddenQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + bool are_hidden_ = false; + + public: + explicit ToggleStoriesHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, bool are_hidden) { + user_id_ = user_id; + are_hidden_ = are_hidden; + auto r_input_user = td_->contacts_manager_->get_input_user(user_id_); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::contacts_toggleStoriesHidden(r_input_user.move_as_ok(), are_hidden), {{user_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 ToggleStoriesHiddenQuery: " << result; + if (result) { + td_->story_manager_->on_update_dialog_stories_hidden(DialogId(user_id_), are_hidden_); + } + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetAllReadUserStoriesQuery final : public Td::ResultHandler { + public: + void send() { + send_query(G()->net_query_creator().create(telegram_api::stories_getAllReadUserStories())); + } + + 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(DEBUG) << "Receive result for GetAllReadUserStoriesQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), Promise()); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetAllReadUserStoriesQuery: " << status; + } +}; + +class ToggleAllStoriesHiddenQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ToggleAllStoriesHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(bool all_stories_hidden) { + send_query( + G()->net_query_creator().create(telegram_api::stories_toggleAllStoriesHidden(all_stories_hidden), {{"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 ToggleAllStoriesHiddenQuery: " << result; + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class IncrementStoryViewsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit IncrementStoryViewsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId owner_dialog_id, const vector &story_ids) { + CHECK(owner_dialog_id.get_type() == DialogType::User); + auto r_input_user = td_->contacts_manager_->get_input_user(owner_dialog_id.get_user_id()); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::stories_incrementStoryViews(r_input_user.move_as_ok(), StoryId::get_input_story_ids(story_ids)), + {{"view_story"}})); + } + + 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 { + promise_.set_error(std::move(status)); + } +}; + +class ReadStoriesQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ReadStoriesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId owner_dialog_id, StoryId max_read_story_id) { + CHECK(owner_dialog_id.get_type() == DialogType::User); + auto r_input_user = td_->contacts_manager_->get_input_user(owner_dialog_id.get_user_id()); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::stories_readStories(r_input_user.move_as_ok(), max_read_story_id.get()), {{"view_story"}})); + } + + 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 { + promise_.set_error(std::move(status)); + } +}; + +class SendStoryReactionQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit SendStoryReactionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(StoryFullId story_full_id, const ReactionType &reaction_type, bool add_to_recent) { + dialog_id_ = story_full_id.get_dialog_id(); + + CHECK(dialog_id_.get_type() == DialogType::User); + auto r_input_user = td_->contacts_manager_->get_input_user(dialog_id_.get_user_id()); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + // auto input_peer = td_->stories_manager_->get_input_peer(dialog_id_, AccessRights::Read); + // CHECK(input_peer != nullptr); + + int32 flags = 0; + if (!reaction_type.is_empty() && add_to_recent) { + flags |= telegram_api::stories_sendReaction::ADD_TO_RECENT_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::stories_sendReaction(flags, false /*ignored*/, r_input_user.move_as_ok(), + story_full_id.get_story_id().get(), reaction_type.get_input_reaction()), + {{story_full_id}, {"view_story"}})); + } + + 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 SendStoryReactionQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "STORY_NOT_MODIFIED") { + return promise_.set_value(Unit()); + } + td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendStoryReactionQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetStoryViewsListQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStoryViewsListQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(StoryId story_id, const string &query, bool only_contacts, bool prefer_with_reaction, const string &offset, + int32 limit) { + int32 flags = 0; + if (!query.empty()) { + flags |= telegram_api::stories_getStoryViewsList::Q_MASK; + } + if (only_contacts) { + flags |= telegram_api::stories_getStoryViewsList::JUST_CONTACTS_MASK; + } + if (prefer_with_reaction) { + flags |= telegram_api::stories_getStoryViewsList::REACTIONS_FIRST_MASK; + } + send_query(G()->net_query_creator().create(telegram_api::stories_getStoryViewsList( + flags, false /*ignored*/, false /*ignored*/, query, story_id.get(), 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()); + } + + promise_.set_value(result_ptr.move_as_ok()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetStoriesByIDQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + vector story_ids_; + + public: + explicit GetStoriesByIDQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, vector story_ids) { + user_id_ = user_id; + story_ids_ = std::move(story_ids); + auto r_input_user = td_->contacts_manager_->get_input_user(user_id_); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::stories_getStoriesByID(r_input_user.move_as_ok(), StoryId::get_input_story_ids(story_ids_)))); + } + + 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 GetStoriesByIDQuery: " << to_string(result); + td_->story_manager_->on_get_stories(DialogId(user_id_), std::move(story_ids_), std::move(result)); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetPinnedStoriesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetPinnedStoriesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(UserId user_id, StoryId offset_story_id, int32 limit) { + auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::stories_getPinnedStories(r_input_user.move_as_ok(), offset_story_id.get(), 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 GetPinnedStoriesQuery: " << to_string(result); + promise_.set_value(std::move(result)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetStoriesArchiveQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStoriesArchiveQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(StoryId offset_story_id, int32 limit) { + send_query(G()->net_query_creator().create(telegram_api::stories_getStoriesArchive(offset_story_id.get(), 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 GetStoriesArchiveQuery: " << to_string(result); + promise_.set_value(std::move(result)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetUserStoriesQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetUserStoriesQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(UserId user_id) { + auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create(telegram_api::stories_getUserStories(r_input_user.move_as_ok()), + {{DialogId(user_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 GetUserStoriesQuery: " << to_string(result); + promise_.set_value(std::move(result)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class EditStoryPrivacyQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit EditStoryPrivacyQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, StoryId story_id, UserPrivacySettingRules &&privacy_rules) { + int32 flags = telegram_api::stories_editStory::PRIVACY_RULES_MASK; + send_query(G()->net_query_creator().create( + telegram_api::stories_editStory(flags, story_id.get(), nullptr, + vector>(), string(), + vector>(), + privacy_rules.get_input_privacy_rules(td_)), + {{StoryFullId{dialog_id, story_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(DEBUG) << "Receive result for EditStoryPrivacyQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (!td_->auth_manager_->is_bot() && status.message() == "STORY_NOT_MODIFIED") { + return promise_.set_value(Unit()); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleStoryPinnedQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ToggleStoryPinnedQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, StoryId story_id, bool is_pinned) { + send_query(G()->net_query_creator().create(telegram_api::stories_togglePinned({story_id.get()}, is_pinned), + {{StoryFullId{dialog_id, story_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(DEBUG) << "Receive result for ToggleStoryPinnedQuery: " << ptr; + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class DeleteStoriesQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit DeleteStoriesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const vector &story_ids) { + send_query( + G()->net_query_creator().create(telegram_api::stories_deleteStories(StoryId::get_input_story_ids(story_ids)))); + } + + 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(DEBUG) << "Receive result for DeleteStoriesQuery: " << ptr; + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetStoriesViewsQuery final : public Td::ResultHandler { + vector story_ids_; + + public: + void send(vector story_ids) { + story_ids_ = std::move(story_ids); + send_query(G()->net_query_creator().create( + telegram_api::stories_getStoriesViews(StoryId::get_input_story_ids(story_ids_)))); + } + + 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(DEBUG) << "Receive result for GetStoriesViewsQuery: " << to_string(ptr); + td_->story_manager_->on_get_story_views(story_ids_, std::move(ptr)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for GetStoriesViewsQuery for " << story_ids_ << ": " << status; + } +}; + +class ReportStoryQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit ReportStoryQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(StoryFullId story_full_id, ReportReason &&report_reason) { + dialog_id_ = story_full_id.get_dialog_id(); + CHECK(dialog_id_.get_type() == DialogType::User); + + auto r_input_user = td_->contacts_manager_->get_input_user(dialog_id_.get_user_id()); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + + send_query(G()->net_query_creator().create( + telegram_api::stories_report(r_input_user.move_as_ok(), {story_full_id.get_story_id().get()}, + report_reason.get_input_report_reason(), report_reason.get_message()))); + } + + 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, "ReportStoryQuery"); + promise_.set_error(std::move(status)); + } +}; + +class ActivateStealthModeQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ActivateStealthModeQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + int32 flags = + telegram_api::stories_activateStealthMode::PAST_MASK | telegram_api::stories_activateStealthMode::FUTURE_MASK; + + send_query(G()->net_query_creator().create( + telegram_api::stories_activateStealthMode(flags, false /*ignored*/, false /*ignored*/), {{"view_story"}})); + } + + 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 ActivateStealthModeQuery: " << 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 CanSendStoryQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CanSendStoryQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::stories_canSendStory())); + } + + 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(td_api::make_object()); + } + + void on_error(Status status) final { + auto result = get_can_send_story_result_object(status); + if (result != nullptr) { + return promise_.set_value(std::move(result)); + } + return promise_.set_error(std::move(status)); + } +}; + +class StoryManager::SendStoryQuery final : public Td::ResultHandler { + FileId file_id_; + unique_ptr pending_story_; + + public: + void send(FileId file_id, unique_ptr pending_story, + telegram_api::object_ptr input_file) { + file_id_ = file_id; + pending_story_ = std::move(pending_story); + CHECK(pending_story_ != nullptr); + + const auto *story = pending_story_->story_.get(); + const StoryContent *content = story->content_.get(); + CHECK(input_file != nullptr); + auto input_media = get_story_content_input_media(td_, content, std::move(input_file)); + CHECK(input_media != nullptr); + + const FormattedText &caption = story->caption_; + auto entities = get_input_message_entities(td_->contacts_manager_.get(), &caption, "SendStoryQuery"); + if (!td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { + entities.clear(); + } + auto privacy_rules = story->privacy_rules_.get_input_privacy_rules(td_); + auto period = story->expire_date_ - story->date_; + int32 flags = 0; + if (!caption.text.empty()) { + flags |= telegram_api::stories_sendStory::CAPTION_MASK; + } + if (!entities.empty()) { + flags |= telegram_api::stories_sendStory::ENTITIES_MASK; + } + if (pending_story_->story_->is_pinned_) { + flags |= telegram_api::stories_sendStory::PINNED_MASK; + } + if (period != 86400) { + flags |= telegram_api::stories_sendStory::PERIOD_MASK; + } + if (story->noforwards_) { + flags |= telegram_api::stories_sendStory::NOFORWARDS_MASK; + } + vector> media_areas; + for (const auto &media_area : story->areas_) { + media_areas.push_back(media_area.get_input_media_area()); + } + if (!media_areas.empty()) { + flags |= telegram_api::stories_sendStory::MEDIA_AREAS_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::stories_sendStory(flags, false /*ignored*/, false /*ignored*/, std::move(input_media), + std::move(media_areas), caption.text, std::move(entities), + std::move(privacy_rules), pending_story_->random_id_, period), + {{pending_story_->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 ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendStoryQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates( + std::move(ptr), PromiseCreator::lambda([file_id = file_id_, pending_story = std::move(pending_story_)]( + Result &&result) mutable { + send_closure(G()->story_manager(), &StoryManager::delete_pending_story, file_id, std::move(pending_story), + result.is_ok() ? Status::OK() : result.move_as_error()); + })); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendStoryQuery: " << status; + if (G()->close_flag() && G()->use_message_database()) { + // do not send error, story will be re-sent after restart + return; + } + + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->story_manager_->on_send_story_file_parts_missing(std::move(pending_story_), std::move(bad_parts)); + return; + } else { + td_->story_manager_->delete_pending_story(file_id_, std::move(pending_story_), std::move(status)); + } + } +}; + +class StoryManager::EditStoryQuery final : public Td::ResultHandler { + FileId file_id_; + unique_ptr pending_story_; + + public: + void send(FileId file_id, const Story *story, unique_ptr pending_story, + telegram_api::object_ptr input_file, const BeingEditedStory *edited_story) { + file_id_ = file_id; + pending_story_ = std::move(pending_story); + CHECK(pending_story_ != nullptr); + + int32 flags = 0; + + telegram_api::object_ptr input_media; + const StoryContent *content = edited_story->content_.get(); + if (content != nullptr) { + CHECK(input_file != nullptr); + input_media = get_story_content_input_media(td_, content, std::move(input_file)); + CHECK(input_media != nullptr); + flags |= telegram_api::stories_editStory::MEDIA_MASK; + } + vector> media_areas; + if (edited_story->edit_media_areas_) { + for (const auto &media_area : edited_story->areas_) { + media_areas.push_back(media_area.get_input_media_area()); + } + } else if (content != nullptr) { + for (const auto &media_area : story->areas_) { + media_areas.push_back(media_area.get_input_media_area()); + } + } + if (!media_areas.empty()) { + flags |= telegram_api::stories_editStory::MEDIA_AREAS_MASK; + } + vector> entities; + if (edited_story->edit_caption_) { + flags |= telegram_api::stories_editStory::CAPTION_MASK; + if (td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { + flags |= telegram_api::stories_editStory::ENTITIES_MASK; + entities = get_input_message_entities(td_->contacts_manager_.get(), &edited_story->caption_, "EditStoryQuery"); + } + } + + send_query(G()->net_query_creator().create( + telegram_api::stories_editStory(flags, pending_story_->story_id_.get(), std::move(input_media), + std::move(media_areas), edited_story->caption_.text, std::move(entities), + Auto()), + {{StoryFullId{pending_story_->dialog_id_, pending_story_->story_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 EditStoryQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates( + std::move(ptr), PromiseCreator::lambda([file_id = file_id_, pending_story = std::move(pending_story_)]( + Result &&result) mutable { + send_closure(G()->story_manager(), &StoryManager::delete_pending_story, file_id, std::move(pending_story), + result.is_ok() ? Status::OK() : result.move_as_error()); + })); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for EditStoryQuery: " << status; + if (G()->close_flag() && G()->use_message_database()) { + // do not send error, story will be edited after restart + return; + } + + if (!td_->auth_manager_->is_bot() && status.message() == "STORY_NOT_MODIFIED") { + return td_->story_manager_->delete_pending_story(file_id_, std::move(pending_story_), Status::OK()); + } + + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->story_manager_->on_send_story_file_parts_missing(std::move(pending_story_), std::move(bad_parts)); + return; + } + td_->story_manager_->delete_pending_story(file_id_, std::move(pending_story_), std::move(status)); + } +}; + +class StoryManager::UploadMediaCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->story_manager(), &StoryManager::on_upload_story, file_id, std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->story_manager(), &StoryManager::on_upload_story_error, file_id, std::move(error)); + } +}; + +StoryManager::PendingStory::PendingStory(DialogId dialog_id, StoryId story_id, uint32 send_story_num, int64 random_id, + unique_ptr &&story) + : dialog_id_(dialog_id) + , story_id_(story_id) + , send_story_num_(send_story_num) + , random_id_(random_id) + , story_(std::move(story)) { +} + +StoryManager::ReadyToSendStory::ReadyToSendStory(FileId file_id, unique_ptr &&pending_story, + telegram_api::object_ptr &&input_file) + : file_id_(file_id), pending_story_(std::move(pending_story)), input_file_(std::move(input_file)) { +} + +template +void StoryManager::Story::store(StorerT &storer) const { + using td::store; + bool has_receive_date = receive_date_ != 0; + bool has_interaction_info = !interaction_info_.is_empty(); + bool has_privacy_rules = privacy_rules_ != UserPrivacySettingRules(); + bool has_content = content_ != nullptr; + bool has_caption = !caption_.text.empty(); + bool has_areas = !areas_.empty(); + bool has_chosen_reaction_type = !chosen_reaction_type_.is_empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_edited_); + STORE_FLAG(is_pinned_); + STORE_FLAG(is_public_); + STORE_FLAG(is_for_close_friends_); + STORE_FLAG(noforwards_); + STORE_FLAG(has_receive_date); + STORE_FLAG(has_interaction_info); + STORE_FLAG(has_privacy_rules); + STORE_FLAG(has_content); + STORE_FLAG(has_caption); + STORE_FLAG(is_for_contacts_); + STORE_FLAG(is_for_selected_contacts_); + STORE_FLAG(has_areas); + STORE_FLAG(has_chosen_reaction_type); + END_STORE_FLAGS(); + store(date_, storer); + store(expire_date_, storer); + if (has_receive_date) { + store(receive_date_, storer); + } + if (has_interaction_info) { + store(interaction_info_, storer); + } + if (has_privacy_rules) { + store(privacy_rules_, storer); + } + if (has_content) { + store_story_content(content_.get(), storer); + } + if (has_caption) { + store(caption_, storer); + } + if (has_areas) { + store(areas_, storer); + } + if (has_chosen_reaction_type) { + store(chosen_reaction_type_, storer); + } +} + +template +void StoryManager::Story::parse(ParserT &parser) { + using td::parse; + bool has_receive_date; + bool has_interaction_info; + bool has_privacy_rules; + bool has_content; + bool has_caption; + bool has_areas; + bool has_chosen_reaction_type; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_edited_); + PARSE_FLAG(is_pinned_); + PARSE_FLAG(is_public_); + PARSE_FLAG(is_for_close_friends_); + PARSE_FLAG(noforwards_); + PARSE_FLAG(has_receive_date); + PARSE_FLAG(has_interaction_info); + PARSE_FLAG(has_privacy_rules); + PARSE_FLAG(has_content); + PARSE_FLAG(has_caption); + PARSE_FLAG(is_for_contacts_); + PARSE_FLAG(is_for_selected_contacts_); + PARSE_FLAG(has_areas); + PARSE_FLAG(has_chosen_reaction_type); + END_PARSE_FLAGS(); + parse(date_, parser); + parse(expire_date_, parser); + if (has_receive_date) { + parse(receive_date_, parser); + } + if (has_interaction_info) { + parse(interaction_info_, parser); + } + if (has_privacy_rules) { + parse(privacy_rules_, parser); + } + if (has_content) { + parse_story_content(content_, parser); + } + if (has_caption) { + parse(caption_, parser); + } + if (has_areas) { + parse(areas_, parser); + } + if (has_chosen_reaction_type) { + parse(chosen_reaction_type_, parser); + } +} + +template +void StoryManager::StoryInfo::store(StorerT &storer) const { + using td::store; + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_for_close_friends_); + END_STORE_FLAGS(); + store(story_id_, storer); + store(date_, storer); + store(expire_date_, storer); +} + +template +void StoryManager::StoryInfo::parse(ParserT &parser) { + using td::parse; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_for_close_friends_); + END_PARSE_FLAGS(); + parse(story_id_, parser); + parse(date_, parser); + parse(expire_date_, parser); +} + +template +void StoryManager::PendingStory::store(StorerT &storer) const { + using td::store; + bool is_edit = story_id_.is_server(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_edit); + END_STORE_FLAGS(); + store(dialog_id_, storer); + if (is_edit) { + store(story_id_, storer); + } else { + store(random_id_, storer); + } + store(story_, storer); +} + +template +void StoryManager::PendingStory::parse(ParserT &parser) { + using td::parse; + bool is_edit; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_edit); + END_PARSE_FLAGS(); + parse(dialog_id_, parser); + if (is_edit) { + parse(story_id_, parser); + } else { + parse(random_id_, parser); + } + parse(story_, parser); +} + +template +void StoryManager::SavedActiveStories::store(StorerT &storer) const { + using td::store; + CHECK(!story_infos_.empty()); + bool has_max_read_story_id = max_read_story_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_max_read_story_id); + END_STORE_FLAGS(); + store(story_infos_, storer); + if (has_max_read_story_id) { + store(max_read_story_id_, storer); + } +} + +template +void StoryManager::SavedActiveStories::parse(ParserT &parser) { + using td::parse; + bool has_max_read_story_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_max_read_story_id); + END_PARSE_FLAGS(); + parse(story_infos_, parser); + if (has_max_read_story_id) { + parse(max_read_story_id_, parser); + } +} + +template +void StoryManager::SavedStoryList::store(StorerT &storer) const { + using td::store; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_more_); + END_STORE_FLAGS(); + store(state_, storer); + store(total_count_, storer); +} + +template +void StoryManager::SavedStoryList::parse(ParserT &parser) { + using td::parse; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_more_); + END_PARSE_FLAGS(); + parse(state_, parser); + parse(total_count_, parser); +} + +StoryManager::StoryManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + upload_media_callback_ = std::make_shared(); + + story_reload_timeout_.set_callback(on_story_reload_timeout_callback); + story_reload_timeout_.set_callback_data(static_cast(this)); + + story_expire_timeout_.set_callback(on_story_expire_timeout_callback); + story_expire_timeout_.set_callback_data(static_cast(this)); + + story_can_get_viewers_timeout_.set_callback(on_story_can_get_viewers_timeout_callback); + story_can_get_viewers_timeout_.set_callback_data(static_cast(this)); + + if (G()->use_message_database() && td_->auth_manager_->is_authorized() && !td_->auth_manager_->is_bot()) { + for (auto story_list_id : {StoryListId::main(), StoryListId::archive()}) { + auto r_value = G()->td_db()->get_story_db_sync()->get_active_story_list_state(story_list_id); + if (r_value.is_ok() && !r_value.ok().empty()) { + SavedStoryList saved_story_list; + auto status = log_event_parse(saved_story_list, r_value.ok().as_slice()); + if (status.is_error()) { + LOG(ERROR) << "Load invalid state for " << story_list_id << " from database"; + } else { + LOG(INFO) << "Load state for " << story_list_id << " from database: " << saved_story_list.state_; + auto &story_list = get_story_list(story_list_id); + story_list.state_ = std::move(saved_story_list.state_); + story_list.server_total_count_ = td::max(saved_story_list.total_count_, 0); + story_list.server_has_more_ = saved_story_list.has_more_; + story_list.database_has_more_ = true; + } + } + } + } +} + +StoryManager::~StoryManager() { + Scheduler::instance()->destroy_on_scheduler( + G()->get_gc_scheduler_id(), story_full_id_to_file_source_id_, stories_, stories_by_global_id_, + inaccessible_story_full_ids_, deleted_story_full_ids_, failed_to_load_story_full_ids_, story_messages_, + active_stories_, updated_active_stories_, max_read_story_ids_, failed_to_load_active_stories_); +} + +void StoryManager::start_up() { + if (!td_->auth_manager_->is_authorized()) { + return; + } + + auto stealth_mode_str = G()->td_db()->get_binlog_pmc()->get(get_story_stealth_mode_key()); + if (!stealth_mode_str.empty()) { + log_event_parse(stealth_mode_, stealth_mode_str).ensure(); + stealth_mode_.update(); + LOG(INFO) << stealth_mode_; + if (stealth_mode_.is_empty()) { + G()->td_db()->get_binlog_pmc()->erase(get_story_stealth_mode_key()); + } else { + schedule_stealth_mode_update(); + } + } + send_update_story_stealth_mode(); + + try_synchronize_archive_all_stories(); + load_expired_database_stories(); + + for (auto story_list_id : {StoryListId::main(), StoryListId::archive()}) { + update_story_list_sent_total_count(story_list_id); + } +} + +void StoryManager::timeout_expired() { + load_expired_database_stories(); +} + +void StoryManager::hangup() { + auto fail_promise_map = [](auto &queries) { + while (!queries.empty()) { + auto it = queries.begin(); + auto promises = std::move(it->second); + queries.erase(it); + fail_promises(promises, Global::request_aborted_error()); + } + }; + fail_promise_map(reload_story_queries_); + fail_promise_map(delete_yet_unsent_story_queries_); + + stop(); +} + +void StoryManager::tear_down() { + parent_.reset(); +} + +void StoryManager::on_story_reload_timeout_callback(void *story_manager_ptr, int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_manager = static_cast(story_manager_ptr); + send_closure_later(story_manager->actor_id(story_manager), &StoryManager::on_story_reload_timeout, story_global_id); +} + +void StoryManager::on_story_reload_timeout(int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_full_id = stories_by_global_id_.get(story_global_id); + auto story = get_story(story_full_id); + if (story == nullptr || opened_stories_.count(story_full_id) == 0) { + LOG(INFO) << "There is no need to reload " << story_full_id; + return; + } + + reload_story(story_full_id, Promise(), "on_story_reload_timeout"); + story_reload_timeout_.set_timeout_in(story_global_id, OPENED_STORY_POLL_PERIOD); +} + +void StoryManager::on_story_expire_timeout_callback(void *story_manager_ptr, int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_manager = static_cast(story_manager_ptr); + send_closure_later(story_manager->actor_id(story_manager), &StoryManager::on_story_expire_timeout, story_global_id); +} + +void StoryManager::on_story_expire_timeout(int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_full_id = stories_by_global_id_.get(story_global_id); + auto story = get_story(story_full_id); + if (story == nullptr) { + return; + } + if (is_active_story(story)) { + // 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); + } + + LOG(INFO) << "Have expired " << story_full_id; + auto owner_dialog_id = story_full_id.get_dialog_id(); + CHECK(owner_dialog_id.is_valid()); + if (!is_story_owned(owner_dialog_id) && story->content_ != nullptr && !story->is_pinned_) { + // non-owned expired non-pinned stories are fully deleted + 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"); + } +} + +void StoryManager::on_story_can_get_viewers_timeout_callback(void *story_manager_ptr, int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_manager = static_cast(story_manager_ptr); + send_closure_later(story_manager->actor_id(story_manager), &StoryManager::on_story_can_get_viewers_timeout, + story_global_id); +} + +void StoryManager::on_story_can_get_viewers_timeout(int64 story_global_id) { + if (G()->close_flag()) { + return; + } + + auto story_full_id = stories_by_global_id_.get(story_global_id); + auto story = get_story(story_full_id); + if (story == nullptr) { + return; + } + + LOG(INFO) << "Have expired viewers in " << story_full_id; + if (can_get_story_viewers(story_full_id, story, true).is_ok()) { + // 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); + } + + // can_get_viewers flag could have been changed; reload the story to repair it + reload_story(story_full_id, Promise(), "on_story_can_get_viewers_timeout"); +} + +void StoryManager::load_expired_database_stories() { + if (!G()->use_message_database()) { + return; + } + + LOG(INFO) << "Load " << load_expired_database_stories_next_limit_ << " expired stories"; + G()->td_db()->get_story_db_async()->get_expiring_stories( + G()->unix_time() - 1, load_expired_database_stories_next_limit_, + PromiseCreator::lambda([actor_id = actor_id(this)](Result> r_stories) { + if (G()->close_flag()) { + return; + } + CHECK(r_stories.is_ok()); + send_closure(actor_id, &StoryManager::on_load_expired_database_stories, r_stories.move_as_ok()); + })); +} + +void StoryManager::on_load_expired_database_stories(vector stories) { + if (G()->close_flag()) { + return; + } + + int32 next_request_delay; + if (stories.size() == static_cast(load_expired_database_stories_next_limit_)) { + CHECK(load_expired_database_stories_next_limit_ < (1 << 30)); + load_expired_database_stories_next_limit_ *= 2; + next_request_delay = 1; + } else { + load_expired_database_stories_next_limit_ = DEFAULT_LOADED_EXPIRED_STORIES; + next_request_delay = Random::fast(300, 420); + } + set_timeout_in(next_request_delay); + + LOG(INFO) << "Receive " << stories.size() << " expired stories with next request in " << next_request_delay + << " seconds"; + for (auto &database_story : stories) { + auto story = parse_story(database_story.story_full_id_, std::move(database_story.data_)); + if (story != nullptr) { + LOG(ERROR) << "Receive non-expired " << database_story.story_full_id_; + } + } +} + +bool StoryManager::is_story_owned(DialogId owner_dialog_id) const { + return owner_dialog_id == DialogId(td_->contacts_manager_->get_my_id()); +} + +bool StoryManager::is_active_story(const Story *story) { + return story != nullptr && G()->unix_time() < story->expire_date_; +} + +int32 StoryManager::get_story_viewers_expire_date(const Story *story) const { + return story->expire_date_ + + narrow_cast(td_->option_manager_->get_option_integer("story_viewers_expiration_delay", 86400)); +} + +const StoryManager::Story *StoryManager::get_story(StoryFullId story_full_id) const { + return stories_.get_pointer(story_full_id); +} + +StoryManager::Story *StoryManager::get_story_editable(StoryFullId story_full_id) { + return stories_.get_pointer(story_full_id); +} + +StoryManager::Story *StoryManager::get_story_force(StoryFullId story_full_id, const char *source) { + if (!story_full_id.is_valid()) { + return nullptr; + } + + auto story = get_story_editable(story_full_id); + if (story != nullptr && story->content_ != nullptr) { + return story; + } + + if (!G()->use_message_database() || failed_to_load_story_full_ids_.count(story_full_id) > 0 || + is_inaccessible_story(story_full_id) || deleted_story_full_ids_.count(story_full_id) > 0 || + !story_full_id.get_story_id().is_server()) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << story_full_id << " from database from " << source; + + auto r_value = G()->td_db()->get_story_db_sync()->get_story(story_full_id); + if (r_value.is_error()) { + failed_to_load_story_full_ids_.insert(story_full_id); + return nullptr; + } + return on_get_story_from_database(story_full_id, r_value.ok(), source); +} + +unique_ptr StoryManager::parse_story(StoryFullId story_full_id, const BufferSlice &value) { + auto story = make_unique(); + auto status = log_event_parse(*story, value.as_slice()); + if (status.is_error()) { + LOG(ERROR) << "Receive invalid " << story_full_id << " from database: " << status << ' ' + << format::as_hex_dump<4>(value.as_slice()); + delete_story_from_database(story_full_id); + reload_story(story_full_id, Auto(), "parse_story"); + return nullptr; + } + if (story->content_ == nullptr) { + LOG(ERROR) << "Receive " << story_full_id << " without content from database"; + delete_story_from_database(story_full_id); + return nullptr; + } + if (!story_full_id.get_story_id().is_server()) { + LOG(ERROR) << "Receive " << story_full_id << " from database"; + delete_story_from_database(story_full_id); + return nullptr; + } + + auto owner_dialog_id = story_full_id.get_dialog_id(); + if (is_active_story(story.get())) { + auto active_stories = get_active_stories(owner_dialog_id); + if (active_stories != nullptr && !contains(active_stories->story_ids_, story_full_id.get_story_id())) { + LOG(INFO) << "Ignore unavailable active " << story_full_id << " from database"; + delete_story_files(story.get()); + delete_story_from_database(story_full_id); + return nullptr; + } + } else { + if (!is_story_owned(owner_dialog_id) && !story->is_pinned_) { + // non-owned expired non-pinned stories are fully deleted + LOG(INFO) << "Delete expired " << story_full_id; + delete_story_files(story.get()); + delete_story_from_database(story_full_id); + return nullptr; + } + } + + return story; +} + +StoryManager::Story *StoryManager::on_get_story_from_database(StoryFullId story_full_id, const BufferSlice &value, + const char *source) { + auto old_story = get_story_editable(story_full_id); + if (old_story != nullptr && old_story->content_ != nullptr) { + return old_story; + } + + if (value.empty()) { + failed_to_load_story_full_ids_.insert(story_full_id); + return nullptr; + } + + auto story = parse_story(story_full_id, value); + if (story == nullptr) { + failed_to_load_story_full_ids_.insert(story_full_id); + return nullptr; + } + + Dependencies dependencies; + add_story_dependencies(dependencies, story.get()); + if (!dependencies.resolve_force(td_, "on_get_story_from_database")) { + reload_story(story_full_id, Auto(), "on_get_story_from_database"); + failed_to_load_story_full_ids_.insert(story_full_id); + return nullptr; + } + + LOG(INFO) << "Load new " << story_full_id << " from " << source; + + auto result = story.get(); + stories_.set(story_full_id, std::move(story)); + register_story_global_id(story_full_id, result); + + CHECK(!is_inaccessible_story(story_full_id)); + CHECK(being_edited_stories_.count(story_full_id) == 0); + + on_story_changed(story_full_id, result, true, false, true); + + return result; +} + +const StoryManager::ActiveStories *StoryManager::get_active_stories(DialogId owner_dialog_id) const { + return active_stories_.get_pointer(owner_dialog_id); +} + +StoryManager::ActiveStories *StoryManager::get_active_stories_editable(DialogId owner_dialog_id) { + return active_stories_.get_pointer(owner_dialog_id); +} + +StoryManager::ActiveStories *StoryManager::get_active_stories_force(DialogId owner_dialog_id, const char *source) { + auto active_stories = get_active_stories_editable(owner_dialog_id); + if (active_stories != nullptr) { + return active_stories; + } + + if (!G()->use_message_database() || failed_to_load_active_stories_.count(owner_dialog_id) > 0 || + !owner_dialog_id.is_valid()) { + return nullptr; + } + + LOG(INFO) << "Trying to load active stories of " << owner_dialog_id << " from database from " << source; + auto r_value = G()->td_db()->get_story_db_sync()->get_active_stories(owner_dialog_id); + if (r_value.is_error()) { + failed_to_load_active_stories_.insert(owner_dialog_id); + return nullptr; + } + return on_get_active_stories_from_database(StoryListId(), owner_dialog_id, r_value.ok(), source); +} + +StoryManager::ActiveStories *StoryManager::on_get_active_stories_from_database(StoryListId story_list_id, + DialogId owner_dialog_id, + const BufferSlice &value, + const char *source) { + auto active_stories = get_active_stories_editable(owner_dialog_id); + if (active_stories != nullptr) { + return active_stories; + } + + if (value.empty()) { + failed_to_load_active_stories_.insert(owner_dialog_id); + return nullptr; + } + + SavedActiveStories saved_active_stories; + auto status = log_event_parse(saved_active_stories, value.as_slice()); + if (status.is_error()) { + LOG(ERROR) << "Receive invalid active stories in " << owner_dialog_id << " from database: " << status << ' ' + << format::as_hex_dump<4>(value.as_slice()); + save_active_stories(owner_dialog_id, nullptr, Promise(), "on_get_active_stories_from_database"); + failed_to_load_active_stories_.insert(owner_dialog_id); + return nullptr; + } + + vector story_ids; + for (auto &story_info : saved_active_stories.story_infos_) { + story_ids.push_back(on_get_story_info(owner_dialog_id, std::move(story_info))); + } + + on_update_active_stories(owner_dialog_id, saved_active_stories.max_read_story_id_, std::move(story_ids), + Promise(), "on_get_active_stories_from_database", true); + + active_stories = get_active_stories_editable(owner_dialog_id); + if (active_stories == nullptr) { + if (!story_list_id.is_valid()) { + story_list_id = get_dialog_story_list_id(owner_dialog_id); + } + if (story_list_id.is_valid()) { + auto &story_list = get_story_list(story_list_id); + 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); + save_story_list(story_list_id, story_list.state_, story_list.server_total_count_, story_list.server_has_more_); + } + } + } + return active_stories; +} + +void StoryManager::add_story_dependencies(Dependencies &dependencies, const Story *story) { + story->interaction_info_.add_dependencies(dependencies); + story->privacy_rules_.add_dependencies(dependencies); + if (story->content_ != nullptr) { + add_story_content_dependencies(dependencies, story->content_.get()); + } + add_formatted_text_dependencies(dependencies, &story->caption_); +} + +void StoryManager::add_pending_story_dependencies(Dependencies &dependencies, const PendingStory *pending_story) { + dependencies.add_dialog_and_dependencies(pending_story->dialog_id_); + add_story_dependencies(dependencies, pending_story->story_.get()); +} + +void StoryManager::load_active_stories(StoryListId story_list_id, Promise &&promise) { + if (!story_list_id.is_valid()) { + return promise.set_error(Status::Error(400, "Story list must be non-empty")); + } + auto &story_list = get_story_list(story_list_id); + if (story_list.list_last_story_date_ == MAX_DIALOG_DATE) { + return promise.set_error(Status::Error(404, "Not found")); + } + + if (story_list.database_has_more_) { + CHECK(G()->use_message_database()); + story_list.load_list_from_database_queries_.push_back(std::move(promise)); + if (story_list.load_list_from_database_queries_.size() == 1u) { + G()->td_db()->get_story_db_async()->get_active_story_list( + story_list_id, story_list.last_loaded_database_dialog_date_.get_order(), + story_list.last_loaded_database_dialog_date_.get_dialog_id(), 10, + PromiseCreator::lambda( + [actor_id = actor_id(this), story_list_id](Result &&result) { + send_closure(actor_id, &StoryManager::on_load_active_stories_from_database, story_list_id, + std::move(result)); + })); + } + return; + } + + if (!story_list.server_has_more_) { + if (story_list.list_last_story_date_ != MAX_DIALOG_DATE) { + auto min_story_date = story_list.list_last_story_date_; + story_list.list_last_story_date_ = MAX_DIALOG_DATE; + for (auto it = story_list.ordered_stories_.upper_bound(min_story_date); it != story_list.ordered_stories_.end(); + ++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); + } + return promise.set_error(Status::Error(404, "Not found")); + } + + load_active_stories_from_server(story_list_id, story_list, !story_list.state_.empty(), std::move(promise)); +} + +void StoryManager::on_load_active_stories_from_database(StoryListId story_list_id, + Result result) { + G()->ignore_result_if_closing(result); + auto &story_list = get_story_list(story_list_id); + auto promises = std::move(story_list.load_list_from_database_queries_); + CHECK(!promises.empty()); + if (result.is_error()) { + return fail_promises(promises, result.move_as_error()); + } + + auto active_story_list = result.move_as_ok(); + + LOG(INFO) << "Load " << active_story_list.active_stories_.size() << " chats with active stories in " << story_list_id + << " from database"; + + Dependencies dependencies; + for (auto &active_stories_it : active_story_list.active_stories_) { + dependencies.add_dialog_and_dependencies(active_stories_it.first); + } + if (!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; + } + + if (active_story_list.active_stories_.empty()) { + story_list.last_loaded_database_dialog_date_ = MAX_DIALOG_DATE; + story_list.database_has_more_ = false; + } else { + for (auto &active_stories_it : active_story_list.active_stories_) { + on_get_active_stories_from_database(story_list_id, active_stories_it.first, active_stories_it.second, + "on_load_active_stories_from_database"); + } + DialogDate max_story_date(active_story_list.next_order_, active_story_list.next_dialog_id_); + if (story_list.last_loaded_database_dialog_date_ < max_story_date) { + story_list.last_loaded_database_dialog_date_ = max_story_date; + + 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"); + } + } + for (auto owner_dialog_id : owner_dialog_ids) { + on_dialog_active_stories_order_updated(owner_dialog_id, "on_load_active_stories_from_database 2"); + } + } + } else { + LOG(ERROR) << "Last database story date didn't increase"; + } + update_story_list_sent_total_count(story_list_id, story_list); + } + + set_promises(promises); +} + +void StoryManager::load_active_stories_from_server(StoryListId story_list_id, StoryList &story_list, bool is_next, + Promise &&promise) { + story_list.load_list_from_server_queries_.push_back(std::move(promise)); + if (story_list.load_list_from_server_queries_.size() == 1u) { + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), story_list_id, is_next, state = story_list.state_]( + Result> r_all_stories) { + send_closure(actor_id, &StoryManager::on_load_active_stories_from_server, story_list_id, is_next, state, + std::move(r_all_stories)); + }); + td_->create_handler(std::move(query_promise))->send(story_list_id, is_next, story_list.state_); + } +} + +void StoryManager::reload_active_stories() { + for (auto story_list_id : {StoryListId::main(), StoryListId::archive()}) { + load_active_stories_from_server(story_list_id, get_story_list(story_list_id), false, Promise()); + } +} + +void StoryManager::on_load_active_stories_from_server( + StoryListId story_list_id, bool is_next, string old_state, + Result> r_all_stories) { + G()->ignore_result_if_closing(r_all_stories); + auto &story_list = get_story_list(story_list_id); + auto promises = std::move(story_list.load_list_from_server_queries_); + CHECK(!promises.empty()); + if (r_all_stories.is_error()) { + return fail_promises(promises, r_all_stories.move_as_error()); + } + auto all_stories = r_all_stories.move_as_ok(); + switch (all_stories->get_id()) { + case telegram_api::stories_allStoriesNotModified::ID: { + auto stories = telegram_api::move_object_as(all_stories); + if (stories->state_.empty()) { + LOG(ERROR) << "Receive empty state in " << to_string(stories); + } else { + story_list.state_ = std::move(stories->state_); + save_story_list(story_list_id, story_list.state_, story_list.server_total_count_, story_list.server_has_more_); + } + on_update_story_stealth_mode(std::move(stories->stealth_mode_)); + break; + } + case telegram_api::stories_allStories::ID: { + auto stories = telegram_api::move_object_as(all_stories); + td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_load_active_stories_from_server"); + if (stories->state_.empty()) { + LOG(ERROR) << "Receive empty state in " << to_string(stories); + } else { + story_list.state_ = std::move(stories->state_); + } + story_list.server_total_count_ = max(stories->count_, 0); + story_list.is_reloaded_server_total_count_ = true; + if (!stories->has_more_ || stories->user_stories_.empty()) { + story_list.server_has_more_ = false; + } + + MultiPromiseActorSafe mpas{"SaveActiveStoryMultiPromiseActor"}; + mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), story_list_id, state = story_list.state_, + server_total_count = story_list.server_total_count_, + has_more = story_list.server_has_more_](Result &&result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &StoryManager::save_story_list, story_list_id, std::move(state), server_total_count, + has_more); + } + })); + auto lock = mpas.get_promise(); + + if (stories->user_stories_.empty() && stories->has_more_) { + LOG(ERROR) << "Receive no stories, but expected more"; + stories->has_more_ = false; + } + + auto max_story_date = MIN_DIALOG_DATE; + vector owner_dialog_ids; + for (auto &user_stories : stories->user_stories_) { + auto owner_dialog_id = on_get_user_stories(DialogId(), std::move(user_stories), mpas.get_promise()); + auto active_stories = get_active_stories(owner_dialog_id); + if (active_stories == nullptr) { + LOG(ERROR) << "Receive invalid stories"; + } else { + DialogDate story_date(active_stories->private_order_, owner_dialog_id); + if (max_story_date < story_date) { + max_story_date = story_date; + } else { + LOG(ERROR) << "Receive " << story_date << " after " << max_story_date << " for " + << (is_next ? "next" : "first") << " request with state \"" << old_state << "\" in " + << story_list_id << " of " << td_->contacts_manager_->get_my_id(); + } + owner_dialog_ids.push_back(owner_dialog_id); + } + } + if (!stories->has_more_) { + max_story_date = MAX_DIALOG_DATE; + } + + vector delete_dialog_ids; + auto min_story_date = is_next ? story_list.list_last_story_date_ : MIN_DIALOG_DATE; + 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 (!td::contains(owner_dialog_ids, dialog_id)) { + delete_dialog_ids.push_back(dialog_id); + } + } + if (story_list.list_last_story_date_ < max_story_date) { + story_list.list_last_story_date_ = max_story_date; + for (auto owner_dialog_id : owner_dialog_ids) { + on_dialog_active_stories_order_updated(owner_dialog_id, "on_load_active_stories_from_server"); + } + } else if (is_next) { + LOG(ERROR) << "Last story date didn't increase"; + } + if (!delete_dialog_ids.empty()) { + LOG(INFO) << "Delete active stories in " << delete_dialog_ids; + } + for (auto dialog_id : delete_dialog_ids) { + on_update_active_stories(dialog_id, StoryId(), vector(), mpas.get_promise(), + "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); + + lock.set_value(Unit()); + + on_update_story_stealth_mode(std::move(stories->stealth_mode_)); + break; + } + default: + UNREACHABLE(); + } + + set_promises(promises); +} + +void StoryManager::save_story_list(StoryListId story_list_id, string state, int32 total_count, bool has_more) { + if (G()->close_flag() || !G()->use_message_database()) { + return; + } + + SavedStoryList saved_story_list; + saved_story_list.state_ = std::move(state); + saved_story_list.total_count_ = total_count; + saved_story_list.has_more_ = has_more; + G()->td_db()->get_story_db_async()->add_active_story_list_state(story_list_id, log_event_store(saved_story_list), + Promise()); +} + +StoryManager::StoryList &StoryManager::get_story_list(StoryListId story_list_id) { + CHECK(!td_->auth_manager_->is_bot()); + CHECK(story_list_id.is_valid()); + return story_lists_[story_list_id == StoryListId::archive()]; +} + +const StoryManager::StoryList &StoryManager::get_story_list(StoryListId story_list_id) const { + CHECK(!td_->auth_manager_->is_bot()); + CHECK(story_list_id.is_valid()); + return story_lists_[story_list_id == StoryListId::archive()]; +} + +td_api::object_ptr StoryManager::get_update_story_list_chat_count_object( + StoryListId story_list_id, const StoryList &story_list) const { + CHECK(story_list_id.is_valid()); + return td_api::make_object(story_list_id.get_story_list_object(), + story_list.sent_total_count_); +} + +void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id) { + if (td_->auth_manager_->is_bot()) { + return; + } + update_story_list_sent_total_count(story_list_id, get_story_list(story_list_id)); +} + +void StoryManager::update_story_list_sent_total_count(StoryListId story_list_id, StoryList &story_list) { + 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; + 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_) { + if (active_stories_.count(it.first) == 0) { + yet_unsent_total_count++; + } + } + new_total_count += yet_unsent_total_count; + if (story_list.list_last_story_date_ != MAX_DIALOG_DATE) { + new_total_count = max(new_total_count, story_list.server_total_count_ + yet_unsent_total_count); + } else if (story_list.server_total_count_ != new_total_count) { + story_list.server_total_count_ = new_total_count; + save_story_list(story_list_id, story_list.state_, story_list.server_total_count_, story_list.server_has_more_); + } + if (story_list.sent_total_count_ != new_total_count) { + story_list.sent_total_count_ = new_total_count; + send_closure(G()->td(), &Td::send_update, get_update_story_list_chat_count_object(story_list_id, story_list)); + } +} + +void StoryManager::reload_all_read_stories() { + td_->create_handler()->send(); +} + +void StoryManager::try_synchronize_archive_all_stories() { + if (G()->close_flag()) { + return; + } + if (has_active_synchronize_archive_all_stories_query_) { + return; + } + if (!td_->option_manager_->get_option_boolean("need_synchronize_archive_all_stories")) { + return; + } + + has_active_synchronize_archive_all_stories_query_ = true; + auto archive_all_stories = td_->option_manager_->get_option_boolean("archive_all_stories"); + + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), archive_all_stories](Result result) { + send_closure(actor_id, &StoryManager::on_synchronized_archive_all_stories, archive_all_stories, std::move(result)); + }); + td_->create_handler(std::move(promise))->send(archive_all_stories); +} + +void StoryManager::on_synchronized_archive_all_stories(bool set_archive_all_stories, Result result) { + if (G()->close_flag()) { + return; + } + CHECK(has_active_synchronize_archive_all_stories_query_); + has_active_synchronize_archive_all_stories_query_ = false; + + auto archive_all_stories = td_->option_manager_->get_option_boolean("archive_all_stories"); + if (archive_all_stories != set_archive_all_stories) { + return try_synchronize_archive_all_stories(); + } + td_->option_manager_->set_option_empty("need_synchronize_archive_all_stories"); + + if (result.is_error()) { + send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise()); + } +} + +void StoryManager::toggle_dialog_stories_hidden(DialogId dialog_id, StoryListId story_list_id, + Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(dialog_id, "toggle_dialog_stories_hidden")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (dialog_id.get_type() != DialogType::User) { + return promise.set_error(Status::Error(400, "Can't archive sender stories")); + } + if (story_list_id == get_dialog_story_list_id(dialog_id)) { + return promise.set_value(Unit()); + } + if (!story_list_id.is_valid()) { + return promise.set_error(Status::Error(400, "Story list must be non-empty")); + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id.get_user_id(), story_list_id == StoryListId::archive()); +} + +void StoryManager::get_dialog_pinned_stories(DialogId owner_dialog_id, StoryId from_story_id, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "get_dialog_pinned_stories")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (owner_dialog_id.get_type() != DialogType::User) { + return promise.set_value(td_api::make_object()); + } + + if (from_story_id != StoryId() && !from_story_id.is_server()) { + return promise.set_error(Status::Error(400, "Invalid value of parameter from_story_id specified")); + } + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), owner_dialog_id, promise = std::move(promise)]( + Result> &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StoryManager::on_get_dialog_pinned_stories, owner_dialog_id, result.move_as_ok(), + std::move(promise)); + }); + td_->create_handler(std::move(query_promise)) + ->send(owner_dialog_id.get_user_id(), from_story_id, limit); +} + +void StoryManager::on_get_dialog_pinned_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&stories, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + auto result = on_get_stories(owner_dialog_id, {}, std::move(stories)); + on_update_dialog_has_pinned_stories(owner_dialog_id, result.first > 0); + promise.set_value(get_stories_object(result.first, transform(result.second, [owner_dialog_id](StoryId story_id) { + return StoryFullId(owner_dialog_id, story_id); + }))); +} + +void StoryManager::get_story_archive(StoryId from_story_id, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + + if (from_story_id != StoryId() && !from_story_id.is_server()) { + return promise.set_error(Status::Error(400, "Invalid value of parameter from_story_id specified")); + } + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( + Result> &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StoryManager::on_get_story_archive, result.move_as_ok(), std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(from_story_id, limit); +} + +void StoryManager::on_get_story_archive(telegram_api::object_ptr &&stories, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + auto result = on_get_stories(dialog_id, {}, std::move(stories)); + promise.set_value(get_stories_object(result.first, transform(result.second, [dialog_id](StoryId story_id) { + return StoryFullId(dialog_id, story_id); + }))); +} + +void StoryManager::get_dialog_expiring_stories(DialogId owner_dialog_id, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "get_dialog_expiring_stories")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (owner_dialog_id.get_type() != DialogType::User) { + if (updated_active_stories_.insert(owner_dialog_id)) { + send_update_chat_active_stories(owner_dialog_id, nullptr, "get_dialog_expiring_stories 1"); + } + return promise.set_value(get_chat_active_stories_object(owner_dialog_id, nullptr)); + } + + LOG(INFO) << "Get active stories in " << owner_dialog_id; + auto active_stories = get_active_stories_force(owner_dialog_id, "get_dialog_expiring_stories"); + if (active_stories != nullptr) { + if (!promise) { + return promise.set_value(nullptr); + } + if (updated_active_stories_.insert(owner_dialog_id)) { + send_update_chat_active_stories(owner_dialog_id, active_stories, "get_dialog_expiring_stories 2"); + } + promise.set_value(get_chat_active_stories_object(owner_dialog_id, active_stories)); + promise = {}; + } + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), owner_dialog_id, promise = std::move(promise)]( + Result> &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StoryManager::on_get_dialog_expiring_stories, owner_dialog_id, result.move_as_ok(), + std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(owner_dialog_id.get_user_id()); +} + +void StoryManager::reload_dialog_expiring_stories(DialogId dialog_id) { + if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + return; + } + td_->messages_manager_->force_create_dialog(dialog_id, "reload_dialog_expiring_stories"); + load_dialog_expiring_stories(dialog_id, 0, "reload_dialog_expiring_stories"); +} + +class StoryManager::LoadDialogExpiringStoriesLogEvent { + public: + DialogId dialog_id_; + + template + void store(StorerT &storer) const { + td::store(dialog_id_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(dialog_id_, parser); + } +}; + +uint64 StoryManager::save_load_dialog_expiring_stories_log_event(DialogId owner_dialog_id) { + LoadDialogExpiringStoriesLogEvent log_event{owner_dialog_id}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::LoadDialogExpiringStories, + get_log_event_storer(log_event)); +} + +void StoryManager::load_dialog_expiring_stories(DialogId owner_dialog_id, uint64 log_event_id, const char *source) { + if (load_expiring_stories_log_event_ids_.count(owner_dialog_id) > 0) { + if (log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), log_event_id); + } + return; + } + LOG(INFO) << "Load active stories in " << owner_dialog_id << " from " << source; + if (log_event_id == 0 && G()->use_message_database()) { + log_event_id = save_load_dialog_expiring_stories_log_event(owner_dialog_id); + } + load_expiring_stories_log_event_ids_[owner_dialog_id] = log_event_id; + + // send later to ensure that active stories are inited before sending the request + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this), owner_dialog_id](Result> &&) { + if (!G()->close_flag()) { + send_closure(actor_id, &StoryManager::on_load_dialog_expiring_stories, owner_dialog_id); + } + }); + send_closure_later(actor_id(this), &StoryManager::get_dialog_expiring_stories, owner_dialog_id, std::move(promise)); +} + +void StoryManager::on_load_dialog_expiring_stories(DialogId owner_dialog_id) { + if (G()->close_flag()) { + return; + } + auto it = load_expiring_stories_log_event_ids_.find(owner_dialog_id); + if (it == load_expiring_stories_log_event_ids_.end()) { + return; + } + auto log_event_id = it->second; + load_expiring_stories_log_event_ids_.erase(it); + if (log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), log_event_id); + } + LOG(INFO) << "Finished loading of active stories in " << owner_dialog_id; +} + +void StoryManager::on_get_dialog_expiring_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&stories, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_get_dialog_expiring_stories"); + owner_dialog_id = on_get_user_stories(owner_dialog_id, std::move(stories->stories_), Promise()); + if (promise) { + 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"); + } + promise.set_value(get_chat_active_stories_object(owner_dialog_id, active_stories)); + } else { + promise.set_value(nullptr); + } +} + +void StoryManager::open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "open_story")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (!story_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid story identifier specified")); + } + + StoryFullId story_full_id{owner_dialog_id, story_id}; + const Story *story = get_story(story_full_id); + if (story == nullptr) { + return promise.set_value(Unit()); + } + + if (is_story_owned(owner_dialog_id) && story_id.is_server()) { + if (opened_owned_stories_.empty()) { + schedule_interaction_info_update(); + } + auto &open_count = opened_owned_stories_[story_full_id]; + if (++open_count == 1) { + td_->create_handler()->send({story_id}); + } + } + + if (story->content_ == nullptr) { + return promise.set_value(Unit()); + } + + if (story_id.is_server()) { + auto &open_count = opened_stories_[story_full_id]; + if (++open_count == 1) { + CHECK(story->global_id_ > 0); + story_reload_timeout_.set_timeout_in(story->global_id_, + story->receive_date_ + OPENED_STORY_POLL_PERIOD - G()->unix_time()); + } + } + + for (auto file_id : get_story_file_ids(story)) { + td_->file_manager_->check_local_location_async(file_id, true); + } + + bool is_active = is_active_story(story); + bool need_increment_story_views = story_id.is_server() && !is_active && story->is_pinned_; + bool need_read_story = story_id.is_server() && is_active; + + if (need_increment_story_views) { + auto &story_views = pending_story_views_[owner_dialog_id]; + story_views.story_ids_.insert(story_id); + if (!story_views.has_query_) { + increment_story_views(owner_dialog_id, story_views); + } + } + + if (need_read_story && on_update_read_stories(owner_dialog_id, story_id)) { + read_stories_on_server(owner_dialog_id, story_id, 0); + } + + promise.set_value(Unit()); +} + +void StoryManager::close_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise) { + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "close_story")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (!story_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid story identifier specified")); + } + + StoryFullId story_full_id{owner_dialog_id, story_id}; + if (is_story_owned(owner_dialog_id) && story_id.is_server()) { + auto &open_count = opened_owned_stories_[story_full_id]; + if (open_count == 0) { + return promise.set_error(Status::Error(400, "The story wasn't opened")); + } + if (--open_count == 0) { + opened_owned_stories_.erase(story_full_id); + if (opened_owned_stories_.empty()) { + interaction_info_update_timeout_.cancel_timeout(); + } + } + } + + const Story *story = get_story(story_full_id); + if (story == nullptr) { + return promise.set_value(Unit()); + } + + if (story_id.is_server()) { + auto &open_count = opened_stories_[story_full_id]; + if (open_count > 0 && --open_count == 0) { + opened_stories_.erase(story_full_id); + story_reload_timeout_.cancel_timeout(story->global_id_); + } + } + + promise.set_value(Unit()); +} + +void StoryManager::view_story_message(StoryFullId story_full_id) { + if (!story_full_id.get_story_id().is_server()) { + return; + } + + const Story *story = get_story_force(story_full_id, "view_story_message"); + if (story == nullptr || story->receive_date_ < G()->unix_time() - VIEWED_STORY_POLL_PERIOD) { + reload_story(story_full_id, Promise(), "view_story_message"); + } +} + +void StoryManager::on_story_replied(StoryFullId story_full_id, UserId replier_user_id) { + if (!replier_user_id.is_valid() || replier_user_id == td_->contacts_manager_->get_my_id() || + !story_full_id.get_story_id().is_server()) { + return; + } + const Story *story = get_story_force(story_full_id, "on_story_replied"); + if (story == nullptr || !is_story_owned(story_full_id.get_dialog_id())) { + return; + } + + if (story->content_ != nullptr && G()->unix_time() < get_story_viewers_expire_date(story) && + story->interaction_info_.definitely_has_no_user(replier_user_id)) { + td_->create_handler()->send({story_full_id.get_story_id()}); + } +} + +bool StoryManager::can_use_story_reaction(const ReactionType &reaction_type) const { + if (reaction_type.is_empty()) { + return true; + } + if (reaction_type.is_custom_reaction()) { + return td_->option_manager_->get_option_boolean("is_premium"); + } + return td_->reaction_manager_->is_active_reaction(reaction_type); +} + +void StoryManager::set_story_reaction(StoryFullId story_full_id, ReactionType reaction_type, bool add_to_recent, + Promise &&promise) { + auto owner_dialog_id = story_full_id.get_dialog_id(); + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "set_story_reaction")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (!story_full_id.get_story_id().is_valid()) { + return promise.set_error(Status::Error(400, "Invalid story identifier specified")); + } + if (!story_full_id.get_story_id().is_server()) { + return promise.set_error(Status::Error(400, "Can't react to the story")); + } + + Story *story = get_story_force(story_full_id, "set_story_reaction"); + if (story == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + + if (!can_use_story_reaction(reaction_type)) { + return promise.set_error(Status::Error(400, "The reaction isn't available for stories")); + } + + if (story->chosen_reaction_type_ == reaction_type) { + return promise.set_value(Unit()); + } + + if (add_to_recent) { + td_->reaction_manager_->add_recent_reaction(reaction_type); + } + + story->chosen_reaction_type_ = reaction_type; + on_story_changed(story_full_id, story, true, true); + + // TODO cancel previous queries, log event + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), story_full_id, + promise = std::move(promise)](Result &&result) mutable { + send_closure(actor_id, &StoryManager::on_set_story_reaction, story_full_id, std::move(result), std::move(promise)); + }); + + td_->create_handler(std::move(query_promise)) + ->send(story_full_id, reaction_type, add_to_recent); +} + +void StoryManager::on_set_story_reaction(StoryFullId story_full_id, Result &&result, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!have_story_force(story_full_id)) { + return promise.set_value(Unit()); + } + + if (result.is_error()) { + reload_story(story_full_id, Promise(), "on_set_story_reaction"); + } + + promise.set_result(std::move(result)); +} + +void StoryManager::schedule_interaction_info_update() { + if (interaction_info_update_timeout_.has_timeout()) { + return; + } + + interaction_info_update_timeout_.set_callback(std::move(update_interaction_info_static)); + interaction_info_update_timeout_.set_callback_data(static_cast(this)); + interaction_info_update_timeout_.set_timeout_in(10.0); +} + +void StoryManager::update_interaction_info_static(void *story_manager) { + if (G()->close_flag()) { + return; + } + + CHECK(story_manager != nullptr); + static_cast(story_manager)->update_interaction_info(); +} + +void StoryManager::update_interaction_info() { + if (opened_owned_stories_.empty()) { + return; + } + FlatHashMap, DialogIdHash> split_story_ids; + for (auto &it : opened_owned_stories_) { + auto story_full_id = it.first; + auto &story_ids = split_story_ids[story_full_id.get_dialog_id()]; + if (story_ids.size() < 100) { + story_ids.push_back(story_full_id.get_story_id()); + } + } + for (auto &story_ids : split_story_ids) { + CHECK(story_ids.first == DialogId(td_->contacts_manager_->get_my_id())); + td_->create_handler()->send(std::move(story_ids.second)); + } +} + +void StoryManager::increment_story_views(DialogId owner_dialog_id, PendingStoryViews &story_views) { + CHECK(!story_views.has_query_); + vector viewed_story_ids; + const size_t MAX_VIEWED_STORIES = 200; // server-side limit + while (!story_views.story_ids_.empty() && viewed_story_ids.size() < MAX_VIEWED_STORIES) { + auto story_id_it = story_views.story_ids_.begin(); + viewed_story_ids.push_back(*story_id_it); + story_views.story_ids_.erase(story_id_it); + } + CHECK(!viewed_story_ids.empty()); + story_views.has_query_ = true; + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), owner_dialog_id](Result) { + send_closure(actor_id, &StoryManager::on_increment_story_views, owner_dialog_id); + }); + td_->create_handler(std::move(promise))->send(owner_dialog_id, std::move(viewed_story_ids)); +} + +void StoryManager::on_increment_story_views(DialogId owner_dialog_id) { + if (G()->close_flag()) { + return; + } + + auto &story_views = pending_story_views_[owner_dialog_id]; + CHECK(story_views.has_query_); + story_views.has_query_ = false; + if (story_views.story_ids_.empty()) { + pending_story_views_.erase(owner_dialog_id); + return; + } + increment_story_views(owner_dialog_id, story_views); +} + +class StoryManager::ReadStoriesOnServerLogEvent { + public: + DialogId dialog_id_; + StoryId max_story_id_; + + template + void store(StorerT &storer) const { + td::store(dialog_id_, storer); + td::store(max_story_id_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(dialog_id_, parser); + td::parse(max_story_id_, parser); + } +}; + +uint64 StoryManager::save_read_stories_on_server_log_event(DialogId dialog_id, StoryId max_story_id) { + ReadStoriesOnServerLogEvent log_event{dialog_id, max_story_id}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadStoriesOnServer, + get_log_event_storer(log_event)); +} + +void StoryManager::read_stories_on_server(DialogId owner_dialog_id, StoryId story_id, uint64 log_event_id) { + CHECK(story_id.is_server()); + if (log_event_id == 0 && G()->use_message_database()) { + log_event_id = save_read_stories_on_server_log_event(owner_dialog_id, story_id); + } + + 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 { + CHECK(story != nullptr); + if (!is_story_owned(story_full_id.get_dialog_id())) { + return Status::Error(400, "Story is not outgoing"); + } + if (!story_full_id.get_story_id().is_server()) { + return Status::Error(400, "Story is not sent yet"); + } + 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())) { + return Status::Error(400, "Story is too old"); + } + return Status::OK(); +} + +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) { + DialogId owner_dialog_id(td_->contacts_manager_->get_my_id()); + StoryFullId story_full_id{owner_dialog_id, story_id}; + const Story *story = get_story(story_full_id); + if (story == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + 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) { + return promise.set_value(td_api::make_object()); + } + + bool is_full = query.empty() && !only_contacts; + bool is_first = is_full && offset.empty(); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), story_id, is_full, is_first, promise = std::move(promise)]( + Result> result) mutable { + send_closure(actor_id, &StoryManager::on_get_story_viewers, story_id, is_full, is_first, std::move(result), + std::move(promise)); + }); + + td_->create_handler(std::move(query_promise)) + ->send(story_full_id.get_story_id(), query, only_contacts, prefer_with_reaction, offset, limit); +} + +void StoryManager::on_get_story_viewers( + StoryId story_id, bool is_full, bool is_first, + Result> r_view_list, + Promise> &&promise) { + G()->ignore_result_if_closing(r_view_list); + if (r_view_list.is_error()) { + return promise.set_error(r_view_list.move_as_error()); + } + auto view_list = r_view_list.move_as_ok(); + + DialogId owner_dialog_id(td_->contacts_manager_->get_my_id()); + CHECK(story_id.is_server()); + StoryFullId story_full_id{owner_dialog_id, story_id}; + Story *story = get_story_editable(story_full_id); + if (story == nullptr) { + return promise.set_value(td_api::make_object()); + } + + td_->contacts_manager_->on_get_users(std::move(view_list->users_), "on_get_story_viewers"); + + auto total_count = view_list->count_; + if (total_count < 0 || static_cast(total_count) < view_list->views_.size()) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << view_list->views_.size() << " story viewers"; + total_count = static_cast(view_list->views_.size()); + } + auto total_reaction_count = view_list->reactions_count_; + if (total_reaction_count < 0 || total_reaction_count > total_count) { + LOG(ERROR) << "Receive total_reaction_count = " << total_reaction_count << " with " << total_count + << " story viewers"; + total_reaction_count = total_count; + } + for (auto &view : view_list->views_) { + td_->contacts_manager_->on_update_user_is_blocked(UserId(view->user_id_), view->blocked_, + view->blocked_my_stories_from_); + } + + StoryViewers story_viewers(total_count, total_reaction_count, std::move(view_list->views_), + std::move(view_list->next_offset_)); + if (story->content_ != nullptr) { + bool is_changed = false; + if (is_full && story->interaction_info_.set_counts(total_count, total_reaction_count)) { + is_changed = true; + } + if (is_first && story->interaction_info_.set_recent_viewer_user_ids(story_viewers.get_user_ids())) { + is_changed = true; + } + if (is_changed) { + on_story_changed(story_full_id, story, true, true); + } + } + + td_->contacts_manager_->on_view_user_active_stories(story_viewers.get_user_ids()); + promise.set_value(story_viewers.get_story_viewers_object(td_->contacts_manager_.get())); +} + +void StoryManager::report_story(StoryFullId story_full_id, ReportReason &&reason, Promise &&promise) { + if (!have_story_force(story_full_id)) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story_full_id.is_server()) { + return promise.set_error(Status::Error(400, "Story can't be reported")); + } + + td_->create_handler(std::move(promise))->send(story_full_id, std::move(reason)); +} + +void StoryManager::activate_stealth_mode(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +bool StoryManager::have_story(StoryFullId story_full_id) const { + return get_story(story_full_id) != nullptr; +} + +bool StoryManager::have_story_force(StoryFullId story_full_id) { + return get_story_force(story_full_id, "have_story_force") != nullptr; +} + +bool StoryManager::is_inaccessible_story(StoryFullId story_full_id) const { + return inaccessible_story_full_ids_.count(story_full_id) > 0; +} + +int32 StoryManager::get_story_duration(StoryFullId story_full_id) const { + const Story *story = get_story(story_full_id); + if (story == nullptr || story->content_ == nullptr) { + return -1; + } + auto *content = story->content_.get(); + auto it = being_edited_stories_.find(story_full_id); + if (it != being_edited_stories_.end()) { + if (it->second->content_ != nullptr) { + content = it->second->content_.get(); + } + } + return get_story_content_duration(td_, content); +} + +void StoryManager::register_story(StoryFullId story_full_id, FullMessageId full_message_id, const char *source) { + if (td_->auth_manager_->is_bot()) { + return; + } + CHECK(story_full_id.is_server()); + + LOG(INFO) << "Register " << story_full_id << " from " << full_message_id << " from " << source; + story_messages_[story_full_id].insert(full_message_id); +} + +void StoryManager::unregister_story(StoryFullId story_full_id, FullMessageId full_message_id, const char *source) { + if (td_->auth_manager_->is_bot()) { + return; + } + CHECK(story_full_id.is_server()); + LOG(INFO) << "Unregister " << story_full_id << " from " << full_message_id << " from " << source; + auto &message_ids = story_messages_[story_full_id]; + auto is_deleted = message_ids.erase(full_message_id) > 0; + LOG_CHECK(is_deleted) << source << ' ' << story_full_id << ' ' << full_message_id; + if (message_ids.empty()) { + story_messages_.erase(story_full_id); + } +} + +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))) { + return {}; + } + StoryInfo story_info; + story_info.story_id_ = story_id; + story_info.date_ = story->date_; + story_info.expire_date_ = story->expire_date_; + story_info.is_for_close_friends_ = story->is_for_close_friends_; + return story_info; +} + +td_api::object_ptr StoryManager::get_story_info_object(StoryFullId story_full_id) const { + auto story_info = get_story_info(story_full_id); + if (!story_info.story_id_.is_valid()) { + return nullptr; + } + return td_api::make_object(story_info.story_id_.get(), story_info.date_, + story_info.is_for_close_friends_); +} + +td_api::object_ptr StoryManager::get_story_object(StoryFullId story_full_id) const { + return get_story_object(story_full_id, get_story(story_full_id)); +} + +td_api::object_ptr StoryManager::get_story_object(StoryFullId story_full_id, const Story *story) const { + if (story == nullptr || story->content_ == nullptr) { + return nullptr; + } + auto dialog_id = story_full_id.get_dialog_id(); + bool is_owned = is_story_owned(dialog_id); + if (!is_owned && !story->is_pinned_ && !is_active_story(story)) { + return nullptr; + } + + td_api::object_ptr privacy_settings = + story->privacy_rules_.get_story_privacy_settings_object(td_); + if (privacy_settings == nullptr) { + if (story->is_public_) { + privacy_settings = td_api::make_object(); + } else if (story->is_for_contacts_) { + privacy_settings = td_api::make_object(); + } else if (story->is_for_close_friends_) { + privacy_settings = td_api::make_object(); + } else { + privacy_settings = td_api::make_object(); + } + } + + bool is_being_edited = false; + bool is_edited = story->is_edited_; + + auto story_id = story_full_id.get_story_id(); + CHECK(story_id.is_valid()); + auto *content = story->content_.get(); + auto *areas = &story->areas_; + auto *caption = &story->caption_; + if (is_owned && story_id.is_server()) { + auto it = being_edited_stories_.find(story_full_id); + if (it != being_edited_stories_.end()) { + if (it->second->content_ != nullptr) { + content = it->second->content_.get(); + } + if (it->second->edit_media_areas_) { + areas = &it->second->areas_; + } + if (it->second->edit_caption_) { + caption = &it->second->caption_; + } + is_being_edited = true; + } + } + + bool is_being_sent = !story_id.is_server(); + auto changelog_dialog_id = get_changelog_story_dialog_id(); + bool is_visible_only_for_self = + !story_id.is_server() || dialog_id == changelog_dialog_id || (!story->is_pinned_ && !is_active_story(story)); + bool can_be_forwarded = !story->noforwards_ && story_id.is_server() && + privacy_settings->get_id() == td_api::storyPrivacySettingsEveryone::ID; + bool can_be_replied = story_id.is_server() && dialog_id != changelog_dialog_id; + bool can_get_viewers = can_get_story_viewers(story_full_id, story, false).is_ok(); + auto interaction_info = story->interaction_info_.get_story_interaction_info_object(td_); + bool has_expired_viewers = is_story_owned(dialog_id) && story_id.is_server() && + G()->unix_time_cached() >= get_story_viewers_expire_date(story) && + interaction_info != nullptr && + interaction_info->view_count_ > interaction_info->reaction_count_; + auto story_areas = transform(*areas, [](const MediaArea &media_area) { return media_area.get_story_area_object(); }); + + story->is_update_sent_ = true; + + return td_api::make_object( + story_id.get(), td_->messages_manager_->get_chat_id_object(dialog_id, "get_story_object"), story->date_, + is_being_sent, is_being_edited, is_edited, story->is_pinned_, is_visible_only_for_self, can_be_forwarded, + can_be_replied, can_get_viewers, has_expired_viewers, std::move(interaction_info), + story->chosen_reaction_type_.get_reaction_type_object(), std::move(privacy_settings), + get_story_content_object(td_, content), std::move(story_areas), + get_formatted_text_object(*caption, true, get_story_content_duration(td_, content))); +} + +td_api::object_ptr StoryManager::get_stories_object(int32 total_count, + const vector &story_full_ids) const { + if (total_count == -1) { + total_count = static_cast(story_full_ids.size()); + } + return td_api::make_object(total_count, transform(story_full_ids, [this](StoryFullId story_full_id) { + return get_story_object(story_full_id); + })); +} + +td_api::object_ptr StoryManager::get_chat_active_stories_object( + DialogId owner_dialog_id, const ActiveStories *active_stories) const { + StoryListId story_list_id; + StoryId max_read_story_id; + vector> stories; + int64 order = 0; + if (active_stories != nullptr) { + story_list_id = active_stories->story_list_id_; + max_read_story_id = active_stories->max_read_story_id_; + for (auto story_id : active_stories->story_ids_) { + auto story_info = get_story_info_object({owner_dialog_id, story_id}); + if (story_info != nullptr) { + stories.push_back(std::move(story_info)); + } + } + if (story_list_id.is_valid()) { + order = active_stories->public_order_; + } + } else { + story_list_id = get_dialog_story_list_id(owner_dialog_id); + } + auto yet_unsent_story_ids_it = yet_unsent_story_ids_.find(owner_dialog_id); + if (yet_unsent_story_ids_it != yet_unsent_story_ids_.end()) { + for (auto story_id : yet_unsent_story_ids_it->second) { + auto story_info = get_story_info_object({owner_dialog_id, story_id}); + if (story_info != nullptr) { + stories.push_back(std::move(story_info)); + } + } + } + return td_api::make_object( + td_->messages_manager_->get_chat_id_object(owner_dialog_id, "updateChatActiveStories"), + story_list_id.get_story_list_object(), order, max_read_story_id.get(), std::move(stories)); +} + +vector StoryManager::get_story_file_ids(const Story *story) const { + if (story == nullptr || story->content_ == nullptr) { + return {}; + } + return get_story_content_file_ids(td_, story->content_.get()); +} + +void StoryManager::delete_story_files(const Story *story) const { + for (auto file_id : get_story_file_ids(story)) { + send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise(), "delete_story_files"); + } +} + +void StoryManager::change_story_files(StoryFullId story_full_id, const Story *story, + const vector &old_file_ids) { + auto new_file_ids = get_story_file_ids(story); + if (new_file_ids == old_file_ids) { + return; + } + + for (auto file_id : old_file_ids) { + if (!td::contains(new_file_ids, file_id)) { + send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise(), "change_story_files"); + } + } + + auto file_source_id = get_story_file_source_id(story_full_id); + if (file_source_id.is_valid()) { + td_->file_manager_->change_files_source(file_source_id, old_file_ids, new_file_ids); + } +} + +StoryId StoryManager::on_get_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item_ptr) { + if (!owner_dialog_id.is_valid()) { + LOG(ERROR) << "Receive a story in " << owner_dialog_id; + return {}; + } + if (td_->auth_manager_->is_bot()) { + return {}; + } + CHECK(story_item_ptr != nullptr); + switch (story_item_ptr->get_id()) { + case telegram_api::storyItemDeleted::ID: + return on_get_deleted_story(owner_dialog_id, + telegram_api::move_object_as(story_item_ptr)); + case telegram_api::storyItemSkipped::ID: + LOG(ERROR) << "Receive " << to_string(story_item_ptr); + return {}; + case telegram_api::storyItem::ID: { + return on_get_new_story(owner_dialog_id, telegram_api::move_object_as(story_item_ptr)); + } + default: + UNREACHABLE(); + } +} + +StoryId StoryManager::on_get_new_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item) { + CHECK(story_item != nullptr); + StoryId story_id(story_item->id_); + if (!story_id.is_server()) { + LOG(ERROR) << "Receive " << to_string(story_item); + return StoryId(); + } + CHECK(owner_dialog_id.is_valid()); + StoryFullId story_full_id{owner_dialog_id, story_id}; + if (deleted_story_full_ids_.count(story_full_id) > 0) { + return StoryId(); + } + + td_->messages_manager_->force_create_dialog(owner_dialog_id, "on_get_new_story"); + + StoryId old_story_id; + auto updates_story_ids_it = update_story_ids_.find(story_full_id); + if (updates_story_ids_it != update_story_ids_.end()) { + old_story_id = updates_story_ids_it->second; + update_story_ids_.erase(updates_story_ids_it); + + LOG(INFO) << "Receive sent " << old_story_id << " as " << story_full_id; + + auto old_story_full_id = StoryFullId(owner_dialog_id, old_story_id); + const Story *old_story = get_story_force(old_story_full_id, "on_get_new_story"); + if (old_story != nullptr) { + delete_story_files(old_story); + stories_.erase(old_story_full_id); + } else { + old_story_id = StoryId(); + } + } + + bool is_bot = td_->auth_manager_->is_bot(); + auto caption = + get_message_text(td_->contacts_manager_.get(), std::move(story_item->caption_), std::move(story_item->entities_), + true, is_bot, story_item->date_, false, "on_get_new_story"); + auto content = get_story_content(td_, std::move(story_item->media_), owner_dialog_id); + if (content == nullptr) { + return StoryId(); + } + + Story *story = get_story_force(story_full_id, "on_get_new_story"); + bool is_changed = false; + bool need_save_to_database = false; + if (story == nullptr) { + auto s = make_unique(); + story = s.get(); + stories_.set(story_full_id, std::move(s)); + is_changed = true; + story_item->min_ = false; + register_story_global_id(story_full_id, story); + + inaccessible_story_full_ids_.erase(story_full_id); + failed_to_load_story_full_ids_.erase(story_full_id); + LOG(INFO) << "Add new " << story_full_id; + } + CHECK(story != nullptr); + + story->receive_date_ = G()->unix_time(); + + const BeingEditedStory *edited_story = nullptr; + auto it = being_edited_stories_.find(story_full_id); + if (it != being_edited_stories_.end()) { + edited_story = it->second.get(); + } + + auto content_type = content->get_type(); + auto old_file_ids = get_story_file_ids(story); + if (edited_story != nullptr && edited_story->content_ != nullptr) { + story->content_ = std::move(content); + need_save_to_database = true; + } else if (story->content_ == nullptr || story->content_->get_type() != content_type) { + story->content_ = std::move(content); + is_changed = true; + } else { + merge_story_contents(td_, story->content_.get(), content.get(), owner_dialog_id, need_save_to_database, is_changed); + story->content_ = std::move(content); + } + + if (is_changed || need_save_to_database) { + change_story_files(story_full_id, story, old_file_ids); + } + + if (story_item->date_ <= 0) { + LOG(ERROR) << "Receive " << story_full_id << " sent at " << story_item->date_; + story_item->date_ = 1; + } + if (story_item->expire_date_ <= story_item->date_) { + LOG(ERROR) << "Receive " << story_full_id << " sent at " << story_item->date_ << ", but expired at " + << story_item->expire_date_; + story_item->expire_date_ = story_item->date_ + 1; + } + + if (story->is_edited_ != story_item->edited_ || story->is_pinned_ != story_item->pinned_ || + story->is_public_ != story_item->public_ || story->is_for_close_friends_ != story_item->close_friends_ || + story->is_for_contacts_ != story_item->contacts_ || + story->is_for_selected_contacts_ != story_item->selected_contacts_ || + story->noforwards_ != story_item->noforwards_ || story->date_ != story_item->date_ || + story->expire_date_ != story_item->expire_date_) { + story->is_edited_ = story_item->edited_; + story->is_pinned_ = story_item->pinned_; + story->is_public_ = story_item->public_; + story->is_for_close_friends_ = story_item->close_friends_; + story->is_for_contacts_ = story_item->contacts_; + story->is_for_selected_contacts_ = story_item->selected_contacts_; + story->noforwards_ = story_item->noforwards_; + story->date_ = story_item->date_; + story->expire_date_ = story_item->expire_date_; + is_changed = true; + } + if (!is_story_owned(owner_dialog_id)) { + story_item->min_ = false; + } + if (!story_item->min_) { + auto privacy_rules = UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(story_item->privacy_)); + auto interaction_info = StoryInteractionInfo(td_, std::move(story_item->views_)); + auto chosen_reaction_type = ReactionType(std::move(story_item->sent_reaction_)); + + if (story->privacy_rules_ != privacy_rules) { + story->privacy_rules_ = std::move(privacy_rules); + is_changed = true; + } + if (story->interaction_info_ != interaction_info) { + story->interaction_info_ = std::move(interaction_info); + is_changed = true; + } + if (story->chosen_reaction_type_ != chosen_reaction_type) { + story->chosen_reaction_type_ = std::move(chosen_reaction_type); + is_changed = true; + } + } + if (story->caption_ != caption) { + story->caption_ = std::move(caption); + if (edited_story != nullptr && edited_story->edit_caption_) { + need_save_to_database = true; + } else { + is_changed = true; + } + } + vector media_areas; + for (auto &media_area_ptr : story_item->media_areas_) { + MediaArea media_area(td_, std::move(media_area_ptr)); + if (media_area.is_valid()) { + media_areas.push_back(std::move(media_area)); + } + } + if (story->areas_ != media_areas) { + story->areas_ = std::move(media_areas); + if (edited_story != nullptr && edited_story->edit_media_areas_) { + need_save_to_database = true; + } else { + is_changed = true; + } + } + + Dependencies dependencies; + add_story_dependencies(dependencies, story); + for (auto dependent_dialog_id : dependencies.get_dialog_ids()) { + td_->messages_manager_->force_create_dialog(dependent_dialog_id, "on_get_new_story", true); + } + + on_story_changed(story_full_id, story, is_changed, need_save_to_database); + + LOG(INFO) << "Receive " << story_full_id; + + if (is_active_story(story)) { + auto active_stories = get_active_stories_force(owner_dialog_id, "on_get_new_story"); + if (active_stories == nullptr) { + if (is_subscribed_to_dialog_stories(owner_dialog_id)) { + load_dialog_expiring_stories(owner_dialog_id, 0, "on_get_new_story"); + + if (updated_active_stories_.count(owner_dialog_id)) { + on_update_active_stories(owner_dialog_id, StoryId(), vector{story_id}, Promise(), + "on_get_new_story 1"); + } else if (old_story_id.is_valid()) { + send_update_chat_active_stories(owner_dialog_id, active_stories, "on_get_new_story 2"); + } + } else if (old_story_id.is_valid()) { + send_update_chat_active_stories(owner_dialog_id, active_stories, "on_get_new_story 3"); + } + } else if (!contains(active_stories->story_ids_, story_id)) { + auto story_ids = active_stories->story_ids_; + story_ids.push_back(story_id); + size_t i = story_ids.size() - 1; + while (i > 0 && story_ids[i - 1].get() > story_id.get()) { + story_ids[i] = story_ids[i - 1]; + i--; + } + story_ids[i] = story_id; + on_update_active_stories(owner_dialog_id, active_stories->max_read_story_id_, std::move(story_ids), + Promise(), "on_get_new_story"); + } else if (old_story_id.is_valid()) { + send_update_chat_active_stories(owner_dialog_id, active_stories, "on_get_new_story 4"); + } + } + + if (old_story_id.is_valid()) { + send_closure(G()->td(), &Td::send_update, + td_api::make_object(get_story_object(story_full_id, story), + old_story_id.get())); + } + + return story_id; +} + +StoryId StoryManager::on_get_skipped_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item) { + CHECK(story_item != nullptr); + StoryInfo story_info; + story_info.story_id_ = StoryId(story_item->id_); + story_info.date_ = story_item->date_; + story_info.expire_date_ = story_item->expire_date_; + story_info.is_for_close_friends_ = story_item->close_friends_; + return on_get_story_info(owner_dialog_id, std::move(story_info)); +} + +StoryId StoryManager::on_get_story_info(DialogId owner_dialog_id, StoryInfo &&story_info) { + StoryId story_id = story_info.story_id_; + if (!story_id.is_server()) { + LOG(ERROR) << "Receive " << story_id; + return StoryId(); + } + if (deleted_story_full_ids_.count({owner_dialog_id, story_id}) > 0) { + return StoryId(); + } + + td_->messages_manager_->force_create_dialog(owner_dialog_id, "on_get_skipped_story"); + + StoryFullId story_full_id{owner_dialog_id, story_id}; + Story *story = get_story_editable(story_full_id); + if (story == nullptr) { + auto s = make_unique(); + story = s.get(); + stories_.set(story_full_id, std::move(s)); + register_story_global_id(story_full_id, story); + + inaccessible_story_full_ids_.erase(story_full_id); + } + CHECK(story != nullptr); + + if (story_info.date_ <= 0) { + LOG(ERROR) << "Receive " << story_full_id << " sent at " << story_info.date_; + story_info.date_ = 1; + } + if (story_info.expire_date_ <= story_info.date_) { + LOG(ERROR) << "Receive " << story_full_id << " sent at " << story_info.date_ << ", but expired at " + << story_info.expire_date_; + story_info.expire_date_ = story_info.date_ + 1; + } + + if (story->date_ != story_info.date_ || story->expire_date_ != story_info.expire_date_ || + story->is_for_close_friends_ != story_info.is_for_close_friends_) { + story->date_ = story_info.date_; + story->expire_date_ = story_info.expire_date_; + story->is_for_close_friends_ = story_info.is_for_close_friends_; + on_story_changed(story_full_id, story, true, true); + } + return story_id; +} + +StoryId StoryManager::on_get_deleted_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item) { + StoryId story_id(story_item->id_); + on_delete_story({owner_dialog_id, story_id}); + return story_id; +} + +void StoryManager::on_delete_story(StoryFullId story_full_id) { + auto story_id = story_full_id.get_story_id(); + if (!story_id.is_server()) { + LOG(ERROR) << "Receive deleted " << story_full_id; + return; + } + + update_story_ids_.erase(story_full_id); + + inaccessible_story_full_ids_.set(story_full_id, Time::now()); + send_closure_later(G()->messages_manager(), + &MessagesManager::update_story_max_reply_media_timestamp_in_replied_messages, story_full_id); + + const Story *story = get_story_force(story_full_id, "on_delete_story"); + auto owner_dialog_id = story_full_id.get_dialog_id(); + if (story != nullptr) { + LOG(INFO) << "Delete " << story_full_id; + if (story->is_update_sent_) { + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + td_->messages_manager_->get_chat_id_object(owner_dialog_id, "updateStoryDeleted"), story_id.get())); + } + delete_story_files(story); + unregister_story_global_id(story); + stories_.erase(story_full_id); + auto edited_stories_it = being_edited_stories_.find(story_full_id); + if (edited_stories_it != being_edited_stories_.end()) { + CHECK(edited_stories_it->second != nullptr); + auto log_event_id = edited_stories_it->second->log_event_id_; + if (log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), log_event_id); + } + being_edited_stories_.erase(edited_stories_it); + } + edit_generations_.erase(story_full_id); + } else { + LOG(INFO) << "Delete not found " << story_full_id; + } + + auto active_stories = get_active_stories_force(owner_dialog_id, "on_get_deleted_story"); + if (active_stories != nullptr && contains(active_stories->story_ids_, story_id)) { + auto story_ids = active_stories->story_ids_; + td::remove(story_ids, story_id); + on_update_active_stories(owner_dialog_id, active_stories->max_read_story_id_, std::move(story_ids), Promise(), + "on_delete_story"); + } + + delete_story_from_database(story_full_id); +} + +void StoryManager::delete_story_from_database(StoryFullId story_full_id) { + if (G()->use_message_database()) { + LOG(INFO) << "Delete " << story_full_id << " from database"; + G()->td_db()->get_story_db_async()->delete_story(story_full_id, Promise()); + } +} + +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()); + } + 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 (story->content_ == nullptr) { + return; + } + if (is_changed || need_save_to_database) { + if (G()->use_message_database() && !from_database) { + LOG(INFO) << "Add " << story_full_id << " to database"; + + int32 expires_at = 0; + if (is_active_story(story) && !is_story_owned(story_full_id.get_dialog_id()) && !story->is_pinned_) { + // non-owned expired non-pinned stories must be deleted + expires_at = story->expire_date_; + } + + G()->td_db()->get_story_db_async()->add_story(story_full_id, expires_at, NotificationId(), + log_event_store(*story), Promise()); + } + + if (is_changed && story->is_update_sent_) { + send_update_story(story_full_id, story); + } + + send_closure_later(G()->messages_manager(), + &MessagesManager::update_story_max_reply_media_timestamp_in_replied_messages, story_full_id); + send_closure_later(G()->web_pages_manager(), &WebPagesManager::on_story_changed, story_full_id); + + if (story_messages_.count(story_full_id) != 0) { + vector full_message_ids; + story_messages_[story_full_id].foreach( + [&full_message_ids](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); }); + CHECK(!full_message_ids.empty()); + for (const auto &full_message_id : full_message_ids) { + td_->messages_manager_->on_external_update_message_content(full_message_id); + } + } + } +} + +void StoryManager::register_story_global_id(StoryFullId story_full_id, Story *story) { + CHECK(story_full_id.is_server()); + CHECK(story->global_id_ == 0); + story->global_id_ = ++max_story_global_id_; + stories_by_global_id_[story->global_id_] = story_full_id; +} + +void StoryManager::unregister_story_global_id(const Story *story) { + CHECK(story->global_id_ > 0); + stories_by_global_id_.erase(story->global_id_); +} + +std::pair> StoryManager::on_get_stories( + DialogId owner_dialog_id, vector &&expected_story_ids, + telegram_api::object_ptr &&stories) { + td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_get_stories"); + + vector story_ids; + for (auto &story : stories->stories_) { + switch (story->get_id()) { + case telegram_api::storyItemDeleted::ID: + on_get_deleted_story(owner_dialog_id, telegram_api::move_object_as(story)); + break; + case telegram_api::storyItemSkipped::ID: + LOG(ERROR) << "Receive " << to_string(story); + break; + case telegram_api::storyItem::ID: { + auto story_id = on_get_new_story(owner_dialog_id, telegram_api::move_object_as(story)); + if (story_id.is_valid()) { + story_ids.push_back(story_id); + } + break; + } + default: + UNREACHABLE(); + } + } + + auto total_count = stories->count_; + if (total_count < static_cast(story_ids.size())) { + LOG(ERROR) << "Expected at most " << total_count << " stories, but receive " << story_ids.size(); + total_count = static_cast(story_ids.size()); + } + if (!expected_story_ids.empty()) { + FlatHashSet all_story_ids; + for (auto expected_story_id : expected_story_ids) { + CHECK(expected_story_id != StoryId()); + all_story_ids.insert(expected_story_id); + } + for (auto story_id : story_ids) { + if (all_story_ids.erase(story_id) == 0) { + LOG(ERROR) << "Receive " << story_id << " in " << owner_dialog_id << ", but didn't request it"; + } + } + for (auto story_id : all_story_ids) { + on_delete_story({owner_dialog_id, story_id}); + } + } + return {total_count, std::move(story_ids)}; +} + +DialogId StoryManager::on_get_user_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&user_stories, + Promise &&promise) { + if (user_stories == nullptr) { + if (owner_dialog_id.is_valid()) { + LOG(INFO) << "Receive no stories in " << owner_dialog_id; + on_update_active_stories(owner_dialog_id, StoryId(), {}, std::move(promise), "on_get_user_stories"); + } else { + promise.set_value(Unit()); + } + return owner_dialog_id; + } + + DialogId story_dialog_id(UserId(user_stories->user_id_)); + if (owner_dialog_id.is_valid() && owner_dialog_id != story_dialog_id) { + LOG(ERROR) << "Receive stories from " << story_dialog_id << " instead of " << owner_dialog_id; + on_update_active_stories(owner_dialog_id, StoryId(), {}, std::move(promise), "on_get_user_stories 2"); + return owner_dialog_id; + } + if (!story_dialog_id.is_valid()) { + LOG(ERROR) << "Receive stories in " << story_dialog_id; + promise.set_value(Unit()); + return owner_dialog_id; + } + owner_dialog_id = story_dialog_id; + + StoryId max_read_story_id(user_stories->max_read_id_); + if (!max_read_story_id.is_server() && max_read_story_id != StoryId()) { + LOG(ERROR) << "Receive max read " << max_read_story_id; + max_read_story_id = StoryId(); + } + + vector story_ids; + for (auto &story : user_stories->stories_) { + switch (story->get_id()) { + case telegram_api::storyItemDeleted::ID: + on_get_deleted_story(owner_dialog_id, telegram_api::move_object_as(story)); + break; + case telegram_api::storyItemSkipped::ID: + story_ids.push_back( + on_get_skipped_story(owner_dialog_id, telegram_api::move_object_as(story))); + break; + case telegram_api::storyItem::ID: + story_ids.push_back( + on_get_new_story(owner_dialog_id, telegram_api::move_object_as(story))); + break; + default: + UNREACHABLE(); + } + } + + on_update_active_stories(story_dialog_id, max_read_story_id, std::move(story_ids), std::move(promise), + "on_get_user_stories 3"); + return story_dialog_id; +} + +void StoryManager::on_update_dialog_max_story_ids(DialogId owner_dialog_id, StoryId max_story_id, + StoryId max_read_story_id) { + switch (owner_dialog_id.get_type()) { + case DialogType::User: + // use send_closure_later because story order can be updated from update_user + send_closure_later(td_->contacts_manager_actor_, &ContactsManager::on_update_user_story_ids, + owner_dialog_id.get_user_id(), max_story_id, max_read_story_id); + break; + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + break; + } +} + +void StoryManager::on_update_dialog_max_read_story_id(DialogId owner_dialog_id, StoryId max_read_story_id) { + switch (owner_dialog_id.get_type()) { + case DialogType::User: + td_->contacts_manager_->on_update_user_max_read_story_id(owner_dialog_id.get_user_id(), max_read_story_id); + break; + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + break; + } +} + +void StoryManager::on_update_dialog_has_pinned_stories(DialogId owner_dialog_id, bool has_pinned_stories) { + switch (owner_dialog_id.get_type()) { + case DialogType::User: + td_->contacts_manager_->on_update_user_has_pinned_stories(owner_dialog_id.get_user_id(), has_pinned_stories); + break; + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + break; + } +} + +void StoryManager::on_update_dialog_stories_hidden(DialogId owner_dialog_id, bool stories_hidden) { + switch (owner_dialog_id.get_type()) { + case DialogType::User: + td_->contacts_manager_->on_update_user_stories_hidden(owner_dialog_id.get_user_id(), stories_hidden); + break; + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + break; + } +} + +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()) { + return true; + } + if (!is_active_story(get_story({owner_dialog_id, story_id}))) { + LOG(INFO) << "Receive expired " << story_id << " in " << owner_dialog_id << " from " << source; + return true; + } + return false; + })) { + from_database = false; + } + if (story_ids.empty() || max_read_story_id.get() < story_ids[0].get()) { + max_read_story_id = StoryId(); + } else if (max_read_story_id != StoryId()) { + CHECK(max_read_story_id.is_server()); + } + + LOG(INFO) << "Update active stories in " << owner_dialog_id << " to " << story_ids << " with max read " + << max_read_story_id << " from " << source; + + if (story_ids.empty()) { + on_update_dialog_max_story_ids(owner_dialog_id, StoryId(), StoryId()); + auto *active_stories = get_active_stories(owner_dialog_id); + if (active_stories != nullptr) { + LOG(INFO) << "Delete active stories for " << owner_dialog_id; + if (active_stories->story_list_id_.is_valid()) { + delete_active_stories_from_story_list(owner_dialog_id, active_stories); + auto &story_list = get_story_list(active_stories->story_list_id_); + if (!from_database && story_list.is_reloaded_server_total_count_ && + story_list.server_total_count_ > static_cast(story_list.ordered_stories_.size())) { + story_list.server_total_count_--; + 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); + } + active_stories_.erase(owner_dialog_id); + send_update_chat_active_stories(owner_dialog_id, nullptr, "on_update_active_stories 1"); + } else { + max_read_story_ids_.erase(owner_dialog_id); + } + if (!from_database) { + save_active_stories(owner_dialog_id, nullptr, std::move(promise), source); + } + failed_to_load_active_stories_.insert(owner_dialog_id); + return; + } + failed_to_load_active_stories_.erase(owner_dialog_id); + + auto &active_stories = active_stories_[owner_dialog_id]; + if (active_stories == nullptr) { + LOG(INFO) << "Create active stories for " << owner_dialog_id << " from " << source; + active_stories = make_unique(); + auto old_max_read_story_id = max_read_story_ids_.get(owner_dialog_id); + if (old_max_read_story_id != StoryId()) { + max_read_story_ids_.erase(owner_dialog_id); + if (old_max_read_story_id.get() > max_read_story_id.get() && old_max_read_story_id.get() >= story_ids[0].get()) { + max_read_story_id = old_max_read_story_id; + } + } + } + on_update_dialog_max_story_ids(owner_dialog_id, story_ids.back(), max_read_story_id); + bool need_save_to_database = false; + if (active_stories->max_read_story_id_ != max_read_story_id || active_stories->story_ids_ != story_ids) { + need_save_to_database = true; + active_stories->max_read_story_id_ = max_read_story_id; + active_stories->story_ids_ = std::move(story_ids); + update_active_stories_order(owner_dialog_id, active_stories.get(), &need_save_to_database); + send_update_chat_active_stories(owner_dialog_id, active_stories.get(), "on_update_active_stories 2"); + } else if (update_active_stories_order(owner_dialog_id, active_stories.get(), &need_save_to_database)) { + send_update_chat_active_stories(owner_dialog_id, active_stories.get(), "on_update_active_stories 3"); + } + if (need_save_to_database && !from_database) { + save_active_stories(owner_dialog_id, active_stories.get(), std::move(promise), source); + } else { + promise.set_value(Unit()); + } +} + +bool StoryManager::update_active_stories_order(DialogId owner_dialog_id, ActiveStories *active_stories, + bool *need_save_to_database) { + if (td_->auth_manager_->is_bot()) { + return false; + } + + CHECK(active_stories != nullptr); + CHECK(!active_stories->story_ids_.empty()); + CHECK(owner_dialog_id.is_valid()); + + auto last_story_id = active_stories->story_ids_.back(); + const Story *last_story = get_story({owner_dialog_id, last_story_id}); + CHECK(last_story != nullptr); + + int64 new_private_order = 0; + new_private_order += last_story->date_; + if (owner_dialog_id.get_type() == DialogType::User && + td_->contacts_manager_->is_user_premium(owner_dialog_id.get_user_id())) { + new_private_order += static_cast(1) << 33; + } + if (owner_dialog_id == get_changelog_story_dialog_id()) { + new_private_order += static_cast(1) << 34; + } + if (active_stories->max_read_story_id_.get() < last_story_id.get()) { + new_private_order += static_cast(1) << 35; + } + if (owner_dialog_id == DialogId(td_->contacts_manager_->get_my_id())) { + new_private_order += static_cast(1) << 36; + } + CHECK(new_private_order != 0); + + StoryListId story_list_id = get_dialog_story_list_id(owner_dialog_id); + LOG(INFO) << "Update order of active stories of " << owner_dialog_id << " in " << story_list_id << " from " + << active_stories->private_order_ << '/' << active_stories->public_order_ << " to " << new_private_order; + + int64 new_public_order = 0; + if (story_list_id.is_valid()) { + auto &story_list = get_story_list(story_list_id); + if (DialogDate(new_private_order, owner_dialog_id) <= story_list.list_last_story_date_) { + new_public_order = new_private_order; + } + + if (active_stories->private_order_ != new_private_order || active_stories->story_list_id_ != story_list_id) { + delete_active_stories_from_story_list(owner_dialog_id, active_stories); + bool is_inserted = story_list.ordered_stories_.insert({new_private_order, owner_dialog_id}).second; + 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(story_list_id, story_list); + } + } 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_); + } + + if (active_stories->private_order_ != new_private_order || active_stories->public_order_ != new_public_order || + active_stories->story_list_id_ != story_list_id) { + LOG(INFO) << "Update order of active stories of " << owner_dialog_id << " to " << new_private_order << '/' + << new_public_order << " in list " << story_list_id; + if (active_stories->private_order_ != new_private_order || active_stories->story_list_id_ != story_list_id) { + *need_save_to_database = true; + } + active_stories->private_order_ = new_private_order; + if (active_stories->public_order_ != new_public_order || active_stories->story_list_id_ != story_list_id) { + if (active_stories->story_list_id_ != story_list_id) { + if (active_stories->story_list_id_.is_valid() && active_stories->public_order_ != 0) { + active_stories->public_order_ = 0; + send_update_chat_active_stories(owner_dialog_id, active_stories, "update_active_stories_order"); + } + active_stories->story_list_id_ = story_list_id; + } + active_stories->public_order_ = new_public_order; + return true; + } + } + + return false; +} + +void StoryManager::delete_active_stories_from_story_list(DialogId owner_dialog_id, + const ActiveStories *active_stories) { + if (!active_stories->story_list_id_.is_valid()) { + return; + } + auto &story_list = get_story_list(active_stories->story_list_id_); + bool is_deleted = story_list.ordered_stories_.erase({active_stories->private_order_, owner_dialog_id}) > 0; + CHECK(is_deleted); +} + +void StoryManager::send_update_story(StoryFullId story_full_id, const Story *story) { + auto story_object = get_story_object(story_full_id, story); + if (story_object == nullptr) { + CHECK(story != nullptr); + CHECK(story->content_ != nullptr); + // the story can be just expired + return; + } + send_closure(G()->td(), &Td::send_update, td_api::make_object(std::move(story_object))); +} + +td_api::object_ptr StoryManager::get_update_chat_active_stories_object( + DialogId owner_dialog_id, const ActiveStories *active_stories) const { + return td_api::make_object( + get_chat_active_stories_object(owner_dialog_id, active_stories)); +} + +void StoryManager::send_update_chat_active_stories(DialogId owner_dialog_id, const ActiveStories *active_stories, + const char *source) { + if (updated_active_stories_.count(owner_dialog_id) == 0) { + if (active_stories == nullptr || active_stories->public_order_ == 0) { + LOG(INFO) << "Skip update about active stories in " << owner_dialog_id << " from " << source; + return; + } + updated_active_stories_.insert(owner_dialog_id); + } + LOG(INFO) << "Send update about active stories in " << owner_dialog_id << " from " << source; + send_closure(G()->td(), &Td::send_update, get_update_chat_active_stories_object(owner_dialog_id, active_stories)); +} + +void StoryManager::save_active_stories(DialogId owner_dialog_id, const ActiveStories *active_stories, + Promise &&promise, const char *source) const { + if (!G()->use_message_database()) { + return promise.set_value(Unit()); + } + if (active_stories == nullptr) { + 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; + 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_; + for (auto story_id : active_stories->story_ids_) { + auto story_info = get_story_info({owner_dialog_id, story_id}); + if (story_info.story_id_.is_valid()) { + 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)); + } +} + +void StoryManager::on_update_story_id(int64 random_id, StoryId new_story_id, const char *source) { + if (!new_story_id.is_server()) { + LOG(ERROR) << "Receive " << new_story_id << " with random_id " << random_id << " from " << source; + return; + } + + auto it = being_sent_stories_.find(random_id); + if (it == being_sent_stories_.end()) { + // update about a new story sent from another device + LOG(INFO) << "Receive not sent outgoing " << new_story_id << " with random_id = " << random_id; + return; + } + auto old_story_full_id = it->second; + being_sent_stories_.erase(it); + auto is_deleted = being_sent_story_random_ids_.erase(old_story_full_id) > 0; + CHECK(is_deleted); + + if (!have_story_force(old_story_full_id)) { + LOG(INFO) << "Can't find sent story " << old_story_full_id; + // delete_sent_story_on_server(old_story_full_id, new_story_id); + return; + } + + auto old_story_id = old_story_full_id.get_story_id(); + auto new_story_full_id = StoryFullId(old_story_full_id.get_dialog_id(), new_story_id); + + LOG(INFO) << "Save correspondence from " << new_story_full_id << " to " << old_story_id; + CHECK(!old_story_id.is_server()); + update_story_ids_[new_story_full_id] = old_story_id; +} + +bool StoryManager::on_update_read_stories(DialogId owner_dialog_id, StoryId max_read_story_id) { + if (!td_->messages_manager_->have_dialog_info_force(owner_dialog_id, "on_update_read_stories")) { + LOG(INFO) << "Can't read stories in unknown " << owner_dialog_id; + return false; + } + if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { + LOG(ERROR) << "Receive max read " << max_read_story_id; + return false; + } + auto active_stories = get_active_stories_force(owner_dialog_id, "on_update_read_stories"); + if (active_stories == nullptr) { + LOG(INFO) << "Can't find active stories in " << owner_dialog_id; + auto old_max_read_story_id = max_read_story_ids_.get(owner_dialog_id); + if (max_read_story_id.get() > old_max_read_story_id.get()) { + LOG(INFO) << "Set max read story identifier in " << owner_dialog_id << " to " << max_read_story_id; + max_read_story_ids_.set(owner_dialog_id, max_read_story_id); + on_update_dialog_max_read_story_id(owner_dialog_id, max_read_story_id); + return true; + } + } else if (max_read_story_id.get() > active_stories->max_read_story_id_.get()) { + LOG(INFO) << "Update max read story identifier in " << owner_dialog_id << " with stories " + << active_stories->story_ids_ << " from " << active_stories->max_read_story_id_ << " to " + << max_read_story_id; + auto story_ids = active_stories->story_ids_; + on_update_active_stories(owner_dialog_id, max_read_story_id, std::move(story_ids), Promise(), + "on_update_read_stories"); + return true; + } else { + LOG(DEBUG) << "Don't need update max read story from " << active_stories->max_read_story_id_ << " to " + << max_read_story_id; + } + return false; +} + +td_api::object_ptr StoryManager::get_update_story_stealth_mode() const { + return stealth_mode_.get_update_story_stealth_mode_object(); +} + +void StoryManager::send_update_story_stealth_mode() const { + send_closure(G()->td(), &Td::send_update, get_update_story_stealth_mode()); +} + +void StoryManager::on_update_story_stealth_mode( + telegram_api::object_ptr &&stealth_mode) { + set_story_stealth_mode(StoryStealthMode(std::move(stealth_mode))); +} + +void StoryManager::on_update_story_chosen_reaction_type(DialogId owner_dialog_id, StoryId story_id, + ReactionType chosen_reaction_type) { + if (!owner_dialog_id.is_valid() || !story_id.is_server()) { + LOG(ERROR) << "Receive chosen reaction in " << story_id << " in " << owner_dialog_id; + return; + } + if (!td_->messages_manager_->have_dialog_info_force(owner_dialog_id, "on_update_story_chosen_reaction_type")) { + return; + } + StoryFullId story_full_id{owner_dialog_id, story_id}; + Story *story = get_story_force(story_full_id, "on_update_story_chosen_reaction_type"); + if (story == nullptr) { + return; + } + if (story->chosen_reaction_type_ != chosen_reaction_type) { + story->chosen_reaction_type_ = std::move(chosen_reaction_type); + on_story_changed(story_full_id, story, true, true); + } +} + +string StoryManager::get_story_stealth_mode_key() { + return "stealth_mode"; +} + +void StoryManager::schedule_stealth_mode_update() { + if (stealth_mode_.is_empty()) { + stealth_mode_update_timeout_.cancel_timeout(); + return; + } + + auto timeout = max(static_cast(stealth_mode_.get_update_date() - G()->unix_time()), 0.1); + LOG(INFO) << "Schedule stealth mode update in " << timeout; + stealth_mode_update_timeout_.set_callback(std::move(update_stealth_mode_static)); + stealth_mode_update_timeout_.set_callback_data(static_cast(this)); + stealth_mode_update_timeout_.set_timeout_in(timeout); +} + +void StoryManager::set_story_stealth_mode(StoryStealthMode stealth_mode) { + stealth_mode.update(); + if (stealth_mode == stealth_mode_) { + return; + } + + stealth_mode_ = stealth_mode; + LOG(INFO) << stealth_mode_; + schedule_stealth_mode_update(); + send_update_story_stealth_mode(); + + if (stealth_mode_.is_empty()) { + G()->td_db()->get_binlog_pmc()->erase(get_story_stealth_mode_key()); + } else { + G()->td_db()->get_binlog_pmc()->set(get_story_stealth_mode_key(), log_event_store(stealth_mode_).as_slice().str()); + } +} + +void StoryManager::update_stealth_mode_static(void *story_manager) { + if (G()->close_flag()) { + return; + } + + CHECK(story_manager != nullptr); + static_cast(story_manager)->update_stealth_mode(); +} + +void StoryManager::update_stealth_mode() { + if (stealth_mode_.update()) { + LOG(INFO) << stealth_mode_; + send_update_story_stealth_mode(); + } + schedule_stealth_mode_update(); +} + +DialogId StoryManager::get_changelog_story_dialog_id() const { + return DialogId(UserId(td_->option_manager_->get_option_integer( + "stories_changelog_user_id", ContactsManager::get_service_notifications_user_id().get()))); +} + +bool StoryManager::is_subscribed_to_dialog_stories(DialogId owner_dialog_id) const { + if (owner_dialog_id == get_changelog_story_dialog_id()) { + return true; + } + switch (owner_dialog_id.get_type()) { + case DialogType::User: + if (owner_dialog_id == DialogId(td_->contacts_manager_->get_my_id())) { + return true; + } + return td_->contacts_manager_->is_user_contact(owner_dialog_id.get_user_id()); + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + return false; + } +} + +StoryListId StoryManager::get_dialog_story_list_id(DialogId owner_dialog_id) const { + if (!is_subscribed_to_dialog_stories(owner_dialog_id)) { + return StoryListId(); + } + switch (owner_dialog_id.get_type()) { + case DialogType::User: + if (owner_dialog_id != DialogId(td_->contacts_manager_->get_my_id()) && + td_->contacts_manager_->get_user_stories_hidden(owner_dialog_id.get_user_id())) { + return StoryListId::archive(); + } + return StoryListId::main(); + case DialogType::Chat: + case DialogType::Channel: + case DialogType::SecretChat: + case DialogType::None: + default: + return StoryListId::archive(); + } +} + +void StoryManager::on_dialog_active_stories_order_updated(DialogId owner_dialog_id, const char *source) { + LOG(INFO) << "Update order of active stories in " << owner_dialog_id << " from " << source; + // called from update_user, must not create the dialog and hence must not load active stories + auto active_stories = get_active_stories_editable(owner_dialog_id); + bool need_save_to_database = false; + if (active_stories != nullptr && + update_active_stories_order(owner_dialog_id, active_stories, &need_save_to_database)) { + send_update_chat_active_stories(owner_dialog_id, active_stories, "on_dialog_active_stories_order_updated"); + } + if (need_save_to_database) { + save_active_stories(owner_dialog_id, active_stories, Promise(), "on_dialog_active_stories_order_updated"); + } +} + +void StoryManager::on_get_story_views(const vector &story_ids, + telegram_api::object_ptr &&story_views) { + schedule_interaction_info_update(); + td_->contacts_manager_->on_get_users(std::move(story_views->users_), "on_get_story_views"); + if (story_ids.size() != story_views->views_.size()) { + LOG(ERROR) << "Receive invalid views for " << story_ids << ": " << to_string(story_views); + return; + } + DialogId owner_dialog_id(td_->contacts_manager_->get_my_id()); + for (size_t i = 0; i < story_ids.size(); i++) { + auto story_id = story_ids[i]; + CHECK(story_id.is_server()); + + StoryFullId story_full_id{owner_dialog_id, story_id}; + Story *story = get_story_editable(story_full_id); + if (story == nullptr || story->content_ == nullptr) { + continue; + } + + StoryInteractionInfo interaction_info(td_, std::move(story_views->views_[i])); + CHECK(!interaction_info.is_empty()); + if (story->interaction_info_ != interaction_info) { + story->interaction_info_ = std::move(interaction_info); + on_story_changed(story_full_id, story, true, true); + } + } +} + +FileSourceId StoryManager::get_story_file_source_id(StoryFullId story_full_id) { + if (td_->auth_manager_->is_bot()) { + return FileSourceId(); + } + + if (!story_full_id.is_server()) { + return FileSourceId(); + } + + auto &file_source_id = story_full_id_to_file_source_id_[story_full_id]; + if (!file_source_id.is_valid()) { + file_source_id = td_->file_reference_manager_->create_story_file_source(story_full_id); + } + return file_source_id; +} + +void StoryManager::reload_story(StoryFullId story_full_id, Promise &&promise, const char *source) { + if (deleted_story_full_ids_.count(story_full_id) > 0) { + return promise.set_value(Unit()); + } + double last_reloaded_at = inaccessible_story_full_ids_.get(story_full_id); + if (last_reloaded_at >= Time::now() - OPENED_STORY_POLL_PERIOD / 2 && last_reloaded_at > 0.0) { + return promise.set_value(Unit()); + } + + LOG(INFO) << "Reload " << story_full_id << " from " << source; + auto dialog_id = story_full_id.get_dialog_id(); + if (dialog_id.get_type() != DialogType::User) { + return promise.set_error(Status::Error(400, "Unsupported story owner")); + } + auto story_id = story_full_id.get_story_id(); + if (!story_id.is_server()) { + return promise.set_error(Status::Error(400, "Invalid story identifier")); + } + + auto &queries = reload_story_queries_[story_full_id]; + if (!queries.empty() && !promise) { + return; + } + queries.push_back(std::move(promise)); + if (queries.size() != 1) { + return; + } + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), story_full_id](Result &&result) mutable { + send_closure(actor_id, &StoryManager::on_reload_story, story_full_id, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(dialog_id.get_user_id(), {story_id}); +} + +void StoryManager::on_reload_story(StoryFullId story_full_id, Result &&result) { + if (G()->close_flag()) { + return; + } + auto it = reload_story_queries_.find(story_full_id); + CHECK(it != reload_story_queries_.end()); + CHECK(!it->second.empty()); + auto promises = std::move(it->second); + reload_story_queries_.erase(it); + + if (result.is_ok()) { + set_promises(promises); + } else { + fail_promises(promises, result.move_as_error()); + } +} + +void StoryManager::get_story(DialogId owner_dialog_id, StoryId story_id, bool only_local, + Promise> &&promise) { + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "get_story")) { + return promise.set_error(Status::Error(400, "Story sender not found")); + } + if (!td_->messages_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the story sender")); + } + if (!story_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid story identifier specified")); + } + + StoryFullId story_full_id{owner_dialog_id, story_id}; + const Story *story = get_story_force(story_full_id, "get_story"); + if (story != nullptr && story->content_ != nullptr) { + if (!story->is_update_sent_) { + send_update_story(story_full_id, story); + } + return promise.set_value(get_story_object(story_full_id, story)); + } + if (only_local || owner_dialog_id.get_type() != DialogType::User || !story_id.is_server()) { + return promise.set_value(nullptr); + } + + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), story_full_id, promise = std::move(promise)](Result &&result) mutable { + send_closure(actor_id, &StoryManager::do_get_story, story_full_id, std::move(result), std::move(promise)); + }); + reload_story(story_full_id, std::move(query_promise), "get_story"); +} + +void StoryManager::do_get_story(StoryFullId story_full_id, Result &&result, + Promise> &&promise) { + G()->ignore_result_if_closing(result); + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + const Story *story = get_story(story_full_id); + if (story != nullptr && story->content_ != nullptr && !story->is_update_sent_) { + send_update_story(story_full_id, story); + } + promise.set_value(get_story_object(story_full_id, story)); +} + +Result StoryManager::get_next_yet_unsent_story_id(DialogId dialog_id) { + auto &story_id = current_yet_unsent_story_ids_[dialog_id]; + if (story_id == 0) { + story_id = StoryId::MAX_SERVER_STORY_ID; + } else if (story_id == std::numeric_limits::max()) { + return Status::Error(400, "Tried to send too many stories above daily limit"); + } + return StoryId(++story_id); +} + +void StoryManager::can_send_story(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +void StoryManager::send_story(td_api::object_ptr &&input_story_content, + td_api::object_ptr &&input_areas, + td_api::object_ptr &&input_caption, + td_api::object_ptr &&settings, int32 active_period, + bool is_pinned, bool protect_content, + Promise> &&promise) { + bool is_bot = td_->auth_manager_->is_bot(); + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + TRY_RESULT_PROMISE(promise, content, get_input_story_content(td_, std::move(input_story_content), dialog_id)); + TRY_RESULT_PROMISE(promise, caption, + get_formatted_text(td_, DialogId(), std::move(input_caption), is_bot, true, false, false)); + TRY_RESULT_PROMISE(promise, privacy_rules, + UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(settings))); + if (active_period != 86400 && !(G()->is_test_dc() && (active_period == 60 || active_period == 300))) { + bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); + if (!is_premium || !td::contains(vector{6 * 3600, 12 * 3600, 2 * 86400}, active_period)) { + return promise.set_error(Status::Error(400, "Invalid story active period specified")); + } + } + TRY_RESULT_PROMISE(promise, story_id, get_next_yet_unsent_story_id(dialog_id)); + vector areas; + if (input_areas != nullptr) { + for (auto &input_area : input_areas->areas_) { + MediaArea media_area(td_, std::move(input_area), Auto()); + if (media_area.is_valid()) { + areas.push_back(std::move(media_area)); + } + } + } + if (!td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { + caption.entities.clear(); + } + + td_->messages_manager_->force_create_dialog(dialog_id, "send_story"); + + auto story = make_unique(); + story->date_ = G()->unix_time(); + story->expire_date_ = story->date_ + active_period; + story->is_pinned_ = is_pinned; + story->noforwards_ = protect_content; + story->privacy_rules_ = std::move(privacy_rules); + story->content_ = std::move(content); + story->areas_ = std::move(areas); + story->caption_ = std::move(caption); + + int64 random_id; + do { + random_id = Random::secure_int64(); + } while (random_id == 0 || being_sent_stories_.count(random_id) > 0); + + auto story_ptr = story.get(); + + auto pending_story = + td::make_unique(dialog_id, story_id, ++send_story_count_, random_id, std::move(story)); + pending_story->log_event_id_ = save_send_story_log_event(pending_story.get()); + + do_send_story(std::move(pending_story), {}); + + promise.set_value(get_story_object({dialog_id, story_id}, story_ptr)); +} + +class StoryManager::SendStoryLogEvent { + public: + const PendingStory *pending_story_in_; + unique_ptr pending_story_out_; + + SendStoryLogEvent() : pending_story_in_(nullptr) { + } + + explicit SendStoryLogEvent(const PendingStory *pending_story) : pending_story_in_(pending_story) { + } + + template + void store(StorerT &storer) const { + td::store(*pending_story_in_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(pending_story_out_, parser); + } +}; + +int64 StoryManager::save_send_story_log_event(const PendingStory *pending_story) { + if (!G()->use_message_database()) { + return 0; + } + + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendStory, + get_log_event_storer(SendStoryLogEvent(pending_story))); +} + +void StoryManager::do_send_story(unique_ptr &&pending_story, vector bad_parts) { + CHECK(pending_story != nullptr); + CHECK(pending_story->story_id_.is_valid()); + CHECK(pending_story->story_ != nullptr); + CHECK(pending_story->story_->content_ != nullptr); + + auto story_full_id = StoryFullId(pending_story->dialog_id_, pending_story->story_id_); + if (bad_parts.empty()) { + if (!pending_story->story_id_.is_server()) { + auto story = make_unique(); + story->date_ = pending_story->story_->date_; + story->expire_date_ = pending_story->story_->expire_date_; + story->is_pinned_ = pending_story->story_->is_pinned_; + story->noforwards_ = pending_story->story_->noforwards_; + story->privacy_rules_ = pending_story->story_->privacy_rules_; + story->content_ = std::move(pending_story->story_->content_); + pending_story->story_->content_ = dup_story_content(td_, story->content_.get()); + story->areas_ = pending_story->story_->areas_; + story->caption_ = pending_story->story_->caption_; + send_update_story(story_full_id, story.get()); + stories_.set(story_full_id, std::move(story)); + + auto active_stories = get_active_stories_force(pending_story->dialog_id_, "do_send_story"); + + CHECK(pending_story->dialog_id_.is_valid()); + CHECK(pending_story->random_id_ != 0); + yet_unsent_stories_[pending_story->dialog_id_].insert(pending_story->send_story_num_); + yet_unsent_story_ids_[pending_story->dialog_id_].push_back(pending_story->story_id_); + being_sent_stories_[pending_story->random_id_] = story_full_id; + being_sent_story_random_ids_[story_full_id] = pending_story->random_id_; + + 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()); + } else { + pending_story->story_->content_ = dup_story_content(td_, pending_story->story_->content_.get()); + } + } + + auto content = pending_story->story_->content_.get(); + auto upload_order = pending_story->send_story_num_; + + FileId file_id = get_story_content_any_file_id(td_, content); + CHECK(file_id.is_valid()); + + LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; + if (!pending_story->story_id_.is_server()) { + being_uploaded_file_ids_[story_full_id] = file_id; + } + bool is_inserted = being_uploaded_files_.emplace(file_id, std::move(pending_story)).second; + CHECK(is_inserted); + // need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_ + // and to send is_uploading_active == true in response + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, upload_order); +} + +void StoryManager::on_upload_story(FileId file_id, telegram_api::object_ptr input_file) { + if (G()->close_flag()) { + return; + } + + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_files_.find(file_id); + if (it == being_uploaded_files_.end()) { + // callback may be called just before the file upload was canceled + return; + } + + auto pending_story = std::move(it->second); + + being_uploaded_files_.erase(it); + + if (!pending_story->story_id_.is_server()) { + being_uploaded_file_ids_.erase({pending_story->dialog_id_, pending_story->story_id_}); + + auto deleted_story_it = delete_yet_unsent_story_queries_.find(pending_story->random_id_); + if (deleted_story_it != delete_yet_unsent_story_queries_.end()) { + auto promises = std::move(deleted_story_it->second); + delete_yet_unsent_story_queries_.erase(deleted_story_it); + fail_promises(promises, Status::Error(400, "Story upload has been already completed")); + } + } + + FileView file_view = td_->file_manager_->get_file_view(file_id); + CHECK(!file_view.is_encrypted()); + if (input_file == nullptr && file_view.has_remote_location()) { + if (file_view.main_remote_location().is_web()) { + delete_pending_story(file_id, std::move(pending_story), Status::Error(400, "Can't use web photo as a story")); + return; + } + if (pending_story->was_reuploaded_) { + delete_pending_story(file_id, std::move(pending_story), Status::Error(500, "Failed to reupload story")); + return; + } + pending_story->was_reuploaded_ = true; + + // delete file reference and forcely reupload the file + td_->file_manager_->delete_file_reference(file_id, file_view.main_remote_location().get_file_reference()); + do_send_story(std::move(pending_story), {-1}); + return; + } + CHECK(input_file != nullptr); + + bool is_edit = pending_story->story_id_.is_server(); + if (is_edit) { + do_edit_story(file_id, std::move(pending_story), std::move(input_file)); + } else { + auto dialog_id = pending_story->dialog_id_; + auto send_story_num = pending_story->send_story_num_; + LOG(INFO) << "Story " << send_story_num << " is ready to be sent"; + ready_to_send_stories_.emplace( + send_story_num, td::make_unique(file_id, std::move(pending_story), std::move(input_file))); + try_send_story(dialog_id); + } +} + +void StoryManager::on_upload_story_error(FileId file_id, Status status) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(INFO) << "File " << file_id << " has upload error " << status; + + auto it = being_uploaded_files_.find(file_id); + if (it == being_uploaded_files_.end()) { + // callback may be called just before the file upload was canceled + return; + } + + auto pending_story = std::move(it->second); + + being_uploaded_files_.erase(it); + + vector> promises; + if (!pending_story->story_id_.is_server()) { + being_uploaded_file_ids_.erase({pending_story->dialog_id_, pending_story->story_id_}); + + auto deleted_story_it = delete_yet_unsent_story_queries_.find(pending_story->random_id_); + if (deleted_story_it != delete_yet_unsent_story_queries_.end()) { + promises = std::move(deleted_story_it->second); + delete_yet_unsent_story_queries_.erase(deleted_story_it); + status = Status::Error(406, "Canceled"); + } + } + + delete_pending_story(file_id, std::move(pending_story), std::move(status)); + set_promises(promises); +} + +void StoryManager::try_send_story(DialogId dialog_id) { + const auto yet_unsent_story_it = yet_unsent_stories_.find(dialog_id); + if (yet_unsent_story_it == yet_unsent_stories_.end()) { + LOG(INFO) << "There is no more stories to send in " << dialog_id; + return; + } + CHECK(!yet_unsent_story_it->second.empty()); + auto send_story_num = *yet_unsent_story_it->second.begin(); + auto it = ready_to_send_stories_.find(send_story_num); + if (it == ready_to_send_stories_.end()) { + LOG(INFO) << "Story " << send_story_num << " isn't ready to be sent or is being sent"; + return; + } + auto ready_to_send_story = std::move(it->second); + ready_to_send_stories_.erase(it); + + td_->create_handler()->send(ready_to_send_story->file_id_, + std::move(ready_to_send_story->pending_story_), + std::move(ready_to_send_story->input_file_)); +} + +void StoryManager::on_send_story_file_parts_missing(unique_ptr &&pending_story, vector &&bad_parts) { + do_send_story(std::move(pending_story), std::move(bad_parts)); +} + +class StoryManager::EditStoryLogEvent { + public: + const PendingStory *pending_story_in_; + unique_ptr pending_story_out_; + bool edit_media_areas_; + vector areas_; + bool edit_caption_; + FormattedText caption_; + + EditStoryLogEvent() : pending_story_in_(nullptr), edit_caption_(false) { + } + + EditStoryLogEvent(const PendingStory *pending_story, bool edit_media_areas, vector areas, + bool edit_caption, const FormattedText &caption) + : pending_story_in_(pending_story) + , edit_media_areas_(edit_media_areas) + , areas_(std::move(areas)) + , edit_caption_(edit_caption) + , caption_(caption) { + } + + template + void store(StorerT &storer) const { + bool has_caption = edit_caption_ && !caption_.text.empty(); + bool has_media_areas = edit_media_areas_ && !areas_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(edit_caption_); + STORE_FLAG(has_caption); + STORE_FLAG(edit_media_areas_); + STORE_FLAG(has_media_areas); + END_STORE_FLAGS(); + td::store(*pending_story_in_, storer); + if (has_caption) { + td::store(caption_, storer); + } + if (has_media_areas) { + td::store(areas_, storer); + } + } + + template + void parse(ParserT &parser) { + bool has_caption; + bool has_media_areas; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(edit_caption_); + PARSE_FLAG(has_caption); + PARSE_FLAG(edit_media_areas_); + PARSE_FLAG(has_media_areas); + END_PARSE_FLAGS(); + td::parse(pending_story_out_, parser); + if (has_caption) { + td::parse(caption_, parser); + } + if (has_media_areas) { + td::parse(areas_, parser); + } + } +}; + +void StoryManager::edit_story(StoryId story_id, td_api::object_ptr &&input_story_content, + td_api::object_ptr &&input_areas, + td_api::object_ptr &&input_caption, Promise &&promise) { + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + StoryFullId story_full_id{dialog_id, story_id}; + const Story *story = get_story(story_full_id); + if (story == nullptr || story->content_ == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story_id.is_server()) { + return promise.set_error(Status::Error(400, "Story can't be edited")); + } + + bool is_bot = td_->auth_manager_->is_bot(); + unique_ptr content; + bool are_media_areas_edited = input_areas != nullptr; + vector areas; + bool is_caption_edited = input_caption != nullptr; + FormattedText caption; + if (input_story_content != nullptr) { + TRY_RESULT_PROMISE_ASSIGN(promise, content, + get_input_story_content(td_, std::move(input_story_content), dialog_id)); + } + if (are_media_areas_edited) { + for (auto &input_area : input_areas->areas_) { + MediaArea media_area(td_, std::move(input_area), story->areas_); + if (media_area.is_valid()) { + areas.push_back(std::move(media_area)); + } + } + auto *current_areas = &story->areas_; + auto it = being_edited_stories_.find(story_full_id); + if (it != being_edited_stories_.end() && it->second->edit_media_areas_) { + current_areas = &it->second->areas_; + } + if (*current_areas == areas) { + are_media_areas_edited = false; + } else if (content == nullptr) { + return promise.set_error(Status::Error(400, "Can't edit story areas without content")); + } + } + if (is_caption_edited) { + TRY_RESULT_PROMISE_ASSIGN( + promise, caption, get_formatted_text(td_, DialogId(), std::move(input_caption), is_bot, true, false, false)); + if (!td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { + caption.entities.clear(); + } + auto *current_caption = &story->caption_; + auto it = being_edited_stories_.find(story_full_id); + if (it != being_edited_stories_.end() && it->second->edit_caption_) { + current_caption = &it->second->caption_; + } + if (*current_caption == caption) { + is_caption_edited = false; + } + } + if (content == nullptr && !are_media_areas_edited && !is_caption_edited) { + return promise.set_value(Unit()); + } + + auto &edited_story = being_edited_stories_[story_full_id]; + if (edited_story == nullptr) { + edited_story = make_unique(); + } + auto &edit_generation = edit_generations_[story_full_id]; + if (content != nullptr) { + edited_story->content_ = std::move(content); + edit_generation++; + } + if (are_media_areas_edited) { + edited_story->areas_ = std::move(areas); + edited_story->edit_media_areas_ = true; + edit_generation++; + } + if (is_caption_edited) { + edited_story->caption_ = std::move(caption); + edited_story->edit_caption_ = true; + edit_generation++; + } + edited_story->promises_.push_back(std::move(promise)); + + auto new_story = make_unique(); + new_story->content_ = copy_story_content(edited_story->content_.get()); + + auto pending_story = + td::make_unique(dialog_id, story_id, std::numeric_limits::max() - (++send_story_count_), + edit_generation, std::move(new_story)); + if (G()->use_message_database()) { + EditStoryLogEvent log_event(pending_story.get(), edited_story->edit_media_areas_, edited_story->areas_, + edited_story->edit_caption_, edited_story->caption_); + auto storer = get_log_event_storer(log_event); + auto &cur_log_event_id = edited_story->log_event_id_; + if (cur_log_event_id == 0) { + cur_log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::EditStory, storer); + LOG(INFO) << "Add edit story log event " << cur_log_event_id; + } else { + auto new_log_event_id = + binlog_rewrite(G()->td_db()->get_binlog(), cur_log_event_id, LogEvent::HandlerType::EditStory, storer); + LOG(INFO) << "Rewrite edit story log event " << cur_log_event_id << " with " << new_log_event_id; + } + } + + on_story_changed(story_full_id, story, true, true); + + if (edited_story->content_ == nullptr) { + return do_edit_story(FileId(), std::move(pending_story), nullptr); + } + + do_send_story(std::move(pending_story), {}); +} + +void StoryManager::do_edit_story(FileId file_id, unique_ptr &&pending_story, + telegram_api::object_ptr input_file) { + StoryFullId story_full_id{pending_story->dialog_id_, pending_story->story_id_}; + const Story *story = get_story(story_full_id); + auto it = being_edited_stories_.find(story_full_id); + if (story == nullptr || it == being_edited_stories_.end() || + edit_generations_[story_full_id] != pending_story->random_id_) { + LOG(INFO) << "Skip outdated edit of " << story_full_id; + if (file_id.is_valid()) { + td_->file_manager_->cancel_upload(file_id); + } + return; + } + CHECK(story->content_ != nullptr); + td_->create_handler()->send(file_id, story, std::move(pending_story), std::move(input_file), + it->second.get()); +} + +void StoryManager::delete_pending_story(FileId file_id, unique_ptr &&pending_story, Status status) { + if (G()->close_flag() && G()->use_message_database()) { + return; + } + if (file_id.is_valid()) { + td_->file_manager_->delete_partial_remote_location(file_id); + } + + CHECK(pending_story != nullptr); + StoryFullId story_full_id{pending_story->dialog_id_, pending_story->story_id_}; + const Story *story = get_story(story_full_id); + bool is_edit = pending_story->story_id_.is_server(); + if (is_edit) { + auto it = being_edited_stories_.find(story_full_id); + if (story == nullptr || it == being_edited_stories_.end() || + edit_generations_[story_full_id] != pending_story->random_id_) { + LOG(INFO) << "Ignore outdated edit of " << story_full_id; + return; + } + CHECK(story->content_ != nullptr); + auto promises = std::move(it->second->promises_); + auto log_event_id = it->second->log_event_id_; + if (log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), log_event_id); + } + being_edited_stories_.erase(it); + + on_story_changed(story_full_id, story, true, true); + + if (status.is_ok()) { + set_promises(promises); + } else { + fail_promises(promises, std::move(status)); + } + CHECK(pending_story->log_event_id_ == 0); + } else { + LOG(INFO) << "Finish sending of story " << pending_story->send_story_num_; + if (story != nullptr) { + if (status.is_ok()) { + LOG(ERROR) << "Failed to receive sent " << story_full_id; + status = Status::Error(500, "Failed to receive a sent story"); + } + auto story_object = get_story_object(story_full_id, story); + delete_story_files(story); + stories_.erase(story_full_id); + send_update_chat_active_stories(pending_story->dialog_id_, get_active_stories(pending_story->dialog_id_), + "delete_pending_story"); + send_closure(G()->td(), &Td::send_update, + td_api::make_object(std::move(story_object), + get_can_send_story_result_object(status, true), + status.code(), status.message().str())); + } + auto it = yet_unsent_stories_.find(pending_story->dialog_id_); + CHECK(it != yet_unsent_stories_.end()); + bool is_deleted = it->second.erase(pending_story->send_story_num_) > 0; + CHECK(is_deleted); + 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()); + } else { + auto story_id_it = yet_unsent_story_ids_.find(pending_story->dialog_id_); + CHECK(story_id_it != yet_unsent_story_ids_.end()); + bool is_story_id_deleted = remove(story_id_it->second, pending_story->story_id_); + CHECK(is_story_id_deleted); + CHECK(!yet_unsent_story_ids_.empty()); + } + being_sent_stories_.erase(pending_story->random_id_); + being_sent_story_random_ids_.erase(story_full_id); + try_send_story(pending_story->dialog_id_); + + if (pending_story->log_event_id_ != 0) { + binlog_erase(G()->td_db()->get_binlog(), pending_story->log_event_id_); + } + } +} + +void StoryManager::set_story_privacy_settings(StoryId story_id, + td_api::object_ptr &&settings, + Promise &&promise) { + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + const Story *story = get_story({dialog_id, story_id}); + if (story == nullptr || story->content_ == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story_id.is_server()) { + return promise.set_error(Status::Error(400, "Story privacy settings can't be edited")); + } + TRY_RESULT_PROMISE(promise, privacy_rules, + UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(settings))); + td_->create_handler(std::move(promise))->send(dialog_id, story_id, std::move(privacy_rules)); +} + +void StoryManager::toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise &&promise) { + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + const Story *story = get_story({dialog_id, story_id}); + if (story == nullptr || story->content_ == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story_id.is_server()) { + return promise.set_error(Status::Error(400, "Story can't be pinned/unpinned")); + } + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), story_id, is_pinned, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StoryManager::on_toggle_story_is_pinned, story_id, is_pinned, std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(dialog_id, story_id, is_pinned); +} + +void StoryManager::on_toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + DialogId dialog_id(td_->contacts_manager_->get_my_id()); + Story *story = get_story_editable({dialog_id, story_id}); + if (story != nullptr) { + CHECK(story->content_ != nullptr); + story->is_pinned_ = is_pinned; + on_story_changed({dialog_id, story_id}, story, true, true); + } + promise.set_value(Unit()); +} + +void StoryManager::delete_story(StoryId story_id, Promise &&promise) { + DialogId owner_dialog_id(td_->contacts_manager_->get_my_id()); + StoryFullId story_full_id{owner_dialog_id, story_id}; + const Story *story = get_story(story_full_id); + if (story == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid story identifier")); + } + if (!story_id.is_server()) { + auto file_id_it = being_uploaded_file_ids_.find(story_full_id); + if (file_id_it == being_uploaded_file_ids_.end()) { + return promise.set_error(Status::Error(400, "Story upload has been already completed")); + } + auto file_id = file_id_it->second; + auto random_id_it = being_sent_story_random_ids_.find(story_full_id); + if (random_id_it == being_sent_story_random_ids_.end()) { + return promise.set_error(Status::Error(400, "Story not found")); + } + int64 random_id = random_id_it->second; + + LOG(INFO) << "Cancel uploading of " << story_full_id; + + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + + delete_yet_unsent_story_queries_[random_id].push_back(std::move(promise)); + return; + } + + delete_story_on_server(story_full_id, 0, std::move(promise)); +} + +class StoryManager::DeleteStoryOnServerLogEvent { + public: + StoryFullId story_full_id_; + + template + void store(StorerT &storer) const { + td::store(story_full_id_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(story_full_id_, parser); + } +}; + +uint64 StoryManager::save_delete_story_on_server_log_event(StoryFullId story_full_id) { + DeleteStoryOnServerLogEvent log_event{story_full_id}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteStoryOnServer, + get_log_event_storer(log_event)); +} + +void StoryManager::delete_story_on_server(StoryFullId story_full_id, uint64 log_event_id, Promise &&promise) { + LOG(INFO) << "Delete " << story_full_id << " from server"; + CHECK(story_full_id.is_server()); + + if (log_event_id == 0) { + log_event_id = save_delete_story_on_server_log_event(story_full_id); + } + + auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise)); + promise = std::move(new_promise); // to prevent self-move + + deleted_story_full_ids_.insert(story_full_id); + + td_->create_handler(std::move(promise))->send({story_full_id.get_story_id()}); + + on_delete_story(story_full_id); +} + +telegram_api::object_ptr StoryManager::get_input_media(StoryFullId story_full_id) const { + auto dialog_id = story_full_id.get_dialog_id(); + CHECK(dialog_id.get_type() == DialogType::User); + auto r_input_user = td_->contacts_manager_->get_input_user(dialog_id.get_user_id()); + if (r_input_user.is_error()) { + return nullptr; + } + return telegram_api::make_object(r_input_user.move_as_ok(), + story_full_id.get_story_id().get()); +} + +void StoryManager::remove_story_notifications_by_story_ids(DialogId dialog_id, const vector &story_ids) { + VLOG(notifications) << "Trying to remove notification about " << story_ids << " in " << dialog_id; + for (auto story_id : story_ids) { + if (!story_id.is_server()) { + LOG(ERROR) << "Tried to delete " << story_id << " in " << dialog_id; + continue; + } + StoryFullId story_full_id{dialog_id, story_id}; + if (!have_story_force(story_full_id)) { + LOG(INFO) << "Can't delete " << story_full_id << " because it is not found"; + // call synchronously to remove them before ProcessPush returns + // td_->notification_manager_->remove_temporary_notification_by_story_id( + // story_notification_group_id, story_full_id, true, "remove_story_notifications_by_story_ids"); + continue; + } + on_delete_story(story_full_id); + } +} + +void StoryManager::get_current_state(vector> &updates) const { + active_stories_.foreach([&](const DialogId &dialog_id, const unique_ptr &active_stories) { + if (updated_active_stories_.count(dialog_id) > 0) { + updates.push_back(get_update_chat_active_stories_object(dialog_id, active_stories.get())); + } + }); + if (!td_->auth_manager_->is_bot()) { + for (auto story_list_id : {StoryListId::main(), StoryListId::archive()}) { + const auto &story_list = get_story_list(story_list_id); + if (story_list.sent_total_count_ != -1) { + updates.push_back(get_update_story_list_chat_count_object(story_list_id, story_list)); + } + } + + updates.push_back(get_update_story_stealth_mode()); + } +} + +void StoryManager::on_binlog_events(vector &&events) { + if (G()->close_flag()) { + return; + } + bool have_old_message_database = G()->use_message_database() && !G()->td_db()->was_dialog_db_created(); + for (auto &event : events) { + CHECK(event.id_ != 0); + switch (event.type_) { + case LogEvent::HandlerType::DeleteStoryOnServer: { + DeleteStoryOnServerLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto owner_dialog_id = log_event.story_full_id_.get_dialog_id(); + if (owner_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + td_->messages_manager_->have_dialog_force(owner_dialog_id, "DeleteStoryOnServerLogEvent"); + delete_story_on_server(log_event.story_full_id_, event.id_, Auto()); + break; + } + case LogEvent::HandlerType::ReadStoriesOnServer: { + ReadStoriesOnServerLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto owner_dialog_id = log_event.dialog_id_; + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "ReadStoriesOnServerLogEvent")) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + auto max_read_story_id = log_event.max_story_id_; + auto active_stories = get_active_stories_force(owner_dialog_id, "ReadStoriesOnServerLogEvent"); + if (active_stories == nullptr) { + max_read_story_ids_[owner_dialog_id] = max_read_story_id; + on_update_dialog_max_read_story_id(owner_dialog_id, max_read_story_id); + } else { + auto story_ids = active_stories->story_ids_; + on_update_active_stories(owner_dialog_id, max_read_story_id, std::move(story_ids), Promise(), + "ReadStoriesOnServerLogEvent"); + } + read_stories_on_server(owner_dialog_id, max_read_story_id, event.id_); + break; + } + case LogEvent::HandlerType::LoadDialogExpiringStories: { + LoadDialogExpiringStoriesLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto owner_dialog_id = log_event.dialog_id_; + if (!td_->messages_manager_->have_dialog_force(owner_dialog_id, "LoadDialogExpiringStoriesLogEvent")) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + load_dialog_expiring_stories(owner_dialog_id, event.id_, "LoadDialogExpiringStoriesLogEvent"); + break; + } + case LogEvent::HandlerType::SendStory: { + if (!have_old_message_database) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + SendStoryLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto pending_story = std::move(log_event.pending_story_out_); + pending_story->log_event_id_ = event.id_; + + CHECK(pending_story->story_->content_ != nullptr); + if (pending_story->story_->content_->get_type() == StoryContentType::Unsupported) { + LOG(ERROR) << "Sent story content is invalid: " << format::as_hex_dump<4>(event.get_data()); + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + Dependencies dependencies; + add_pending_story_dependencies(dependencies, pending_story.get()); + if (!dependencies.resolve_force(td_, "SendStoryLogEvent")) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + ++send_story_count_; + CHECK(!pending_story->story_id_.is_server()); + pending_story->story_id_ = get_next_yet_unsent_story_id(pending_story->dialog_id_).move_as_ok(); + pending_story->send_story_num_ = send_story_count_; + do_send_story(std::move(pending_story), {}); + break; + } + case LogEvent::HandlerType::EditStory: { + if (!have_old_message_database) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + EditStoryLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto pending_story = std::move(log_event.pending_story_out_); + CHECK(pending_story->story_id_.is_server()); + StoryFullId story_full_id{pending_story->dialog_id_, pending_story->story_id_}; + const Story *story = get_story_force(story_full_id, "EditStoryLogEvent"); + if (story == nullptr || story->content_ == nullptr) { + LOG(INFO) << "Failed to find " << story_full_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + if (pending_story->story_->content_ != nullptr && + pending_story->story_->content_->get_type() == StoryContentType::Unsupported) { + LOG(ERROR) << "Sent story content is invalid: " << format::as_hex_dump<4>(event.get_data()); + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + Dependencies dependencies; + add_pending_story_dependencies(dependencies, pending_story.get()); + if (!dependencies.resolve_force(td_, "EditStoryLogEvent")) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + + auto &edited_story = being_edited_stories_[story_full_id]; + if (edited_story != nullptr) { + LOG(INFO) << "Ignore outdated edit of " << story_full_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + break; + } + edited_story = make_unique(); + if (pending_story->story_->content_ != nullptr) { + edited_story->content_ = std::move(pending_story->story_->content_); + } + if (log_event.edit_media_areas_) { + edited_story->areas_ = std::move(log_event.areas_); + edited_story->edit_media_areas_ = true; + } + if (log_event.edit_caption_) { + edited_story->caption_ = std::move(log_event.caption_); + edited_story->edit_caption_ = true; + } + edited_story->log_event_id_ = event.id_; + + ++send_story_count_; + pending_story->send_story_num_ = std::numeric_limits::max() - send_story_count_; + pending_story->random_id_ = ++edit_generations_[story_full_id]; + + if (edited_story->content_ == nullptr) { + do_edit_story(FileId(), std::move(pending_story), nullptr); + } else { + pending_story->story_->content_ = copy_story_content(edited_story->content_.get()); + do_send_story(std::move(pending_story), {}); + } + break; + } + default: + LOG(FATAL) << "Unsupported log event type " << event.type_; + } + } +} + +} // namespace td diff --git a/td/telegram/StoryManager.h b/td/telegram/StoryManager.h new file mode 100644 index 000000000000..16f623c51b8a --- /dev/null +++ b/td/telegram/StoryManager.h @@ -0,0 +1,640 @@ +// +// 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/DialogDate.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileSourceId.h" +#include "td/telegram/FullMessageId.h" +#include "td/telegram/MediaArea.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/ReactionType.h" +#include "td/telegram/StoryDb.h" +#include "td/telegram/StoryFullId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryInteractionInfo.h" +#include "td/telegram/StoryListId.h" +#include "td/telegram/StoryStealthMode.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" +#include "td/telegram/UserPrivacySettingRule.h" + +#include "td/actor/actor.h" +#include "td/actor/MultiTimeout.h" +#include "td/actor/Timeout.h" + +#include "td/utils/buffer.h" +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" +#include "td/utils/WaitFreeHashMap.h" +#include "td/utils/WaitFreeHashSet.h" + +#include +#include +#include + +namespace td { + +struct BinlogEvent; +class Dependencies; +class ReportReason; +class StoryContent; +struct StoryDbStory; +class Td; + +class StoryManager final : public Actor { + struct Story { + int32 date_ = 0; + int32 expire_date_ = 0; + int32 receive_date_ = 0; + bool is_edited_ = false; + bool is_pinned_ = false; + bool is_public_ = false; + bool is_for_close_friends_ = false; + bool is_for_contacts_ = false; + bool is_for_selected_contacts_ = false; + bool noforwards_ = false; + mutable bool is_update_sent_ = false; // whether the story is known to the app + StoryInteractionInfo interaction_info_; + ReactionType chosen_reaction_type_; + UserPrivacySettingRules privacy_rules_; + unique_ptr content_; + vector areas_; + FormattedText caption_; + int64 global_id_ = 0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct StoryInfo { + StoryId story_id_; + int32 date_ = 0; + int32 expire_date_ = 0; + bool is_for_close_friends_ = false; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct BeingEditedStory { + unique_ptr content_; + vector areas_; + FormattedText caption_; + bool edit_media_areas_ = false; + bool edit_caption_ = false; + vector> promises_; + int64 log_event_id_ = 0; + }; + + struct PendingStory { + DialogId dialog_id_; + StoryId story_id_; + uint64 log_event_id_ = 0; + uint32 send_story_num_ = 0; + int64 random_id_ = 0; + bool was_reuploaded_ = false; + unique_ptr story_; + + PendingStory() = default; + + PendingStory(DialogId dialog_id, StoryId story_id, uint32 send_story_num, int64 random_id, + unique_ptr &&story); + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct ReadyToSendStory { + FileId file_id_; + unique_ptr pending_story_; + telegram_api::object_ptr input_file_; + + ReadyToSendStory(FileId file_id, unique_ptr &&pending_story, + telegram_api::object_ptr &&input_file); + }; + + struct PendingStoryViews { + FlatHashSet story_ids_; + bool has_query_ = false; + }; + + struct ActiveStories { + StoryId max_read_story_id_; + vector story_ids_; + StoryListId story_list_id_; + int64 private_order_ = 0; + int64 public_order_ = 0; + }; + + struct SavedActiveStories { + StoryId max_read_story_id_; + vector story_infos_; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct StoryList { + int32 server_total_count_ = -1; + int32 sent_total_count_ = -1; + string state_; + + bool is_reloaded_server_total_count_ = false; + bool server_has_more_ = true; + bool database_has_more_ = false; + + vector> load_list_from_server_queries_; + vector> load_list_from_database_queries_; + + std::set ordered_stories_; // all known active stories from the story list + + DialogDate last_loaded_database_dialog_date_ = MIN_DIALOG_DATE; // in memory + DialogDate list_last_story_date_ = MIN_DIALOG_DATE; // in memory + }; + + struct SavedStoryList { + string state_; + int32 total_count_ = -1; + bool has_more_ = true; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + public: + StoryManager(Td *td, ActorShared<> parent); + StoryManager(const StoryManager &) = delete; + StoryManager &operator=(const StoryManager &) = delete; + StoryManager(StoryManager &&) = delete; + StoryManager &operator=(StoryManager &&) = delete; + ~StoryManager() final; + + void get_story(DialogId owner_dialog_id, StoryId story_id, bool only_local, + Promise> &&promise); + + void can_send_story(Promise> &&promise); + + void send_story(td_api::object_ptr &&input_story_content, + td_api::object_ptr &&input_areas, + td_api::object_ptr &&input_caption, + td_api::object_ptr &&settings, int32 active_period, bool is_pinned, + bool protect_content, Promise> &&promise); + + void on_send_story_file_parts_missing(unique_ptr &&pending_story, vector &&bad_parts); + + void edit_story(StoryId story_id, td_api::object_ptr &&input_story_content, + td_api::object_ptr &&input_areas, + td_api::object_ptr &&input_caption, Promise &&promise); + + void set_story_privacy_settings(StoryId story_id, td_api::object_ptr &&settings, + Promise &&promise); + + void toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise &&promise); + + void delete_story(StoryId story_id, Promise &&promise); + + void load_active_stories(StoryListId story_list_id, Promise &&promise); + + void reload_active_stories(); + + void reload_all_read_stories(); + + void toggle_dialog_stories_hidden(DialogId dialog_id, StoryListId story_list_id, Promise &&promise); + + void get_dialog_pinned_stories(DialogId owner_dialog_id, StoryId from_story_id, int32 limit, + Promise> &&promise); + + void get_story_archive(StoryId from_story_id, int32 limit, Promise> &&promise); + + void get_dialog_expiring_stories(DialogId owner_dialog_id, + Promise> &&promise); + + void reload_dialog_expiring_stories(DialogId dialog_id); + + void open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise); + + void close_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise); + + void view_story_message(StoryFullId story_full_id); + + void on_story_replied(StoryFullId story_full_id, UserId replier_user_id); + + void set_story_reaction(StoryFullId story_full_id, ReactionType reaction_type, bool add_to_recent, + Promise &&promise); + + void get_story_viewers(StoryId story_id, const string &query, bool only_contacts, bool prefer_with_reaction, + const string &offset, int32 limit, + Promise> &&promise); + + void report_story(StoryFullId story_full_id, ReportReason &&reason, Promise &&promise); + + void activate_stealth_mode(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); + + std::pair> on_get_stories(DialogId owner_dialog_id, vector &&expected_story_ids, + telegram_api::object_ptr &&stories); + + DialogId on_get_user_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&user_stories, + Promise &&promise); + + void on_update_story_id(int64 random_id, StoryId new_story_id, const char *source); + + bool on_update_read_stories(DialogId owner_dialog_id, StoryId max_read_story_id); + + void on_update_story_stealth_mode(telegram_api::object_ptr &&stealth_mode); + + void on_update_story_chosen_reaction_type(DialogId owner_dialog_id, StoryId story_id, + ReactionType chosen_reaction_type); + + void on_update_dialog_stories_hidden(DialogId owner_dialog_id, bool stories_hidden); + + 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; + + void on_get_story_views(const vector &story_ids, + telegram_api::object_ptr &&story_views); + + bool have_story(StoryFullId story_full_id) const; + + bool have_story_force(StoryFullId story_full_id); + + bool is_inaccessible_story(StoryFullId story_full_id) const; + + int32 get_story_duration(StoryFullId story_full_id) const; + + void register_story(StoryFullId story_full_id, FullMessageId full_message_id, const char *source); + + void unregister_story(StoryFullId story_full_id, FullMessageId full_message_id, const char *source); + + td_api::object_ptr get_story_object(StoryFullId story_full_id) const; + + td_api::object_ptr get_stories_object(int32 total_count, + const vector &story_full_ids) const; + + FileSourceId get_story_file_source_id(StoryFullId story_full_id); + + telegram_api::object_ptr get_input_media(StoryFullId story_full_id) const; + + void reload_story(StoryFullId story_full_id, Promise &&promise, const char *source); + + void try_synchronize_archive_all_stories(); + + void get_current_state(vector> &updates) const; + + void on_binlog_events(vector &&events); + + private: + class UploadMediaCallback; + + class SendStoryQuery; + class EditStoryQuery; + + class DeleteStoryOnServerLogEvent; + class ReadStoriesOnServerLogEvent; + class LoadDialogExpiringStoriesLogEvent; + class SendStoryLogEvent; + class EditStoryLogEvent; + + static constexpr int32 OPENED_STORY_POLL_PERIOD = 60; + static constexpr int32 VIEWED_STORY_POLL_PERIOD = 300; + + static constexpr int32 DEFAULT_LOADED_EXPIRED_STORIES = 50; + + void start_up() final; + + void timeout_expired() final; + + void hangup() final; + + void tear_down() final; + + static void on_story_reload_timeout_callback(void *story_manager_ptr, int64 story_global_id); + + void on_story_reload_timeout(int64 story_global_id); + + static void on_story_expire_timeout_callback(void *story_manager_ptr, int64 story_global_id); + + void on_story_expire_timeout(int64 story_global_id); + + static void on_story_can_get_viewers_timeout_callback(void *story_manager_ptr, int64 story_global_id); + + void on_story_can_get_viewers_timeout(int64 story_global_id); + + bool is_story_owned(DialogId owner_dialog_id) const; + + int32 get_story_viewers_expire_date(const Story *story) const; + + static bool is_active_story(const Story *story); + + DialogId get_changelog_story_dialog_id() const; + + bool is_subscribed_to_dialog_stories(DialogId owner_dialog_id) const; + + StoryListId get_dialog_story_list_id(DialogId owner_dialog_id) const; + + void add_story_dependencies(Dependencies &dependencies, const Story *story); + + void add_pending_story_dependencies(Dependencies &dependencies, const PendingStory *pending_story); + + const Story *get_story(StoryFullId story_full_id) const; + + Story *get_story_editable(StoryFullId story_full_id); + + Story *get_story_force(StoryFullId story_full_id, const char *source); + + unique_ptr parse_story(StoryFullId story_full_id, const BufferSlice &value); + + Story *on_get_story_from_database(StoryFullId story_full_id, const BufferSlice &value, const char *source); + + const ActiveStories *get_active_stories(DialogId owner_dialog_id) const; + + ActiveStories *get_active_stories_editable(DialogId owner_dialog_id); + + ActiveStories *get_active_stories_force(DialogId owner_dialog_id, const char *source); + + ActiveStories *on_get_active_stories_from_database(StoryListId story_list_id, DialogId owner_dialog_id, + const BufferSlice &value, const char *source); + + void on_story_changed(StoryFullId story_full_id, const Story *story, bool is_changed, bool need_save_to_database, + bool from_database = false); + + void register_story_global_id(StoryFullId story_full_id, Story *story); + + void unregister_story_global_id(const Story *story); + + StoryId on_get_story_info(DialogId owner_dialog_id, StoryInfo &&story_info); + + StoryInfo get_story_info(StoryFullId story_full_id) const; + + td_api::object_ptr get_story_info_object(StoryFullId story_full_id) const; + + td_api::object_ptr get_story_object(StoryFullId story_full_id, const Story *story) const; + + td_api::object_ptr get_chat_active_stories_object( + DialogId owner_dialog_id, const ActiveStories *active_stories) const; + + StoryId on_get_new_story(DialogId owner_dialog_id, telegram_api::object_ptr &&story_item); + + StoryId on_get_skipped_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item); + + StoryId on_get_deleted_story(DialogId owner_dialog_id, + telegram_api::object_ptr &&story_item); + + void on_delete_story(StoryFullId story_full_id); + + void on_get_dialog_pinned_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&stories, + Promise> &&promise); + + void on_get_story_archive(telegram_api::object_ptr &&stories, + Promise> &&promise); + + void on_get_dialog_expiring_stories(DialogId owner_dialog_id, + telegram_api::object_ptr &&stories, + Promise> &&promise); + + static uint64 save_load_dialog_expiring_stories_log_event(DialogId owner_dialog_id); + + void load_dialog_expiring_stories(DialogId owner_dialog_id, uint64 log_event_id, const char *source); + + void on_load_dialog_expiring_stories(DialogId owner_dialog_id); + + void on_load_active_stories_from_database(StoryListId story_list_id, Result result); + + void load_active_stories_from_server(StoryListId story_list_id, StoryList &story_list, bool is_next, + Promise &&promise); + + void on_load_active_stories_from_server( + StoryListId story_list_id, bool is_next, string old_state, + Result> r_all_stories); + + void save_story_list(StoryListId story_list_id, string state, int32 total_count, bool has_more); + + StoryList &get_story_list(StoryListId story_list_id); + + const StoryList &get_story_list(StoryListId story_list_id) const; + + 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, StoryList &story_list); + + vector get_story_file_ids(const Story *story) const; + + static uint64 save_delete_story_on_server_log_event(StoryFullId story_full_id); + + void delete_story_on_server(StoryFullId story_full_id, uint64 log_event_id, Promise &&promise); + + void delete_story_from_database(StoryFullId story_full_id); + + void delete_story_files(const Story *story) const; + + void change_story_files(StoryFullId story_full_id, const Story *story, const vector &old_file_ids); + + void do_get_story(StoryFullId story_full_id, Result &&result, + Promise> &&promise); + + void on_reload_story(StoryFullId story_full_id, Result &&result); + + int64 save_send_story_log_event(const PendingStory *pending_story); + + void delete_pending_story(FileId file_id, unique_ptr &&pending_story, Status status); + + Result get_next_yet_unsent_story_id(DialogId dialog_id); + + void do_send_story(unique_ptr &&pending_story, vector bad_parts); + + void on_upload_story(FileId file_id, telegram_api::object_ptr input_file); + + void on_upload_story_error(FileId file_id, Status status); + + void try_send_story(DialogId dialog_id); + + void do_edit_story(FileId file_id, unique_ptr &&pending_story, + telegram_api::object_ptr input_file); + + void on_toggle_story_is_pinned(StoryId story_id, bool is_pinned, Promise &&promise); + + void on_update_dialog_max_story_ids(DialogId owner_dialog_id, StoryId max_story_id, StoryId max_read_story_id); + + void on_update_dialog_max_read_story_id(DialogId owner_dialog_id, StoryId max_read_story_id); + + void on_update_dialog_has_pinned_stories(DialogId owner_dialog_id, bool has_pinned_stories); + + 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); + + bool update_active_stories_order(DialogId owner_dialog_id, ActiveStories *active_stories, + bool *need_save_to_database); + + void delete_active_stories_from_story_list(DialogId owner_dialog_id, const ActiveStories *active_stories); + + void send_update_story(StoryFullId story_full_id, const Story *story); + + td_api::object_ptr get_update_chat_active_stories_object( + DialogId owner_dialog_id, const ActiveStories *active_stories) const; + + void send_update_chat_active_stories(DialogId owner_dialog_id, const ActiveStories *active_stories, + const char *source); + + void save_active_stories(DialogId owner_dialog_id, const ActiveStories *active_stories, Promise &&promise, + const char *source) const; + + void increment_story_views(DialogId owner_dialog_id, PendingStoryViews &story_views); + + void on_increment_story_views(DialogId owner_dialog_id); + + static uint64 save_read_stories_on_server_log_event(DialogId dialog_id, StoryId max_story_id); + + void read_stories_on_server(DialogId owner_dialog_id, StoryId story_id, uint64 log_event_id); + + bool can_use_story_reaction(const ReactionType &reaction_type) const; + + void schedule_interaction_info_update(); + + static void update_interaction_info_static(void *story_manager); + + void update_interaction_info(); + + void on_synchronized_archive_all_stories(bool set_archive_all_stories, Result result); + + td_api::object_ptr get_update_story_stealth_mode() const; + + void send_update_story_stealth_mode() const; + + void schedule_stealth_mode_update(); + + static void update_stealth_mode_static(void *story_manager); + + void update_stealth_mode(); + + static string get_story_stealth_mode_key(); + + void set_story_stealth_mode(StoryStealthMode stealth_mode); + + void on_get_story_viewers(StoryId story_id, bool is_full, bool is_first, + Result> r_view_list, + Promise> &&promise); + + void on_set_story_reaction(StoryFullId story_full_id, Result &&result, Promise &&promise); + + void load_expired_database_stories(); + + void on_load_expired_database_stories(vector stories); + + std::shared_ptr upload_media_callback_; + + WaitFreeHashMap story_full_id_to_file_source_id_; + + WaitFreeHashMap, StoryFullIdHash> stories_; + + WaitFreeHashMap stories_by_global_id_; + + WaitFreeHashMap inaccessible_story_full_ids_; + + WaitFreeHashSet deleted_story_full_ids_; + + WaitFreeHashSet failed_to_load_story_full_ids_; + + WaitFreeHashMap, StoryFullIdHash> story_messages_; + + WaitFreeHashMap, DialogIdHash> active_stories_; + + WaitFreeHashSet updated_active_stories_; + + WaitFreeHashMap max_read_story_ids_; + + WaitFreeHashSet failed_to_load_active_stories_; + + FlatHashMap load_expiring_stories_log_event_ids_; + + FlatHashMap, StoryFullIdHash> being_edited_stories_; + + FlatHashMap edit_generations_; + + FlatHashMap pending_story_views_; + + FlatHashMap opened_owned_stories_; + + FlatHashMap opened_stories_; + + FlatHashMap>, StoryFullIdHash> reload_story_queries_; + + FlatHashMap, FileIdHash> being_uploaded_files_; + + FlatHashMap, DialogIdHash> yet_unsent_stories_; + + FlatHashMap, DialogIdHash> yet_unsent_story_ids_; + + FlatHashMap being_sent_stories_; + + FlatHashMap being_sent_story_random_ids_; + + FlatHashMap being_uploaded_file_ids_; + + FlatHashMap update_story_ids_; + + FlatHashMap>> delete_yet_unsent_story_queries_; + + FlatHashMap> ready_to_send_stories_; + + StoryList story_lists_[2]; + + StoryStealthMode stealth_mode_; + + uint32 send_story_count_ = 0; + + int64 max_story_global_id_ = 0; + + FlatHashMap current_yet_unsent_story_ids_; + + bool has_active_synchronize_archive_all_stories_query_ = false; + + Timeout stealth_mode_update_timeout_; + + Timeout interaction_info_update_timeout_; + + int32 load_expired_database_stories_next_limit_ = DEFAULT_LOADED_EXPIRED_STORIES; + + MultiTimeout story_reload_timeout_{"StoryReloadTimeout"}; + MultiTimeout story_expire_timeout_{"StoryExpireTimeout"}; + MultiTimeout story_can_get_viewers_timeout_{"StoryCanGetViewersTimeout"}; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/td/telegram/StoryNotificationSettings.h b/td/telegram/StoryNotificationSettings.h new file mode 100644 index 000000000000..65f8d9bb45be --- /dev/null +++ b/td/telegram/StoryNotificationSettings.h @@ -0,0 +1,31 @@ +// +// 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" + +namespace td { + +class StoryNotificationSettings { + public: + const bool need_dialog_settings_ = false; + const bool need_top_dialogs_ = false; + const bool are_muted_ = false; + const bool hide_sender_ = false; + const int64 ringtone_id_ = 0; + + StoryNotificationSettings(bool need_dialog_settings, bool need_top_dialogs, bool are_muted, bool hide_sender, + int64 ringtone_id) + : need_dialog_settings_(need_dialog_settings) + , need_top_dialogs_(need_top_dialogs) + , are_muted_(are_muted) + , hide_sender_(hide_sender) + , ringtone_id_(ringtone_id) { + } +}; + +} // namespace td diff --git a/td/telegram/StoryStealthMode.cpp b/td/telegram/StoryStealthMode.cpp new file mode 100644 index 000000000000..fa278d9adf80 --- /dev/null +++ b/td/telegram/StoryStealthMode.cpp @@ -0,0 +1,63 @@ +// +// 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/StoryStealthMode.h" + +#include "td/telegram/Global.h" + +namespace td { + +StoryStealthMode::StoryStealthMode(telegram_api::object_ptr &&stealth_mode) + : active_until_date_(stealth_mode->active_until_date_), cooldown_until_date_(stealth_mode->cooldown_until_date_) { + update(); +} + +bool StoryStealthMode::update() { + auto current_time = G()->unix_time(); + bool result = false; + if (active_until_date_ != 0 && active_until_date_ <= current_time) { + active_until_date_ = 0; + result = true; + } + if (cooldown_until_date_ != 0 && cooldown_until_date_ <= current_time) { + cooldown_until_date_ = 0; + result = true; + } + return result; +} + +int32 StoryStealthMode::get_update_date() const { + if (active_until_date_ > 0) { + if (cooldown_until_date_ > 0) { + return min(active_until_date_, cooldown_until_date_); + } + return active_until_date_; + } + if (cooldown_until_date_ > 0) { + return cooldown_until_date_; + } + return 0; +} + +td_api::object_ptr StoryStealthMode::get_update_story_stealth_mode_object() const { + return td_api::make_object(active_until_date_, cooldown_until_date_); +} + +bool operator==(const StoryStealthMode &lhs, const StoryStealthMode &rhs) { + return lhs.active_until_date_ == rhs.active_until_date_ && lhs.cooldown_until_date_ == rhs.cooldown_until_date_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryStealthMode &mode) { + if (mode.active_until_date_) { + return string_builder << "Stealth mode is active until " << mode.active_until_date_; + } + if (mode.cooldown_until_date_) { + return string_builder << "Stealth mode can't be activated until " << mode.cooldown_until_date_; + } + return string_builder << "Stealth mode can be activated"; +} + +} // namespace td diff --git a/td/telegram/StoryStealthMode.h b/td/telegram/StoryStealthMode.h new file mode 100644 index 000000000000..67cf057c985f --- /dev/null +++ b/td/telegram/StoryStealthMode.h @@ -0,0 +1,55 @@ +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class StoryStealthMode { + int32 active_until_date_ = 0; + int32 cooldown_until_date_ = 0; + + friend bool operator==(const StoryStealthMode &lhs, const StoryStealthMode &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StoryStealthMode &mode); + + public: + StoryStealthMode() = default; + + explicit StoryStealthMode(telegram_api::object_ptr &&stealth_mode); + + bool is_empty() const { + return active_until_date_ == 0 && cooldown_until_date_ == 0; + } + + int32 get_update_date() const; + + bool update(); + + td_api::object_ptr get_update_story_stealth_mode_object() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const StoryStealthMode &lhs, const StoryStealthMode &rhs); + +inline bool operator!=(const StoryStealthMode &lhs, const StoryStealthMode &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryStealthMode &mode); + +} // namespace td diff --git a/td/telegram/StoryStealthMode.hpp b/td/telegram/StoryStealthMode.hpp new file mode 100644 index 000000000000..cb1778038ca9 --- /dev/null +++ b/td/telegram/StoryStealthMode.hpp @@ -0,0 +1,50 @@ +// +// 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/StoryStealthMode.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void StoryStealthMode::store(StorerT &storer) const { + using td::store; + bool has_active_until_date = active_until_date_ != 0; + bool has_cooldown_until_date = cooldown_until_date_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_active_until_date); + STORE_FLAG(has_cooldown_until_date); + END_STORE_FLAGS(); + if (has_active_until_date) { + store(active_until_date_, storer); + } + if (has_cooldown_until_date) { + store(cooldown_until_date_, storer); + } +} + +template +void StoryStealthMode::parse(ParserT &parser) { + using td::parse; + bool has_active_until_date; + bool has_cooldown_until_date; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_active_until_date); + PARSE_FLAG(has_cooldown_until_date); + END_PARSE_FLAGS(); + if (has_active_until_date) { + parse(active_until_date_, parser); + } + if (has_cooldown_until_date) { + parse(cooldown_until_date_, parser); + } +} + +} // namespace td diff --git a/td/telegram/StoryViewer.cpp b/td/telegram/StoryViewer.cpp new file mode 100644 index 000000000000..69e7030676e7 --- /dev/null +++ b/td/telegram/StoryViewer.cpp @@ -0,0 +1,61 @@ +// +// 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/StoryViewer.h" + +#include "td/telegram/BlockListId.h" +#include "td/telegram/ContactsManager.h" + +#include "td/utils/algorithm.h" +#include "td/utils/logging.h" + +namespace td { + +td_api::object_ptr StoryViewer::get_story_viewer_object(ContactsManager *contacts_manager) const { + auto block_list_id = BlockListId(is_blocked_, is_blocked_for_stories_); + return td_api::make_object( + contacts_manager->get_user_id_object(user_id_, "get_story_viewer_object"), date_, + block_list_id.get_block_list_object(), reaction_type_.get_reaction_type_object()); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewer &viewer) { + return string_builder << '[' << viewer.user_id_ << " with " << viewer.reaction_type_ << " at " << viewer.date_ << ']'; +} + +StoryViewers::StoryViewers(int32 total_count, int32 total_reaction_count, + vector> &&story_views, + string &&next_offset) + : total_count_(total_count), total_reaction_count_(total_reaction_count), next_offset_(std::move(next_offset)) { + for (auto &story_view : story_views) { + story_viewers_.emplace_back(std::move(story_view)); + auto user_id = story_viewers_.back().get_user_id(); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id << " as a viewer of a story"; + story_viewers_.pop_back(); + } + } +} + +vector StoryViewers::get_user_ids() const { + return transform(story_viewers_, [](auto &viewer) { return viewer.get_user_id(); }); +} + +td_api::object_ptr StoryViewers::get_story_viewers_object( + ContactsManager *contacts_manager) const { + return td_api::make_object( + total_count_, total_reaction_count_, + transform(story_viewers_, + [contacts_manager](const StoryViewer &story_viewer) { + return story_viewer.get_story_viewer_object(contacts_manager); + }), + next_offset_); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewers &viewers) { + return string_builder << viewers.story_viewers_; +} + +} // namespace td diff --git a/td/telegram/StoryViewer.h b/td/telegram/StoryViewer.h new file mode 100644 index 000000000000..aeb930241d02 --- /dev/null +++ b/td/telegram/StoryViewer.h @@ -0,0 +1,67 @@ +// +// 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/ReactionType.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" + +namespace td { + +class ContactsManager; + +class StoryViewer { + UserId user_id_; + int32 date_ = 0; + bool is_blocked_ = false; + bool is_blocked_for_stories_ = false; + ReactionType reaction_type_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewer &viewer); + + public: + StoryViewer(telegram_api::object_ptr &&story_view) + : user_id_(story_view->user_id_) + , date_(td::max(story_view->date_, static_cast(0))) + , is_blocked_(story_view->blocked_) + , is_blocked_for_stories_(story_view->blocked_my_stories_from_) + , reaction_type_(story_view->reaction_) { + } + + UserId get_user_id() const { + return user_id_; + } + + td_api::object_ptr get_story_viewer_object(ContactsManager *contacts_manager) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewer &viewer); + +class StoryViewers { + int32 total_count_ = 0; + int32 total_reaction_count_ = 0; + vector story_viewers_; + string next_offset_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewers &viewers); + + public: + StoryViewers(int32 total_count, int32 total_reaction_count, + vector> &&story_views, string &&next_offset); + + vector get_user_ids() const; + + td_api::object_ptr get_story_viewers_object(ContactsManager *contacts_manager) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const StoryViewers &viewers); + +} // namespace td diff --git a/td/telegram/SuggestedAction.cpp b/td/telegram/SuggestedAction.cpp index dc35bd8564ff..b476ed4975e8 100644 --- a/td/telegram/SuggestedAction.cpp +++ b/td/telegram/SuggestedAction.cpp @@ -42,6 +42,8 @@ SuggestedAction::SuggestedAction(Slice action_str) { init(Type::UpgradePremium); } else if (action_str == Slice("PREMIUM_ANNUAL")) { init(Type::SubscribeToAnnualPremium); + } else if (action_str == Slice("PREMIUM_RESTORE")) { + init(Type::RestorePremium); } } @@ -91,6 +93,9 @@ SuggestedAction::SuggestedAction(const td_api::object_ptr SuggestedAction::get_suggested_actio return td_api::make_object(); case Type::SubscribeToAnnualPremium: return td_api::make_object(); + case Type::RestorePremium: + return td_api::make_object(); default: UNREACHABLE(); return nullptr; @@ -199,6 +208,7 @@ void dismiss_suggested_action(SuggestedAction action, Promise &&promise) { case SuggestedAction::Type::ViewChecksHint: case SuggestedAction::Type::UpgradePremium: case SuggestedAction::Type::SubscribeToAnnualPremium: + case SuggestedAction::Type::RestorePremium: return send_closure_later(G()->config_manager(), &ConfigManager::dismiss_suggested_action, std::move(action), std::move(promise)); case SuggestedAction::Type::ConvertToGigagroup: diff --git a/td/telegram/SuggestedAction.h b/td/telegram/SuggestedAction.h index 7b61d45abaf7..a2d6a75f3bda 100644 --- a/td/telegram/SuggestedAction.h +++ b/td/telegram/SuggestedAction.h @@ -25,7 +25,8 @@ struct SuggestedAction { CheckPassword, SetPassword, UpgradePremium, - SubscribeToAnnualPremium + SubscribeToAnnualPremium, + RestorePremium }; Type type_ = Type::Empty; DialogId dialog_id_; diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 7b42c7be6c78..a3142b8fe650 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -6,7 +6,7 @@ // #include "td/telegram/Td.h" -#include "td/telegram/Account.h" +#include "td/telegram/AccountManager.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/Application.h" #include "td/telegram/AttachMenuManager.h" @@ -60,6 +60,7 @@ #include "td/telegram/FullMessageId.h" #include "td/telegram/GameManager.h" #include "td/telegram/Global.h" +#include "td/telegram/GlobalPrivacySettings.h" #include "td/telegram/GroupCallId.h" #include "td/telegram/GroupCallManager.h" #include "td/telegram/HashtagHints.h" @@ -95,6 +96,7 @@ #include "td/telegram/NotificationGroupId.h" #include "td/telegram/NotificationId.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/NotificationObjectId.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSettingsScope.h" #include "td/telegram/OptionManager.h" @@ -106,6 +108,8 @@ #include "td/telegram/Premium.h" #include "td/telegram/PrivacyManager.h" #include "td/telegram/PublicDialogType.h" +#include "td/telegram/ReactionManager.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/ReportReason.h" #include "td/telegram/RequestActor.h" #include "td/telegram/ScopeNotificationSettings.h" @@ -121,11 +125,14 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/StickerType.h" #include "td/telegram/StorageManager.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryListId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Support.h" #include "td/telegram/td_api.hpp" #include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.hpp" +#include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogCategory.h" #include "td/telegram/TopDialogManager.h" @@ -165,7 +172,6 @@ #include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/Timer.h" -#include "td/utils/tl_parsers.h" #include "td/utils/utf8.h" #include @@ -901,6 +907,26 @@ class GetInactiveSupergroupChatsRequest final : public RequestActor<> { } }; +class SearchRecentlyFoundChatsRequest final : public RequestActor<> { + string query_; + int32 limit_; + + std::pair> dialog_ids_; + + void do_run(Promise &&promise) final { + dialog_ids_ = td_->messages_manager_->search_recently_found_dialogs(query_, limit_, std::move(promise)); + } + + void do_send_result() final { + send_result(td_->messages_manager_->get_chats_object(dialog_ids_, "SearchRecentlyFoundChatsRequest")); + } + + public: + SearchRecentlyFoundChatsRequest(ActorShared td, uint64 request_id, string query, int32 limit) + : RequestActor(std::move(td), request_id), query_(std::move(query)), limit_(limit) { + } +}; + class GetRecentlyOpenedChatsRequest final : public RequestActor<> { int32 limit_; @@ -1868,6 +1894,22 @@ class ChangeImportedContactsRequest final : public RequestActor<> { } }; +class GetCloseFriendsRequest final : public RequestActor<> { + vector user_ids_; + + void do_run(Promise &&promise) final { + user_ids_ = td_->contacts_manager_->get_close_friends(std::move(promise)); + } + + void do_send_result() final { + send_result(td_->contacts_manager_->get_users_object(-1, user_ids_)); + } + + public: + GetCloseFriendsRequest(ActorShared td, uint64 request_id) : RequestActor(std::move(td), request_id) { + } +}; + class GetRecentInlineBotsRequest final : public RequestActor<> { vector user_ids_; @@ -1962,6 +2004,35 @@ class GetStickersRequest final : public RequestActor<> { } }; +class GetAllStickerEmojisRequest final : public RequestActor<> { + StickerType sticker_type_; + string query_; + DialogId dialog_id_; + bool return_only_main_emoji_; + + vector sticker_ids_; + + void do_run(Promise &&promise) final { + sticker_ids_ = td_->stickers_manager_->get_stickers(sticker_type_, query_, 1000000, dialog_id_, get_tries() < 2, + std::move(promise)); + } + + void do_send_result() final { + send_result(td_->stickers_manager_->get_sticker_emojis_object(sticker_ids_, return_only_main_emoji_)); + } + + public: + GetAllStickerEmojisRequest(ActorShared td, uint64 request_id, StickerType sticker_type, string &&query, + int64 dialog_id, bool return_only_main_emoji) + : RequestActor(std::move(td), request_id) + , sticker_type_(sticker_type) + , query_(std::move(query)) + , dialog_id_(dialog_id) + , return_only_main_emoji_(return_only_main_emoji) { + set_tries(4); + } +}; + class GetInstalledStickerSetsRequest final : public RequestActor<> { StickerType sticker_type_; @@ -2752,6 +2823,7 @@ bool Td::is_synchronous_request(const td_api::Function *function) { case td_api::parseTextEntities::ID: case td_api::parseMarkdown::ID: case td_api::getMarkdownText::ID: + case td_api::searchStringsByPrefix::ID: case td_api::getFileMimeType::ID: case td_api::getFileExtension::ID: case td_api::cleanFileName::ID: @@ -2945,7 +3017,10 @@ void Td::run_request(uint64 id, tl_object_ptr function) { Result r_opened_database) mutable { send_closure(actor_id, &Td::init, std::move(parameters), std::move(r_opened_database)); }); - return TdDb::open(get_database_scheduler_id(), std::move(parameters.second), std::move(promise)); + auto use_sqlite_pmc = parameters.second.use_message_database_ || parameters.second.use_chat_info_database_ || + parameters.second.use_file_database_; + return TdDb::open(use_sqlite_pmc ? G()->get_database_scheduler_id() : G()->get_slow_net_scheduler_id(), + std::move(parameters.second), std::move(promise)); } default: if (is_preinitialization_request(function_id)) { @@ -2988,6 +3063,7 @@ td_api::object_ptr Td::static_request(td_api::object_ptr Td::extract_handler(uint64 id) { return result; } -void Td::on_update(BufferSlice &&update, uint64 auth_key_id) { +void Td::on_update(telegram_api::object_ptr updates, uint64 auth_key_id) { if (close_flag_ > 1) { return; } - TlBufferParser parser(&update); - auto ptr = telegram_api::Updates::fetch(parser); - parser.fetch_end(); - if (parser.get_error()) { - LOG(ERROR) << "Failed to fetch update: " << parser.get_error() << format::as_hex_dump<4>(update.as_slice()); - updates_manager_->schedule_get_difference("failed to fetch update"); + if (updates == nullptr) { + updates_manager_->schedule_get_difference("failed to fetch updates"); } else { updates_manager_->on_update_from_auth_key_id(auth_key_id); - updates_manager_->on_get_updates(std::move(ptr), Promise()); + updates_manager_->on_get_updates(std::move(updates), Promise()); if (auth_manager_->is_bot() && auth_manager_->is_authorized()) { alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID, PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5)); @@ -3063,14 +3135,16 @@ void Td::on_result(NetQueryPtr query) { if (handler != nullptr) { CHECK(query->is_ready()); if (query->is_ok()) { - handler->on_result(std::move(query->ok())); + handler->on_result(query->move_as_ok()); } else { - handler->on_error(std::move(query->error())); + handler->on_error(query->move_as_error()); + } + } else { + if (!query->is_ok() || query->ok_tl_constructor() != telegram_api::upload_file::ID) { + LOG(WARNING) << query << " is ignored: no handlers found"; } - } else if (!query->is_ok() || query->ok_tl_constructor() != telegram_api::upload_file::ID) { - LOG(WARNING) << query << " is ignored: no handlers found"; + query->clear(); } - query->clear(); } void Td::on_connection_state_changed(ConnectionState new_state) { @@ -3154,6 +3228,8 @@ void Td::dec_actor_refcnt() { } else if (close_flag_ == 3) { LOG(INFO) << "All actors were closed"; Timer timer; + account_manager_.reset(); + LOG(DEBUG) << "AccountManager was cleared" << timer; animations_manager_.reset(); LOG(DEBUG) << "AnimationsManager was cleared" << timer; attach_menu_manager_.reset(); @@ -3202,10 +3278,16 @@ void Td::dec_actor_refcnt() { LOG(DEBUG) << "NotificationSettingsManager was cleared" << timer; poll_manager_.reset(); LOG(DEBUG) << "PollManager was cleared" << timer; + privacy_manager_.reset(); + LOG(DEBUG) << "PrivacyManager was cleared" << timer; + reaction_manager_.reset(); + LOG(DEBUG) << "ReactionManager was cleared" << timer; sponsored_message_manager_.reset(); LOG(DEBUG) << "SponsoredMessageManager was cleared" << timer; stickers_manager_.reset(); LOG(DEBUG) << "StickersManager was cleared" << timer; + story_manager_.reset(); + LOG(DEBUG) << "StoryManager was cleared" << timer; theme_manager_.reset(); LOG(DEBUG) << "ThemeManager was cleared" << timer; top_dialog_manager_.reset(); @@ -3343,8 +3425,6 @@ void Td::clear() { LOG(DEBUG) << "NetStatsManager was cleared" << timer; password_manager_.reset(); LOG(DEBUG) << "PasswordManager was cleared" << timer; - privacy_manager_.reset(); - LOG(DEBUG) << "PrivacyManager was cleared" << timer; secure_manager_.reset(); LOG(DEBUG) << "SecureManager was cleared" << timer; secret_chats_manager_.reset(); @@ -3360,6 +3440,8 @@ void Td::clear() { LOG(DEBUG) << "TempAuthKeyWatchdog was cleared" << timer; // clear actors which are unique pointers + account_manager_actor_.reset(); + LOG(DEBUG) << "AccountManager actor was cleared" << timer; animations_manager_actor_.reset(); LOG(DEBUG) << "AnimationsManager actor was cleared" << timer; attach_menu_manager_actor_.reset(); @@ -3402,10 +3484,16 @@ void Td::clear() { LOG(DEBUG) << "NotificationSettingsManager actor was cleared" << timer; poll_manager_actor_.reset(); LOG(DEBUG) << "PollManager actor was cleared" << timer; + privacy_manager_actor_.reset(); + LOG(DEBUG) << "PrivacyManager actor was cleared" << timer; + reaction_manager_actor_.reset(); + LOG(DEBUG) << "ReactionManager actor was cleared" << timer; sponsored_message_manager_actor_.reset(); LOG(DEBUG) << "SponsoredMessageManager actor was cleared" << timer; stickers_manager_actor_.reset(); LOG(DEBUG) << "StickersManager actor was cleared" << timer; + story_manager_actor_.reset(); + LOG(DEBUG) << "StoryManager actor was cleared" << timer; theme_manager_actor_.reset(); LOG(DEBUG) << "ThemeManager actor was cleared" << timer; top_dialog_manager_actor_.reset(); @@ -3503,12 +3591,6 @@ void Td::complete_pending_preauthentication_requests(const T &func) { } } -int32 Td::get_database_scheduler_id() { - auto current_scheduler_id = Scheduler::instance()->sched_id(); - auto scheduler_count = Scheduler::instance()->sched_count(); - return min(current_scheduler_id + 1, scheduler_count - 1); -} - void Td::finish_set_parameters() { CHECK(set_parameters_request_id_ != 0); set_parameters_request_id_ = 0; @@ -3648,10 +3730,6 @@ void Td::init(Parameters parameters, Result r_opened_datab on_save_app_log_binlog_event(this, std::move(event)); } - if (option_manager_->get_option_boolean("default_reaction_needs_sync")) { - send_set_default_reaction_query(this); - } - if (is_online_) { on_online_updated(true, true); } @@ -3685,6 +3763,8 @@ void Td::init(Parameters parameters, Result r_opened_datab send_closure_later(messages_manager_actor_, &MessagesManager::on_binlog_events, std::move(events.to_messages_manager)); + send_closure_later(story_manager_actor_, &StoryManager::on_binlog_events, std::move(events.to_story_manager)); + send_closure_later(notification_manager_actor_, &NotificationManager::on_binlog_events, std::move(events.to_notification_manager)); @@ -3838,6 +3918,9 @@ void Td::init_managers() { documents_manager_ = make_unique(this); videos_manager_ = make_unique(this); + account_manager_ = make_unique(this, create_reference()); + account_manager_actor_ = register_actor("AccountManager", account_manager_.get()); + G()->set_account_manager(account_manager_actor_.get()); animations_manager_ = make_unique(this, create_reference()); animations_manager_actor_ = register_actor("AnimationsManager", animations_manager_.get()); G()->set_animations_manager(animations_manager_actor_.get()); @@ -3889,12 +3972,20 @@ void Td::init_managers() { G()->set_notification_settings_manager(notification_settings_manager_actor_.get()); poll_manager_ = make_unique(this, create_reference()); poll_manager_actor_ = register_actor("PollManager", poll_manager_.get()); + privacy_manager_ = make_unique(this, create_reference()); + privacy_manager_actor_ = register_actor("PrivacyManager", privacy_manager_.get()); + reaction_manager_ = make_unique(this, create_reference()); + reaction_manager_actor_ = register_actor("ReactionManager", reaction_manager_.get()); + G()->set_reaction_manager(reaction_manager_actor_.get()); sponsored_message_manager_ = make_unique(this, create_reference()); sponsored_message_manager_actor_ = register_actor("SponsoredMessageManager", sponsored_message_manager_.get()); G()->set_sponsored_message_manager(sponsored_message_manager_actor_.get()); stickers_manager_ = make_unique(this, create_reference()); stickers_manager_actor_ = register_actor("StickersManager", stickers_manager_.get()); G()->set_stickers_manager(stickers_manager_actor_.get()); + story_manager_ = make_unique(this, create_reference()); + story_manager_actor_ = register_actor("StoryManager", story_manager_.get()); + G()->set_story_manager(story_manager_actor_.get()); theme_manager_ = make_unique(this, create_reference()); theme_manager_actor_ = register_actor("ThemeManager", theme_manager_.get()); G()->set_theme_manager(theme_manager_actor_.get()); @@ -3926,7 +4017,6 @@ void Td::init_managers() { G()->set_language_pack_manager(language_pack_manager_.get()); password_manager_ = create_actor("PasswordManager", create_reference()); G()->set_password_manager(password_manager_.get()); - privacy_manager_ = create_actor("PrivacyManager", create_reference()); secure_manager_ = create_actor("SecureManager", create_reference()); verify_phone_number_manager_ = create_actor( "VerifyPhoneNumberManager", PhoneNumberManager::Type::VerifyPhone, create_reference()); @@ -4029,7 +4119,7 @@ void Td::answer_ok_query(uint64 id, Status status) { } Promise Td::create_ok_request_promise(uint64 id) { - return PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result result) { + return PromiseCreator::lambda([actor_id = actor_id(this), id](Result result) { if (result.is_error()) { send_closure(actor_id, &Td::send_error, id, result.move_as_error()); } else { @@ -4235,7 +4325,7 @@ void Td::on_request(uint64 id, td_api::checkAuthenticationBotToken &request) { void Td::on_request(uint64 id, td_api::confirmQrCodeAuthentication &request) { CLEAN_INPUT_STRING(request.link_); CREATE_REQUEST_PROMISE(); - confirm_qr_code_authentication(this, request.link_, std::move(promise)); + account_manager_->confirm_qr_code_authentication(request.link_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getCurrentState &request) { @@ -4261,6 +4351,8 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { stickers_manager_->get_current_state(updates); + reaction_manager_->get_current_state(updates); + notification_settings_manager_->get_current_state(updates); dialog_filter_manager_->get_current_state(updates); @@ -4269,10 +4361,14 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { notification_manager_->get_current_state(updates); + story_manager_->get_current_state(updates); + config_manager_.get_actor_unsafe()->get_current_state(updates); autosave_manager_->get_current_state(updates); + account_manager_->get_current_state(updates); + // TODO updateFileGenerationStart generation_id:int64 original_path:string destination_path:string conversion:string = Update; // TODO updateCall call:call = Update; // TODO updateGroupCall call:groupCall = Update; @@ -4449,14 +4545,13 @@ void Td::on_request(uint64 id, td_api::registerDevice &request) { void Td::on_request(uint64 id, td_api::getUserPrivacySettingRules &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - send_closure(privacy_manager_, &PrivacyManager::get_privacy, std::move(request.setting_), std::move(promise)); + privacy_manager_->get_privacy(std::move(request.setting_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setUserPrivacySettingRules &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - send_closure(privacy_manager_, &PrivacyManager::set_privacy, std::move(request.setting_), std::move(request.rules_), - std::move(promise)); + privacy_manager_->set_privacy(std::move(request.setting_), std::move(request.rules_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getDefaultMessageAutoDeleteTime &request) { @@ -4469,7 +4564,7 @@ void Td::on_request(uint64 id, const td_api::getDefaultMessageAutoDeleteTime &re promise.set_value(td_api::make_object(result.ok())); } }); - get_default_message_ttl(this, std::move(query_promise)); + account_manager_->get_default_message_ttl(std::move(query_promise)); } void Td::on_request(uint64 id, const td_api::setDefaultMessageAutoDeleteTime &request) { @@ -4478,7 +4573,7 @@ void Td::on_request(uint64 id, const td_api::setDefaultMessageAutoDeleteTime &re return send_error_raw(id, 400, "New default message auto-delete time must be non-empty"); } CREATE_OK_REQUEST_PROMISE(); - set_default_message_ttl(this, request.message_auto_delete_time_->time_, std::move(promise)); + account_manager_->set_default_message_ttl(request.message_auto_delete_time_->time_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getAccountTtl &request) { @@ -4491,7 +4586,7 @@ void Td::on_request(uint64 id, const td_api::getAccountTtl &request) { promise.set_value(td_api::make_object(result.ok())); } }); - get_account_ttl(this, std::move(query_promise)); + account_manager_->get_account_ttl(std::move(query_promise)); } void Td::on_request(uint64 id, const td_api::setAccountTtl &request) { @@ -4500,7 +4595,7 @@ void Td::on_request(uint64 id, const td_api::setAccountTtl &request) { return send_error_raw(id, 400, "New account TTL must be non-empty"); } CREATE_OK_REQUEST_PROMISE(); - set_account_ttl(this, request.ttl_->days_, std::move(promise)); + account_manager_->set_account_ttl(request.ttl_->days_, std::move(promise)); } void Td::on_request(uint64 id, td_api::deleteAccount &request) { @@ -4530,69 +4625,75 @@ void Td::on_request(uint64 id, td_api::resendChangePhoneNumberCode &request) { void Td::on_request(uint64 id, const td_api::getUserLink &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_user_link(std::move(promise)); + account_manager_->get_user_link(std::move(promise)); } void Td::on_request(uint64 id, td_api::searchUserByToken &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.token_); CREATE_REQUEST_PROMISE(); - contacts_manager_->search_user_by_token(std::move(request.token_), std::move(promise)); + account_manager_->import_contact_token(std::move(request.token_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getActiveSessions &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - get_active_sessions(this, std::move(promise)); + account_manager_->get_active_sessions(std::move(promise)); } void Td::on_request(uint64 id, const td_api::terminateSession &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - terminate_session(this, request.session_id_, std::move(promise)); + account_manager_->terminate_session(request.session_id_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::terminateAllOtherSessions &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - terminate_all_other_sessions(this, std::move(promise)); + account_manager_->terminate_all_other_sessions(std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::confirmSession &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + account_manager_->confirm_session(request.session_id_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSessionCanAcceptCalls &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - toggle_session_can_accept_calls(this, request.session_id_, request.can_accept_calls_, std::move(promise)); + account_manager_->toggle_session_can_accept_calls(request.session_id_, request.can_accept_calls_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSessionCanAcceptSecretChats &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - toggle_session_can_accept_secret_chats(this, request.session_id_, request.can_accept_secret_chats_, - std::move(promise)); + account_manager_->toggle_session_can_accept_secret_chats(request.session_id_, request.can_accept_secret_chats_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::setInactiveSessionTtl &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - set_inactive_session_ttl_days(this, request.inactive_session_ttl_days_, std::move(promise)); + account_manager_->set_inactive_session_ttl_days(request.inactive_session_ttl_days_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getConnectedWebsites &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - get_connected_websites(this, std::move(promise)); + account_manager_->get_connected_websites(std::move(promise)); } void Td::on_request(uint64 id, const td_api::disconnectWebsite &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - disconnect_website(this, request.website_id_, std::move(promise)); + account_manager_->disconnect_website(request.website_id_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::disconnectAllWebsites &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - disconnect_all_websites(this, std::move(promise)); + account_manager_->disconnect_all_websites(std::move(promise)); } void Td::on_request(uint64 id, const td_api::getMe &request) { @@ -4664,6 +4765,13 @@ void Td::on_request(uint64 id, const td_api::getChatSponsoredMessages &request) sponsored_message_manager_->get_dialog_sponsored_messages(DialogId(request.chat_id_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::clickChatSponsoredMessage &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + sponsored_message_manager_->click_sponsored_message(DialogId(request.chat_id_), MessageId(request.message_id_), + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getMessageThread &request) { CHECK_IS_USER(); CREATE_REQUEST(GetMessageThreadRequest, request.chat_id_, request.message_id_); @@ -5038,6 +5146,12 @@ void Td::on_request(uint64 id, const td_api::getInactiveSupergroupChats &request CREATE_NO_ARGS_REQUEST(GetInactiveSupergroupChatsRequest); } +void Td::on_request(uint64 id, td_api::searchRecentlyFoundChats &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.query_); + CREATE_REQUEST(SearchRecentlyFoundChatsRequest, request.query_, request.limit_); +} + void Td::on_request(uint64 id, const td_api::addRecentlyFoundChat &request) { CHECK_IS_USER(); answer_ok_query(id, messages_manager_->add_recently_found_dialog(DialogId(request.chat_id_))); @@ -5264,7 +5378,7 @@ void Td::on_request(uint64 id, const td_api::getChatScheduledMessages &request) void Td::on_request(uint64 id, const td_api::getEmojiReaction &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - stickers_manager_->get_emoji_reaction(request.emoji_, std::move(promise)); + reaction_manager_->get_emoji_reaction(request.emoji_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getCustomEmojiReactionAnimations &request) { @@ -5287,14 +5401,14 @@ void Td::on_request(uint64 id, const td_api::getMessageAvailableReactions &reque void Td::on_request(uint64 id, const td_api::clearRecentReactions &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - stickers_manager_->clear_recent_reactions(std::move(promise)); + reaction_manager_->clear_recent_reactions(std::move(promise)); } void Td::on_request(uint64 id, td_api::addMessageReaction &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); messages_manager_->add_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, - get_message_reaction_string(request.reaction_type_), request.is_big_, + ReactionType(request.reaction_type_), request.is_big_, request.update_recent_reactions_, std::move(promise)); } @@ -5302,7 +5416,7 @@ void Td::on_request(uint64 id, td_api::removeMessageReaction &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); messages_manager_->remove_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, - get_message_reaction_string(request.reaction_type_), std::move(promise)); + ReactionType(request.reaction_type_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getMessageAddedReactions &request) { @@ -5310,14 +5424,14 @@ void Td::on_request(uint64 id, td_api::getMessageAddedReactions &request) { CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); get_message_added_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, - get_message_reaction_string(request.reaction_type_), std::move(request.offset_), - request.limit_, std::move(promise)); + ReactionType(request.reaction_type_), std::move(request.offset_), request.limit_, + std::move(promise)); } void Td::on_request(uint64 id, td_api::setDefaultReactionType &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - set_default_reaction(this, get_message_reaction_string(request.reaction_type_), std::move(promise)); + reaction_manager_->set_default_reaction(ReactionType(request.reaction_type_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { @@ -5341,7 +5455,7 @@ void Td::on_request(uint64 id, const td_api::removeNotificationGroup &request) { CREATE_OK_REQUEST_PROMISE(); send_closure(notification_manager_actor_, &NotificationManager::remove_notification_group, NotificationGroupId(request.notification_group_id_), NotificationId(request.max_notification_id_), - MessageId(), -1, true, std::move(promise)); + NotificationObjectId(), -1, true, std::move(promise)); } void Td::on_request(uint64 id, const td_api::deleteMessages &request) { @@ -5413,7 +5527,7 @@ void Td::on_request(uint64 id, const td_api::setChatMessageSender &request) { void Td::on_request(uint64 id, td_api::sendMessage &request) { auto r_sent_message = messages_manager_->send_message( - DialogId(request.chat_id_), MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_), + DialogId(request.chat_id_), MessageId(request.message_thread_id_), std::move(request.reply_to_), std::move(request.options_), std::move(request.reply_markup_), std::move(request.input_message_content_)); if (r_sent_message.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error()); @@ -5424,7 +5538,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_), MessageId(request.reply_to_message_id_), + 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_); if (r_messages.is_error()) { send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error()); @@ -5455,8 +5569,8 @@ void Td::on_request(uint64 id, td_api::sendInlineQueryResultMessage &request) { 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_), MessageId(request.reply_to_message_id_), - std::move(request.options_), request.query_id_, request.result_id_, request.hide_via_bot_); + 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()); } @@ -5471,9 +5585,9 @@ void Td::on_request(uint64 id, td_api::addLocalMessage &request) { CHECK_IS_USER(); DialogId dialog_id(request.chat_id_); - auto r_new_message_id = messages_manager_->add_local_message( - dialog_id, std::move(request.sender_id_), MessageId(request.reply_to_message_id_), request.disable_notification_, - std::move(request.input_message_content_)); + auto r_new_message_id = + messages_manager_->add_local_message(dialog_id, std::move(request.sender_id_), std::move(request.reply_to_), + request.disable_notification_, std::move(request.input_message_content_)); if (r_new_message_id.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error()); } @@ -5558,6 +5672,66 @@ void Td::on_request(uint64 id, td_api::editMessageSchedulingState &request) { std::move(request.scheduling_state_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getStory &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->get_story(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_), request.only_local_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::canSendStory &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->can_send_story(std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::sendStory &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->send_story(std::move(request.content_), std::move(request.areas_), std::move(request.caption_), + std::move(request.privacy_settings_), request.active_period_, request.is_pinned_, + request.protect_content_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editStory &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->edit_story(StoryId(request.story_id_), std::move(request.content_), std::move(request.areas_), + std::move(request.caption_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::setStoryPrivacySettings &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->set_story_privacy_settings(StoryId(request.story_id_), std::move(request.privacy_settings_), + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::toggleStoryIsPinned &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->toggle_story_is_pinned(StoryId(request.story_id_), request.is_pinned_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::deleteStory &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->delete_story(StoryId(request.story_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::loadActiveStories &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->load_active_stories(StoryListId(request.story_list_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::setChatActiveStoriesList &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->toggle_dialog_stories_hidden(DialogId(request.chat_id_), StoryListId(request.story_list_), + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getForumTopicDefaultIcons &request) { CREATE_REQUEST_PROMISE(); stickers_manager_->get_default_topic_icons(false, std::move(promise)); @@ -5673,11 +5847,6 @@ void Td::on_request(uint64 id, td_api::sendChatAction &request) { DialogAction(std::move(request.action_)), std::move(promise)); } -void Td::on_request(uint64 id, td_api::sendChatScreenshotTakenNotification &request) { - CHECK_IS_USER(); - answer_ok_query(id, messages_manager_->send_screenshot_taken_notification_message(DialogId(request.chat_id_))); -} - void Td::on_request(uint64 id, td_api::forwardMessages &request) { auto input_message_ids = MessageId::get_message_ids(request.message_ids_); auto message_copy_options = @@ -6140,6 +6309,19 @@ void Td::on_request(uint64 id, const td_api::getChatFolderChatsToLeave &request) std::move(promise)); } +void Td::on_request(uint64 id, td_api::getChatFolderChatCount &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(make_tl_object(result.move_as_ok())); + } + }); + messages_manager_->get_dialog_filter_dialog_count(std::move(request.folder_), std::move(query_promise)); +} + void Td::on_request(uint64 id, const td_api::reorderChatFolders &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6216,6 +6398,19 @@ void Td::on_request(uint64 id, const td_api::processChatFolderNewChats &request) DialogFilterId(request.chat_folder_id_), DialogId::get_dialog_ids(request.added_chat_ids_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getArchiveChatListSettings &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + GlobalPrivacySettings::get_global_privacy_settings(this, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::setArchiveChatListSettings &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + GlobalPrivacySettings::set_global_privacy_settings(this, GlobalPrivacySettings(std::move(request.settings_)), + std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setChatTitle &request) { CLEAN_INPUT_STRING(request.title_); CREATE_OK_REQUEST_PROMISE(); @@ -6284,9 +6479,9 @@ void Td::on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request request.is_marked_as_unread_)); } -void Td::on_request(uint64 id, const td_api::toggleMessageSenderIsBlocked &request) { +void Td::on_request(uint64 id, const td_api::setMessageSenderBlockList &request) { CHECK_IS_USER(); - answer_ok_query(id, messages_manager_->toggle_message_sender_is_blocked(request.sender_id_, request.is_blocked_)); + answer_ok_query(id, messages_manager_->set_message_sender_block_list(request.sender_id_, request.block_list_)); } void Td::on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request) { @@ -6307,6 +6502,81 @@ void Td::on_request(uint64 id, const td_api::readChatList &request) { messages_manager_->read_all_dialogs_from_list(DialogListId(request.chat_list_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getStoryNotificationSettingsExceptions &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + notification_settings_manager_->get_story_notification_settings_exceptions(std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getChatActiveStories &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->get_dialog_expiring_stories(DialogId(request.chat_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getChatPinnedStories &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->get_dialog_pinned_stories(DialogId(request.chat_id_), StoryId(request.from_story_id_), request.limit_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getArchivedStories &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + story_manager_->get_story_archive(StoryId(request.from_story_id_), request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::openStory &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->open_story(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::closeStory &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->close_story(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getStoryAvailableReactions &request) { + CHECK_IS_USER(); + send_closure(actor_id(this), &Td::send_result, id, reaction_manager_->get_available_reactions(request.row_size_)); +} + +void Td::on_request(uint64 id, const td_api::setStoryReaction &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->set_story_reaction({DialogId(request.story_sender_chat_id_), StoryId(request.story_id_)}, + ReactionType(request.reaction_type_), request.update_recent_reactions_, + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getStoryViewers &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.query_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + story_manager_->get_story_viewers(StoryId(request.story_id_), request.query_, request.only_contacts_, + request.prefer_with_reaction_, request.offset_, request.limit_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::reportStory &request) { + CHECK_IS_USER(); + auto r_report_reason = ReportReason::get_report_reason(std::move(request.reason_), std::move(request.text_)); + if (r_report_reason.is_error()) { + return send_error_raw(id, r_report_reason.error().code(), r_report_reason.error().message()); + } + CREATE_OK_REQUEST_PROMISE(); + story_manager_->report_story({DialogId(request.story_sender_chat_id_), StoryId(request.story_id_)}, + r_report_reason.move_as_ok(), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::activateStoryStealthMode &request) { + CREATE_OK_REQUEST_PROMISE(); + story_manager_->activate_stealth_mode(std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getAttachmentMenuBot &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -6860,7 +7130,7 @@ void Td::on_request(uint64 id, const td_api::blockMessageSenderFromReplies &requ void Td::on_request(uint64 id, const td_api::getBlockedMessageSenders &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - messages_manager_->get_blocked_dialogs(request.offset_, request.limit_, std::move(promise)); + messages_manager_->get_blocked_dialogs(request.block_list_, request.offset_, request.limit_, std::move(promise)); } void Td::on_request(uint64 id, td_api::addContact &request) { @@ -6928,6 +7198,17 @@ void Td::on_request(uint64 id, const td_api::clearImportedContacts &request) { contacts_manager_->clear_imported_contacts(std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getCloseFriends &request) { + CHECK_IS_USER(); + CREATE_NO_ARGS_REQUEST(GetCloseFriendsRequest); +} + +void Td::on_request(uint64 id, const td_api::setCloseFriends &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + contacts_manager_->set_close_friends(UserId::get_user_ids(request.user_ids_), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setUserPersonalProfilePhoto &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6998,7 +7279,7 @@ void Td::on_request(uint64 id, td_api::reorderActiveUsernames &request) { void Td::on_request(uint64 id, const td_api::setEmojiStatus &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_emoji_status(EmojiStatus(request.emoji_status_, request.duration_), std::move(promise)); + contacts_manager_->set_emoji_status(EmojiStatus(request.emoji_status_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getThemedEmojiStatuses &request) { @@ -7070,6 +7351,27 @@ void Td::on_request(uint64 id, const td_api::setDefaultChannelAdministratorRight AdministratorRights(request.default_channel_administrator_rights_, ChannelType::Broadcast), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::canBotSendMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + bot_info_manager_->can_bot_send_messages(UserId(request.bot_user_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::allowBotToSendMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + bot_info_manager_->allow_bot_to_send_messages(UserId(request.bot_user_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::sendWebAppCustomRequest &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.method_); + CLEAN_INPUT_STRING(request.parameters_); + CREATE_REQUEST_PROMISE(); + attach_menu_manager_->invoke_web_view_custom_method(UserId(request.bot_user_id_), request.method_, + request.parameters_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setBotName &request) { CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); @@ -7102,7 +7404,7 @@ void Td::on_request(uint64 id, td_api::toggleBotUsernameIsActive &request) { request.is_active_, std::move(promise)); } -void Td::on_request(uint64 id, td_api::reorderActiveBotUsernames &request) { +void Td::on_request(uint64 id, td_api::reorderBotActiveUsernames &request) { CHECK_IS_USER(); for (auto &username : request.usernames_) { CLEAN_INPUT_STRING(username); @@ -7307,6 +7609,13 @@ void Td::on_request(uint64 id, td_api::getStickers &request) { request.chat_id_); } +void Td::on_request(uint64 id, td_api::getAllStickerEmojis &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.query_); + CREATE_REQUEST(GetAllStickerEmojisRequest, get_sticker_type(request.sticker_type_), std::move(request.query_), + request.chat_id_, request.return_only_main_emoji_); +} + void Td::on_request(uint64 id, td_api::searchStickers &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.emojis_); @@ -7839,16 +8148,8 @@ void Td::on_request(uint64 id, td_api::setPollAnswer &request) { void Td::on_request(uint64 id, td_api::getPollVoters &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - auto query_promise = PromiseCreator::lambda( - [promise = std::move(promise), td = this](Result>> result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - promise.set_value(td->contacts_manager_->get_users_object(result.ok().first, result.ok().second)); - } - }); messages_manager_->get_poll_voters({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.option_id_, - request.offset_, request.limit_, std::move(query_promise)); + request.offset_, request.limit_, std::move(promise)); } void Td::on_request(uint64 id, td_api::stopPoll &request) { @@ -7968,7 +8269,7 @@ void Td::on_request(uint64 id, td_api::openWebApp &request) { CLEAN_INPUT_STRING(request.application_name_); CREATE_REQUEST_PROMISE(); attach_menu_manager_->request_web_view(DialogId(request.chat_id_), UserId(request.bot_user_id_), - MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_), + MessageId(request.message_thread_id_), std::move(request.reply_to_), std::move(request.url_), std::move(request.theme_), std::move(request.application_name_), std::move(promise)); } @@ -8426,7 +8727,7 @@ void Td::on_request(uint64 id, td_api::assignGooglePlayTransaction &request) { void Td::on_request(uint64 id, td_api::acceptTermsOfService &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.terms_of_service_id_); - auto promise = PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result<> result) { + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), id](Result<> result) { if (result.is_error()) { send_closure(actor_id, &Td::send_error, id, result.move_as_error()); } else { @@ -8602,6 +8903,10 @@ void Td::on_request(uint64 id, const td_api::getMarkdownText &request) { UNREACHABLE(); } +void Td::on_request(uint64 id, const td_api::searchStringsByPrefix &request) { + UNREACHABLE(); +} + void Td::on_request(uint64 id, const td_api::getFileMimeType &request) { UNREACHABLE(); } @@ -8767,6 +9072,21 @@ td_api::object_ptr Td::do_static_request(td_api::getMarkdownText std::numeric_limits::max()); } +td_api::object_ptr Td::do_static_request(td_api::searchStringsByPrefix &request) { + if (!check_utf8(request.query_)) { + return make_error(400, "Strings must be encoded in UTF-8"); + } + for (auto &str : request.strings_) { + if (!check_utf8(str)) { + return make_error(400, "Strings must be encoded in UTF-8"); + } + } + int32 total_count = 0; + auto result = search_strings_by_prefix(std::move(request.strings_), std::move(request.query_), request.limit_, + !request.return_none_for_empty_query_, total_count); + return td_api::make_object(total_count, std::move(result)); +} + td_api::object_ptr Td::do_static_request(const td_api::getFileMimeType &request) { // don't check file name UTF-8 correctness return make_tl_object(MimeType::from_extension(PathView(request.file_name_).extension())); diff --git a/td/telegram/Td.h b/td/telegram/Td.h index e6ac45bd73d0..75c38b257313 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -37,6 +37,7 @@ namespace td { +class AccountManager; class AnimationsManager; class AttachMenuManager; class AudiosManager; @@ -71,12 +72,14 @@ class PasswordManager; class PhoneNumberManager; class PollManager; class PrivacyManager; +class ReactionManager; class SecureManager; class SecretChatsManager; class SponsoredMessageManager; class StateManager; class StickersManager; class StorageManager; +class StoryManager; class ThemeManager; class TopDialogManager; class TranslationManager; @@ -120,7 +123,7 @@ class Td final : public Actor { void reload_promo_data(); - void on_update(BufferSlice &&update, uint64 auth_key_id); + void on_update(telegram_api::object_ptr updates, uint64 auth_key_id); void on_result(NetQueryPtr query); @@ -146,6 +149,8 @@ class Td final : public Actor { unique_ptr option_manager_; unique_ptr videos_manager_; + unique_ptr account_manager_; + ActorOwn account_manager_actor_; unique_ptr animations_manager_; ActorOwn animations_manager_actor_; unique_ptr attach_menu_manager_; @@ -188,10 +193,16 @@ class Td final : public Actor { ActorOwn notification_settings_manager_actor_; unique_ptr poll_manager_; ActorOwn poll_manager_actor_; + unique_ptr privacy_manager_; + ActorOwn privacy_manager_actor_; + unique_ptr reaction_manager_; + ActorOwn reaction_manager_actor_; unique_ptr sponsored_message_manager_; ActorOwn sponsored_message_manager_actor_; unique_ptr stickers_manager_; ActorOwn stickers_manager_actor_; + unique_ptr story_manager_; + ActorOwn story_manager_actor_; unique_ptr theme_manager_; ActorOwn theme_manager_actor_; unique_ptr top_dialog_manager_; @@ -216,7 +227,6 @@ class Td final : public Actor { ActorOwn language_pack_manager_; ActorOwn net_stats_manager_; ActorOwn password_manager_; - ActorOwn privacy_manager_; ActorOwn secret_chats_manager_; ActorOwn secure_manager_; ActorOwn state_manager_; @@ -390,7 +400,7 @@ class Td final : public Actor { template Promise create_request_promise(uint64 id) { - return PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result r_state) { + return PromiseCreator::lambda([actor_id = actor_id(this), id](Result r_state) { if (r_state.is_error()) { send_closure(actor_id, &Td::send_error, id, r_state.move_as_error()); } else { @@ -522,6 +532,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::terminateAllOtherSessions &request); + void on_request(uint64 id, const td_api::confirmSession &request); + void on_request(uint64 id, const td_api::toggleSessionCanAcceptCalls &request); void on_request(uint64 id, const td_api::toggleSessionCanAcceptSecretChats &request); @@ -570,6 +582,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatSponsoredMessages &request); + void on_request(uint64 id, const td_api::clickChatSponsoredMessage &request); + void on_request(uint64 id, const td_api::getMessageLink &request); void on_request(uint64 id, const td_api::getMessageEmbeddingCode &request); @@ -632,6 +646,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::searchChatsNearby &request); + void on_request(uint64 id, td_api::searchRecentlyFoundChats &request); + void on_request(uint64 id, const td_api::addRecentlyFoundChat &request); void on_request(uint64 id, const td_api::removeRecentlyFoundChat &request); @@ -778,6 +794,24 @@ class Td final : public Actor { void on_request(uint64 id, td_api::editMessageSchedulingState &request); + void on_request(uint64 id, const td_api::getStory &request); + + void on_request(uint64 id, const td_api::canSendStory &request); + + void on_request(uint64 id, td_api::sendStory &request); + + void on_request(uint64 id, td_api::editStory &request); + + void on_request(uint64 id, td_api::setStoryPrivacySettings &request); + + void on_request(uint64 id, const td_api::toggleStoryIsPinned &request); + + void on_request(uint64 id, const td_api::deleteStory &request); + + void on_request(uint64 id, const td_api::loadActiveStories &request); + + void on_request(uint64 id, const td_api::setChatActiveStoriesList &request); + void on_request(uint64 id, const td_api::getForumTopicDefaultIcons &request); void on_request(uint64 id, td_api::createForumTopic &request); @@ -812,8 +846,6 @@ class Td final : public Actor { void on_request(uint64 id, td_api::sendChatAction &request); - void on_request(uint64 id, td_api::sendChatScreenshotTakenNotification &request); - void on_request(uint64 id, td_api::forwardMessages &request); void on_request(uint64 id, const td_api::resendMessages &request); @@ -928,6 +960,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatFolderChatsToLeave &request); + void on_request(uint64 id, td_api::getChatFolderChatCount &request); + void on_request(uint64 id, const td_api::reorderChatFolders &request); void on_request(uint64 id, const td_api::getChatsForChatFolderInviteLink &request); @@ -948,6 +982,10 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::processChatFolderNewChats &request); + void on_request(uint64 id, const td_api::getArchiveChatListSettings &request); + + void on_request(uint64 id, td_api::setArchiveChatListSettings &request); + void on_request(uint64 id, td_api::setChatTitle &request); void on_request(uint64 id, const td_api::setChatPhoto &request); @@ -970,7 +1008,7 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request); - void on_request(uint64 id, const td_api::toggleMessageSenderIsBlocked &request); + void on_request(uint64 id, const td_api::setMessageSenderBlockList &request); void on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request); @@ -978,6 +1016,28 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::readChatList &request); + void on_request(uint64 id, const td_api::getStoryNotificationSettingsExceptions &request); + + void on_request(uint64 id, const td_api::getChatActiveStories &request); + + void on_request(uint64 id, const td_api::getChatPinnedStories &request); + + void on_request(uint64 id, const td_api::getArchivedStories &request); + + void on_request(uint64 id, const td_api::openStory &request); + + void on_request(uint64 id, const td_api::closeStory &request); + + void on_request(uint64 id, const td_api::getStoryAvailableReactions &request); + + void on_request(uint64 id, const td_api::setStoryReaction &request); + + void on_request(uint64 id, td_api::getStoryViewers &request); + + void on_request(uint64 id, td_api::reportStory &request); + + void on_request(uint64 id, const td_api::activateStoryStealthMode &request); + void on_request(uint64 id, const td_api::getAttachmentMenuBot &request); void on_request(uint64 id, const td_api::toggleBotIsAddedToAttachmentMenu &request); @@ -1118,6 +1178,10 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::clearImportedContacts &request); + void on_request(uint64 id, const td_api::getCloseFriends &request); + + void on_request(uint64 id, const td_api::setCloseFriends &request); + void on_request(uint64 id, td_api::setUserPersonalProfilePhoto &request); void on_request(uint64 id, td_api::suggestUserProfilePhoto &request); @@ -1162,6 +1226,12 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::setDefaultChannelAdministratorRights &request); + void on_request(uint64 id, const td_api::canBotSendMessages &request); + + void on_request(uint64 id, const td_api::allowBotToSendMessages &request); + + void on_request(uint64 id, td_api::sendWebAppCustomRequest &request); + void on_request(uint64 id, td_api::setBotName &request); void on_request(uint64 id, const td_api::getBotName &request); @@ -1170,7 +1240,7 @@ class Td final : public Actor { void on_request(uint64 id, td_api::toggleBotUsernameIsActive &request); - void on_request(uint64 id, td_api::reorderActiveBotUsernames &request); + void on_request(uint64 id, td_api::reorderBotActiveUsernames &request); void on_request(uint64 id, td_api::setBotInfoDescription &request); @@ -1224,6 +1294,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getStickers &request); + void on_request(uint64 id, td_api::getAllStickerEmojis &request); + void on_request(uint64 id, td_api::searchStickers &request); void on_request(uint64 id, const td_api::getPremiumStickers &request); @@ -1562,6 +1634,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getMarkdownText &request); + void on_request(uint64 id, const td_api::searchStringsByPrefix &request); + void on_request(uint64 id, const td_api::getFileMimeType &request); void on_request(uint64 id, const td_api::getFileExtension &request); @@ -1622,6 +1696,7 @@ class Td final : public Actor { static td_api::object_ptr do_static_request(td_api::parseTextEntities &request); static td_api::object_ptr do_static_request(td_api::parseMarkdown &request); static td_api::object_ptr do_static_request(td_api::getMarkdownText &request); + static td_api::object_ptr do_static_request(td_api::searchStringsByPrefix &request); static td_api::object_ptr do_static_request(const td_api::getFileMimeType &request); static td_api::object_ptr do_static_request(const td_api::getFileExtension &request); static td_api::object_ptr do_static_request(const td_api::cleanFileName &request); @@ -1644,8 +1719,6 @@ class Td final : public Actor { static DbKey as_db_key(string key); - static int32 get_database_scheduler_id(); - struct Parameters { int32 api_id_ = 0; string api_hash_; diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index b143733b032a..de78cbeba929 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -13,6 +13,7 @@ #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageDb.h" #include "td/telegram/MessageThreadDb.h" +#include "td/telegram/StoryDb.h" #include "td/telegram/Td.h" #include "td/telegram/Version.h" @@ -126,6 +127,13 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p case LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer: events.to_messages_manager.push_back(event.clone()); break; + case LogEvent::HandlerType::DeleteStoryOnServer: + case LogEvent::HandlerType::ReadStoriesOnServer: + case LogEvent::HandlerType::LoadDialogExpiringStories: + case LogEvent::HandlerType::SendStory: + case LogEvent::HandlerType::EditStory: + events.to_story_manager.push_back(event.clone()); + break; case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer: events.to_notification_settings_manager.push_back(event.clone()); break; @@ -226,6 +234,14 @@ DialogDbAsyncInterface *TdDb::get_dialog_db_async() { return dialog_db_async_.get(); } +StoryDbSyncInterface *TdDb::get_story_db_sync() { + return &story_db_sync_safe_->get(); +} + +StoryDbAsyncInterface *TdDb::get_story_db_async() { + return story_db_async_.get(); +} + void TdDb::flush_all() { LOG(INFO) << "Flush all databases"; if (message_db_async_) { @@ -237,6 +253,9 @@ void TdDb::flush_all() { if (dialog_db_async_) { dialog_db_async_->force_flush(); } + if (story_db_async_) { + story_db_async_->force_flush(); + } binlog_->force_flush(); } @@ -292,6 +311,11 @@ void TdDb::do_close(Promise<> on_finished, bool destroy_flag) { dialog_db_async_->close(mpas.get_promise()); } + story_db_sync_safe_.reset(); + if (story_db_async_) { + story_db_async_->close(mpas.get_promise()); + } + // binlog_pmc is dependent on binlog_ and anyway it doesn't support close_and_destroy CHECK(binlog_pmc_.unique()); binlog_pmc_.reset(); @@ -318,10 +342,11 @@ Status TdDb::init_sqlite(const Parameters ¶meters, const DbKey &key, const D const string sql_database_path = get_sqlite_path(parameters); bool use_sqlite = parameters.use_file_database_; - bool use_file_database_ = parameters.use_file_database_; + bool use_file_database = parameters.use_file_database_; bool use_dialog_db = parameters.use_message_database_; bool use_message_thread_db = parameters.use_message_database_ && false; - bool use_message_database_ = parameters.use_message_database_; + bool use_message_database = parameters.use_message_database_; + bool use_story_database = parameters.use_message_database_; was_dialog_db_created_ = false; @@ -363,14 +388,21 @@ Status TdDb::init_sqlite(const Parameters ¶meters, const DbKey &key, const D } // init MessageDb - if (use_message_database_) { + if (use_message_database) { TRY_STATUS(init_message_db(db, user_version)); } else { TRY_STATUS(drop_message_db(db, user_version)); } + // init StoryDb + if (use_story_database) { + TRY_STATUS(init_story_db(db, user_version)); + } else { + TRY_STATUS(drop_story_db(db, user_version)); + } + // init FileDb - if (use_file_database_) { + if (use_file_database) { TRY_STATUS(init_file_db(db, user_version)); } else { TRY_STATUS(drop_file_db(db, user_version)); @@ -389,7 +421,7 @@ Status TdDb::init_sqlite(const Parameters ¶meters, const DbKey &key, const D binlog_pmc.erase_by_prefix("unread_message_count"); binlog_pmc.erase_by_prefix("unread_dialog_count"); binlog_pmc.erase("sponsored_dialog_id"); - binlog_pmc.erase_by_prefix("top_dialogs"); + binlog_pmc.erase_by_prefix("top_dialogs#"); binlog_pmc.erase("dlds_counter"); binlog_pmc.erase_by_prefix("dlds#"); binlog_pmc.erase("fetched_marks_as_unread"); @@ -425,11 +457,16 @@ Status TdDb::init_sqlite(const Parameters ¶meters, const DbKey &key, const D message_thread_db_async_ = create_message_thread_db_async(message_thread_db_sync_safe_); } - if (use_message_database_) { + if (use_message_database) { message_db_sync_safe_ = create_message_db_sync(sql_connection_); message_db_async_ = create_message_db_async(message_db_sync_safe_); } + if (use_story_database) { + story_db_sync_safe_ = create_story_db_sync(sql_connection_); + story_db_async_ = create_story_db_async(story_db_sync_safe_); + } + return Status::OK(); } @@ -637,6 +674,7 @@ Result TdDb::get_stats() { << mask << "'", PSLICE() << table << ":" << mask); }; + TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM stories WHERE 1", "stories")); TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM messages WHERE 1", "messages")); TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM dialogs WHERE 1", "dialogs")); TRY_STATUS(run_kv_query("%", "common")); diff --git a/td/telegram/TdDb.h b/td/telegram/TdDb.h index 8dc9977f815c..f3670b4294e3 100644 --- a/td/telegram/TdDb.h +++ b/td/telegram/TdDb.h @@ -11,6 +11,7 @@ #include "td/db/DbKey.h" #include "td/db/KeyValueSyncInterface.h" +#include "td/utils/common.h" #include "td/utils/Promise.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" @@ -38,6 +39,9 @@ class SqliteConnectionSafe; class SqliteKeyValueSafe; class SqliteKeyValueAsyncInterface; class SqliteKeyValue; +class StoryDbSyncInterface; +class StoryDbSyncSafeInterface; +class StoryDbAsyncInterface; class TdDb { public: @@ -72,6 +76,7 @@ class TdDb { vector to_messages_manager; vector to_notification_manager; vector to_notification_settings_manager; + vector to_story_manager; int64 since_last_open = 0; }; @@ -140,6 +145,9 @@ class TdDb { DialogDbSyncInterface *get_dialog_db_sync(); DialogDbAsyncInterface *get_dialog_db_async(); + StoryDbSyncInterface *get_story_db_sync(); + StoryDbAsyncInterface *get_story_db_async(); + void change_key(DbKey key, Promise<> promise); void with_db_path(const std::function &callback); @@ -167,6 +175,9 @@ class TdDb { std::shared_ptr dialog_db_sync_safe_; std::shared_ptr dialog_db_async_; + std::shared_ptr story_db_sync_safe_; + std::shared_ptr story_db_async_; + std::shared_ptr> binlog_pmc_; std::shared_ptr> config_pmc_; std::shared_ptr binlog_; diff --git a/td/telegram/TermsOfService.cpp b/td/telegram/TermsOfService.cpp index 5aab9c4876c5..3988454b3e88 100644 --- a/td/telegram/TermsOfService.cpp +++ b/td/telegram/TermsOfService.cpp @@ -10,6 +10,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" diff --git a/td/telegram/ThemeManager.cpp b/td/telegram/ThemeManager.cpp index b02783100dc2..c834e090f8b3 100644 --- a/td/telegram/ThemeManager.cpp +++ b/td/telegram/ThemeManager.cpp @@ -13,6 +13,7 @@ #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" diff --git a/td/telegram/TopDialogManager.cpp b/td/telegram/TopDialogManager.cpp index 0f3d995dda5a..65208dfa012b 100644 --- a/td/telegram/TopDialogManager.cpp +++ b/td/telegram/TopDialogManager.cpp @@ -19,6 +19,7 @@ #include "td/telegram/StateManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/actor/PromiseFuture.h" @@ -276,11 +277,8 @@ void TopDialogManager::get_top_dialogs(TopDialogCategory category, int32 limit, if (limit <= 0) { return promise.set_error(Status::Error(400, "Limit must be positive")); } - if (!is_active_) { - return promise.set_error(Status::Error(400, "Not supported without chat info database")); - } if (!is_enabled_) { - return promise.set_error(Status::Error(400, "Top chats computation is disabled")); + return promise.set_error(Status::Error(400, "Top chat computation is disabled")); } GetTopDialogsQuery query; @@ -291,6 +289,29 @@ void TopDialogManager::get_top_dialogs(TopDialogCategory category, int32 limit, loop(); } +int TopDialogManager::is_top_dialog(TopDialogCategory category, size_t limit, DialogId dialog_id) const { + CHECK(category != TopDialogCategory::Size); + CHECK(category != TopDialogCategory::ForwardUsers); + CHECK(limit > 0); + if (!is_active_) { + return -1; + } + if (!is_enabled_) { + return 0; + } + + vector dialog_ids; + auto pos = static_cast(category); + CHECK(pos < by_category_.size()); + const auto &dialogs = by_category_[pos].dialogs; + for (size_t i = 0; i < limit && i < dialogs.size(); i++) { + if (dialogs[i].dialog_id == dialog_id) { + return 1; + } + } + return is_synchronized_ ? 0 : -1; +} + void TopDialogManager::update_rating_e_decay() { if (!is_active_) { return; @@ -455,6 +476,7 @@ void TopDialogManager::on_get_top_peers(Resulttd_db()->get_binlog_pmc()->set(key, log_event_store(top_dialogs).as_slice().str()); + if (G()->use_chat_info_database()) { + auto top_dialog_category = TopDialogCategory(top_dialog_category_i); + auto key = PSTRING() << "top_dialogs#" << get_top_dialog_category_name(top_dialog_category); + G()->td_db()->get_binlog_pmc()->set(key, log_event_store(top_dialogs).as_slice().str()); + } } db_sync_state_ = SyncState::Ok; first_unsync_change_ = Timestamp(); @@ -530,7 +553,7 @@ void TopDialogManager::init() { return; } - is_active_ = G()->use_chat_info_database() && !td_->auth_manager_->is_bot(); + is_active_ = !td_->auth_manager_->is_bot(); is_enabled_ = !G()->get_option_boolean("disable_top_chats"); update_rating_e_decay(); @@ -562,9 +585,10 @@ void TopDialogManager::try_start() { if (last_server_sync_.is_in_past()) { server_sync_state_ = SyncState::Ok; } + is_synchronized_ = G()->use_chat_info_database(); } - if (is_enabled_) { + if (is_enabled_ && G()->use_chat_info_database()) { for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) { auto top_dialog_category = TopDialogCategory(top_dialog_category_i); auto key = PSTRING() << "top_dialogs#" << get_top_dialog_category_name(top_dialog_category); @@ -581,9 +605,7 @@ void TopDialogManager::try_start() { } else { G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs#"); for (auto &top_dialogs : by_category_) { - top_dialogs.is_dirty = false; - top_dialogs.rating_timestamp = 0; - top_dialogs.dialogs.clear(); + top_dialogs = {}; } } db_sync_state_ = SyncState::Ok; @@ -606,7 +628,7 @@ void TopDialogManager::loop() { return; } - if (!pending_get_top_dialogs_.empty()) { + if (!pending_get_top_dialogs_.empty() && (is_synchronized_ || !is_enabled_)) { for (auto &query : pending_get_top_dialogs_) { do_get_top_dialogs(std::move(query)); } @@ -616,7 +638,8 @@ void TopDialogManager::loop() { // server sync Timestamp server_sync_timeout; if (server_sync_state_ == SyncState::Ok) { - server_sync_timeout = Timestamp::at(last_server_sync_.at() + SERVER_SYNC_DELAY); + server_sync_timeout = pending_get_top_dialogs_.empty() ? Timestamp::at(last_server_sync_.at() + SERVER_SYNC_DELAY) + : Timestamp::now_cached(); if (server_sync_timeout.is_in_past()) { server_sync_state_ = SyncState::None; } @@ -625,7 +648,7 @@ void TopDialogManager::loop() { Timestamp wakeup_timeout; if (server_sync_state_ == SyncState::Ok) { wakeup_timeout.relax(server_sync_timeout); - } else if (server_sync_state_ == SyncState::None && was_first_sync_) { + } else if (server_sync_state_ == SyncState::None && (was_first_sync_ || !pending_get_top_dialogs_.empty())) { server_sync_state_ = SyncState::Pending; do_get_top_peers(); } diff --git a/td/telegram/TopDialogManager.h b/td/telegram/TopDialogManager.h index afb35a5e45e8..efa19230353b 100644 --- a/td/telegram/TopDialogManager.h +++ b/td/telegram/TopDialogManager.h @@ -37,6 +37,8 @@ class TopDialogManager final : public Actor { void get_top_dialogs(TopDialogCategory category, int32 limit, Promise> &&promise); + int is_top_dialog(TopDialogCategory category, size_t limit, DialogId dialog_id) const; + void update_rating_e_decay(); void update_is_enabled(bool is_enabled); @@ -52,6 +54,7 @@ class TopDialogManager final : public Actor { bool is_active_ = false; bool is_enabled_ = true; + bool is_synchronized_ = false; int32 rating_e_decay_ = 241920; bool have_toggle_top_peers_query_ = false; diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index 279ec5f1e301..d69ad1813b3a 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -6,6 +6,7 @@ // #include "td/telegram/UpdatesManager.h" +#include "td/telegram/AccountManager.h" #include "td/telegram/AnimationsManager.h" #include "td/telegram/AttachMenuManager.h" #include "td/telegram/AuthManager.h" @@ -45,6 +46,8 @@ #include "td/telegram/PollManager.h" #include "td/telegram/PrivacyManager.h" #include "td/telegram/PublicDialogType.h" +#include "td/telegram/ReactionManager.h" +#include "td/telegram/ReactionType.h" #include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/SecretChatsManager.h" @@ -54,9 +57,12 @@ #include "td/telegram/StickerSetId.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickerType.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" #include "td/telegram/telegram_api.hpp" #include "td/telegram/ThemeManager.h" #include "td/telegram/Usernames.h" @@ -153,6 +159,19 @@ class PingServerQuery final : public Td::ResultHandler { } }; +class InitSessionQuery final : public Td::ResultHandler { + public: + void send() { + send_query(G()->net_query_creator().create(telegram_api::help_getCdnConfig())); + } + + void on_result(BufferSlice) final { + } + + void on_error(Status) final { + } +}; + class GetDifferenceQuery final : public Td::ResultHandler { Promise> promise_; @@ -162,7 +181,7 @@ class GetDifferenceQuery final : public Td::ResultHandler { } void send(int32 pts, int32 date, int32 qts) { - send_query(G()->net_query_creator().create(telegram_api::updates_getDifference(0, pts, 0, date, qts))); + send_query(G()->net_query_creator().create(telegram_api::updates_getDifference(0, pts, 0, 0, date, qts, 0))); } void on_result(BufferSlice packet) final { @@ -180,6 +199,58 @@ class GetDifferenceQuery final : public Td::ResultHandler { } }; +class ConfirmPtsQtsQuery final : public Td::ResultHandler { + public: + void send(int32 pts, int32 qts) { + int32 flags = + telegram_api::updates_getDifference::PTS_LIMIT_MASK | telegram_api::updates_getDifference::QTS_LIMIT_MASK; + send_query(G()->net_query_creator().create( + telegram_api::updates_getDifference(flags, pts, 1, 0, std::numeric_limits::max(), qts, 1))); + } + + 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()); + } + } + + void on_error(Status status) final { + if (!G()->is_expected_error(status)) { + LOG(ERROR) << "Failed to confirm PTS/QTS: " << status; + } + } +}; + +class GetPtsUpdateQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetPtsUpdateQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(int32 pts) { + int32 flags = + telegram_api::updates_getDifference::PTS_LIMIT_MASK | telegram_api::updates_getDifference::QTS_LIMIT_MASK; + send_query(G()->net_query_creator().create( + telegram_api::updates_getDifference(flags, pts, 1, 0, std::numeric_limits::max(), 0, 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()); + } + + promise_.set_value(result_ptr.move_as_ok()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + UpdatesManager::UpdatesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { last_pts_save_time_ = last_qts_save_time_ = Time::now() - 2 * MAX_PTS_SAVE_DELAY; @@ -239,6 +310,38 @@ ActorShared UpdatesManager::create_reference() { return actor_shared(this, 1); } +void UpdatesManager::check_pts_gap(void *td) { + if (G()->close_flag()) { + return; + } + + CHECK(td != nullptr); + static_cast(td)->updates_manager_.get()->repair_pts_gap(); +} + +void UpdatesManager::repair_pts_gap() { + if (!td_->auth_manager_->is_authorized() || running_get_difference_ || !postponed_pts_updates_.empty()) { + return; + } + auto pts = get_pts() + 1; + if (pending_pts_updates_.empty()) { + return; + } + auto it = pending_pts_updates_.begin(); + if (it->second.pts != pts + it->second.pts_count) { + return; + } + VLOG(get_difference) << "Fetch update with PTS = " << pts; + pts_short_gap_++; + auto promise = + PromiseCreator::lambda([pts](Result> result) { + if (result.is_ok()) { + send_closure(G()->updates_manager(), &UpdatesManager::on_get_pts_update, pts, result.move_as_ok()); + } + }); + td_->create_handler(std::move(promise))->send(pts - 1); +} + void UpdatesManager::fill_pts_gap(void *td) { if (G()->close_flag()) { return; @@ -247,16 +350,31 @@ void UpdatesManager::fill_pts_gap(void *td) { CHECK(td != nullptr); auto updates_manager = static_cast(td)->updates_manager_.get(); auto min_pts = std::numeric_limits::max(); + auto min_pts_count = 0; + const telegram_api::Update *first_update = nullptr; auto max_pts = 0; if (!updates_manager->pending_pts_updates_.empty()) { - min_pts = min(min_pts, updates_manager->pending_pts_updates_.begin()->first); + auto &min_update = updates_manager->pending_pts_updates_.begin()->second; + if (min_update.pts < min_pts) { + min_pts = min_update.pts; + min_pts_count = min_update.pts_count; + first_update = min_update.update.get(); + } max_pts = max(max_pts, updates_manager->pending_pts_updates_.rbegin()->first); } if (!updates_manager->postponed_pts_updates_.empty()) { - min_pts = min(min_pts, updates_manager->postponed_pts_updates_.begin()->first); + auto &min_update = updates_manager->postponed_pts_updates_.begin()->second; + if (min_update.pts < min_pts) { + min_pts = min_update.pts; + min_pts_count = min_update.pts_count; + first_update = min_update.update.get(); + } max_pts = max(max_pts, updates_manager->postponed_pts_updates_.rbegin()->first); } - string source = PSTRING() << "PTS from " << updates_manager->get_pts() << " to " << min_pts << '-' << max_pts; + string source = PSTRING() << "PTS from " << updates_manager->get_pts() << " to " << min_pts << "(-" << min_pts_count + << ")-" << max_pts << ' ' + << (first_update == nullptr ? string() : oneline(to_string(*first_update))); + updates_manager->pts_gap_++; fill_gap(td, source.c_str()); } @@ -291,6 +409,7 @@ void UpdatesManager::fill_qts_gap(void *td) { max_qts = updates_manager->pending_qts_updates_.rbegin()->first; } string source = PSTRING() << "QTS from " << updates_manager->get_qts() << " to " << min_qts << '-' << max_qts; + updates_manager->qts_gap_++; fill_gap(td, source.c_str()); } @@ -499,25 +618,32 @@ void UpdatesManager::timeout_expired() { Promise<> UpdatesManager::set_pts(int32 pts, const char *source) { if (pts == std::numeric_limits::max()) { LOG(WARNING) << "Update PTS from " << get_pts() << " to -1 from " << source; - save_pts(pts); + save_pts(pts); // drop saved PTS value auto result = add_pts(pts); init_state(); return result; } Promise<> result; - if (pts > get_pts() || (0 < pts && pts < get_pts() - 399999)) { // PTS can only go up or drop cardinally - if (pts < get_pts() - 399999) { - LOG(WARNING) << "PTS decreases from " << get_pts() << " to " << pts << " from " << source; + auto old_pts = get_pts(); + if (pts > old_pts || (0 < pts && pts < old_pts - 1000009)) { // PTS can only go up or drop cardinally + if (pts < old_pts - 1000009) { + LOG(WARNING) << "PTS decreases from " << old_pts << " to " << pts << " from " << source; } else { - LOG(INFO) << "Update PTS from " << get_pts() << " to " << pts << " from " << source; + 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; + } } result = add_pts(pts); - if (last_confirmed_pts_ < get_pts() - FORCED_GET_DIFFERENCE_PTS_DIFF) { - if (last_confirmed_pts_ != 0) { - schedule_get_difference("rare PTS getDifference"); - } - last_confirmed_pts_ = get_pts(); + if (last_confirmed_pts_ < get_pts() - FORCED_GET_DIFFERENCE_PTS_DIFF && last_confirmed_pts_ != 0) { + confirm_pts_qts(get_qts()); } } else if (pts < get_pts() && (pts > 1 || td_->option_manager_->get_option_integer("session_count") <= 1)) { LOG(ERROR) << "Receive wrong PTS = " << pts << " from " << source << ". Current PTS = " << get_pts(); @@ -563,7 +689,8 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source) } bool UpdatesManager::is_acceptable_user(UserId user_id) const { - return td_->contacts_manager_->have_user_force(user_id) && td_->contacts_manager_->have_user(user_id); + return td_->contacts_manager_->have_user_force(user_id, "is_acceptable_user") && + td_->contacts_manager_->have_user(user_id); } bool UpdatesManager::is_acceptable_chat(ChatId chat_id) const { @@ -639,15 +766,30 @@ bool UpdatesManager::is_acceptable_reply_markup(const tl_object_ptr &header) const { + const telegram_api::object_ptr &header) const { if (header == nullptr) { return true; } - if (!is_acceptable_peer(header->reply_to_peer_id_)) { - return false; + 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; + } + 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; + } + default: + UNREACHABLE(); + return true; } - return true; } bool UpdatesManager::is_acceptable_message_forward_header( @@ -701,19 +843,25 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ if (message->media_ != nullptr) { auto media_id = message->media_->get_id(); if (media_id == telegram_api::messageMediaContact::ID) { - auto message_media_contact = static_cast(message->media_.get()); - UserId user_id(message_media_contact->user_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()); + UserId user_id(message_media->user_id_); + if (!is_acceptable_user(user_id)) { + return false; + } + } /* - // the users are always min, so no need to check + // 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_user_id : message_media_poll->results_->recent_voters_) { - UserId user_id(recent_voter_user_id); - if (!is_acceptable_user(user_id)) { + for (auto recent_voter : message_media_poll->results_->recent_voters_) { + if (!is_acceptable_peer(recent_voter)) { return false; } } @@ -935,6 +1083,10 @@ bool UpdatesManager::is_acceptable_update(const telegram_api::Update *update) co } 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)); +} + +void UpdatesManager::on_get_updates_impl(tl_object_ptr updates_ptr, Promise promise) { CHECK(updates_ptr != nullptr); promise = PromiseCreator::lambda( [promise = std::move(promise), update_ids = get_update_ids(updates_ptr.get())](Result result) mutable { @@ -994,15 +1146,14 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID; auto message = make_tl_object( - update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, 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_); + 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_); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, - std::move(promise), "telegram_api::updatesShortMessage"); + std::move(promise), "telegram_api::updateShortMessage"); break; } case telegram_api::updateShortChatMessage::ID: { @@ -1018,15 +1169,14 @@ void UpdatesManager::on_get_updates(tl_object_ptr &&updat update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID; auto message = make_tl_object( - update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_, - make_tl_object(update->from_id_), + 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_), 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_); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, - std::move(promise), "telegram_api::updatesShortChatMessage"); + std::move(promise), "telegram_api::updateShortChatMessage"); break; } case telegram_api::updateShort::ID: { @@ -1463,6 +1613,9 @@ vector UpdatesManager::get_chat_dialog_ids(const telegram_api::Updates LOG(ERROR) << "Can't find identifier of " << oneline(to_string(chat)); } } + if (dialog_ids.size() > 1) { + td::remove(dialog_ids, DialogId(ContactsManager::get_unsupported_chat_id())); + } return dialog_ids; } @@ -1530,7 +1683,7 @@ void UpdatesManager::init_state() { return; } - bool drop_state = td_->can_ignore_background_updates() && td_->auth_manager_->is_bot() && + bool drop_state = get_pts() == -1 && td_->can_ignore_background_updates() && td_->auth_manager_->is_bot() && td_->option_manager_->get_option_integer("since_last_open") >= 2 * 86400; auto pmc = G()->td_db()->get_binlog_pmc(); @@ -1621,6 +1774,26 @@ void UpdatesManager::on_server_pong(tl_object_ptr & } } +void UpdatesManager::init_sessions(bool is_first) { + if (G()->close_flag() || !td_->auth_manager_->is_authorized()) { + return; + } + if (are_sessions_inited_ == is_first) { + return; + } + are_sessions_inited_ = true; + + auto session_count = td_->option_manager_->get_option_integer("session_count", 1); + if (session_count <= 1) { + return; + } + + LOG(INFO) << "Init " << session_count << " sessions"; + for (int64 i = 0; i < session_count; i++) { + td_->create_handler()->send(); + } +} + void UpdatesManager::process_get_difference_updates( vector> &&new_messages, vector> &&new_encrypted_messages, @@ -1639,6 +1812,14 @@ void UpdatesManager::process_get_difference_updates( CHECK(!running_get_difference_); } + if (constructor_id == telegram_api::updateStoryID::ID) { + LOG(INFO) << "Receive update about sent story " << to_string(update); + auto update_story_id = move_tl_object_as(update); + td_->story_manager_->on_update_story_id(update_story_id->random_id_, StoryId(update_story_id->id_), + "getDifference"); + CHECK(!running_get_difference_); + } + if (constructor_id == telegram_api::updateEncryption::ID) { on_update(move_tl_object_as(update), Promise()); CHECK(!running_get_difference_); @@ -1693,14 +1874,13 @@ void UpdatesManager::process_get_difference_updates( } void UpdatesManager::on_get_difference(tl_object_ptr &&difference_ptr) { - VLOG(get_difference) << "----- END GET DIFFERENCE-----"; - running_get_difference_ = false; - - if (!td_->auth_manager_->is_authorized()) { - // just in case + if (G()->close_flag() || !td_->auth_manager_->is_authorized()) { return; } + VLOG(get_difference) << "----- END GET DIFFERENCE-----"; + running_get_difference_ = false; + LOG(DEBUG) << "Result of get difference: " << to_string(difference_ptr); CHECK(difference_ptr != nullptr); @@ -1740,6 +1920,17 @@ void UpdatesManager::on_get_difference(tl_object_ptrcontacts_manager_->on_get_users(std::move(difference->users_), "updates.difference"); td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.difference"); + if (get_difference_retry_count_ <= 5) { + for (const auto &message : difference->new_messages_) { + if (MessagesManager::is_invalid_poll_message(message.get())) { + get_difference_retry_count_++; + LOG(ERROR) << "Receive invalid poll message in updates.difference after " << get_difference_retry_count_ + << " tries"; + return run_get_difference(true, "reget difference"); + } + } + } + process_get_difference_updates(std::move(difference->new_messages_), std::move(difference->new_encrypted_messages_), std::move(difference->other_updates_)); @@ -1765,6 +1956,17 @@ void UpdatesManager::on_get_difference(tl_object_ptrcontacts_manager_->on_get_users(std::move(difference->users_), "updates.differenceSlice"); td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.differenceSlice"); + if (get_difference_retry_count_ <= 5) { + for (const auto &message : difference->new_messages_) { + if (MessagesManager::is_invalid_poll_message(message.get())) { + get_difference_retry_count_++; + LOG(ERROR) << "Receive invalid poll message in updates.differenceSlice after " + << get_difference_retry_count_ << " tries"; + return run_get_difference(true, "reget difference"); + } + } + } + process_get_difference_updates(std::move(difference->new_messages_), std::move(difference->new_encrypted_messages_), std::move(difference->other_updates_)); @@ -1813,11 +2015,97 @@ void UpdatesManager::on_get_difference(tl_object_ptr difference_ptr) { + if (G()->close_flag() || !td_->auth_manager_->is_authorized()) { + return; + } + if (get_pts() != pts - 1 || running_get_difference_ || !postponed_pts_updates_.empty() || + pending_pts_updates_.empty() || pending_pts_updates_.begin()->first != pts + 1) { + return; + } + + LOG(DEBUG) << "Receive update with PTS " << pts << ": " << to_string(difference_ptr); + + switch (difference_ptr->get_id()) { + case telegram_api::updates_difference::ID: { + auto difference = move_tl_object_as(difference_ptr); + difference_ptr = telegram_api::make_object( + std::move(difference->new_messages_), std::move(difference->new_encrypted_messages_), + std::move(difference->other_updates_), std::move(difference->chats_), std::move(difference->users_), + std::move(difference->state_)); + } + // fallthrough + case telegram_api::updates_differenceSlice::ID: { + auto difference = move_tl_object_as(difference_ptr); + + if (have_update_pts_changed(difference->other_updates_)) { + return; + } + + td_->contacts_manager_->on_get_users(std::move(difference->users_), "on_get_pts_update"); + td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "on_get_pts_update"); + + for (auto &message : difference->new_messages_) { + difference->other_updates_.push_back( + telegram_api::make_object(std::move(message), pts, 1)); + } + telegram_api::object_ptr *update_ptr = nullptr; + size_t update_count = 0; + for (auto &update : difference->other_updates_) { + auto constructor_id = update->get_id(); + if (constructor_id == telegram_api::updateMessageID::ID) { + // in getDifference updateMessageID can't be received for scheduled messages + LOG(INFO) << "Receive update about sent message " << to_string(update); + auto update_message_id = move_tl_object_as(update); + td_->messages_manager_->on_update_message_id( + update_message_id->random_id_, MessageId(ServerMessageId(update_message_id->id_)), "on_get_pts_update"); + continue; + } + update_ptr = &update; + update_count++; + } + + if (!difference->new_encrypted_messages_.empty() || update_count != 1) { + LOG(ERROR) << "Receive unexpected updates with PTS " << pts << ": " << to_string(difference_ptr); + break; + } + + CHECK(update_ptr != nullptr); + VLOG(get_difference) << "Repair update with PTS " << pts; + pts_fixed_short_gap_++; + add_pending_pts_update(std::move(*update_ptr), pts, 1, Time::now(), Promise(), "on_get_pts_update"); + break; + } + case telegram_api::updates_differenceEmpty::ID: + case telegram_api::updates_differenceTooLong::ID: { + LOG(ERROR) << "Receive " << to_string(difference_ptr); + break; + default: + UNREACHABLE(); + } + } +} + +void UpdatesManager::confirm_pts_qts(int32 qts) { + int32 pts = get_pts(); + if (pts < 0) { + pts = 0; + } + + td_->create_handler()->send(pts, qts); + + last_confirmed_pts_ = pts; + last_confirmed_qts_ = qts; +} + void UpdatesManager::after_get_difference() { CHECK(!running_get_difference_); @@ -1903,6 +2191,8 @@ void UpdatesManager::after_get_difference() { send_closure(G()->state_manager(), &StateManager::on_synchronized, true); get_difference_start_time_ = 0.0; + init_sessions(true); + try_reload_data(); } @@ -1957,9 +2247,9 @@ void UpdatesManager::try_reload_data() { Auto()); td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Auto()); - td_->stickers_manager_->reload_reactions(); - td_->stickers_manager_->reload_recent_reactions(); - td_->stickers_manager_->reload_top_reactions(); + td_->reaction_manager_->reload_reactions(); + td_->reaction_manager_->reload_recent_reactions(); + td_->reaction_manager_->reload_top_reactions(); for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { auto sticker_type = static_cast(type); td_->stickers_manager_->get_installed_sticker_sets(sticker_type, Auto()); @@ -1976,6 +2266,8 @@ void UpdatesManager::try_reload_data() { 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_->story_manager_->reload_active_stories(); + td_->story_manager_->reload_all_read_stories(); schedule_data_reload(); } @@ -2189,7 +2481,6 @@ void UpdatesManager::on_pending_updates(vectorget_id(); if (id == telegram_api::updateMessageID::ID) { - LOG(INFO) << "Receive from " << source << " " << to_string(update); auto sent_message_update = move_tl_object_as(update); MessageId message_id; if (ordinary_new_message_count != 0) { @@ -2204,6 +2495,11 @@ void UpdatesManager::on_pending_updates(vector(update); + td_->story_manager_->on_update_story_id(update_story_id->random_id_, StoryId(update_story_id->id_), source); + update = nullptr; + } if (id == telegram_api::updateFolderPeers::ID) { on_update(move_tl_object_as(update), get_promise()); update = nullptr; @@ -2332,7 +2628,7 @@ void UpdatesManager::add_pending_qts_update(tl_object_ptr int32 old_qts = get_qts(); LOG(INFO) << "Process update with QTS = " << qts << ", current QTS = " << old_qts; - if (qts < old_qts - 100001) { + if (qts < old_qts - 1000009) { LOG(WARNING) << "Restore QTS after QTS overflow from " << old_qts << " to " << qts << " by " << oneline(to_string(update)); add_qts(qts - 1).set_value(Unit()); @@ -2528,7 +2824,7 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr return promise.set_value(Unit()); } - if (DROP_PTS_UPDATES) { + if (DROP_PTS_UPDATES && Slice(source) != Slice("on_get_pts_update")) { set_pts_gap_timeout(1.0); return promise.set_value(Unit()); } @@ -2537,8 +2833,14 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr if (new_pts < old_pts - 99 && source != AFTER_GET_DIFFERENCE_SOURCE) { bool need_restore_pts = new_pts < old_pts - 19999; auto now = Time::now(); - if (now > last_pts_jump_warning_time_ + 1 && (need_restore_pts || now < last_pts_jump_warning_time_ + 5) && - !(old_pts == std::numeric_limits::max() && running_get_difference_)) { + if (old_pts == 2100000000 && new_pts < 1100000000 && pts_count <= 10000 && + td_->option_manager_->get_option_integer("session_count") > 1) { + set_pts(1, "restore PTS").set_value(Unit()); + old_pts = get_pts(); + set_pts_gap_timeout(0.001); + return promise.set_value(Unit()); + } else if (now > last_pts_jump_warning_time_ + 1 && need_restore_pts && + !(old_pts == std::numeric_limits::max() && running_get_difference_)) { LOG(ERROR) << "Restore PTS after delete_first_messages from " << old_pts << " to " << new_pts << " is disabled, pts_count = " << pts_count << ", update is from " << source << ": " << oneline(to_string(update)); @@ -2546,13 +2848,6 @@ void UpdatesManager::add_pending_pts_update(tl_object_ptr } if (need_restore_pts) { set_pts_gap_timeout(0.001); - - /* - LOG(WARNING) << "Restore PTS after delete_first_messages"; - set_pts(new_pts - 1, "restore PTS after delete_first_messages"); - old_pts = get_pts(); - CHECK(old_pts == new_pts - 1); - */ } } @@ -2671,11 +2966,14 @@ void UpdatesManager::process_seq_updates(int32 seq_end, int32 date, void UpdatesManager::process_qts_update(tl_object_ptr &&update_ptr, int32 qts, Promise &&promise) { LOG(DEBUG) << "Process " << to_string(update_ptr); - if (last_confirmed_qts_ < qts - FORCED_GET_DIFFERENCE_PTS_DIFF) { - if (last_confirmed_qts_ != 0) { - schedule_get_difference("rare QTS getDifference"); - } - last_confirmed_qts_ = qts; + if (last_confirmed_qts_ < qts - FORCED_GET_DIFFERENCE_PTS_DIFF && last_confirmed_qts_ != 0) { + confirm_pts_qts(qts); + } + qts_diff_++; + if (qts_diff_ >= 1000000) { + LOG(WARNING) << "Fixed " << qts_gap_ << " QTS gaps"; + qts_gap_ = 0; + qts_diff_ = 0; } switch (update_ptr->get_id()) { case telegram_api::updateNewEncryptedMessage::ID: { @@ -2686,8 +2984,8 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up } case telegram_api::updateMessagePollVote::ID: { auto update = move_tl_object_as(update_ptr); - td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), UserId(update->user_id_), - std::move(update->options_)); + DialogId dialog_id(update->peer_); + td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), dialog_id, std::move(update->options_)); add_qts(qts).set_value(Unit()); break; } @@ -2755,6 +3053,7 @@ void UpdatesManager::process_all_pending_pts_updates() { void UpdatesManager::drop_all_pending_pts_updates() { accumulated_pts_count_ = 0; accumulated_pts_ = -1; + min_pts_gap_timeout_.cancel_timeout(); pts_gap_timeout_.cancel_timeout(); pending_pts_updates_.clear(); } @@ -2876,6 +3175,7 @@ void UpdatesManager::process_pending_pts_updates() { pending_pts_updates_.erase(update_it); } if (applied_update_count > 0) { + min_pts_gap_timeout_.cancel_timeout(); pts_gap_timeout_.cancel_timeout(); } if (!pending_pts_updates_.empty()) { @@ -3012,6 +3312,12 @@ void UpdatesManager::process_pending_qts_updates() { void UpdatesManager::set_pts_gap_timeout(double timeout) { if (!pts_gap_timeout_.has_timeout() || timeout < pts_gap_timeout_.get_timeout()) { + if (timeout > 2 * MIN_UNFILLED_GAP_TIME) { + min_pts_gap_timeout_.set_callback(std::move(check_pts_gap)); + min_pts_gap_timeout_.set_callback_data(static_cast(td_)); + min_pts_gap_timeout_.set_timeout_in(MIN_UNFILLED_GAP_TIME); + } + pts_gap_timeout_.set_callback(std::move(fill_pts_gap)); pts_gap_timeout_.set_callback_data(static_cast(td_)); pts_gap_timeout_.set_timeout_in(timeout); @@ -3058,6 +3364,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { LOG(ERROR) << "Receive not in getDifference and not in on_pending_updates " << to_string(update); + promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, @@ -3182,7 +3489,8 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->messages_manager_->on_update_channel_max_unavailable_message_id( - ChannelId(update->channel_id_), MessageId(ServerMessageId(update->available_min_id_))); + ChannelId(update->channel_id_), MessageId(ServerMessageId(update->available_min_id_)), + "updateChannelAvailableMessages"); promise.set_value(Unit()); } @@ -3334,7 +3642,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->stickers_manager_->reload_recent_reactions(); + td_->reaction_manager_->reload_recent_reactions(); promise.set_value(Unit()); } @@ -3575,7 +3883,8 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->messages_manager_->on_update_dialog_is_blocked(DialogId(update->peer_id_), update->blocked_); + td_->messages_manager_->on_update_dialog_is_blocked(DialogId(update->peer_id_), update->blocked_, + update->blocked_my_stories_from_); promise.set_value(Unit()); } @@ -3678,13 +3987,13 @@ void UpdatesManager::on_update(tl_object_ptr upda } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->inline_queries_manager_->on_new_query(update->query_id_, UserId(update->user_id_), Location(update->geo_), + td_->inline_queries_manager_->on_new_query(update->query_id_, UserId(update->user_id_), Location(td_, update->geo_), std::move(update->peer_type_), update->query_, update->offset_); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->inline_queries_manager_->on_chosen_result(UserId(update->user_id_), Location(update->geo_), update->query_, + td_->inline_queries_manager_->on_chosen_result(UserId(update->user_id_), Location(td_, update->geo_), update->query_, update->id_, std::move(update->msg_id_)); promise.set_value(Unit()); } @@ -3721,7 +4030,18 @@ void UpdatesManager::on_update(tl_object_ptr update, } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - set_pts(std::numeric_limits::max(), "updatePtsChanged").set_value(Unit()); + if (td_->option_manager_->get_option_integer("session_count") > 1) { + auto old_pts = get_pts(); + auto new_pts = 1; + if (old_pts != new_pts) { + LOG(WARNING) << "PTS changes from " << old_pts << " from updatePtsChanged"; + save_pts(new_pts); + add_pts(new_pts).set_value(Unit()); + get_difference("updatePtsChanged"); + } + } else { + set_pts(std::numeric_limits::max(), "updatePtsChanged").set_value(Unit()); + } promise.set_value(Unit()); } @@ -3742,7 +4062,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - send_closure(td_->privacy_manager_, &PrivacyManager::update_privacy, std::move(update)); + td_->privacy_manager_->on_update_privacy(std::move(update)); promise.set_value(Unit()); } @@ -3999,6 +4319,42 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->story_manager_->on_get_story(DialogId(UserId(update->user_id_)), std::move(update->story_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->story_manager_->on_update_read_stories(DialogId(UserId(update->user_id_)), StoryId(update->max_id_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->story_manager_->on_update_story_stealth_mode(std::move(update->stealth_mode_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + td_->story_manager_->on_update_story_chosen_reaction_type( + DialogId(UserId(update->user_id_)), StoryId(update->story_id_), ReactionType(update->reaction_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + LOG(ERROR) << "Receive not in getDifference and not in on_pending_updates " << to_string(update); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + if (update->unconfirmed_) { + td_->account_manager_->on_new_unconfirmed_authorization(update->hash_, update->date_, std::move(update->device_), + std::move(update->location_)); + } else { + td_->account_manager_->on_confirm_authorization(update->hash_); + } + promise.set_value(Unit()); +} + // unsupported updates } // namespace td diff --git a/td/telegram/UpdatesManager.h b/td/telegram/UpdatesManager.h index fb9e3436063c..c68126320066 100644 --- a/td/telegram/UpdatesManager.h +++ b/td/telegram/UpdatesManager.h @@ -139,6 +139,8 @@ class UpdatesManager final : public Actor { void ping_server(); + void init_sessions(bool is_first); + bool running_get_difference() const { return running_get_difference_; } @@ -148,6 +150,7 @@ class UpdatesManager final : public Actor { private: static constexpr int32 FORCED_GET_DIFFERENCE_PTS_DIFF = 100000; static constexpr int32 GAP_TIMEOUT_UPDATE_COUNT = 20; + static constexpr double MIN_UNFILLED_GAP_TIME = 0.05; static constexpr double MAX_UNFILLED_GAP_TIME = 0.7; static constexpr double MAX_PTS_SAVE_DELAY = 0.05; static constexpr double UPDATE_APPLY_WARNING_TIME = 0.25; @@ -217,6 +220,14 @@ class UpdatesManager final : public Actor { int32 pending_pts_ = 0; int32 pending_qts_ = 0; + int32 pts_short_gap_ = 0; + int32 pts_fixed_short_gap_ = 0; + int32 pts_gap_ = 0; + int32 pts_diff_ = 0; + + int32 qts_gap_ = 0; + int32 qts_diff_ = 0; + int64 being_processed_updates_ = 0; int32 short_update_date_ = 0; @@ -234,6 +245,7 @@ class UpdatesManager final : public Actor { std::map pending_qts_updates_; // updates with too big QTS + Timeout min_pts_gap_timeout_; Timeout pts_gap_timeout_; Timeout seq_gap_timeout_; @@ -248,6 +260,8 @@ class UpdatesManager final : public Actor { bool is_ping_sent_ = false; + bool are_sessions_inited_ = false; + bool running_get_difference_ = false; bool finished_first_get_difference_ = false; int32 last_confirmed_pts_ = 0; @@ -255,6 +269,7 @@ class UpdatesManager final : public Actor { int32 min_postponed_update_pts_ = 0; int32 min_postponed_update_qts_ = 0; double get_difference_start_time_ = 0; // time from which we started to get difference without success + int32 get_difference_retry_count_ = 0; FlatHashMap pending_audio_transcriptions_; MultiTimeout pending_audio_transcription_timeout_{"PendingAudioTranscriptionTimeout"}; @@ -307,6 +322,8 @@ class UpdatesManager final : public Actor { void on_get_updates_state(tl_object_ptr &&state, const char *source); + void on_get_updates_impl(tl_object_ptr updates_ptr, Promise promise); + void on_server_pong(tl_object_ptr &&state); void on_get_difference(tl_object_ptr &&difference_ptr); @@ -350,6 +367,8 @@ class UpdatesManager final : public Actor { void process_pending_qts_updates(); + static void check_pts_gap(void *td); + static void fill_pts_gap(void *td); static void fill_seq_gap(void *td); @@ -362,6 +381,10 @@ class UpdatesManager final : public Actor { static void on_pending_audio_transcription_timeout_callback(void *td, int64 transcription_id); + void repair_pts_gap(); + + void on_get_pts_update(int32 pts, telegram_api::object_ptr difference_ptr); + void set_pts_gap_timeout(double timeout); void set_seq_gap_timeout(double timeout); @@ -370,6 +393,8 @@ class UpdatesManager final : public Actor { void run_get_difference(bool is_recursive, const char *source); + void confirm_pts_qts(int32 qts); + void on_failed_get_updates_state(Status &&error); void on_failed_get_difference(Status &&error); @@ -427,7 +452,7 @@ class UpdatesManager final : public Actor { bool is_acceptable_reply_markup(const tl_object_ptr &reply_markup) const; bool is_acceptable_message_reply_header( - const telegram_api::object_ptr &header) const; + const telegram_api::object_ptr &header) const; bool is_acceptable_message_forward_header( const telegram_api::object_ptr &header) const; @@ -595,6 +620,18 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + // unsupported updates }; diff --git a/td/telegram/UserPrivacySetting.cpp b/td/telegram/UserPrivacySetting.cpp new file mode 100644 index 000000000000..3aa9a6943b73 --- /dev/null +++ b/td/telegram/UserPrivacySetting.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/UserPrivacySetting.h" + +namespace td { + +Result UserPrivacySetting::get_user_privacy_setting( + td_api::object_ptr key) { + if (key == nullptr) { + return Status::Error(400, "UserPrivacySetting must be non-empty"); + } + return UserPrivacySetting(*key); +} + +UserPrivacySetting::UserPrivacySetting(const telegram_api::PrivacyKey &key) { + switch (key.get_id()) { + case telegram_api::privacyKeyStatusTimestamp::ID: + type_ = Type::UserStatus; + break; + case telegram_api::privacyKeyChatInvite::ID: + type_ = Type::ChatInvite; + break; + case telegram_api::privacyKeyPhoneCall::ID: + type_ = Type::Call; + break; + case telegram_api::privacyKeyPhoneP2P::ID: + type_ = Type::PeerToPeerCall; + break; + case telegram_api::privacyKeyForwards::ID: + type_ = Type::LinkInForwardedMessages; + break; + case telegram_api::privacyKeyProfilePhoto::ID: + type_ = Type::UserProfilePhoto; + break; + case telegram_api::privacyKeyPhoneNumber::ID: + type_ = Type::UserPhoneNumber; + break; + case telegram_api::privacyKeyAddedByPhone::ID: + type_ = Type::FindByPhoneNumber; + break; + case telegram_api::privacyKeyVoiceMessages::ID: + type_ = Type::VoiceMessages; + break; + case telegram_api::privacyKeyAbout::ID: + type_ = Type::UserBio; + break; + default: + UNREACHABLE(); + type_ = Type::UserStatus; + } +} + +td_api::object_ptr UserPrivacySetting::get_user_privacy_setting_object() const { + switch (type_) { + case Type::UserStatus: + return make_tl_object(); + case Type::ChatInvite: + return make_tl_object(); + case Type::Call: + return make_tl_object(); + case Type::PeerToPeerCall: + return make_tl_object(); + case Type::LinkInForwardedMessages: + return make_tl_object(); + case Type::UserProfilePhoto: + return make_tl_object(); + case Type::UserPhoneNumber: + return make_tl_object(); + case Type::FindByPhoneNumber: + return make_tl_object(); + case Type::VoiceMessages: + return make_tl_object(); + case Type::UserBio: + return make_tl_object(); + default: + UNREACHABLE(); + return nullptr; + } +} +telegram_api::object_ptr UserPrivacySetting::get_input_privacy_key() const { + switch (type_) { + case Type::UserStatus: + return make_tl_object(); + case Type::ChatInvite: + return make_tl_object(); + case Type::Call: + return make_tl_object(); + case Type::PeerToPeerCall: + return make_tl_object(); + case Type::LinkInForwardedMessages: + return make_tl_object(); + case Type::UserProfilePhoto: + return make_tl_object(); + case Type::UserPhoneNumber: + return make_tl_object(); + case Type::FindByPhoneNumber: + return make_tl_object(); + case Type::VoiceMessages: + return make_tl_object(); + case Type::UserBio: + return make_tl_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +UserPrivacySetting::UserPrivacySetting(const td_api::UserPrivacySetting &key) { + switch (key.get_id()) { + case td_api::userPrivacySettingShowStatus::ID: + type_ = Type::UserStatus; + break; + case td_api::userPrivacySettingAllowChatInvites::ID: + type_ = Type::ChatInvite; + break; + case td_api::userPrivacySettingAllowCalls::ID: + type_ = Type::Call; + break; + case td_api::userPrivacySettingAllowPeerToPeerCalls::ID: + type_ = Type::PeerToPeerCall; + break; + case td_api::userPrivacySettingShowLinkInForwardedMessages::ID: + type_ = Type::LinkInForwardedMessages; + break; + case td_api::userPrivacySettingShowProfilePhoto::ID: + type_ = Type::UserProfilePhoto; + break; + case td_api::userPrivacySettingShowPhoneNumber::ID: + type_ = Type::UserPhoneNumber; + break; + case td_api::userPrivacySettingAllowFindingByPhoneNumber::ID: + type_ = Type::FindByPhoneNumber; + break; + case td_api::userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages::ID: + type_ = Type::VoiceMessages; + break; + case td_api::userPrivacySettingShowBio::ID: + type_ = Type::UserBio; + break; + default: + UNREACHABLE(); + type_ = Type::UserStatus; + } +} + +} // namespace td diff --git a/td/telegram/UserPrivacySetting.h b/td/telegram/UserPrivacySetting.h new file mode 100644 index 000000000000..b69a6f8369f7 --- /dev/null +++ b/td/telegram/UserPrivacySetting.h @@ -0,0 +1,51 @@ +// +// 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/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" + +namespace td { + +class UserPrivacySetting { + public: + enum class Type : int32 { + UserStatus, + ChatInvite, + Call, + PeerToPeerCall, + LinkInForwardedMessages, + UserProfilePhoto, + UserPhoneNumber, + FindByPhoneNumber, + VoiceMessages, + UserBio, + Size + }; + + explicit UserPrivacySetting(const telegram_api::PrivacyKey &key); + + static Result get_user_privacy_setting(td_api::object_ptr key); + + td_api::object_ptr get_user_privacy_setting_object() const; + + telegram_api::object_ptr get_input_privacy_key() const; + + Type type() const { + return type_; + } + + private: + Type type_; + + explicit UserPrivacySetting(const td_api::UserPrivacySetting &key); +}; + +} // namespace td diff --git a/td/telegram/UserPrivacySettingRule.cpp b/td/telegram/UserPrivacySettingRule.cpp new file mode 100644 index 000000000000..cf708323cca5 --- /dev/null +++ b/td/telegram/UserPrivacySettingRule.cpp @@ -0,0 +1,393 @@ +// +// 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/UserPrivacySettingRule.h" + +#include "td/telegram/ChannelId.h" +#include "td/telegram/ChatId.h" +#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/Td.h" + +#include "td/utils/algorithm.h" +#include "td/utils/logging.h" + +#include + +namespace td { + +void UserPrivacySettingRule::set_dialog_ids(Td *td, const vector &chat_ids) { + dialog_ids_.clear(); + for (auto chat_id : chat_ids) { + DialogId dialog_id(chat_id); + if (!td->messages_manager_->have_dialog_force(dialog_id, "UserPrivacySettingRule::set_dialog_ids")) { + LOG(INFO) << "Ignore not found " << dialog_id; + continue; + } + + switch (dialog_id.get_type()) { + case DialogType::Chat: + dialog_ids_.push_back(dialog_id); + break; + case DialogType::Channel: { + auto channel_id = dialog_id.get_channel_id(); + if (!td->contacts_manager_->is_megagroup_channel(channel_id)) { + LOG(INFO) << "Ignore broadcast " << channel_id; + break; + } + dialog_ids_.push_back(dialog_id); + break; + } + default: + LOG(INFO) << "Ignore " << dialog_id; + } + } +} + +UserPrivacySettingRule::UserPrivacySettingRule(Td *td, const td_api::UserPrivacySettingRule &rule) { + switch (rule.get_id()) { + case td_api::userPrivacySettingRuleAllowContacts::ID: + type_ = Type::AllowContacts; + break; + case td_api::userPrivacySettingRuleAllowAll::ID: + type_ = Type::AllowAll; + break; + case td_api::userPrivacySettingRuleAllowUsers::ID: + type_ = Type::AllowUsers; + user_ids_ = UserId::get_user_ids(static_cast(rule).user_ids_); + break; + case td_api::userPrivacySettingRuleAllowChatMembers::ID: + type_ = Type::AllowChatParticipants; + set_dialog_ids(td, static_cast(rule).chat_ids_); + break; + case td_api::userPrivacySettingRuleRestrictContacts::ID: + type_ = Type::RestrictContacts; + break; + case td_api::userPrivacySettingRuleRestrictAll::ID: + type_ = Type::RestrictAll; + break; + case td_api::userPrivacySettingRuleRestrictUsers::ID: + type_ = Type::RestrictUsers; + user_ids_ = + UserId::get_user_ids(static_cast(rule).user_ids_); + break; + case td_api::userPrivacySettingRuleRestrictChatMembers::ID: + type_ = Type::RestrictChatParticipants; + set_dialog_ids(td, static_cast(rule).chat_ids_); + break; + default: + UNREACHABLE(); + } +} + +UserPrivacySettingRule::UserPrivacySettingRule(Td *td, + const telegram_api::object_ptr &rule) { + CHECK(rule != nullptr); + switch (rule->get_id()) { + case telegram_api::privacyValueAllowContacts::ID: + type_ = Type::AllowContacts; + break; + case telegram_api::privacyValueAllowCloseFriends::ID: + type_ = Type::AllowCloseFriends; + break; + case telegram_api::privacyValueAllowAll::ID: + type_ = Type::AllowAll; + break; + case telegram_api::privacyValueAllowUsers::ID: + type_ = Type::AllowUsers; + user_ids_ = UserId::get_user_ids(static_cast(*rule).users_); + break; + case telegram_api::privacyValueAllowChatParticipants::ID: + type_ = Type::AllowChatParticipants; + set_dialog_ids_from_server(td, + static_cast(*rule).chats_); + break; + case telegram_api::privacyValueDisallowContacts::ID: + type_ = Type::RestrictContacts; + break; + case telegram_api::privacyValueDisallowAll::ID: + type_ = Type::RestrictAll; + break; + case telegram_api::privacyValueDisallowUsers::ID: + type_ = Type::RestrictUsers; + user_ids_ = UserId::get_user_ids(static_cast(*rule).users_); + break; + case telegram_api::privacyValueDisallowChatParticipants::ID: + type_ = Type::RestrictChatParticipants; + set_dialog_ids_from_server(td, + static_cast(*rule).chats_); + break; + default: + UNREACHABLE(); + } + td::remove_if(user_ids_, [td](UserId user_id) { + if (!td->contacts_manager_->have_user(user_id)) { + LOG(ERROR) << "Receive unknown " << user_id; + return true; + } + return false; + }); +} + +void UserPrivacySettingRule::set_dialog_ids_from_server(Td *td, const vector &server_chat_ids) { + dialog_ids_.clear(); + for (auto server_chat_id : server_chat_ids) { + ChatId chat_id(server_chat_id); + DialogId dialog_id(chat_id); + if (!td->contacts_manager_->have_chat(chat_id)) { + ChannelId channel_id(server_chat_id); + dialog_id = DialogId(channel_id); + if (!td->contacts_manager_->have_channel(channel_id)) { + LOG(ERROR) << "Receive unknown group " << server_chat_id << " from the server"; + continue; + } + } + td->messages_manager_->force_create_dialog(dialog_id, "set_dialog_ids_from_server"); + dialog_ids_.push_back(dialog_id); + } +} + +td_api::object_ptr UserPrivacySettingRule::get_user_privacy_setting_rule_object( + Td *td) const { + switch (type_) { + case Type::AllowContacts: + return make_tl_object(); + case Type::AllowCloseFriends: + LOG(ERROR) << "Have AllowCloseFriends rule"; + return make_tl_object(); + case Type::AllowAll: + return make_tl_object(); + case Type::AllowUsers: + return make_tl_object( + td->contacts_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleAllowUsers")); + case Type::AllowChatParticipants: + return make_tl_object( + td->messages_manager_->get_chat_ids_object(dialog_ids_, "UserPrivacySettingRule")); + case Type::RestrictContacts: + return make_tl_object(); + case Type::RestrictAll: + return make_tl_object(); + case Type::RestrictUsers: + return make_tl_object( + td->contacts_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleRestrictUsers")); + case Type::RestrictChatParticipants: + return make_tl_object( + td->messages_manager_->get_chat_ids_object(dialog_ids_, "UserPrivacySettingRule")); + default: + UNREACHABLE(); + return nullptr; + } +} + +telegram_api::object_ptr UserPrivacySettingRule::get_input_privacy_rule(Td *td) const { + switch (type_) { + case Type::AllowContacts: + return make_tl_object(); + case Type::AllowCloseFriends: + return make_tl_object(); + case Type::AllowAll: + return make_tl_object(); + case Type::AllowUsers: + return make_tl_object(get_input_users(td)); + case Type::AllowChatParticipants: + return make_tl_object(get_input_chat_ids(td)); + case Type::RestrictContacts: + return make_tl_object(); + case Type::RestrictAll: + return make_tl_object(); + case Type::RestrictUsers: + return make_tl_object(get_input_users(td)); + case Type::RestrictChatParticipants: + return make_tl_object(get_input_chat_ids(td)); + default: + UNREACHABLE(); + } +} + +vector> UserPrivacySettingRule::get_input_users(Td *td) const { + vector> result; + for (auto user_id : user_ids_) { + auto r_input_user = td->contacts_manager_->get_input_user(user_id); + if (r_input_user.is_ok()) { + result.push_back(r_input_user.move_as_ok()); + } else { + LOG(INFO) << "Have no access to " << user_id; + } + } + return result; +} + +vector UserPrivacySettingRule::get_input_chat_ids(Td *td) const { + vector result; + for (auto dialog_id : dialog_ids_) { + switch (dialog_id.get_type()) { + case DialogType::Chat: + result.push_back(dialog_id.get_chat_id().get()); + break; + case DialogType::Channel: + result.push_back(dialog_id.get_channel_id().get()); + break; + default: + UNREACHABLE(); + } + } + return result; +} + +vector UserPrivacySettingRule::get_restricted_user_ids() const { + if (type_ == Type::RestrictUsers) { + return user_ids_; + } + return {}; +} + +void UserPrivacySettingRule::add_dependencies(Dependencies &dependencies) const { + for (auto user_id : user_ids_) { + dependencies.add(user_id); + } + for (auto dialog_id : dialog_ids_) { + dependencies.add_dialog_and_dependencies(dialog_id); + } +} + +UserPrivacySettingRules UserPrivacySettingRules::get_user_privacy_setting_rules( + Td *td, telegram_api::object_ptr rules) { + td->contacts_manager_->on_get_users(std::move(rules->users_), "on get privacy rules"); + td->contacts_manager_->on_get_chats(std::move(rules->chats_), "on get privacy rules"); + return get_user_privacy_setting_rules(td, std::move(rules->rules_)); +} + +UserPrivacySettingRules UserPrivacySettingRules::get_user_privacy_setting_rules( + Td *td, vector> rules) { + UserPrivacySettingRules result; + for (auto &rule : rules) { + result.rules_.push_back(UserPrivacySettingRule(td, std::move(rule))); + } + if (!result.rules_.empty() && result.rules_.back().type_ == UserPrivacySettingRule::Type::RestrictAll) { + result.rules_.pop_back(); + } + return result; +} + +Result UserPrivacySettingRules::get_user_privacy_setting_rules( + Td *td, td_api::object_ptr rules) { + if (rules == nullptr) { + return Status::Error(400, "UserPrivacySettingRules must be non-empty"); + } + UserPrivacySettingRules result; + for (auto &rule : rules->rules_) { + if (rule == nullptr) { + return Status::Error(400, "UserPrivacySettingRule must be non-empty"); + } + result.rules_.emplace_back(td, *rule); + } + return result; +} + +Result UserPrivacySettingRules::get_user_privacy_setting_rules( + Td *td, td_api::object_ptr settings) { + if (settings == nullptr) { + return Status::Error(400, "StoryPrivacySettings must be non-empty"); + } + UserPrivacySettingRules result; + switch (settings->get_id()) { + case td_api::storyPrivacySettingsEveryone::ID: { + auto user_ids = std::move(static_cast(*settings).except_user_ids_); + if (!user_ids.empty()) { + result.rules_.emplace_back(td, td_api::userPrivacySettingRuleRestrictUsers(std::move(user_ids))); + } + result.rules_.emplace_back(td, td_api::userPrivacySettingRuleAllowAll()); + break; + } + case td_api::storyPrivacySettingsContacts::ID: { + auto user_ids = std::move(static_cast(*settings).except_user_ids_); + if (!user_ids.empty()) { + result.rules_.emplace_back(td, td_api::userPrivacySettingRuleRestrictUsers(std::move(user_ids))); + } + result.rules_.emplace_back(td, td_api::userPrivacySettingRuleAllowContacts()); + break; + } + case td_api::storyPrivacySettingsCloseFriends::ID: { + UserPrivacySettingRule rule; + rule.type_ = UserPrivacySettingRule::Type::AllowCloseFriends; + result.rules_.push_back(std::move(rule)); + break; + } + case td_api::storyPrivacySettingsSelectedUsers::ID: { + auto user_ids = std::move(static_cast(*settings).user_ids_); + result.rules_.emplace_back(td, td_api::userPrivacySettingRuleAllowUsers(std::move(user_ids))); + break; + } + default: + UNREACHABLE(); + } + return result; +} + +td_api::object_ptr UserPrivacySettingRules::get_user_privacy_setting_rules_object( + Td *td) const { + return make_tl_object( + transform(rules_, [td](const auto &rule) { return rule.get_user_privacy_setting_rule_object(td); })); +} + +td_api::object_ptr UserPrivacySettingRules::get_story_privacy_settings_object( + Td *td) const { + if (rules_.empty()) { + return nullptr; + } + if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowAll) { + return td_api::make_object(); + } + if (rules_.size() == 2u && rules_[0].type_ == UserPrivacySettingRule::Type::RestrictUsers && + rules_[1].type_ == UserPrivacySettingRule::Type::AllowAll) { + return td_api::make_object( + td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsEveryone")); + } + if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowContacts) { + return td_api::make_object(); + } + if (rules_.size() == 2u && rules_[0].type_ == UserPrivacySettingRule::Type::RestrictUsers && + rules_[1].type_ == UserPrivacySettingRule::Type::AllowContacts) { + return td_api::make_object( + td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsContacts")); + } + if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowCloseFriends) { + return td_api::make_object(); + } + if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowUsers) { + return td_api::make_object( + td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsSelectedUsers")); + } + return td_api::make_object(); +} + +vector> UserPrivacySettingRules::get_input_privacy_rules( + Td *td) const { + auto result = transform(rules_, [td](const auto &rule) { return rule.get_input_privacy_rule(td); }); + if (!result.empty() && result.back()->get_id() == telegram_api::inputPrivacyValueDisallowAll::ID) { + result.pop_back(); + } + return result; +} + +vector UserPrivacySettingRules::get_restricted_user_ids() const { + vector result; + for (auto &rule : rules_) { + combine(result, rule.get_restricted_user_ids()); + } + std::sort(result.begin(), result.end(), [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); }); + result.erase(std::unique(result.begin(), result.end()), result.end()); + return result; +} + +void UserPrivacySettingRules::add_dependencies(Dependencies &dependencies) const { + for (auto &rule : rules_) { + rule.add_dependencies(dependencies); + } +} + +} // namespace td diff --git a/td/telegram/UserPrivacySettingRule.h b/td/telegram/UserPrivacySettingRule.h new file mode 100644 index 000000000000..e18b09b249fc --- /dev/null +++ b/td/telegram/UserPrivacySettingRule.h @@ -0,0 +1,153 @@ +// +// 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/DialogId.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/tl_helpers.h" + +namespace td { + +class Dependencies; +class Td; + +class UserPrivacySettingRule { + public: + UserPrivacySettingRule() = default; + + UserPrivacySettingRule(Td *td, const td_api::UserPrivacySettingRule &rule); + + UserPrivacySettingRule(Td *td, const telegram_api::object_ptr &rule); + + td_api::object_ptr get_user_privacy_setting_rule_object(Td *td) const; + + telegram_api::object_ptr get_input_privacy_rule(Td *td) const; + + bool operator==(const UserPrivacySettingRule &other) const { + return type_ == other.type_ && user_ids_ == other.user_ids_ && dialog_ids_ == other.dialog_ids_; + } + + vector get_restricted_user_ids() const; + + void add_dependencies(Dependencies &dependencies) const; + + template + void store(StorerT &storer) const { + td::store(type_, storer); + if (type_ == Type::AllowUsers || type_ == Type::RestrictUsers) { + td::store(user_ids_, storer); + } + if (type_ == Type::AllowChatParticipants || type_ == Type::RestrictChatParticipants) { + td::store(dialog_ids_, storer); + } + } + + template + void parse(ParserT &parser) { + td::parse(type_, parser); + if (type_ == Type::AllowUsers || type_ == Type::RestrictUsers) { + td::parse(user_ids_, parser); + for (auto user_id : user_ids_) { + if (!user_id.is_valid()) { + parser.set_error("Failed to parse user identifiers"); + } + } + } else if (type_ == Type::AllowChatParticipants || type_ == Type::RestrictChatParticipants) { + td::parse(dialog_ids_, parser); + for (auto dialog_id : dialog_ids_) { + auto dialog_type = dialog_id.get_type(); + if (!dialog_id.is_valid() || (dialog_type != DialogType::Chat && dialog_type != DialogType::Channel)) { + parser.set_error("Failed to parse chat identifiers"); + } + } + } else if (type_ != Type::AllowContacts && type_ != Type::AllowCloseFriends && type_ != Type::AllowAll && + type_ != Type::RestrictContacts && type_ != Type::RestrictAll) { + parser.set_error("Invalid privacy rule type"); + } + } + + private: + enum class Type : int32 { + AllowContacts, + AllowCloseFriends, + AllowAll, + AllowUsers, + AllowChatParticipants, + RestrictContacts, + RestrictAll, + RestrictUsers, + RestrictChatParticipants + } type_ = Type::RestrictAll; + + friend class UserPrivacySettingRules; + + vector user_ids_; + vector dialog_ids_; + + vector> get_input_users(Td *td) const; + + vector get_input_chat_ids(Td *td) const; + + void set_dialog_ids(Td *td, const vector &chat_ids); + + void set_dialog_ids_from_server(Td *td, const vector &chat_ids); +}; + +class UserPrivacySettingRules { + public: + UserPrivacySettingRules() = default; + + static UserPrivacySettingRules get_user_privacy_setting_rules( + Td *td, telegram_api::object_ptr rules); + + static UserPrivacySettingRules get_user_privacy_setting_rules( + Td *td, vector> rules); + + static Result get_user_privacy_setting_rules( + Td *td, td_api::object_ptr rules); + + static Result get_user_privacy_setting_rules( + Td *td, td_api::object_ptr settings); + + td_api::object_ptr get_user_privacy_setting_rules_object(Td *td) const; + + td_api::object_ptr get_story_privacy_settings_object(Td *td) const; + + vector> get_input_privacy_rules(Td *td) const; + + bool operator==(const UserPrivacySettingRules &other) const { + return rules_ == other.rules_; + } + + bool operator!=(const UserPrivacySettingRules &other) const { + return !(rules_ == other.rules_); + } + + vector get_restricted_user_ids() const; + + void add_dependencies(Dependencies &dependencies) const; + + template + void store(StorerT &storer) const { + td::store(rules_, storer); + } + + template + void parse(ParserT &parser) { + td::parse(rules_, parser); + } + + private: + vector rules_; +}; + +} // namespace td diff --git a/td/telegram/Venue.cpp b/td/telegram/Venue.cpp index bd44546356f4..35595014638a 100644 --- a/td/telegram/Venue.cpp +++ b/td/telegram/Venue.cpp @@ -11,9 +11,9 @@ namespace td { -Venue::Venue(const tl_object_ptr &geo_point_ptr, string title, string address, string provider, - string id, string type) - : location_(geo_point_ptr) +Venue::Venue(Td *td, const tl_object_ptr &geo_point_ptr, string title, string address, + string provider, string id, string type) + : location_(td, geo_point_ptr) , title_(std::move(title)) , address_(std::move(address)) , provider_(std::move(provider)) @@ -76,6 +76,12 @@ tl_object_ptr Venue::get_input_bo flags, location_.get_input_geo_point(), title_, address_, provider_, id_, type_, std::move(reply_markup)); } +telegram_api::object_ptr Venue::get_input_media_area_venue( + telegram_api::object_ptr &&coordinates) const { + return telegram_api::make_object(std::move(coordinates), location_.get_fake_geo_point(), + title_, address_, provider_, id_, type_); +} + bool operator==(const Venue &lhs, const Venue &rhs) { return lhs.location_ == rhs.location_ && lhs.title_ == rhs.title_ && lhs.address_ == rhs.address_ && lhs.provider_ == rhs.provider_ && lhs.id_ == rhs.id_ && lhs.type_ == rhs.type_; diff --git a/td/telegram/Venue.h b/td/telegram/Venue.h index 5d5c2ff80ecc..24d358884c3c 100644 --- a/td/telegram/Venue.h +++ b/td/telegram/Venue.h @@ -19,6 +19,8 @@ namespace td { +class Td; + class Venue { Location location_; string title_; @@ -34,8 +36,8 @@ class Venue { public: Venue() = default; - Venue(const tl_object_ptr &geo_point_ptr, string title, string address, string provider, - string id, string type); + Venue(Td *td, const tl_object_ptr &geo_point_ptr, string title, string address, + string provider, string id, string type); Venue(Location location, string title, string address, string provider, string id, string type); @@ -43,6 +45,10 @@ class Venue { bool empty() const; + bool is_same(const string &provider, const string &id) const { + return provider_ == provider && id_ == id; + } + Location &location(); const Location &location() const; @@ -56,6 +62,9 @@ class Venue { tl_object_ptr get_input_bot_inline_message_media_venue( tl_object_ptr &&reply_markup) const; + telegram_api::object_ptr get_input_media_area_venue( + telegram_api::object_ptr &&coordinates) const; + template void store(StorerT &storer) const { using td::store; diff --git a/td/telegram/Version.h b/td/telegram/Version.h index 72aff6812b44..6919bde8aa46 100644 --- a/td/telegram/Version.h +++ b/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 158; +constexpr int32 MTPROTO_LAYER = 163; enum class Version : int32 { Initial, // 0 @@ -61,15 +61,16 @@ enum class Version : int32 { AddMessageMediaSpoiler, // 45 MakeParticipantFlags64Bit, AddDocumentFlags, + AddUserFlags2, Next }; enum class DbVersion : int32 { - DialogDbCreated = 3, - MessageDbMediaIndex, - MessageDb30MediaIndex, - MessageDbFts, - MessagesCallIndex, + CreateDialogDb = 3, + AddMessageDbMediaIndex, + AddMessageDb30MediaIndex, + AddMessageDbFts, + AddMessagesCallIndex, FixFileRemoteLocationKeyBug, AddNotificationsSupport, AddFolders, diff --git a/td/telegram/VideoNotesManager.cpp b/td/telegram/VideoNotesManager.cpp index 928c4785f608..1c377b92756d 100644 --- a/td/telegram/VideoNotesManager.cpp +++ b/td/telegram/VideoNotesManager.cpp @@ -350,8 +350,9 @@ tl_object_ptr VideoNotesManager::get_input_media( narrow_cast(td_->option_manager_->get_option_integer("suggested_video_note_length", 384)); attributes.push_back(make_tl_object( telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK, false /*ignored*/, false /*ignored*/, - video_note->duration, video_note->dimensions.width ? video_note->dimensions.width : suggested_video_note_length, - video_note->dimensions.height ? video_note->dimensions.height : suggested_video_note_length)); + false /*ignored*/, video_note->duration, + video_note->dimensions.width ? video_note->dimensions.width : suggested_video_note_length, + video_note->dimensions.height ? video_note->dimensions.height : suggested_video_note_length, 0)); int32 flags = telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; if (input_thumbnail != nullptr) { flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK; diff --git a/td/telegram/VideosManager.cpp b/td/telegram/VideosManager.cpp index 6fc23218af19..13e4cfac9131 100644 --- a/td/telegram/VideosManager.cpp +++ b/td/telegram/VideosManager.cpp @@ -36,7 +36,7 @@ int32 VideosManager::get_video_duration(FileId file_id) const { return video->duration; } -tl_object_ptr VideosManager::get_video_object(FileId file_id) const { +td_api::object_ptr VideosManager::get_video_object(FileId file_id) const { if (!file_id.is_valid()) { return nullptr; } @@ -46,10 +46,26 @@ tl_object_ptr VideosManager::get_video_object(FileId file_id) con auto thumbnail = video->animated_thumbnail.file_id.is_valid() ? get_thumbnail_object(td_->file_manager_.get(), video->animated_thumbnail, PhotoFormat::Mpeg4) : get_thumbnail_object(td_->file_manager_.get(), video->thumbnail, PhotoFormat::Jpeg); - return make_tl_object(video->duration, video->dimensions.width, video->dimensions.height, - video->file_name, video->mime_type, video->has_stickers, - video->supports_streaming, get_minithumbnail_object(video->minithumbnail), - std::move(thumbnail), td_->file_manager_->get_file_object(file_id)); + return td_api::make_object(video->duration, video->dimensions.width, video->dimensions.height, + video->file_name, video->mime_type, video->has_stickers, + video->supports_streaming, get_minithumbnail_object(video->minithumbnail), + std::move(thumbnail), td_->file_manager_->get_file_object(file_id)); +} + +td_api::object_ptr VideosManager::get_story_video_object(FileId file_id) const { + if (!file_id.is_valid()) { + return nullptr; + } + + auto video = get_video(file_id); + CHECK(video != nullptr); + auto thumbnail = video->animated_thumbnail.file_id.is_valid() + ? get_thumbnail_object(td_->file_manager_.get(), video->animated_thumbnail, PhotoFormat::Mpeg4) + : get_thumbnail_object(td_->file_manager_.get(), video->thumbnail, PhotoFormat::Jpeg); + return td_api::make_object( + video->precise_duration, video->dimensions.width, video->dimensions.height, video->has_stickers, + video->is_animation, get_minithumbnail_object(video->minithumbnail), std::move(thumbnail), + video->preload_prefix_size, td_->file_manager_->get_file_object(file_id)); } FileId VideosManager::on_get_video(unique_ptr