From 6c109e52fe243bd7723bc350763e7fc2f9cfbb11 Mon Sep 17 00:00:00 2001 From: Kristofer Berggren Date: Sun, 25 Aug 2024 17:44:48 +0800 Subject: [PATCH] update tdlib to 1.8.35 from tdlib/td@8d08b34 --- lib/common/src/version.h | 2 +- lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake | 4 +- lib/tgchat/ext/td/CMakeLists.txt | 39 +- lib/tgchat/ext/td/README.md | 2 +- lib/tgchat/ext/td/SplitSource.php | 8 + lib/tgchat/ext/td/benchmark/CMakeLists.txt | 2 +- lib/tgchat/ext/td/example/android/Dockerfile | 2 +- lib/tgchat/ext/td/example/cpp/CMakeLists.txt | 2 +- .../ext/td/example/uwp/Telegram.Td.UWP.nuspec | 2 +- .../ext/td/example/uwp/extension.vsixmanifest | 2 +- .../ext/td/td/generate/scheme/td_api.tl | 252 ++++- .../ext/td/td/generate/scheme/telegram_api.tl | 46 +- lib/tgchat/ext/td/td/telegram/AuthManager.cpp | 12 +- .../ext/td/td/telegram/BotInfoManager.cpp | 2 +- .../td/telegram/BusinessConnectionManager.cpp | 80 +- .../td/telegram/BusinessConnectionManager.h | 4 + .../ext/td/td/telegram/BusinessManager.cpp | 10 +- lib/tgchat/ext/td/td/telegram/ChatManager.cpp | 150 ++- lib/tgchat/ext/td/td/telegram/ChatManager.h | 6 +- .../ext/td/td/telegram/ChatReactions.cpp | 30 +- lib/tgchat/ext/td/td/telegram/ChatReactions.h | 10 +- .../ext/td/td/telegram/ChatReactions.hpp | 2 + .../ext/td/td/telegram/ConfigManager.cpp | 31 +- lib/tgchat/ext/td/td/telegram/ConfigManager.h | 2 +- .../td/td/telegram/ConnectionStateManager.cpp | 61 ++ .../td/td/telegram/ConnectionStateManager.h | 39 + .../ext/td/td/telegram/DialogEventLog.cpp | 22 +- .../ext/td/td/telegram/DialogInviteLink.cpp | 32 +- .../ext/td/td/telegram/DialogInviteLink.h | 7 +- .../ext/td/td/telegram/DialogInviteLink.hpp | 23 +- .../td/telegram/DialogInviteLinkManager.cpp | 110 +- .../td/td/telegram/DialogInviteLinkManager.h | 15 +- .../ext/td/td/telegram/DialogParticipant.cpp | 31 +- .../ext/td/td/telegram/DialogParticipant.h | 6 +- .../td/telegram/DialogParticipantManager.cpp | 14 +- .../ext/td/td/telegram/DocumentsManager.cpp | 6 + .../ext/td/td/telegram/DocumentsManager.h | 3 + lib/tgchat/ext/td/td/telegram/Global.h | 18 + lib/tgchat/ext/td/td/telegram/LinkManager.cpp | 54 + lib/tgchat/ext/td/td/telegram/LinkManager.h | 3 + lib/tgchat/ext/td/td/telegram/MediaArea.cpp | 4 +- .../ext/td/td/telegram/MessageContent.cpp | 25 +- .../ext/td/td/telegram/MessageContent.h | 8 + .../td/td/telegram/MessageExtendedMedia.cpp | 16 + .../ext/td/td/telegram/MessageExtendedMedia.h | 4 + .../ext/td/td/telegram/MessageReaction.cpp | 278 ++++- .../ext/td/td/telegram/MessageReaction.h | 28 +- .../ext/td/td/telegram/MessageReaction.hpp | 12 + .../ext/td/td/telegram/MessageReactor.cpp | 102 ++ .../ext/td/td/telegram/MessageReactor.h | 82 ++ .../ext/td/td/telegram/MessageReactor.hpp | 46 + .../ext/td/td/telegram/MessagesManager.cpp | 598 ++++++++--- .../ext/td/td/telegram/MessagesManager.h | 53 +- .../td/td/telegram/NotificationManager.cpp | 3 +- .../ext/td/td/telegram/OnlineManager.cpp | 164 +++ lib/tgchat/ext/td/td/telegram/OnlineManager.h | 60 ++ .../ext/td/td/telegram/OptionManager.cpp | 13 +- lib/tgchat/ext/td/td/telegram/Payments.cpp | 32 +- lib/tgchat/ext/td/td/telegram/PollManager.cpp | 3 +- lib/tgchat/ext/td/td/telegram/PollManager.hpp | 1 + lib/tgchat/ext/td/td/telegram/Premium.cpp | 1 + .../ext/td/td/telegram/PromoDataManager.cpp | 148 +++ .../ext/td/td/telegram/PromoDataManager.h | 47 + .../ext/td/td/telegram/QuickReplyManager.cpp | 2 +- .../ext/td/td/telegram/ReactionManager.cpp | 17 +- .../ext/td/td/telegram/ReactionType.cpp | 54 +- lib/tgchat/ext/td/td/telegram/ReactionType.h | 6 +- .../ext/td/td/telegram/RestrictionReason.cpp | 33 +- .../ext/td/td/telegram/RestrictionReason.h | 9 + .../td/telegram/SponsoredMessageManager.cpp | 23 +- lib/tgchat/ext/td/td/telegram/StarManager.cpp | 260 ++++- lib/tgchat/ext/td/td/telegram/StarManager.h | 23 +- .../ext/td/td/telegram/StarSubscription.cpp | 41 + .../ext/td/td/telegram/StarSubscription.h | 47 + .../td/telegram/StarSubscriptionPricing.cpp | 58 + .../td/td/telegram/StarSubscriptionPricing.h | 55 + .../td/telegram/StarSubscriptionPricing.hpp | 32 + .../ext/td/td/telegram/StoryContent.cpp | 2 +- lib/tgchat/ext/td/td/telegram/StoryContent.h | 2 +- .../td/td/telegram/StoryInteractionInfo.cpp | 6 +- .../ext/td/td/telegram/StoryManager.cpp | 24 +- .../ext/td/td/telegram/SuggestedAction.cpp | 12 +- .../ext/td/td/telegram/SuggestedAction.h | 3 +- .../td/td/telegram/SynchronousRequests.cpp | 400 +++++++ .../ext/td/td/telegram/SynchronousRequests.h | 89 ++ lib/tgchat/ext/td/td/telegram/Td.cpp | 988 +++--------------- lib/tgchat/ext/td/td/telegram/Td.h | 124 +-- lib/tgchat/ext/td/td/telegram/TdDb.cpp | 7 + lib/tgchat/ext/td/td/telegram/TdDb.h | 2 + .../ext/td/td/telegram/TermsOfService.cpp | 91 +- .../ext/td/td/telegram/TermsOfService.h | 40 +- .../ext/td/td/telegram/TermsOfService.hpp | 40 + .../td/td/telegram/TermsOfServiceManager.cpp | 201 ++++ .../td/td/telegram/TermsOfServiceManager.h | 59 ++ .../ext/td/td/telegram/UpdatesManager.cpp | 16 +- lib/tgchat/ext/td/td/telegram/UserManager.cpp | 76 +- lib/tgchat/ext/td/td/telegram/UserManager.h | 11 +- lib/tgchat/ext/td/td/telegram/Version.h | 2 +- .../ext/td/td/telegram/WebPageBlock.cpp | 15 + lib/tgchat/ext/td/td/telegram/WebPageBlock.h | 2 + .../ext/td/td/telegram/WebPagesManager.cpp | 164 ++- .../ext/td/td/telegram/WebPagesManager.h | 7 + lib/tgchat/ext/td/td/telegram/cli.cpp | 130 ++- .../td/td/telegram/net/ConnectionCreator.cpp | 146 ++- .../td/td/telegram/net/ConnectionCreator.h | 60 +- lib/tgchat/ext/td/tdactor/CMakeLists.txt | 5 - lib/tgchat/ext/td/tddb/CMakeLists.txt | 1 - lib/tgchat/ext/td/tdnet/CMakeLists.txt | 4 - lib/tgchat/ext/td/tdtl/CMakeLists.txt | 3 - lib/tgchat/ext/td/tdutils/CMakeLists.txt | 2 - lib/tgchat/ext/td/test/CMakeLists.txt | 2 - lib/tgchat/ext/td/test/link.cpp | 10 + lib/tgchat/src/tgchat.cpp | 2 +- src/nchat.1 | 2 +- 114 files changed, 4490 insertions(+), 1794 deletions(-) create mode 100644 lib/tgchat/ext/td/td/telegram/ConnectionStateManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/ConnectionStateManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/MessageReactor.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/MessageReactor.h create mode 100644 lib/tgchat/ext/td/td/telegram/MessageReactor.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/OnlineManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/OnlineManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/PromoDataManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/PromoDataManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/StarSubscription.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/StarSubscription.h create mode 100644 lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.h create mode 100644 lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/SynchronousRequests.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/SynchronousRequests.h create mode 100644 lib/tgchat/ext/td/td/telegram/TermsOfService.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.h diff --git a/lib/common/src/version.h b/lib/common/src/version.h index 5f0dc6d7..04188acd 100644 --- a/lib/common/src/version.h +++ b/lib/common/src/version.h @@ -7,4 +7,4 @@ #pragma once -#define NCHAT_VERSION "5.2.1" +#define NCHAT_VERSION "5.2.2" diff --git a/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake b/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake index e469aa6b..747d0aaa 100644 --- a/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake +++ b/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake @@ -1,4 +1,4 @@ -# - Configures C++14 compiler, setting TDLib-specific compilation options. +# Configures C++14 compiler, setting TDLib-specific compilation options. function(td_set_up_compiler) set(CMAKE_EXPORT_COMPILE_COMMANDS 1 PARENT_SCOPE) @@ -127,6 +127,8 @@ function(td_set_up_compiler) add_cxx_compiler_flag("-Wodr") add_cxx_compiler_flag("-flto-odr-type-merging") add_cxx_compiler_flag("-Wno-psabi") + add_cxx_compiler_flag("-Wunused-member-function") + add_cxx_compiler_flag("-Wunused-private-field") # add_cxx_compiler_flag("-Werror") diff --git a/lib/tgchat/ext/td/CMakeLists.txt b/lib/tgchat/ext/td/CMakeLists.txt index 87be0463..d3918fac 100644 --- a/lib/tgchat/ext/td/CMakeLists.txt +++ b/lib/tgchat/ext/td/CMakeLists.txt @@ -6,7 +6,7 @@ if (POLICY CMP0065) cmake_policy(SET CMP0065 NEW) endif() -project(TDLib VERSION 1.8.34 LANGUAGES CXX C) +project(TDLib VERSION 1.8.35 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -72,7 +72,7 @@ if (POLICY CMP0069) endif() endif() -# Configure CCache if available +# Configure Ccache if available find_program(CCACHE_FOUND ccache) #set(CCACHE_FOUND 0) if (CCACHE_FOUND) @@ -221,7 +221,7 @@ if (HAS_PARENT) endif() -#SOURCE SETS +# SOURCE SETS set_source_files_properties(${TL_TD_API_AUTO_SOURCE} PROPERTIES GENERATED TRUE) if (TD_ENABLE_JNI OR ANDROID) @@ -361,6 +361,7 @@ set(TDLIB_SOURCE_PART1 td/telegram/CommonDialogManager.cpp td/telegram/ConfigManager.cpp td/telegram/ConnectionState.cpp + td/telegram/ConnectionStateManager.cpp td/telegram/Contact.cpp td/telegram/CountryInfoManager.cpp td/telegram/DelayDispatcher.cpp @@ -460,6 +461,7 @@ set(TDLIB_SOURCE_PART1 td/telegram/MessageOrigin.cpp td/telegram/MessageQuote.cpp td/telegram/MessageReaction.cpp + td/telegram/MessageReactor.cpp td/telegram/MessageReplyHeader.cpp td/telegram/MessageReplyInfo.cpp td/telegram/MessageSearchFilter.cpp @@ -504,6 +506,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/NotificationSettingsScope.cpp td/telegram/NotificationSound.cpp td/telegram/NotificationType.cpp + td/telegram/OnlineManager.cpp td/telegram/OptionManager.cpp td/telegram/OrderedMessage.cpp td/telegram/OrderInfo.cpp @@ -519,6 +522,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/Premium.cpp td/telegram/PremiumGiftOption.cpp td/telegram/PrivacyManager.cpp + td/telegram/PromoDataManager.cpp td/telegram/QueryCombiner.cpp td/telegram/QueryMerger.cpp td/telegram/QuickReplyManager.cpp @@ -550,6 +554,8 @@ set(TDLIB_SOURCE_PART2 td/telegram/SpecialStickerSetType.cpp td/telegram/SponsoredMessageManager.cpp td/telegram/StarManager.cpp + td/telegram/StarSubscription.cpp + td/telegram/StarSubscriptionPricing.cpp td/telegram/StateManager.cpp td/telegram/StatisticsManager.cpp td/telegram/StickerFormat.cpp @@ -570,9 +576,11 @@ set(TDLIB_SOURCE_PART2 td/telegram/StoryViewer.cpp td/telegram/SuggestedAction.cpp td/telegram/Support.cpp + td/telegram/SynchronousRequests.cpp td/telegram/Td.cpp td/telegram/TdDb.cpp td/telegram/TermsOfService.cpp + td/telegram/TermsOfServiceManager.cpp td/telegram/ThemeManager.cpp td/telegram/ThemeSettings.cpp td/telegram/TimeZoneManager.cpp @@ -647,6 +655,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/CommonDialogManager.h td/telegram/ConfigManager.h td/telegram/ConnectionState.h + td/telegram/ConnectionStateManager.h td/telegram/Contact.h td/telegram/CountryInfoManager.h td/telegram/CustomEmojiId.h @@ -771,6 +780,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/MessageOrigin.h td/telegram/MessageQuote.h td/telegram/MessageReaction.h + td/telegram/MessageReactor.h td/telegram/MessageReplyHeader.h td/telegram/MessageReplyInfo.h td/telegram/MessageSearchFilter.h @@ -829,6 +839,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/NotificationSound.h td/telegram/NotificationSoundType.h td/telegram/NotificationType.h + td/telegram/OnlineManager.h td/telegram/OptionManager.h td/telegram/OrderedMessage.h td/telegram/OrderInfo.h @@ -846,6 +857,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/Premium.h td/telegram/PremiumGiftOption.h td/telegram/PrivacyManager.h + td/telegram/PromoDataManager.h td/telegram/PtsManager.h td/telegram/PublicDialogType.h td/telegram/QueryCombiner.h @@ -888,6 +900,8 @@ set(TDLIB_SOURCE_PART2 td/telegram/SpecialStickerSetType.h td/telegram/SponsoredMessageManager.h td/telegram/StarManager.h + td/telegram/StarSubscription.h + td/telegram/StarSubscriptionPricing.h td/telegram/StateManager.h td/telegram/StatisticsManager.h td/telegram/StickerFormat.h @@ -912,10 +926,12 @@ set(TDLIB_SOURCE_PART2 td/telegram/StoryViewer.h td/telegram/SuggestedAction.h td/telegram/Support.h + td/telegram/SynchronousRequests.h td/telegram/Td.h td/telegram/TdCallback.h td/telegram/TdDb.h td/telegram/TermsOfService.h + td/telegram/TermsOfServiceManager.h td/telegram/ThemeManager.h td/telegram/ThemeSettings.h td/telegram/TimeZoneManager.h @@ -988,6 +1004,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/MessageOrigin.hpp td/telegram/MessageQuote.hpp td/telegram/MessageReaction.hpp + td/telegram/MessageReactor.hpp td/telegram/MessageReplyInfo.hpp td/telegram/MinChannel.hpp td/telegram/NotificationGroupInfo.hpp @@ -1009,6 +1026,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/SecureValue.hpp td/telegram/SendCodeHelper.hpp td/telegram/SharedDialog.hpp + td/telegram/StarSubscriptionPricing.hpp td/telegram/StickerMaskPosition.hpp td/telegram/StickerPhotoSize.hpp td/telegram/StickersManager.hpp @@ -1016,6 +1034,7 @@ set(TDLIB_SOURCE_PART2 td/telegram/StoryInteractionInfo.hpp td/telegram/StoryStealthMode.hpp td/telegram/SuggestedAction.hpp + td/telegram/TermsOfService.hpp td/telegram/ThemeSettings.hpp td/telegram/TranscriptionInfo.hpp td/telegram/VideoNotesManager.hpp @@ -1046,7 +1065,7 @@ set(MEMPROF_STAT_SOURCE memprof/memprof_stat.h ) -#LIBRARIES +# LIBRARIES # memprof - simple library for memory usage profiling add_library(memprof STATIC ${MEMPROF_SOURCE}) @@ -1100,7 +1119,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_dependencies(tdmtproto tl_generate_mtproto) endif() -# tdcore - mostly internal TDLib interface. One should use tdactor for interactions with it. +# tdcore - internal TDLib interface if (MSVC AND TD_ENABLE_LTO) add_library(tdcore_part1 STATIC ${TDLIB_SOURCE_PART1}) target_include_directories(tdcore_part1 PUBLIC $ $) @@ -1163,7 +1182,7 @@ if (TD_ENABLE_DOTNET) endif() endif() -# tdc - TDLib interface in pure c. +# tdc - TDLib interface in pure C add_library(tdc STATIC EXCLUDE_FROM_ALL ${TL_C_SCHEME_SOURCE} td/telegram/td_c_client.cpp td/telegram/td_c_client.h) target_include_directories(tdc PUBLIC $ @@ -1207,6 +1226,7 @@ target_include_directories(tdjson_static PUBLIC $ $) +# EXECUTABLES if (EMSCRIPTEN) set(TD_EMSCRIPTEN_SRC td/telegram/td_emscripten.cpp) add_executable(${TD_EMSCRIPTEN} ${TD_EMSCRIPTEN_SRC}) @@ -1214,7 +1234,6 @@ if (EMSCRIPTEN) target_link_libraries(${TD_EMSCRIPTEN} PRIVATE tdjson_static tdactor) endif() -#EXECUTABLES if (NOT CMAKE_CROSSCOMPILING) add_executable(tg_cli td/telegram/cli.cpp ${TL_TD_JSON_SOURCE}) @@ -1245,7 +1264,7 @@ if (NOT CMAKE_CROSSCOMPILING) add_dependencies(tg_cli tl_generate_json) endif() -#Exported libraries +# Exported libraries add_library(TdStatic INTERFACE) target_link_libraries(TdStatic INTERFACE tdclient) @@ -1338,9 +1357,7 @@ install(FILES "TdConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/TdConfigVersion.cmak ) # Add SOVERSION to shared libraries -set_property(TARGET tdclient PROPERTY SOVERSION "${TDLib_VERSION}") -set_property(TARGET tdapi PROPERTY SOVERSION "${TDLib_VERSION}") -set_property(TARGET tdjson PROPERTY SOVERSION "${TDLib_VERSION}") +set_property(TARGET tdapi tdclient tdjson PROPERTY SOVERSION "${TDLib_VERSION}") # nchat additions start add_library(tdclientshared SHARED td/telegram/Client.cpp td/telegram/Client.h td/telegram/Log.cpp td/telegram/Log.h) diff --git a/lib/tgchat/ext/td/README.md b/lib/tgchat/ext/td/README.md index b17eae87..ae6e1922 100644 --- a/lib/tgchat/ext/td/README.md +++ b/lib/tgchat/ext/td/README.md @@ -103,7 +103,7 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic) Or you could install `TDLib` and then reference it in your CMakeLists.txt like this: ``` -find_package(Td 1.8.34 REQUIRED) +find_package(Td 1.8.35 REQUIRED) target_link_libraries(YourTarget PRIVATE Td::TdStatic) ``` See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt). diff --git a/lib/tgchat/ext/td/SplitSource.php b/lib/tgchat/ext/td/SplitSource.php index 90ecf98d..f3ac19b1 100644 --- a/lib/tgchat/ext/td/SplitSource.php +++ b/lib/tgchat/ext/td/SplitSource.php @@ -329,6 +329,7 @@ function ($matches) use ($needed_std_headers) { 'ChatId' => 'ChatId', 'chat_manager[_(-](?![.]get[(][)])|ChatManager([^ ;.]| [^*])' => 'ChatManager', 'common_dialog_manager[_(-](?![.]get[(][)])|CommonDialogManager' => 'CommonDialogManager', + 'connection_state_manager[_(-](?![.]get[(][)])|ConnectionStateManager' => 'ConnectionStateManager', 'country_info_manager[_(-](?![.]get[(][)])|CountryInfoManager' => 'CountryInfoManager', 'CustomEmojiId' => 'CustomEmojiId', 'device_token_manager[_(-](?![.]get[(][)])|DeviceTokenManager' => 'DeviceTokenManager', @@ -377,6 +378,7 @@ function ($matches) use ($needed_std_headers) { 'MessageLinkInfo' => 'MessageLinkInfo', 'MessageQuote' => 'MessageQuote', 'MessageReaction|UnreadMessageReaction|[a-z_]*message[a-z_]*reaction' => 'MessageReaction', + 'MessageReactor' => 'MessageReactor', 'MessageSearchOffset' => 'MessageSearchOffset', '[a-z_]*_message_sender' => 'MessageSender', 'messages_manager[_(-](?![.]get[(][)])|MessagesManager' => 'MessagesManager', @@ -385,6 +387,7 @@ function ($matches) use ($needed_std_headers) { 'MissingInvitee' => 'MissingInvitee', 'notification_manager[_(-](?![.]get[(][)])|NotificationManager|notifications[)]' => 'NotificationManager', 'notification_settings_manager[_(-](?![.]get[(][)])|NotificationSettingsManager' => 'NotificationSettingsManager', + 'online_manager[_(-](?![.]get[(][)])|OnlineManager' => 'OnlineManager', 'option_manager[_(-](?![.]get[(][)])|OptionManager' => 'OptionManager', 'password_manager[_(-](?![.]get[(][)])|PasswordManager' => 'PasswordManager', 'people_nearby_manager[_(-](?![.]get[(][)])|PeopleNearbyManager' => 'PeopleNearbyManager', @@ -392,6 +395,7 @@ function ($matches) use ($needed_std_headers) { 'PhotoSizeSource' => 'PhotoSizeSource', 'poll_manager[_(-](?![.]get[(][)])|PollManager' => 'PollManager', 'privacy_manager[_(-](?![.]get[(][)])|PrivacyManager' => 'PrivacyManager', + 'promo_data_manager[_(-](?![.]get[(][)])|PromoDataManager' => 'PromoDataManager', 'PublicDialogType|get_public_dialog_type' => 'PublicDialogType', 'quick_reply_manager[_(-](?![.]get[(][)])|QuickReplyManager' => 'QuickReplyManager', 'ReactionListType|[a-z_]*_reaction_list_type' => 'ReactionListType', @@ -409,6 +413,8 @@ function ($matches) use ($needed_std_headers) { 'SharedDialog' => 'SharedDialog', 'sponsored_message_manager[_(-](?![.]get[(][)])|SponsoredMessageManager' => 'SponsoredMessageManager', 'star_manager[_(-](?![.]get[(][)])|StarManager' => 'StarManager', + 'StarSubscription[^P]' => 'StarSubscription', + 'StarSubscriptionPricing' => 'StarSubscriptionPricing', 'state_manager[_(-](?![.]get[(][)])|StateManager' => 'StateManager', 'statistics_manager[_(-](?![.]get[(][)])|StatisticsManager' => 'StatisticsManager', 'StickerSetId' => 'StickerSetId', @@ -418,9 +424,11 @@ function ($matches) use ($needed_std_headers) { 'StoryListId' => 'StoryListId', 'story_manager[_(-](?![.]get[(][)])|StoryManager' => 'StoryManager', 'SuggestedAction|[a-z_]*_suggested_action' => 'SuggestedAction', + 'SynchronousRequests' => 'SynchronousRequests', 'td_api' => 'td_api', 'td_db[(][)]|TdDb[^A-Za-z]' => 'TdDb', 'telegram_api' => 'telegram_api', + 'terms_of_service_manager[_(-](?![.]get[(][)])|TermsOfServiceManager' => 'TermsOfServiceManager', 'theme_manager[_(-](?![.]get[(][)])|ThemeManager' => 'ThemeManager', 'ThemeSettings' => 'ThemeSettings', 'time_zone_manager[_(-](?![.]get[(][)])|TimeZoneManager' => 'TimeZoneManager', diff --git a/lib/tgchat/ext/td/benchmark/CMakeLists.txt b/lib/tgchat/ext/td/benchmark/CMakeLists.txt index 07654b97..cdf359e9 100644 --- a/lib/tgchat/ext/td/benchmark/CMakeLists.txt +++ b/lib/tgchat/ext/td/benchmark/CMakeLists.txt @@ -7,7 +7,7 @@ if (NOT OPENSSL_FOUND) find_package(ZLIB REQUIRED) endif() -#TODO: all benchmarks in one file +# TODO: all benchmarks in one file add_executable(bench_crypto bench_crypto.cpp) target_link_libraries(bench_crypto PRIVATE tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES}) if (WIN32) diff --git a/lib/tgchat/ext/td/example/android/Dockerfile b/lib/tgchat/ext/td/example/android/Dockerfile index b4a8c08d..0f00a668 100644 --- a/lib/tgchat/ext/td/example/android/Dockerfile +++ b/lib/tgchat/ext/td/example/android/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 ubuntu:24.04 as build +FROM --platform=linux/amd64 ubuntu:24.04 AS build RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq default-jdk g++ git gperf make perl php-cli unzip wget && rm -rf /var/lib/apt/lists/* diff --git a/lib/tgchat/ext/td/example/cpp/CMakeLists.txt b/lib/tgchat/ext/td/example/cpp/CMakeLists.txt index 473d76d0..62648d80 100644 --- a/lib/tgchat/ext/td/example/cpp/CMakeLists.txt +++ b/lib/tgchat/ext/td/example/cpp/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.4 FATAL_ERROR) project(TdExample VERSION 1.0 LANGUAGES CXX) -find_package(Td 1.8.34 REQUIRED) +find_package(Td 1.8.35 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec b/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec index 915c8b3e..84e08b4a 100644 --- a/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec +++ b/lib/tgchat/ext/td/example/uwp/Telegram.Td.UWP.nuspec @@ -2,7 +2,7 @@ Telegram.Td.UWP - 1.8.34 + 1.8.35 TDLib for Universal Windows Platform Telegram Telegram diff --git a/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest b/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest index 98c5c5d9..094406a1 100644 --- a/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest +++ b/lib/tgchat/ext/td/example/uwp/extension.vsixmanifest @@ -1,6 +1,6 @@ - + TDLib for Universal Windows Platform TDLib is a library for building Telegram clients https://core.telegram.org/tdlib diff --git a/lib/tgchat/ext/td/td/generate/scheme/td_api.tl b/lib/tgchat/ext/td/td/generate/scheme/td_api.tl index bffa48a2..cb798a2d 100644 --- a/lib/tgchat/ext/td/td/generate/scheme/td_api.tl +++ b/lib/tgchat/ext/td/td/generate/scheme/td_api.tl @@ -798,6 +798,30 @@ chatPermissions can_send_basic_messages:Bool can_send_audios:Bool can_send_docum chatAdministratorRights can_manage_chat:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_manage_topics:Bool can_promote_members:Bool can_manage_video_chats:Bool can_post_stories:Bool can_edit_stories:Bool can_delete_stories:Bool is_anonymous:Bool = ChatAdministratorRights; +//@description Describes subscription plan paid in Telegram Stars +//@period The number of seconds between consecutive Telegram Star debiting +//@star_count The amount of Telegram Stars that must be paid for each period +starSubscriptionPricing period:int32 star_count:int53 = StarSubscriptionPricing; + +//@description Contains information about subscription to a channel chat paid in Telegram Stars +//@id Unique identifier of the subscription +//@chat_id Identifier of the channel chat that is subscribed +//@expiration_date Point in time (Unix timestamp) when the subscription will expire or expired +//@can_reuse True, if the subscription is active and the user can use the method reuseStarSubscription to join the subscribed chat again +//@is_canceled True, if the subscription was canceled +//@is_expiring True, if the subscription expires soon and there are no enough Telegram Stars on the user's balance to extend it +//@invite_link The invite link that can be used to renew the subscription if it has been expired; may be empty, if the link isn't available anymore +//@pricing The subscription plan +starSubscription id:string chat_id:int53 expiration_date:int32 can_reuse:Bool is_canceled:Bool is_expiring:Bool invite_link:string pricing:starSubscriptionPricing = StarSubscription; + +//@description Represents a list of Telegram Star subscriptions +//@star_count The amount of owned Telegram Stars +//@subscriptions List of subbscriptions for Telegram Stars +//@required_star_count The number of Telegram Stars required to buy to extend subscriptions expiring soon +//@next_offset The offset for the next request. If empty, then there are no more results +starSubscriptions star_count:int53 subscriptions:vector required_star_count:int53 next_offset:string = StarSubscriptions; + + //@description Contains information about a product that can be paid with invoice //@title Product title //@param_description Product description @@ -864,6 +888,33 @@ starTransactionDirectionIncoming = StarTransactionDirection; starTransactionDirectionOutgoing = StarTransactionDirection; +//@class BotTransactionPurpose @description Describes purpose of a transaction with a bot + +//@description Paid media were bought @media The bought media if the trancastion wasn't refunded +botTransactionPurposePaidMedia media:vector = BotTransactionPurpose; + +//@description User bought a product from the bot +//@product_info Information about the bought product; may be null if not applicable +//@invoice_payload Invoice payload; for bots only +botTransactionPurposeInvoicePayment product_info:productInfo invoice_payload:bytes = BotTransactionPurpose; + + +//@class ChannelTransactionPurpose @description Describes purpose of a transaction with a channel + +//@description Paid media were bought +//@message_id Identifier of the corresponding message with paid media; can be an identifier of a deleted message +//@media The bought media if the trancastion wasn't refunded +channelTransactionPurposePaidMedia message_id:int53 media:vector = ChannelTransactionPurpose; + +//@description User joined the channel and subscribed to regular payments in Telegram Stars +//@period The number of seconds between consecutive Telegram Star debiting +channelTransactionPurposeJoin period:int32 = ChannelTransactionPurpose; + +//@description User paid for a reaction +//@message_id Identifier of the reacted message; can be an identifier of a deleted message +channelTransactionPurposeReaction message_id:int53 = ChannelTransactionPurpose; + + //@class StarTransactionPartner @description Describes source or recipient of a transaction with Telegram Stars //@description The transaction is a transaction with Telegram through a bot @@ -881,17 +932,14 @@ starTransactionPartnerFragment withdrawal_state:RevenueWithdrawalState = StarTra //@description The transaction is a transaction with Telegram Ad platform starTransactionPartnerTelegramAds = StarTransactionPartner; -//@description The transaction is a transaction with a bot -//@user_id Identifier of the bot for the user, or the user for the bot -//@product_info Information about the bought product; may be null if not applicable -//@invoice_payload Invoice payload; for bots only -starTransactionPartnerBot user_id:int53 product_info:productInfo invoice_payload:bytes = StarTransactionPartner; +//@description The transaction is a transaction with a bot @user_id Identifier of the bot @purpose Purpose of the transaction +starTransactionPartnerBot user_id:int53 purpose:BotTransactionPurpose = StarTransactionPartner; -//@description The transaction is a transaction with a channel chat -//@chat_id Identifier of the chat -//@paid_media_message_id Identifier of the corresponding message with paid media; can be an identifier of a deleted message -//@media Information about the bought media -starTransactionPartnerChannel chat_id:int53 paid_media_message_id:int53 media:vector = StarTransactionPartner; +//@description The transaction is a transaction with a business account @user_id Identifier of the business account user @media The bought media if the trancastion wasn't refunded +starTransactionPartnerBusiness user_id:int53 media:vector = StarTransactionPartner; + +//@description The transaction is a transaction with a channel chat @chat_id Identifier of the chat @purpose Purpose of the transaction +starTransactionPartnerChannel chat_id:int53 purpose:ChannelTransactionPurpose = StarTransactionPartner; //@description The transaction is a gift of Telegram Stars from another user //@user_id Identifier of the user; 0 if the gift was anonymous @@ -1031,6 +1079,7 @@ user id:int53 first_name:string last_name:string usernames:usernames phone_numbe //@animation Animation shown in the chat with the bot if the chat is empty; may be null //@menu_button Information about a button to show instead of the bot commands menu button; may be null if ordinary bot commands menu must be shown //@commands List of the bot commands +//@privacy_policy_url The HTTP link to the privacy policy of the bot. If empty, then /privacy command must be used if supported by the bot. If the command isn't supported, then https://telegram.org/privacy-tpa must be opened //@default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null //@default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null //@has_media_previews True, if the bot has media previews @@ -1038,7 +1087,7 @@ user id:int53 first_name:string last_name:string usernames:usernames phone_numbe //@edit_description_link The internal link, which can be used to edit bot description; may be null //@edit_description_media_link The internal link, which can be used to edit the photo or animation shown in the chat with the bot if the chat is empty; may be null //@edit_settings_link The internal link, which can be used to edit bot settings; may be null -botInfo short_description:string description:string photo:photo animation:animation menu_button:botMenuButton commands:vector default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights has_media_previews:Bool edit_commands_link:InternalLinkType edit_description_link:InternalLinkType edit_description_media_link:InternalLinkType edit_settings_link:InternalLinkType = BotInfo; +botInfo short_description:string description:string photo:photo animation:animation menu_button:botMenuButton commands:vector privacy_policy_url:string default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights has_media_previews:Bool edit_commands_link:InternalLinkType edit_description_link:InternalLinkType edit_description_media_link:InternalLinkType edit_settings_link:InternalLinkType = BotInfo; //@description Contains full information about a user //@personal_photo User profile photo set by the current user for the contact; may be null. If null and user.profile_photo is null, then the photo is empty; otherwise, it is unknown. @@ -1096,7 +1145,8 @@ chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = C chatMemberStatusAdministrator custom_title:string can_be_edited:Bool rights:chatAdministratorRights = ChatMemberStatus; //@description The user is a member of the chat, without any additional privileges or restrictions -chatMemberStatusMember = ChatMemberStatus; +//@member_until_date Point in time (Unix timestamp) when the user will be removed from the chat because of the expired subscription; 0 if never. Ignored in setChatMemberStatus +chatMemberStatusMember member_until_date:int32 = ChatMemberStatus; //@description The user is under certain restrictions in the chat. Not supported in basic groups and channels //@is_member True, if the user is a member of the chat @@ -1181,13 +1231,15 @@ supergroupMembersFilterBots = SupergroupMembersFilter; //@date Point in time (Unix timestamp) when the link was created //@edit_date Point in time (Unix timestamp) when the link was last edited; 0 if never or unknown //@expiration_date Point in time (Unix timestamp) when the link will expire; 0 if never +//@subscription_pricing Information about subscription plan that is applied to the users joining the chat by the link; may be null if the link doesn't require subscription //@member_limit The maximum number of members, which can join the chat using the link simultaneously; 0 if not limited. Always 0 if the link requires approval //@member_count Number of chat members, which joined the chat using the link +//@expired_member_count Number of chat members, which joined the chat using the link, but have already left because of expired subscription; for subscription links only //@pending_join_request_count Number of pending join requests created using this link //@creates_join_request True, if the link only creates join request. If true, total number of joining members will be unlimited //@is_primary True, if the link is primary. Primary invite link can't have name, expiration date, or usage limit. There is exactly one primary invite link for each administrator with can_invite_users right at a given time //@is_revoked True, if the link was revoked -chatInviteLink invite_link:string name:string creator_user_id:int53 date:int32 edit_date:int32 expiration_date:int32 member_limit:int32 member_count:int32 pending_join_request_count:int32 creates_join_request:Bool is_primary:Bool is_revoked:Bool = ChatInviteLink; +chatInviteLink invite_link:string name:string creator_user_id:int53 date:int32 edit_date:int32 expiration_date:int32 subscription_pricing:starSubscriptionPricing member_limit:int32 member_count:int32 expired_member_count:int32 pending_join_request_count:int32 creates_join_request:Bool is_primary:Bool is_revoked:Bool = ChatInviteLink; //@description Contains a list of chat invite links @total_count Approximate total number of chat invite links found @invite_links List of invite links chatInviteLinks total_count:int32 invite_links:vector = ChatInviteLinks; @@ -1224,6 +1276,12 @@ inviteLinkChatTypeSupergroup = InviteLinkChatType; inviteLinkChatTypeChannel = InviteLinkChatType; +//@description Contains information about subscription plan that must be paid by the user to use a chat invite link +//@pricing Information about subscription plan that must be paid by the user to use the link +//@can_reuse True, if the user has already paid for the subscription and can use joinChatByInviteLink to join the subscribed chat again +//@form_id Identifier of the payment form to use for subscription payment; 0 if the subscription can't be paid +chatInviteLinkSubscriptionInfo pricing:starSubscriptionPricing can_reuse:Bool form_id:int64 = ChatInviteLinkSubscriptionInfo; + //@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 @@ -1234,12 +1292,13 @@ inviteLinkChatTypeChannel = InviteLinkChatType; //@param_description Chat description //@member_count Number of members in the chat //@member_user_ids User identifiers of some chat members that may be known to the current user +//@subscription_info Information about subscription plan that must be paid by the user to use the link; may be null if the link doesn't require subscription //@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 //@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 accent_color_id:int32 description:string member_count:int32 member_user_ids:vector creates_join_request:Bool is_public:Bool is_verified:Bool is_scam:Bool is_fake:Bool = ChatInviteLinkInfo; +chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:InviteLinkChatType title:string photo:chatPhotoInfo accent_color_id:int32 description:string member_count:int32 member_user_ids:vector subscription_info:chatInviteLinkSubscriptionInfo 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 @@ -1286,7 +1345,8 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@boost_level Approximate boost level for the chat //@has_linked_chat True, if the channel has a discussion group, or the supergroup is the designated discussion group for a channel //@has_location True, if the supergroup is connected to a location, i.e. the supergroup is a location-based supergroup -//@sign_messages True, if messages sent to the channel need to contain information about the sender. This field is only applicable to channels +//@sign_messages True, if messages sent to the channel contains name of the sender. This field is only applicable to channels +//@show_message_sender True, if messages sent to the channel have information about the sender user. This field is only applicable to channels //@join_to_send_messages True, if users need to join the supergroup before they can send messages. Always true for channels and non-discussion supergroups //@join_by_request True, if all users directly joining the supergroup need to be approved by supergroup administrators. Always false for channels and supergroups without username, location, or a linked chat //@is_slow_mode_enabled True, if the slow mode is enabled in the supergroup @@ -1294,12 +1354,13 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@is_broadcast_group True, if the supergroup is a broadcast group, i.e. only administrators can send messages and there is no limit on the number of members //@is_forum True, if the supergroup is a forum with topics //@is_verified True, if the supergroup or channel is verified +//@has_sensitive_content True, if content of media messages in the supergroup or channel chat must be hidden with 18+ spoiler //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted //@is_scam True, if many users reported this supergroup or channel as a scam //@is_fake True, if many users reported this supergroup or channel as a fake account //@has_active_stories True, if the supergroup or channel has non-expired stories available to the current user //@has_unread_active_stories True, if the supergroup or channel has unread non-expired stories available to the current user -supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus member_count:int32 boost_level:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_forum:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool has_active_stories:Bool has_unread_active_stories:Bool = Supergroup; +supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus member_count:int32 boost_level:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool show_message_sender:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_forum:Bool is_verified:Bool has_sensitive_content:Bool restriction_reason:string is_scam:Bool is_fake:Bool has_active_stories:Bool has_unread_active_stories:Bool = Supergroup; //@description Contains full information about a supergroup or channel //@photo Chat photo; may be null if empty or unknown. If non-null, then it is the same photo as in chat.photo @@ -1311,6 +1372,7 @@ supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus membe //@linked_chat_id Chat identifier of a discussion group for the channel, or a channel, for which the supergroup is the designated discussion group; 0 if none or unknown //@slow_mode_delay Delay between consecutive sent messages for non-administrator supergroup members, in seconds //@slow_mode_delay_expires_in Time left before next message can be sent in the supergroup, in seconds. An updateSupergroupFullInfo update is not triggered when value of this field changes, but both new and old values are non-zero +//@can_enable_paid_reaction True, if paid reaction can be enabled in the channel chat; for channels only //@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 @@ -1335,7 +1397,7 @@ supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus membe //@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_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool can_get_revenue_statistics:Bool can_get_star_revenue_statistics:Bool can_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool can_have_sponsored_messages:Bool has_aggressive_anti_spam_enabled:Bool has_paid_media_allowed:Bool has_pinned_stories:Bool my_boost_count:int32 unrestrict_boost_count:int32 sticker_set_id:int64 custom_emoji_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_enable_paid_reaction:Bool 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_get_revenue_statistics:Bool can_get_star_revenue_statistics:Bool can_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool can_have_sponsored_messages:Bool has_aggressive_anti_spam_enabled:Bool has_paid_media_allowed:Bool has_pinned_stories:Bool my_boost_count:int32 unrestrict_boost_count:int32 sticker_set_id:int64 custom_emoji_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 @@ -1445,6 +1507,17 @@ reactionTypeEmoji emoji:string = ReactionType; //@description A reaction with a custom emoji @custom_emoji_id Unique identifier of the custom emoji reactionTypeCustomEmoji custom_emoji_id:int64 = ReactionType; +//@description The paid reaction in a channel chat +reactionTypePaid = ReactionType; + + +//@description Contains information about a user that added paid reactions +//@sender_id Identifier of the user or chat that added the reactions; may be null for anonymous reactors that aren't the current user +//@star_count Number of Telegram Stars added +//@is_top True, if the reactor is one of the most active reactors; can be false if the reactor is the current user +//@is_me True, if the paid reaction was added by the current user +//@is_anonymous True, if the reactor is anonymous +paidReactor sender_id:MessageSender star_count:int32 is_top:Bool is_me:Bool is_anonymous:Bool = PaidReactor; //@description Contains information about a forwarded message //@origin Origin of the forwarded message @@ -1474,9 +1547,12 @@ messageReplyInfo reply_count:int32 recent_replier_ids:vector last //@recent_sender_ids Identifiers of at most 3 recent message senders, added the reaction; available in private, basic group and supergroup chats messageReaction type:ReactionType total_count:int32 is_chosen:Bool used_sender_id:MessageSender recent_sender_ids:vector = MessageReaction; -//@description Contains a list of reactions added to a message @reactions List of added reactions @are_tags True, if the reactions are tags and Telegram Premium users can filter messages by them -messageReactions reactions:vector are_tags:Bool = MessageReactions; - +//@description Contains a list of reactions added to a message +//@reactions List of added reactions +//@are_tags True, if the reactions are tags and Telegram Premium users can filter messages by them +//@paid_reactors Information about top users that added the paid reaction +//@can_get_added_reactions True, if the list of added reactions is available using getMessageAddedReactions +messageReactions reactions:vector are_tags:Bool paid_reactors:vector can_get_added_reactions:Bool = MessageReactions; //@description Contains information about interactions with a message //@view_count Number of times the message was viewed @@ -1613,10 +1689,11 @@ factCheck text:formattedText country_code:string = FactCheck; //@author_signature For channel posts and anonymous group messages, optional author signature //@media_album_id Unique identifier of an album this message belongs to; 0 if none. Only audios, documents, photos and videos can be grouped together in albums //@effect_id Unique identifier of the effect added to the message; 0 if none +//@has_sensitive_content True, if media content of the message must be hidden with 18+ spoiler //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@content Content of the message //@reply_markup Reply markup for the message; may be null if none -message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool is_from_offline:Bool can_be_saved:Bool has_timestamped_media:Bool is_channel_post:Bool is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo import_info:messageImportInfo interaction_info:messageInteractionInfo unread_reactions:vector fact_check:factCheck reply_to:MessageReplyTo message_thread_id:int53 saved_messages_topic_id:int53 self_destruct_type:MessageSelfDestructType self_destruct_in:double auto_delete_in:double via_bot_user_id:int53 sender_business_bot_user_id:int53 sender_boost_count:int32 author_signature:string media_album_id:int64 effect_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message; +message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool is_from_offline:Bool can_be_saved:Bool has_timestamped_media:Bool is_channel_post:Bool is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo import_info:messageImportInfo interaction_info:messageInteractionInfo unread_reactions:vector fact_check:factCheck reply_to:MessageReplyTo message_thread_id:int53 saved_messages_topic_id:int53 self_destruct_type:MessageSelfDestructType self_destruct_in:double auto_delete_in:double via_bot_user_id:int53 sender_business_bot_user_id:int53 sender_boost_count:int32 author_signature:string media_album_id:int64 effect_id:int64 has_sensitive_content:Bool 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 @@ -1691,7 +1768,7 @@ messageSponsor url:string photo:photo info:string = MessageSponsor; //@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" //@can_be_reported True, if the message can be reported to Telegram moderators through reportChatSponsoredMessage -//@content Content of the message. Currently, can be only of the type messageText +//@content Content of the message. Currently, can be only of the types messageText, messageAnimation, messagePhoto, or messageVideo //@sponsor Information about the sponsor of the message //@title Title of the sponsored message //@button_text Text for the message action button @@ -1926,7 +2003,8 @@ chatPosition list:ChatList order:int64 is_pinned:Bool source:ChatSource = ChatPo //@class ChatAvailableReactions @description Describes reactions available in the chat -//@description All reactions are available in the chat @max_reaction_count The maximum allowed number of reactions per message; 1-11 +//@description All reactions are available in the chat, excluding the paid reaction and custom reactions in channel chats +//@max_reaction_count The maximum allowed number of reactions per message; 1-11 chatAvailableReactionsAll max_reaction_count:int32 = ChatAvailableReactions; //@description Only specific reactions are available in the chat @reactions The list of reactions @max_reaction_count The maximum allowed number of reactions per message; 1-11 @@ -2595,8 +2673,10 @@ linkPreviewTypeArticle photo:photo author:string = LinkPreviewType; //@author Author of the audio linkPreviewTypeAudio url:string mime_type:string audio:audio duration:int32 author:string = LinkPreviewType; -//@description The link is a link to a background. Link preview title and description are available only for filled backgrounds @document Document with the background; may be null for filled backgrounds -linkPreviewTypeBackground document:document = LinkPreviewType; +//@description The link is a link to a background. Link preview title and description are available only for filled backgrounds +//@document Document with the background; may be null for filled backgrounds +//@background_type Type of the background; may be null if unknown +linkPreviewTypeBackground document:document background_type:BackgroundType = LinkPreviewType; //@description The link is a link to boost a channel chat @photo Photo of the chat; may be null linkPreviewTypeChannelBoost photo:chatPhoto = LinkPreviewType; @@ -2652,7 +2732,7 @@ linkPreviewTypePremiumGiftCode = LinkPreviewType; //@description The link is a link to a shareable chat folder linkPreviewTypeShareableChatFolder = LinkPreviewType; -//@description The link is a link to a sticker message @sticker The sticker +//@description The link is a link to a sticker @sticker The sticker. It can be an arbitrary WEBP image and can have dimensions bigger than 512 linkPreviewTypeSticker sticker:sticker = LinkPreviewType; //@description The link is a link to a sticker set @stickers Up to 4 stickers from the sticker set @@ -3883,7 +3963,6 @@ inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool cop //@can_be_saved True, if content of the message can be saved locally or copied using inputMessageForwarded or forwardMessages with copy options //@can_be_shared_in_story True, if the message can be shared in a story using inputStoryAreaTypeMessage //@can_edit_scheduling_state True, if scheduling state of the message can be edited -//@can_get_added_reactions True, if the list of added reactions is available using getMessageAddedReactions //@can_get_embedding_code True, if code for message embedding can be received using getMessageEmbeddingCode //@can_get_link True, if a link can be generated for the message using getMessageLink //@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or link preview description using getMessageLink @@ -3897,7 +3976,7 @@ inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool cop //@can_report_supergroup_spam True, if the message can be reported using reportSupergroupSpam //@can_set_fact_check True, if fact check for the message can be changed through setMessageFactCheck //@need_show_statistics True, if message statistics must be available from context menu of the message -messageProperties can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_paid:Bool can_be_pinned:Bool can_be_replied:Bool can_be_replied_in_another_chat:Bool can_be_saved:Bool can_be_shared_in_story:Bool can_edit_scheduling_state:Bool can_get_added_reactions:Bool can_get_embedding_code:Bool can_get_link:Bool can_get_media_timestamp_links:Bool can_get_message_thread:Bool can_get_read_date:Bool can_get_statistics:Bool can_get_viewers:Bool can_recognize_speech:Bool can_report_chat:Bool can_report_reactions:Bool can_report_supergroup_spam:Bool can_set_fact_check:Bool need_show_statistics:Bool = MessageProperties; +messageProperties can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_paid:Bool can_be_pinned:Bool can_be_replied:Bool can_be_replied_in_another_chat:Bool can_be_saved:Bool can_be_shared_in_story:Bool can_edit_scheduling_state:Bool can_get_embedding_code:Bool can_get_link:Bool can_get_media_timestamp_links:Bool can_get_message_thread:Bool can_get_read_date:Bool can_get_statistics:Bool can_get_viewers:Bool can_recognize_speech:Bool can_report_chat:Bool can_report_reactions:Bool can_report_supergroup_spam:Bool can_set_fact_check:Bool need_show_statistics:Bool = MessageProperties; //@class SearchMessagesFilter @description Represents a filter for message search results @@ -5277,6 +5356,9 @@ chatEventHasAggressiveAntiSpamEnabledToggled has_aggressive_anti_spam_enabled:Bo //@description The sign_messages setting of a channel was toggled @sign_messages New value of sign_messages chatEventSignMessagesToggled sign_messages:Bool = ChatEventAction; +//@description The show_message_sender setting of a channel was toggled @show_message_sender New value of show_message_sender +chatEventShowMessageSenderToggled show_message_sender:Bool = ChatEventAction; + //@description A chat invite link was edited @old_invite_link Previous information about the invite link @new_invite_link New information about the invite link chatEventInviteLinkEdited old_invite_link:chatInviteLink new_invite_link:chatInviteLink = ChatEventAction; @@ -5706,6 +5788,9 @@ telegramPaymentPurposeStars currency:string amount:int53 star_count:int53 = Tele //@star_count Number of bought Telegram Stars telegramPaymentPurposeGiftedStars user_id:int53 currency:string amount:int53 star_count:int53 = TelegramPaymentPurpose; +//@description The user joins a chat and subscribes to regular payments in Telegram Stars @invite_link Invite link to use +telegramPaymentPurposeJoinChat invite_link:string = TelegramPaymentPurpose; + //@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. //-To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org @@ -6429,7 +6514,7 @@ 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 show a disclaimer about Mini Apps being third-party apps, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu. +//-If the bot isn't added to attachment menu, then show a disclaimer about Mini Apps being third-party applications, 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 @@ -6475,7 +6560,12 @@ internalLinkTypeBotStartInGroup bot_username:string start_parameter:string admin //@link_name Name of the link internalLinkTypeBusinessChat link_name:string = InternalLinkType; -//@description The link is a link to the change phone number section of the app +//@description The link is a link to the Telegram Star purchase section of the application +//@star_count The number of Telegram Stars that must be owned by the user +//@purpose Purpose of Telegram Star purchase. Arbitrary string specified by the server, for example, "subs" if the Telegram Stars are required to extend channel subscriptions +internalLinkTypeBuyStars star_count:int53 purpose:string = InternalLinkType; + +//@description The link is a link to the change phone number section of the application internalLinkTypeChangePhoneNumber = InternalLinkType; //@description The link is a link to boost a Telegram chat. Call getChatBoostLinkInfo with the given URL to process the link. @@ -6489,7 +6579,7 @@ internalLinkTypeChatBoost url:string = InternalLinkType; //@invite_link Internal representation of the invite link internalLinkTypeChatFolderInvite invite_link:string = InternalLinkType; -//@description The link is a link to the folder section of the app settings +//@description The link is a link to the folder section of the application settings internalLinkTypeChatFolderSettings = InternalLinkType; //@description The link is a chat invite link. Call checkChatInviteLink with the given invite link to process the link. @@ -6497,10 +6587,10 @@ internalLinkTypeChatFolderSettings = InternalLinkType; //@invite_link Internal representation of the invite link internalLinkTypeChatInvite invite_link:string = InternalLinkType; -//@description The link is a link to the default message auto-delete timer settings section of the app settings +//@description The link is a link to the default message auto-delete timer settings section of the application settings internalLinkTypeDefaultMessageAutoDeleteTimerSettings = InternalLinkType; -//@description The link is a link to the edit profile section of the app settings +//@description The link is a link to the edit profile section of the application settings internalLinkTypeEditProfileSettings = InternalLinkType; //@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, @@ -6523,12 +6613,12 @@ internalLinkTypeInvoice invoice_name:string = InternalLinkType; //@language_pack_id Language pack identifier internalLinkTypeLanguagePack language_pack_id:string = InternalLinkType; -//@description The link is a link to the language section of the app settings +//@description The link is a link to the language section of the application settings internalLinkTypeLanguageSettings = InternalLinkType; //@description The link is a link to the main Web App of a bot. Call searchPublicChat with the given bot username, check that the user is a bot and has the main Web App. //-If the bot can be added to attachment menu, then use getAttachmentMenuBot to receive information about the bot, then if the bot isn't added to side menu, -//-show a disclaimer about Mini Apps being third-party apps, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu, +//-show a disclaimer about Mini Apps being third-party applications, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu, //-then if the user accepts the terms and confirms adding, use toggleBotIsAddedToAttachmentMenu to add the bot. //-Then, use getMainWebApp with the given start parameter and open the returned URL as a Web App //@bot_username Username of the bot @@ -6573,7 +6663,7 @@ internalLinkTypePremiumGift referrer:string = InternalLinkType; //@code The Telegram Premium gift code internalLinkTypePremiumGiftCode code:string = InternalLinkType; -//@description The link is a link to the privacy and security section of the app settings +//@description The link is a link to the privacy and security section of the application settings 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 @@ -6614,7 +6704,7 @@ internalLinkTypeStory story_sender_username:string story_id:int32 = InternalLink //@description The link is a link to a cloud theme. TDLib has no theme support yet @theme_name Name of the theme internalLinkTypeTheme theme_name:string = InternalLinkType; -//@description The link is a link to the theme section of the app settings +//@description The link is a link to the theme section of the application settings internalLinkTypeThemeSettings = InternalLinkType; //@description The link is an unknown tg: link. Call getDeepLinkInfo to process the link @link Link to be passed to getDeepLinkInfo @@ -6642,7 +6732,7 @@ 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. If the bot can be added to attachment or side menu, but isn't added yet, then show a disclaimer about Mini Apps being third-party apps +//-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 third-party applications //-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 @@ -6982,6 +7072,10 @@ suggestedActionSetBirthdate = SuggestedAction; //@description Suggests the user to extend their expiring Telegram Premium subscription @manage_premium_subscription_url A URL for managing Telegram Premium subscription suggestedActionExtendPremium manage_premium_subscription_url:string = SuggestedAction; +//@description Suggests the user to extend their expiring Telegram Star subscriptions. Call getStarSubscriptions with only_expiring == true +//-to get the number of expiring subscriptions and the number of required to buy Telegram Stars +suggestedActionExtendStarSubscriptions = SuggestedAction; + //@description Contains a counter @count Count count count:int32 = Count; @@ -7743,6 +7837,10 @@ updateDefaultReactionType reaction_type:ReactionType = Update; //@tags The new tags updateSavedMessagesTags saved_messages_topic_id:int53 tags:savedMessagesTags = Update; +//@description The list of messages with active live location that need to be updated by the application has changed. The list is persistent across application restarts only if the message database is used +//@messages The list of messages with active live locations +updateActiveLiveLocationMessages messages:vector = Update; + //@description The number of Telegram Stars owned by the current user has changed @star_count The new number of Telegram Stars owned updateOwnedStarCount star_count:int53 = Update; @@ -8470,9 +8568,6 @@ deleteAllCallMessages revoke:Bool = Ok; //@description Returns information about the recent locations of chat members that were sent to the chat. Returns up to 1 location message per user @chat_id Chat identifier @limit The maximum number of messages to be returned searchChatRecentLocationMessages chat_id:int53 limit:int32 = Messages; -//@description Returns all active live locations that need to be updated by the application. The list is persistent across application restarts only if the message database is used -getActiveLiveLocationMessages = Messages; - //@description Returns the last message sent in a chat no later than the specified date @chat_id Chat identifier @date Point in time (Unix timestamp) relative to which to search for messages getChatMessageByDate chat_id:int53 date:int32 = Message; @@ -8980,7 +9075,7 @@ clearRecentReactions = Ok; //@description Adds a reaction or a tag to a message. Use getMessageAvailableReactions to receive the list of available reactions for the message //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message -//@reaction_type Type of the reaction to add +//@reaction_type Type of the reaction to add. Use addPaidMessageReaction instead to add the paid reaction //@is_big Pass true if the reaction is added with a big animation //@update_recent_reactions Pass true if the reaction needs to be added to recent reactions; tags are never added to the list of recent reactions addMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType is_big:Bool update_recent_reactions:Bool = Ok; @@ -8988,9 +9083,27 @@ addMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType is_ //@description Removes a reaction from a message. A chosen reaction can always be removed //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message -//@reaction_type Type of the reaction to remove +//@reaction_type Type of the reaction to remove. The paid reaction can't be removed removeMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType = Ok; +//@description Adds the paid message reaction to a message. Use getMessageAvailableReactions to receive the list of available reactions for the message +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +//@star_count Number of Telegram Stars to be used for the reaction; 1-getOption("paid_reaction_star_count_max") +//@is_anonymous Pass true to make paid reaction of the user on the message anonymous; pass false to make the user's profile visible among top reactors +addPaidMessageReaction chat_id:int53 message_id:int53 star_count:int53 is_anonymous:Bool = Ok; + +//@description Removes all pending paid reactions on a message. Can be called within 5 seconds after the last addPaidMessageReaction call +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +removePendingPaidMessageReactions chat_id:int53 message_id:int53 = Ok; + +//@description Changes whether the paid message reaction of the user to a message is anonymous. The message must have paid reaction added by the user +//@chat_id Identifier of the chat to which the message belongs +//@message_id Identifier of the message +//@is_anonymous Pass true to make paid reaction of the user on the message anonymous; pass false to make the user's profile visible among top reactors +togglePaidMessageReactionIsAnonymous chat_id:int53 message_id:int53 is_anonymous:Bool = Ok; + //@description Sets reactions on a message; for bots only //@chat_id Identifier of the chat to which the message belongs //@message_id Identifier of the message @@ -9000,13 +9113,13 @@ setMessageReactions chat_id:int53 message_id:int53 reaction_types:vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int = Chat; +channel#fe4478bd flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; -channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; +channelFull#bbab348d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -629,11 +629,11 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; -chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite; +chatInviteExported#a22cbd96 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int subscription_expired:flags.10?int title:flags.8?string subscription_pricing:flags.9?StarsSubscriptionPricing = ExportedChatInvite; chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#cde0ec40 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:int = ChatInvite; +chatInvite#fe65389d 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 can_refulfill_subscription:flags.11?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector color:int subscription_pricing:flags.10?StarsSubscriptionPricing subscription_form_id:flags.12?long = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -655,7 +655,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#8f300b57 flags:# has_preview_medias:flags.6?true user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector menu_button:flags.3?BotMenuButton = BotInfo; +botInfo#82437e74 flags:# has_preview_medias:flags.6?true user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector menu_button:flags.3?BotMenuButton privacy_policy_url:flags.7?string = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; @@ -719,8 +719,8 @@ updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:fl channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; -channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant; -channelParticipantSelf#35a8bfa7 flags:# via_request:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; +channelParticipant#cb397619 flags:# user_id:long date:int subscription_until_date:flags.0?int = ChannelParticipant; +channelParticipantSelf#4f607bef flags:# via_request:flags.0?true user_id:long inviter_id:long date:int subscription_until_date:flags.1?int = ChannelParticipant; channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant; @@ -1036,6 +1036,7 @@ channelAdminLogEventActionChangeProfilePeerColor#5e477b25 prev_value:PeerColor n channelAdminLogEventActionChangeWallpaper#31bb5d52 prev_value:WallPaper new_value:WallPaper = ChannelAdminLogEventAction; channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_value:EmojiStatus = ChannelAdminLogEventAction; channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleSignatureProfiles#60a79c79 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1401,7 +1402,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#bdedf566 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; +sponsoredMessage#4d93a990 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string 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; @@ -1424,7 +1425,7 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount; -messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true reactions_as_tags:flags.3?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; +messageReactions#a339f0b flags:# min:flags.0?true can_see_list:flags.2?true reactions_as_tags:flags.3?true results:Vector recent_reactions:flags.1?Vector top_reactors:flags.4?Vector = MessageReactions; messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; @@ -1481,6 +1482,7 @@ inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; inputInvoiceStars#65f00ce3 purpose:InputStorePaymentPurpose = InputInvoice; +inputInvoiceChatInviteSubscription#34e793f1 hash:string = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1509,6 +1511,7 @@ account.emojiStatuses#90c467d1 hash:long statuses:Vector = account. reactionEmpty#79f5d419 = Reaction; reactionEmoji#1b2286b8 emoticon:string = Reaction; reactionCustomEmoji#8935fc73 document_id:long = Reaction; +reactionPaid#523da4eb = Reaction; chatReactionsNone#eafc32bc = ChatReactions; chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions; @@ -1835,9 +1838,9 @@ starsTransactionPeerAds#60682812 = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#2db5418f flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector = StarsTransaction; +starsTransaction#433aeb2b flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int = StarsTransaction; -payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; +payments.starsStatus#bbfa316c flags:# balance:long subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; foundStory#e87acbc0 peer:Peer story:StoryItem = FoundStory; @@ -1863,6 +1866,12 @@ botPreviewMedia#23e91ba3 date:int media:MessageMedia = BotPreviewMedia; bots.previewInfo#ca71d64 media:Vector lang_codes:Vector = bots.PreviewInfo; +starsSubscriptionPricing#5416d58 period:int amount:long = StarsSubscriptionPricing; + +starsSubscription#538ecf18 flags:# canceled:flags.0?true can_refulfill:flags.1?true missing_balance:flags.2?true id:string peer:Peer until_date:int pricing:StarsSubscriptionPricing chat_invite_hash:flags.3?string = StarsSubscription; + +messageReactor#4ba3a95a flags:# top:flags.0?true my:flags.1?true anonymous:flags.2?true peer_id:flags.3?Peer count:int = MessageReactor; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2082,7 +2091,7 @@ messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers; messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; -messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite; +messages.exportChatInvite#a455de90 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string subscription_pricing:flags.5?StarsSubscriptionPricing = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#c8a0ec74 stickerset:InputStickerSet hash:int = messages.StickerSet; @@ -2182,7 +2191,7 @@ messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:Input messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool; messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool; messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites; -messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; +messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true subscription_expired:flags.3?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; @@ -2196,7 +2205,7 @@ messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector = Updates; messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates; messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList; -messages.setChatAvailableReactions#5a150bd4 flags:# peer:InputPeer available_reactions:ChatReactions reactions_limit:flags.0?int = Updates; +messages.setChatAvailableReactions#864b2581 flags:# peer:InputPeer available_reactions:ChatReactions reactions_limit:flags.0?int paid_enabled:flags.1?Bool = Updates; messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector text:flags.1?Vector to_lang:string = messages.TranslatedText; @@ -2259,6 +2268,8 @@ messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities = messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates; messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector = Vector; messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult; +messages.sendPaidReaction#25c8fe3e flags:# private:flags.0?true peer:InputPeer msg_id:int count:int random_id:long = Updates; +messages.togglePaidReactionPrivacy#849ad397 peer:InputPeer msg_id:int private:Bool = Bool; updates.getState#edd4882a = updates.State; 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; @@ -2324,7 +2335,7 @@ channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.inviteToChannel#c9e33d54 channel:InputChannel users:Vector = messages.InvitedUsers; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; -channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; +channels.toggleSignatures#418d549c flags:# signatures_enabled:flags.0?true profiles_enabled:flags.1?true channel:InputChannel = Updates; channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_personal:flags.2?true = messages.Chats; channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; @@ -2413,7 +2424,7 @@ payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayI payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; payments.getStarsTopupOptions#c00ec7d3 = Vector; payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; -payments.getStarsTransactions#97938d5a flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true peer:InputPeer offset:string limit:int = payments.StarsStatus; +payments.getStarsTransactions#69da4557 flags:# inbound:flags.0?true outbound:flags.1?true ascending:flags.2?true subscription_id:flags.3?string peer:InputPeer offset:string limit:int = payments.StarsStatus; payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; payments.getStarsRevenueStats#d91ffad6 flags:# dark:flags.0?true peer:InputPeer = payments.StarsRevenueStats; @@ -2421,6 +2432,9 @@ payments.getStarsRevenueWithdrawalUrl#13bbe8b3 peer:InputPeer stars:long passwor payments.getStarsRevenueAdsAccountUrl#d1d7efc5 peer:InputPeer = payments.StarsRevenueAdsAccountUrl; payments.getStarsTransactionsByID#27842d2e peer:InputPeer id:Vector = payments.StarsStatus; payments.getStarsGiftOptions#d3c96bc8 flags:# user_id:flags.0?InputUser = Vector; +payments.getStarsSubscriptions#32512c5 flags:# missing_balance:flags.0?true peer:InputPeer offset:string = payments.StarsStatus; +payments.changeStarsSubscription#c7770878 flags:# peer:InputPeer subscription_id:string canceled:flags.0?Bool = Bool; +payments.fulfillStarsSubscription#cc5bebb3 peer:InputPeer subscription_id:string = Bool; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; diff --git a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp index 16f91729..39ba1f2a 100644 --- a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp @@ -19,8 +19,10 @@ #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/NewPasswordState.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PasswordManager.h" +#include "td/telegram/PromoDataManager.h" #include "td/telegram/ReactionManager.h" #include "td/telegram/SendCodeHelper.hpp" #include "td/telegram/StateManager.h" @@ -28,6 +30,8 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/TermsOfService.hpp" +#include "td/telegram/TermsOfServiceManager.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" @@ -1265,18 +1269,16 @@ 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_->online_manager_->init(); + td_->promo_data_manager_->init(); td_->reaction_manager_->init(); td_->stickers_manager_->init(); + td_->terms_of_service_manager_->init(); td_->theme_manager_->init(); td_->top_dialog_manager_->init(); td_->updates_manager_->get_difference("on_get_authorization"); if (!is_bot()) { - td_->on_online_updated(false, true); - td_->schedule_get_terms_of_service(0); - td_->reload_promo_data(); G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1"); - } else { - td_->set_is_bot_online(true); } send_closure(G()->config_manager(), &ConfigManager::request_config, false); on_current_query_ok(); diff --git a/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp b/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp index ceef5db8..ab3b0021 100644 --- a/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp @@ -812,7 +812,7 @@ void BotInfoManager::do_add_bot_media_preview(unique_ptr auto content = pending_preview->content_.get(); auto upload_order = pending_preview->upload_order_; - FileId file_id = get_story_content_any_file_id(td_, content); + FileId file_id = get_story_content_any_file_id(content); CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp index 30063ce4..3534f1e9 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp @@ -783,10 +783,8 @@ Result BusinessConnectionManager::process_input_message_con if (message_content_id == td_api::inputMessageForwarded::ID) { return Status::Error(400, "Can't forward messages as business"); } - if (message_content_id == td_api::inputMessagePaidMedia::ID) { - return Status::Error(400, "Can't send paid media as business"); - } - return get_input_message_content(DialogId(), std::move(input_message_content), td_, true); + return get_input_message_content(td_->dialog_manager_->get_my_dialog_id(), std::move(input_message_content), td_, + true); } unique_ptr BusinessConnectionManager::create_business_message_to_send( @@ -848,6 +846,42 @@ void BusinessConnectionManager::do_send_message(unique_ptr &&mes return; } + if (content_type == MessageContentType::PaidMedia) { + auto message_contents = get_individual_message_contents(content); + auto request_id = ++current_media_group_send_request_id_; + auto &request = media_group_send_requests_[request_id]; + request.upload_results_.resize(message_contents.size()); + request.paid_media_promise_ = std::move(promise); + request.paid_media_message_ = std::move(message); + + for (size_t media_pos = 0; media_pos < message_contents.size(); media_pos++) { + auto fake_message = make_unique(); + fake_message->dialog_id_ = request.paid_media_message_->dialog_id_; + fake_message->business_connection_id_ = request.paid_media_message_->business_connection_id_; + fake_message->content_ = std::move(message_contents[media_pos]); + auto input_media = get_message_content_input_media(fake_message->content_.get(), td_, MessageSelfDestructType(), + string(), td_->auth_manager_->is_bot()); + if (input_media != nullptr) { + auto file_id = get_message_file_id(fake_message); + CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (file_view.has_remote_location()) { + UploadMediaResult result; + result.message_ = std::move(fake_message); + result.input_media_ = std::move(input_media); + on_upload_message_paid_media(request_id, media_pos, std::move(result)); + continue; + } + } + upload_media(std::move(fake_message), PromiseCreator::lambda([actor_id = actor_id(this), request_id, media_pos]( + Result &&result) mutable { + send_closure(actor_id, &BusinessConnectionManager::on_upload_message_paid_media, request_id, + media_pos, std::move(result)); + })); + } + return; + } + auto input_media = get_message_content_input_media(content, td_, message->ttl_, message->send_emoji_, td_->auth_manager_->is_bot()); if (input_media != nullptr) { @@ -1129,6 +1163,7 @@ void BusinessConnectionManager::on_upload_message_album_media(int64 request_id, auto upload_results = std::move(request.upload_results_); auto promise = std::move(request.promise_); + CHECK(request.paid_media_message_ == nullptr); media_group_send_requests_.erase(it); for (auto &r_upload_result : upload_results) { @@ -1183,6 +1218,43 @@ void BusinessConnectionManager::process_sent_business_message_album( promise.set_value(std::move(messages)); } +void BusinessConnectionManager::on_upload_message_paid_media(int64 request_id, size_t media_pos, + Result &&result) { + G()->ignore_result_if_closing(result); + auto it = media_group_send_requests_.find(request_id); + CHECK(it != media_group_send_requests_.end()); + auto &request = it->second; + + request.upload_results_[media_pos] = std::move(result); + request.finished_count_++; + + LOG(INFO) << "Receive uploaded paid media " << media_pos << " for request " << request_id; + if (request.finished_count_ != request.upload_results_.size()) { + return; + } + + auto upload_results = std::move(request.upload_results_); + auto message = std::move(request.paid_media_message_); + auto promise = std::move(request.paid_media_promise_); + media_group_send_requests_.erase(it); + + CHECK(message != nullptr); + for (auto &r_upload_result : upload_results) { + if (r_upload_result.is_error()) { + return promise.set_error(r_upload_result.move_as_error()); + } + } + vector> input_media; + for (auto &r_upload_result : upload_results) { + auto upload_result = r_upload_result.move_as_ok(); + input_media.push_back(std::move(upload_result.input_media_)); + } + auto input_media_paid_media = telegram_api::make_object( + get_message_content_star_count(message->content_.get()), std::move(input_media)); + td_->create_handler(std::move(promise)) + ->send(std::move(message), std::move(input_media_paid_media)); +} + void BusinessConnectionManager::edit_business_message_text( BusinessConnectionId business_connection_id, DialogId dialog_id, MessageId message_id, td_api::object_ptr &&reply_markup, diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h index 97207c84..6563aee3 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h @@ -132,6 +132,8 @@ class BusinessConnectionManager final : public Actor { size_t finished_count_ = 0; vector> upload_results_; Promise> promise_; + unique_ptr paid_media_message_; + Promise> paid_media_promise_; }; void tear_down() final; @@ -192,6 +194,8 @@ class BusinessConnectionManager final : public Actor { void process_sent_business_message_album(telegram_api::object_ptr &&updates_ptr, Promise> &&promise); + void on_upload_message_paid_media(int64 request_id, size_t media_pos, Result &&result); + void do_edit_business_message_media(Result &&result, Promise> &&promise); diff --git a/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp b/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp index cf187cb1..5cac17ca 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp @@ -381,7 +381,7 @@ class UpdateBusinessLocationQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->user_manager_->on_update_user_location(td_->user_manager_->get_my_id(), std::move(location_)); + td_->user_manager_->on_update_my_user_location(std::move(location_)); promise_.set_value(Unit()); } @@ -415,7 +415,7 @@ class UpdateBusinessWorkHoursQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->user_manager_->on_update_user_work_hours(td_->user_manager_->get_my_id(), std::move(work_hours_)); + td_->user_manager_->on_update_my_user_work_hours(std::move(work_hours_)); promise_.set_value(Unit()); } @@ -450,7 +450,7 @@ class UpdateBusinessGreetingMessageQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->user_manager_->on_update_user_greeting_message(td_->user_manager_->get_my_id(), std::move(greeting_message_)); + td_->user_manager_->on_update_my_user_greeting_message(std::move(greeting_message_)); promise_.set_value(Unit()); } @@ -485,7 +485,7 @@ class UpdateBusinessAwayMessageQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->user_manager_->on_update_user_away_message(td_->user_manager_->get_my_id(), std::move(away_message_)); + td_->user_manager_->on_update_my_user_away_message(std::move(away_message_)); promise_.set_value(Unit()); } @@ -520,7 +520,7 @@ class UpdateBusinessIntroQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->user_manager_->on_update_user_intro(td_->user_manager_->get_my_id(), std::move(intro_)); + td_->user_manager_->on_update_my_user_intro(std::move(intro_)); promise_.set_value(Unit()); } diff --git a/lib/tgchat/ext/td/td/telegram/ChatManager.cpp b/lib/tgchat/ext/td/td/telegram/ChatManager.cpp index 9b268a5f..8f48822e 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ChatManager.cpp @@ -569,12 +569,20 @@ class ToggleChannelSignaturesQuery final : public Td::ResultHandler { explicit ToggleChannelSignaturesQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(ChannelId channel_id, bool sign_messages) { + void send(ChannelId channel_id, bool sign_messages, bool show_message_sender) { channel_id_ = channel_id; auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); + int32 flags = 0; + if (sign_messages) { + flags |= telegram_api::channels_toggleSignatures::SIGNATURES_ENABLED_MASK; + } + if (show_message_sender) { + flags |= telegram_api::channels_toggleSignatures::PROFILES_ENABLED_MASK; + } send_query(G()->net_query_creator().create( - telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages), {{channel_id}})); + telegram_api::channels_toggleSignatures(flags, false /*ignored*/, false /*ignored*/, std::move(input_channel)), + {{channel_id}})); } void on_result(BufferSlice packet) final { @@ -1806,7 +1814,7 @@ void ChatManager::Chat::parse(ParserT &parser) { } else if (is_administrator && !everyone_is_administrator) { status = DialogParticipantStatus::GroupAdministrator(false); } else { - status = DialogParticipantStatus::Member(); + status = DialogParticipantStatus::Member(0); } default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, everyone_is_administrator, everyone_is_administrator, @@ -1970,6 +1978,7 @@ void ChatManager::Channel::store(StorerT &storer) const { STORE_FLAG(has_profile_background_custom_emoji_id); STORE_FLAG(has_boost_level); STORE_FLAG(has_emoji_status); + STORE_FLAG(show_message_sender); END_STORE_FLAGS(); } @@ -2098,6 +2107,7 @@ void ChatManager::Channel::parse(ParserT &parser) { PARSE_FLAG(has_profile_background_custom_emoji_id); PARSE_FLAG(has_boost_level); PARSE_FLAG(has_emoji_status); + PARSE_FLAG(show_message_sender); END_PARSE_FLAGS(); } @@ -2113,7 +2123,7 @@ void ChatManager::Channel::parse(ParserT &parser) { } else if (can_edit || can_moderate) { status = DialogParticipantStatus::ChannelAdministrator(false, is_megagroup); } else { - status = DialogParticipantStatus::Member(); + status = DialogParticipantStatus::Member(0); } } parse(access_hash, parser); @@ -2192,11 +2202,15 @@ void ChatManager::Channel::parse(ParserT &parser) { if (legacy_has_active_group_call) { cache_version = 0; } - if (!is_megagroup && status.is_restricted()) { - if (status.is_member()) { - status = DialogParticipantStatus::Member(); - } else { - status = DialogParticipantStatus::Left(); + if (is_megagroup) { + show_message_sender = true; + } else { + if (status.is_restricted()) { + if (status.is_member()) { + status = DialogParticipantStatus::Member(0); + } else { + status = DialogParticipantStatus::Left(); + } } } } @@ -3096,7 +3110,8 @@ void ChatManager::set_channel_unrestrict_boost_count(ChannelId channel_id, int32 ->send(channel_id, unrestrict_boost_count); } -void ChatManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise) { +void ChatManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, bool show_message_sender, + Promise &&promise) { auto c = get_channel(channel_id); if (c == nullptr) { return promise.set_error(Status::Error(400, "Supergroup not found")); @@ -3108,7 +3123,8 @@ void ChatManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_m return promise.set_error(Status::Error(400, "Not enough rights to toggle channel sign messages")); } - td_->create_handler(std::move(promise))->send(channel_id, sign_messages); + td_->create_handler(std::move(promise)) + ->send(channel_id, sign_messages, show_message_sender); } void ChatManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise &&promise) { @@ -5260,7 +5276,7 @@ void ChatManager::on_get_chat_full(tl_object_ptr &&chat_ "on_get_chat_full"); td_->messages_manager_->on_update_dialog_available_reactions( - DialogId(chat_id), std::move(chat->available_reactions_), chat->reactions_limit_); + DialogId(chat_id), std::move(chat->available_reactions_), chat->reactions_limit_, false); td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_)); @@ -5317,7 +5333,8 @@ void ChatManager::on_get_chat_full(tl_object_ptr &&chat_ td_->messages_manager_->on_update_dialog_background(DialogId(channel_id), std::move(channel->wallpaper_)); td_->messages_manager_->on_update_dialog_available_reactions( - DialogId(channel_id), std::move(channel->available_reactions_), channel->reactions_limit_); + DialogId(channel_id), std::move(channel->available_reactions_), channel->reactions_limit_, + channel->paid_reactions_available_); td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_)); @@ -6054,7 +6071,7 @@ void ChatManager::on_update_chat_full_invite_link(ChatFull *chat_full, tl_object_ptr &&invite_link) { CHECK(chat_full != nullptr); if (update_permanent_invite_link(chat_full->invite_link, - DialogInviteLink(std::move(invite_link), false, "ChatFull"))) { + DialogInviteLink(std::move(invite_link), false, false, "ChatFull"))) { chat_full->is_changed = true; } } @@ -6063,7 +6080,7 @@ void ChatManager::on_update_channel_full_invite_link(ChannelFull *channel_full, tl_object_ptr &&invite_link) { CHECK(channel_full != nullptr); if (update_permanent_invite_link(channel_full->invite_link, - DialogInviteLink(std::move(invite_link), false, "ChannelFull"))) { + DialogInviteLink(std::move(invite_link), false, false, "ChannelFull"))) { channel_full->is_changed = true; } } @@ -6318,7 +6335,7 @@ void ChatManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id chat_full->participants.push_back(DialogParticipant{DialogId(user_id), inviter_user_id, date, user_id == chat_full->creator_user_id ? DialogParticipantStatus::Creator(true, false, string()) - : DialogParticipantStatus::Member()}); + : DialogParticipantStatus::Member(0)}); update_chat_online_member_count(chat_full, chat_id, false); chat_full->is_changed = true; update_chat_full(chat_full, chat_id, "on_update_chat_add_user"); @@ -6367,7 +6384,7 @@ void ChatManager::on_update_chat_edit_administrator(ChatId chat_id, UserId user_ CHECK(c->version >= 0); auto status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->status.is_creator()) - : DialogParticipantStatus::Member(); + : DialogParticipantStatus::Member(0); if (version > c->version) { if (version != c->version + 1) { LOG(INFO) << "Administrators of " << chat_id << " with version " << c->version @@ -7838,6 +7855,18 @@ bool ChatManager::get_channel_sign_messages(const Channel *c) { return c->sign_messages; } +bool ChatManager::get_channel_show_message_sender(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_show_message_sender(c); +} + +bool ChatManager::get_channel_show_message_sender(const Channel *c) { + return c->show_message_sender; +} + bool ChatManager::get_channel_has_linked_channel(ChannelId channel_id) const { auto c = get_channel(channel_id); if (c == nullptr) { @@ -8217,7 +8246,7 @@ void ChatManager::on_get_chat(telegram_api::chat &chat, const char *source) { } else if (has_left) { return DialogParticipantStatus::Left(); } else { - return DialogParticipantStatus::Member(); + return DialogParticipantStatus::Member(0); } }(); @@ -8362,6 +8391,7 @@ void ChatManager::on_get_channel(telegram_api::channel &channel, const char *sou int32 participant_count = have_participant_count ? channel.participants_count_ : 0; bool stories_available = channel.stories_max_id_ > 0; bool stories_unavailable = channel.stories_unavailable_; + bool show_message_sender = channel.signature_profiles_; auto boost_level = channel.level_; if (have_participant_count) { @@ -8381,8 +8411,10 @@ void ChatManager::on_get_channel(telegram_api::channel &channel, const char *sou if (is_megagroup) { LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id << " from " << source; sign_messages = true; + show_message_sender = true; } else { - LOG_IF(ERROR, is_slow_mode_enabled) << "Slow mode enabled in the " << channel_id << " from " << source; + LOG_IF(ERROR, is_slow_mode_enabled && channel_id.get() >= 8000000000) + << "Slow mode enabled in the " << channel_id << " from " << source; LOG_IF(ERROR, is_gigagroup) << "Receive broadcast group as " << channel_id << " from " << source; LOG_IF(ERROR, is_forum) << "Receive broadcast forum as " << channel_id << " from " << source; is_slow_mode_enabled = false; @@ -8394,27 +8426,6 @@ void ChatManager::on_get_channel(telegram_api::channel &channel, const char *sou SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); } - DialogParticipantStatus status = [&] { - bool has_left = (channel.flags_ & CHANNEL_FLAG_USER_HAS_LEFT) != 0; - bool is_creator = (channel.flags_ & CHANNEL_FLAG_USER_IS_CREATOR) != 0; - - if (is_creator) { - bool is_anonymous = channel.admin_rights_ != nullptr && - (channel.admin_rights_->flags_ & telegram_api::chatAdminRights::ANONYMOUS_MASK) != 0; - return DialogParticipantStatus::Creator(!has_left, is_anonymous, string()); - } else if (channel.admin_rights_ != nullptr) { - return DialogParticipantStatus(false, std::move(channel.admin_rights_), string(), - is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); - } else if (channel.banned_rights_ != nullptr) { - return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_), - is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); - } else if (has_left) { - return DialogParticipantStatus::Left(); - } else { - return DialogParticipantStatus::Member(); - } - }(); - if (is_min) { Channel *c = get_channel_force(channel_id, source); if (c != nullptr) { @@ -8500,6 +8511,26 @@ void ChatManager::on_get_channel(telegram_api::channel &channel, const char *sou return; } + DialogParticipantStatus status = [&] { + bool has_left = (channel.flags_ & CHANNEL_FLAG_USER_HAS_LEFT) != 0; + bool is_creator = (channel.flags_ & CHANNEL_FLAG_USER_IS_CREATOR) != 0; + + if (is_creator) { + bool is_anonymous = channel.admin_rights_ != nullptr && + (channel.admin_rights_->flags_ & telegram_api::chatAdminRights::ANONYMOUS_MASK) != 0; + return DialogParticipantStatus::Creator(!has_left, is_anonymous, string()); + } else if (channel.admin_rights_ != nullptr) { + return DialogParticipantStatus(false, std::move(channel.admin_rights_), string(), + is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); + } else if (channel.banned_rights_ != nullptr) { + return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_), + is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast); + } else if (has_left) { + return DialogParticipantStatus::Left(); + } else { + return DialogParticipantStatus::Member(channel.subscription_until_date_); + } + }(); if (status.is_creator()) { // to correctly calculate is_ownership_transferred in on_update_channel_status get_channel_force(channel_id, source); @@ -8556,9 +8587,11 @@ void ChatManager::on_get_channel(telegram_api::channel &channel, const char *sou c->need_save_to_database = true; } - if (c->is_verified != is_verified || c->sign_messages != sign_messages) { + if (c->is_verified != is_verified || c->sign_messages != sign_messages || + c->show_message_sender != show_message_sender) { c->is_verified = is_verified; c->sign_messages = sign_messages; + c->show_message_sender = show_message_sender; c->is_changed = true; } @@ -8649,6 +8682,7 @@ void ChatManager::on_get_channel_forbidden(telegram_api::channelForbidden &chann } bool sign_messages = false; + bool show_message_sender = false; bool join_to_send = false; bool join_request = false; bool is_slow_mode_enabled = false; @@ -8666,6 +8700,7 @@ void ChatManager::on_get_channel_forbidden(telegram_api::channelForbidden &chann if (is_megagroup) { sign_messages = true; + show_message_sender = true; } bool need_invalidate_channel_full = false; @@ -8690,9 +8725,11 @@ void ChatManager::on_get_channel_forbidden(telegram_api::channelForbidden &chann c->need_save_to_database = true; } - if (c->sign_messages != sign_messages || c->is_verified != is_verified) { - c->sign_messages = sign_messages; + if (c->is_verified != is_verified || c->sign_messages != sign_messages || + c->show_message_sender != show_message_sender) { c->is_verified = is_verified; + c->sign_messages = sign_messages; + c->show_message_sender = show_message_sender; c->is_changed = true; } @@ -8814,8 +8851,8 @@ td_api::object_ptr ChatManager::get_update_unknown_sup bool is_megagroup = min_channel == nullptr ? false : min_channel->is_megagroup_; return td_api::make_object(td_api::make_object( channel_id.get(), nullptr, 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, 0, false, - false, false, !is_megagroup, false, false, !is_megagroup, false, false, false, string(), false, false, false, - false)); + false, false, false, !is_megagroup, false, false, !is_megagroup, false, false, false, false, string(), false, + false, false, false)); } int64 ChatManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { @@ -8852,9 +8889,10 @@ tl_object_ptr ChatManager::get_supergroup_object(ChannelId c return td_api::make_object( channel_id.get(), c->usernames.get_usernames_object(), c->date, get_channel_status(c).get_chat_member_status_object(), c->participant_count, c->boost_level, - c->has_linked_channel, c->has_location, c->sign_messages, get_channel_join_to_send(c), + c->has_linked_channel, c->has_location, c->sign_messages, c->show_message_sender, get_channel_join_to_send(c), get_channel_join_request(c), c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_forum, - c->is_verified, get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake, + c->is_verified, get_restriction_reason_has_sensitive_content(c->restriction_reasons), + get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake, c->max_active_story_id.is_valid(), get_channel_has_unread_stories(c)); } @@ -8878,15 +8916,15 @@ tl_object_ptr ChatManager::get_supergroup_full_info_ get_chat_photo_object(td_->file_manager_.get(), channel_full->photo), channel_full->description, 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_sticker_set, - channel_full->can_set_location, channel_full->can_view_statistics, channel_full->can_view_revenue, - channel_full->can_view_star_revenue, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full).is_ok(), - channel_full->is_all_history_available, channel_full->can_have_sponsored_messages, - channel_full->has_aggressive_anti_spam_enabled, channel_full->has_paid_media_allowed, - channel_full->has_pinned_stories, channel_full->boost_count, channel_full->unrestrict_boost_count, - channel_full->sticker_set_id.get(), channel_full->emoji_sticker_set_id.get(), - channel_full->location.get_chat_location_object(), + slow_mode_delay_expires_in, channel_full->has_paid_media_allowed, channel_full->can_get_participants, + has_hidden_participants, 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, + channel_full->can_view_revenue, channel_full->can_view_star_revenue, + can_toggle_channel_aggressive_anti_spam(channel_id, channel_full).is_ok(), channel_full->is_all_history_available, + channel_full->can_have_sponsored_messages, channel_full->has_aggressive_anti_spam_enabled, + channel_full->has_paid_media_allowed, channel_full->has_pinned_stories, channel_full->boost_count, + channel_full->unrestrict_boost_count, channel_full->sticker_set_id.get(), + channel_full->emoji_sticker_set_id.get(), channel_full->location.get_chat_location_object(), channel_full->invite_link.get_chat_invite_link_object(td_->user_manager_.get()), std::move(bot_commands), get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_supergroup_full_info_object"), channel_full->migrated_from_max_message_id.get()); diff --git a/lib/tgchat/ext/td/td/telegram/ChatManager.h b/lib/tgchat/ext/td/td/telegram/ChatManager.h index 5a26304b..166dfd9b 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatManager.h +++ b/lib/tgchat/ext/td/td/telegram/ChatManager.h @@ -243,7 +243,8 @@ class ChatManager final : public Actor { void set_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count, Promise &&promise); - void toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise); + void toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, bool show_message_sender, + Promise &&promise); void toggle_channel_join_to_send(ChannelId channel_id, bool joint_to_send, Promise &&promise); @@ -351,6 +352,7 @@ class ChatManager final : public Actor { bool get_channel_is_fake(ChannelId channel_id) const; int32 get_channel_participant_count(ChannelId channel_id) const; bool get_channel_sign_messages(ChannelId channel_id) const; + bool get_channel_show_message_sender(ChannelId channel_id) const; bool get_channel_has_linked_channel(ChannelId channel_id) const; bool get_channel_join_request(ChannelId channel_id) const; bool get_channel_can_be_deleted(ChannelId channel_id) const; @@ -491,6 +493,7 @@ class ChatManager final : public Actor { bool has_linked_channel = false; bool has_location = false; bool sign_messages = false; + bool show_message_sender = false; bool is_slow_mode_enabled = false; bool noforwards = false; bool can_be_deleted = false; @@ -702,6 +705,7 @@ class ChatManager final : public Actor { static DialogParticipantStatus get_channel_status(const Channel *c); DialogParticipantStatus get_channel_permissions(ChannelId channel_id, const Channel *c) const; static bool get_channel_sign_messages(const Channel *c); + static bool get_channel_show_message_sender(const Channel *c); static bool get_channel_has_linked_channel(const Channel *c); static bool get_channel_can_be_deleted(const Channel *c); static bool get_channel_join_to_send(const Channel *c); diff --git a/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp b/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp index fa75c49f..a379a45e 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp @@ -10,11 +10,18 @@ #include "td/telegram/Td.h" #include "td/utils/algorithm.h" +#include "td/utils/logging.h" namespace td { +bool ChatReactions::remove_paid_reactions() { + return td::remove_if(reaction_types_, + [&](const ReactionType &reaction_type) { return reaction_type.is_paid_reaction(); }); +} + ChatReactions::ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, - int32 reactions_limit) { + int32 reactions_limit, bool paid_reactions_available) + : paid_reactions_available_(paid_reactions_available) { if (chat_reactions_ptr == nullptr) { return; } @@ -30,6 +37,9 @@ ChatReactions::ChatReactions(telegram_api::object_ptr(chat_reactions_ptr); reaction_types_ = ReactionType::get_reaction_types(chat_reactions->reactions_); + if (remove_paid_reactions()) { + LOG(ERROR) << "Receive paid reaction allowed"; + } break; } default: @@ -55,6 +65,7 @@ ChatReactions::ChatReactions(td_api::object_ptr auto chat_reactions = move_tl_object_as(chat_reactions_ptr); reaction_types_ = ReactionType::get_reaction_types(chat_reactions->reactions_); reactions_limit_ = chat_reactions->max_reaction_count_; + paid_reactions_available_ = remove_paid_reactions(); break; } default: @@ -80,6 +91,9 @@ bool ChatReactions::is_allowed_reaction_type(const ReactionType &reaction_type) if (allow_all_custom_ && reaction_type.is_custom_reaction()) { return true; } + if (reaction_type.is_paid_reaction() && paid_reactions_available_) { + return true; + } return td::contains(reaction_types_, reaction_type); } @@ -89,10 +103,11 @@ td_api::object_ptr ChatReactions::get_chat_avail reactions_uniq_max = reactions_limit_; } if (allow_all_regular_) { + LOG_IF(ERROR, paid_reactions_available_) << "Have paid reaction in a non-channel chat"; return td_api::make_object(reactions_uniq_max); } return td_api::make_object( - ReactionType::get_reaction_types_object(reaction_types_), reactions_uniq_max); + ReactionType::get_reaction_types_object(reaction_types_, paid_reactions_available_), reactions_uniq_max); } telegram_api::object_ptr ChatReactions::get_input_chat_reactions() const { @@ -110,16 +125,25 @@ telegram_api::object_ptr ChatReactions::get_input_c return telegram_api::make_object(); } +void ChatReactions::ignore_non_paid_reaction_types() { + reaction_types_.clear(); + allow_all_regular_ = false; + allow_all_custom_ = false; +} + bool operator==(const ChatReactions &lhs, const ChatReactions &rhs) { // don't compare allow_all_custom_ return lhs.reaction_types_ == rhs.reaction_types_ && lhs.allow_all_regular_ == rhs.allow_all_regular_ && - lhs.reactions_limit_ == rhs.reactions_limit_; + lhs.reactions_limit_ == rhs.reactions_limit_ && lhs.paid_reactions_available_ == rhs.paid_reactions_available_; } StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions) { if (reactions.reactions_limit_ != 0) { string_builder << '[' << reactions.reactions_limit_ << "] "; } + if (reactions.paid_reactions_available_) { + string_builder << "Paid"; + } if (reactions.allow_all_regular_) { if (reactions.allow_all_custom_) { return string_builder << "AllReactions"; diff --git a/lib/tgchat/ext/td/td/telegram/ChatReactions.h b/lib/tgchat/ext/td/td/telegram/ChatReactions.h index fc69b09d..f1ed0aea 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.h +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.h @@ -23,6 +23,7 @@ struct ChatReactions { bool allow_all_regular_ = false; // implies empty reaction_types_ bool allow_all_custom_ = false; // implies allow_all_regular_ int32 reactions_limit_ = 0; + bool paid_reactions_available_ = false; ChatReactions() = default; @@ -32,7 +33,8 @@ struct ChatReactions { return result; } - ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, int32 reactions_limit); + ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, int32 reactions_limit, + bool paid_reactions_available); ChatReactions(td_api::object_ptr &&chat_reactions_ptr, bool allow_all_custom); @@ -50,9 +52,13 @@ struct ChatReactions { td_api::object_ptr get_chat_available_reactions_object(Td *td) const; bool empty() const { - return reaction_types_.empty() && !allow_all_regular_; + return reaction_types_.empty() && !allow_all_regular_ && !paid_reactions_available_; } + void ignore_non_paid_reaction_types(); + + bool remove_paid_reactions(); + template void store(StorerT &storer) const; diff --git a/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp b/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp index 0a3d40ef..692049f9 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp @@ -23,6 +23,7 @@ void ChatReactions::store(StorerT &storer) const { STORE_FLAG(allow_all_custom_); STORE_FLAG(has_reactions); STORE_FLAG(has_reactions_limit); + STORE_FLAG(paid_reactions_available_); END_STORE_FLAGS(); if (has_reactions) { td::store(reaction_types_, storer); @@ -41,6 +42,7 @@ void ChatReactions::parse(ParserT &parser) { PARSE_FLAG(allow_all_custom_); PARSE_FLAG(has_reactions); PARSE_FLAG(has_reactions_limit); + PARSE_FLAG(paid_reactions_available_); END_PARSE_FLAGS(); if (has_reactions) { td::parse(reaction_types_, parser); diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp index e75cb9c2..a6b6dc65 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp @@ -419,14 +419,14 @@ static ActorOwn<> get_full_config(DcOption option, Promise public_rsa_key_; vector> auth_key_listeners_; - + /* void notify() { td::remove_if(auth_key_listeners_, [&](auto &listener) { CHECK(listener != nullptr); return !listener->notify(); }); } - + */ string auth_key_key() const { return PSTRING() << "config_recovery_auth" << dc_id().get_raw_id(); } @@ -1043,11 +1043,12 @@ void ConfigManager::request_config_from_dc_impl(DcId dc_id, bool reopen_sessions } void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions) { - G()->set_option_boolean("ignore_sensitive_content_restrictions", ignore_sensitive_content_restrictions); - bool have_ignored_restriction_reasons = G()->have_option("ignored_restriction_reasons"); - if (have_ignored_restriction_reasons != ignore_sensitive_content_restrictions) { - reget_app_config(Auto()); + if (G()->have_option("ignore_sensitive_content_restrictions") && + G()->get_option_boolean("ignore_sensitive_content_restrictions") == ignore_sensitive_content_restrictions) { + return; } + G()->set_option_boolean("ignore_sensitive_content_restrictions", ignore_sensitive_content_restrictions); + reget_app_config(Auto()); } void ConfigManager::hide_suggested_action(SuggestedAction suggested_action) { @@ -1348,7 +1349,7 @@ void ConfigManager::process_config(tl_object_ptr config) { if (is_from_main_dc && !options.have_option("default_reaction_need_sync")) { ReactionType reaction_type(config->reactions_default_); - if (!reaction_type.is_empty()) { + if (!reaction_type.is_empty() && !reaction_type.is_paid_reaction()) { options.set_option_string("default_reaction", reaction_type.get_string()); } } @@ -2054,6 +2055,22 @@ void ConfigManager::process_app_config(tl_object_ptr &c G()->set_option_boolean("can_gift_stars", get_json_value_bool(std::move(key_value->value_), key)); continue; } + if (key == "stars_paid_reaction_amount_max") { + G()->set_option_integer("paid_reaction_star_count_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stars_subscription_amount_max") { + G()->set_option_integer("subscription_star_count_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stars_usd_sell_rate_x1000") { + G()->set_option_integer("usd_to_1000_star_rate", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "stars_usd_withdraw_rate_x1000") { + G()->set_option_integer("1000_star_to_usd_rate", get_json_value_int(std::move(key_value->value_), key)); + continue; + } new_values.push_back(std::move(key_value)); } diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.h b/lib/tgchat/ext/td/td/telegram/ConfigManager.h index d299f4db..787e84b7 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.h +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.h @@ -85,7 +85,7 @@ class ConfigManager final : public NetQueryCallback { private: struct AppConfig { - static constexpr int32 CURRENT_VERSION = 56; + static constexpr int32 CURRENT_VERSION = 59; int32 version_ = 0; int32 hash_ = 0; telegram_api::object_ptr config_; diff --git a/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.cpp b/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.cpp new file mode 100644 index 00000000..f760fb4a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.cpp @@ -0,0 +1,61 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/ConnectionStateManager.h" + +#include "td/telegram/Global.h" +#include "td/telegram/StateManager.h" +#include "td/telegram/Td.h" + +#include "td/utils/logging.h" + +namespace td { + +ConnectionStateManager::ConnectionStateManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void ConnectionStateManager::tear_down() { + parent_.reset(); +} + +void ConnectionStateManager::start_up() { + class StateCallback final : public StateManager::Callback { + public: + explicit StateCallback(ActorId parent) : parent_(std::move(parent)) { + } + bool on_state(ConnectionState state) final { + send_closure(parent_, &ConnectionStateManager::on_connection_state_changed, state); + return parent_.is_alive(); + } + + private: + ActorId parent_; + }; + send_closure(td_->state_manager_, &StateManager::add_callback, make_unique(actor_id(this))); +} + +void ConnectionStateManager::on_connection_state_changed(ConnectionState new_state) { + if (G()->close_flag()) { + return; + } + if (new_state == connection_state_) { + LOG(ERROR) << "State manager sent update about unchanged state " << static_cast(new_state); + return; + } + connection_state_ = new_state; + + send_closure(G()->td(), &Td::send_update, get_update_connection_state_object(connection_state_)); +} + +void ConnectionStateManager::get_current_state(vector> &updates) const { + if (connection_state_ == ConnectionState::Empty) { + return; + } + + updates.push_back(get_update_connection_state_object(connection_state_)); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.h b/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.h new file mode 100644 index 00000000..a0201539 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ConnectionStateManager.h @@ -0,0 +1,39 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/ConnectionState.h" +#include "td/telegram/td_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" + +namespace td { + +class Td; + +class ConnectionStateManager final : public Actor { + public: + ConnectionStateManager(Td *td, ActorShared<> parent); + + void get_current_state(vector> &updates) const; + + private: + void tear_down() final; + + void start_up() final; + + void on_connection_state_changed(ConnectionState new_state); + + ConnectionState connection_state_ = ConnectionState::Empty; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp index a02ee1af..4b5f69a1 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp @@ -48,7 +48,7 @@ static td_api::object_ptr get_chat_event_action_object( return td_api::make_object(); case telegram_api::channelAdminLogEventActionParticipantJoinByInvite::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_), true, + DialogInviteLink invite_link(std::move(action->invite_), true, false, "channelAdminLogEventActionParticipantJoinByInvite"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong invite link: " << invite_link; @@ -59,7 +59,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionParticipantJoinByRequest::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_), true, + DialogInviteLink invite_link(std::move(action->invite_), true, true, "channelAdminLogEventActionParticipantJoinByRequest"); UserId approver_user_id(action->approved_by_); if (!approver_user_id.is_valid()) { @@ -165,6 +165,10 @@ static td_api::object_ptr get_chat_event_action_object( auto action = move_tl_object_as(action_ptr); return td_api::make_object(action->new_value_); } + case telegram_api::channelAdminLogEventActionToggleSignatureProfiles::ID: { + auto action = move_tl_object_as(action_ptr); + return td_api::make_object(action->new_value_); + } case telegram_api::channelAdminLogEventActionUpdatePinned::ID: { auto action = move_tl_object_as(action_ptr); auto message = td->messages_manager_->get_dialog_event_log_message_object( @@ -272,9 +276,9 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteEdit::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink old_invite_link(std::move(action->prev_invite_), true, + DialogInviteLink old_invite_link(std::move(action->prev_invite_), true, false, "channelAdminLogEventActionExportedInviteEdit"); - DialogInviteLink new_invite_link(std::move(action->new_invite_), true, + DialogInviteLink new_invite_link(std::move(action->new_invite_), true, false, "channelAdminLogEventActionExportedInviteEdit"); if (!old_invite_link.is_valid() || !new_invite_link.is_valid()) { LOG(ERROR) << "Wrong edited invite link: " << old_invite_link << " -> " << new_invite_link; @@ -286,7 +290,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteRevoke::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_), true, "channelAdminLogEventActionExportedInviteRevoke"); + DialogInviteLink invite_link(std::move(action->invite_), true, false, + "channelAdminLogEventActionExportedInviteRevoke"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong revoked invite link: " << invite_link; return nullptr; @@ -296,7 +301,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionExportedInviteDelete::ID: { auto action = move_tl_object_as(action_ptr); - DialogInviteLink invite_link(std::move(action->invite_), true, "channelAdminLogEventActionExportedInviteDelete"); + DialogInviteLink invite_link(std::move(action->invite_), true, false, + "channelAdminLogEventActionExportedInviteDelete"); if (!invite_link.is_valid()) { LOG(ERROR) << "Wrong deleted invite link: " << invite_link; return nullptr; @@ -367,8 +373,8 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionChangeAvailableReactions::ID: { auto action = move_tl_object_as(action_ptr); - ChatReactions old_available_reactions(std::move(action->prev_value_), 0); - ChatReactions new_available_reactions(std::move(action->new_value_), 0); + ChatReactions old_available_reactions(std::move(action->prev_value_), 0, false); + ChatReactions new_available_reactions(std::move(action->new_value_), 0, false); return td_api::make_object( old_available_reactions.get_chat_available_reactions_object(td), new_available_reactions.get_chat_available_reactions_object(td)); diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp index f0e0b21b..a0aa2833 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp @@ -15,29 +15,29 @@ namespace td { -DialogInviteLink::DialogInviteLink(tl_object_ptr exported_invite_ptr, - bool allow_truncated, const char *source) { +DialogInviteLink::DialogInviteLink(telegram_api::object_ptr exported_invite_ptr, + bool allow_truncated, bool expect_join_request, const char *source) { if (exported_invite_ptr == nullptr) { return; } if (exported_invite_ptr->get_id() != telegram_api::chatInviteExported::ID) { CHECK(exported_invite_ptr->get_id() == telegram_api::chatInvitePublicJoinRequests::ID); - Slice slice(source); - if (slice != "channelAdminLogEventActionParticipantJoinByRequest" && slice != "updateChatParticipant" && - slice != "updateChannelParticipant" && slice != "updateBotChatInviteRequester") { + if (!expect_join_request) { LOG(ERROR) << "Receive from " << source << ' ' << to_string(exported_invite_ptr); } return; } - auto exported_invite = move_tl_object_as(exported_invite_ptr); + auto exported_invite = telegram_api::move_object_as(exported_invite_ptr); invite_link_ = std::move(exported_invite->link_); title_ = std::move(exported_invite->title_); creator_user_id_ = UserId(exported_invite->admin_id_); + pricing_ = StarSubscriptionPricing(std::move(exported_invite->subscription_pricing_)); date_ = exported_invite->date_; expire_date_ = exported_invite->expire_date_; usage_limit_ = exported_invite->usage_limit_; usage_count_ = exported_invite->usage_; + expired_usage_count_ = exported_invite->subscription_expired_; edit_date_ = exported_invite->start_date_; request_count_ = exported_invite->requested_; creates_join_request_ = exported_invite->request_needed_; @@ -66,6 +66,10 @@ DialogInviteLink::DialogInviteLink(tl_object_ptr DialogInviteLink::get_chat_invite_lin return td_api::make_object( invite_link_, title_, user_manager->get_user_id_object(creator_user_id_, "get_chat_invite_link_object"), date_, - edit_date_, expire_date_, usage_limit_, usage_count_, request_count_, creates_join_request_, is_permanent_, - is_revoked_); + edit_date_, expire_date_, pricing_.get_star_subscription_pricing_object(), usage_limit_, usage_count_, + expired_usage_count_, request_count_, creates_join_request_, is_permanent_, is_revoked_); } bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs) { return lhs.invite_link_ == rhs.invite_link_ && lhs.title_ == rhs.title_ && - lhs.creator_user_id_ == rhs.creator_user_id_ && lhs.date_ == rhs.date_ && lhs.edit_date_ == rhs.edit_date_ && - lhs.expire_date_ == rhs.expire_date_ && lhs.usage_limit_ == rhs.usage_limit_ && - lhs.usage_count_ == rhs.usage_count_ && lhs.request_count_ == rhs.request_count_ && + lhs.creator_user_id_ == rhs.creator_user_id_ && lhs.pricing_ == rhs.pricing_ && lhs.date_ == rhs.date_ && + lhs.edit_date_ == rhs.edit_date_ && lhs.expire_date_ == rhs.expire_date_ && + lhs.usage_limit_ == rhs.usage_limit_ && lhs.usage_count_ == rhs.usage_count_ && + lhs.expired_usage_count_ == rhs.expired_usage_count_ && lhs.request_count_ == rhs.request_count_ && lhs.creates_join_request_ == rhs.creates_join_request_ && lhs.is_permanent_ == rhs.is_permanent_ && lhs.is_revoked_ == rhs.is_revoked_; } @@ -129,8 +134,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink << (invite_link.creates_join_request_ ? " creating join request" : "") << " by " << invite_link.creator_user_id_ << " created at " << invite_link.date_ << " edited at " << invite_link.edit_date_ << " expiring at " << invite_link.expire_date_ << " used by " - << invite_link.usage_count_ << " with usage limit " << invite_link.usage_limit_ << " and " - << invite_link.request_count_ << " pending join requests]"; + << invite_link.usage_count_ << " + " << invite_link.expired_usage_count_ << " with usage limit " + << invite_link.usage_limit_ << ", " << invite_link.request_count_ + << " pending join requests and " << invite_link.pricing_ << "]"; } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h index accc0415..6c2345c8 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/StarSubscriptionPricing.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -22,11 +23,13 @@ class DialogInviteLink { string invite_link_; string title_; UserId creator_user_id_; + StarSubscriptionPricing pricing_; int32 date_ = 0; int32 edit_date_ = 0; int32 expire_date_ = 0; int32 usage_limit_ = 0; int32 usage_count_ = 0; + int32 expired_usage_count_ = 0; int32 request_count_ = 0; bool creates_join_request_ = false; bool is_revoked_ = false; @@ -39,8 +42,8 @@ class DialogInviteLink { public: DialogInviteLink() = default; - DialogInviteLink(tl_object_ptr exported_invite_ptr, bool allow_truncated, - const char *source); + DialogInviteLink(telegram_api::object_ptr exported_invite_ptr, bool allow_truncated, + bool expect_join_request, const char *source); static bool is_valid_invite_link(Slice invite_link, bool allow_truncated = false); diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.hpp b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.hpp index c26d702a..2d8cccae 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.hpp +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.hpp @@ -8,7 +8,8 @@ #include "td/telegram/DialogInviteLink.h" -#include "td/utils/common.h" +#include "td/telegram/StarSubscriptionPricing.hpp" + #include "td/utils/tl_helpers.h" namespace td { @@ -22,6 +23,8 @@ void DialogInviteLink::store(StorerT &storer) const { bool has_edit_date = edit_date_ != 0; bool has_request_count = request_count_ != 0; bool has_title = !title_.empty(); + bool has_pricing = !pricing_.is_empty(); + bool has_expired_usage_count = expired_usage_count_ != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(is_revoked_); STORE_FLAG(is_permanent_); @@ -32,6 +35,8 @@ void DialogInviteLink::store(StorerT &storer) const { STORE_FLAG(has_request_count); STORE_FLAG(creates_join_request_); STORE_FLAG(has_title); + STORE_FLAG(has_pricing); + STORE_FLAG(has_expired_usage_count); END_STORE_FLAGS(); store(invite_link_, storer); store(creator_user_id_, storer); @@ -54,6 +59,12 @@ void DialogInviteLink::store(StorerT &storer) const { if (has_title) { store(title_, storer); } + if (has_pricing) { + store(pricing_, storer); + } + if (has_expired_usage_count) { + store(expired_usage_count_, storer); + } } template @@ -65,6 +76,8 @@ void DialogInviteLink::parse(ParserT &parser) { bool has_edit_date; bool has_request_count; bool has_title; + bool has_pricing; + bool has_expired_usage_count; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_revoked_); PARSE_FLAG(is_permanent_); @@ -75,6 +88,8 @@ void DialogInviteLink::parse(ParserT &parser) { PARSE_FLAG(has_request_count); PARSE_FLAG(creates_join_request_); PARSE_FLAG(has_title); + PARSE_FLAG(has_pricing); + PARSE_FLAG(has_expired_usage_count); END_PARSE_FLAGS(); parse(invite_link_, parser); parse(creator_user_id_, parser); @@ -97,6 +112,12 @@ void DialogInviteLink::parse(ParserT &parser) { if (has_title) { parse(title_, parser); } + if (has_pricing) { + parse(pricing_, parser); + } + if (has_expired_usage_count) { + parse(expired_usage_count_, parser); + } if (creates_join_request_) { usage_limit_ = 0; } diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp index d1f20746..155dad3e 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp @@ -111,7 +111,7 @@ class ExportChatInviteQuery final : public Td::ResultHandler { } void send(DialogId dialog_id, const string &title, int32 expire_date, int32 usage_limit, bool creates_join_request, - bool is_permanent) { + StarSubscriptionPricing subscription_pricing, bool is_permanent) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); @@ -132,9 +132,13 @@ class ExportChatInviteQuery final : public Td::ResultHandler { if (!title.empty()) { flags |= telegram_api::messages_exportChatInvite::TITLE_MASK; } + if (!subscription_pricing.is_empty()) { + flags |= telegram_api::messages_exportChatInvite::SUBSCRIPTION_PRICING_MASK; + } send_query(G()->net_query_creator().create(telegram_api::messages_exportChatInvite( - flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), expire_date, usage_limit, title))); + flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), expire_date, usage_limit, title, + subscription_pricing.get_input_stars_subscription_pricing()))); } void on_result(BufferSlice packet) final { @@ -146,7 +150,7 @@ class ExportChatInviteQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for ExportChatInviteQuery: " << to_string(ptr); - DialogInviteLink invite_link(std::move(ptr), false, "ExportChatInviteQuery"); + DialogInviteLink invite_link(std::move(ptr), false, false, "ExportChatInviteQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -175,15 +179,17 @@ class EditChatInviteLinkQuery final : public Td::ResultHandler { } void send(DialogId dialog_id, const string &invite_link, const string &title, int32 expire_date, int32 usage_limit, - bool creates_join_request) { + bool creates_join_request, bool is_subscription) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); - int32 flags = telegram_api::messages_editExportedChatInvite::EXPIRE_DATE_MASK | - telegram_api::messages_editExportedChatInvite::USAGE_LIMIT_MASK | - telegram_api::messages_editExportedChatInvite::REQUEST_NEEDED_MASK | - telegram_api::messages_editExportedChatInvite::TITLE_MASK; + int32 flags = telegram_api::messages_editExportedChatInvite::TITLE_MASK; + if (!is_subscription) { + flags |= telegram_api::messages_editExportedChatInvite::EXPIRE_DATE_MASK | + telegram_api::messages_editExportedChatInvite::USAGE_LIMIT_MASK | + telegram_api::messages_editExportedChatInvite::REQUEST_NEEDED_MASK; + } send_query(G()->net_query_creator().create( telegram_api::messages_editExportedChatInvite(flags, false /*ignored*/, std::move(input_peer), invite_link, expire_date, usage_limit, creates_join_request, title))); @@ -206,7 +212,7 @@ class EditChatInviteLinkQuery final : public Td::ResultHandler { td_->user_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery"); - DialogInviteLink invite_link(std::move(invite->invite_), false, "EditChatInviteLinkQuery"); + DialogInviteLink invite_link(std::move(invite->invite_), false, false, "EditChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -253,7 +259,7 @@ class GetExportedChatInviteQuery final : public Td::ResultHandler { td_->user_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery"); - DialogInviteLink invite_link(std::move(result->invite_), false, "GetExportedChatInviteQuery"); + DialogInviteLink invite_link(std::move(result->invite_), false, false, "GetExportedChatInviteQuery"); if (!invite_link.is_valid()) { LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; return on_error(Status::Error(500, "Receive invalid invite link")); @@ -313,7 +319,7 @@ class GetExportedChatInvitesQuery final : public Td::ResultHandler { } vector> invite_links; for (auto &invite : result->invites_) { - DialogInviteLink invite_link(std::move(invite), false, "GetExportedChatInvitesQuery"); + DialogInviteLink invite_link(std::move(invite), false, false, "GetExportedChatInvitesQuery"); if (!invite_link.is_valid()) { LOG(ERROR) << "Receive invalid invite link in " << dialog_id_; total_count--; @@ -387,7 +393,8 @@ class GetChatInviteImportersQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(DialogId dialog_id, const string &invite_link, int32 offset_date, UserId offset_user_id, int32 limit) { + void send(DialogId dialog_id, const string &invite_link, bool subscription_expired, int32 offset_date, + UserId offset_user_id, int32 limit) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); CHECK(input_peer != nullptr); @@ -398,9 +405,12 @@ class GetChatInviteImportersQuery final : public Td::ResultHandler { } int32 flags = telegram_api::messages_getChatInviteImporters::LINK_MASK; - send_query(G()->net_query_creator().create( - telegram_api::messages_getChatInviteImporters(flags, false /*ignored*/, std::move(input_peer), invite_link, - string(), offset_date, r_input_user.move_as_ok(), limit))); + if (subscription_expired) { + flags |= telegram_api::messages_getChatInviteImporters::SUBSCRIPTION_EXPIRED_MASK; + } + send_query(G()->net_query_creator().create(telegram_api::messages_getChatInviteImporters( + flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), invite_link, string(), offset_date, + r_input_user.move_as_ok(), limit))); } void on_result(BufferSlice packet) final { @@ -477,7 +487,7 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { td_->user_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); - DialogInviteLink invite_link(std::move(invite->invite_), false, "RevokeChatInviteLinkQuery"); + DialogInviteLink invite_link(std::move(invite->invite_), false, false, "RevokeChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } @@ -489,8 +499,8 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { td_->user_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery replaced"); - DialogInviteLink invite_link(std::move(invite->invite_), false, "RevokeChatInviteLinkQuery replaced"); - DialogInviteLink new_invite_link(std::move(invite->new_invite_), false, + DialogInviteLink invite_link(std::move(invite->invite_), false, false, "RevokeChatInviteLinkQuery replaced"); + DialogInviteLink new_invite_link(std::move(invite->new_invite_), false, false, "RevokeChatInviteLinkQuery new replaced"); if (!invite_link.is_valid() || !new_invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); @@ -730,6 +740,9 @@ void DialogInviteLinkManager::on_get_dialog_invite_link_info( invite_link_info->description = std::move(chat_invite->about_); invite_link_info->participant_count = chat_invite->participants_count_; invite_link_info->participant_user_ids = std::move(participant_user_ids); + invite_link_info->subscription_pricing = StarSubscriptionPricing(std::move(chat_invite->subscription_pricing_)); + invite_link_info->subscription_form_id = chat_invite->subscription_form_id_; + invite_link_info->can_refulfill_subscription = chat_invite->can_refulfill_subscription_; invite_link_info->creates_join_request = std::move(chat_invite->request_needed_); invite_link_info->is_chat = !chat_invite->channel_; invite_link_info->is_channel = chat_invite->channel_; @@ -772,7 +785,7 @@ td_api::object_ptr DialogInviteLinkManager::get_chat return nullptr; } - auto invite_link_info = it->second.get(); + const auto *invite_link_info = it->second.get(); CHECK(invite_link_info != nullptr); DialogId dialog_id = invite_link_info->dialog_id; @@ -785,6 +798,7 @@ td_api::object_ptr DialogInviteLinkManager::get_chat string description; int32 participant_count = 0; vector member_user_ids; + td_api::object_ptr subscription_info; bool creates_join_request = false; bool is_public = false; bool is_member = false; @@ -834,6 +848,12 @@ td_api::object_ptr DialogInviteLinkManager::get_chat participant_count = invite_link_info->participant_count; member_user_ids = td_->user_manager_->get_user_ids_object(invite_link_info->participant_user_ids, "get_chat_invite_link_info_object"); + auto subscription_pricing = invite_link_info->subscription_pricing.get_star_subscription_pricing_object(); + if (subscription_pricing != nullptr) { + subscription_info = td_api::make_object( + std::move(subscription_pricing), invite_link_info->can_refulfill_subscription, + invite_link_info->subscription_form_id); + } creates_join_request = invite_link_info->creates_join_request; is_public = invite_link_info->is_public; is_verified = invite_link_info->is_verified; @@ -861,7 +881,8 @@ td_api::object_ptr DialogInviteLinkManager::get_chat return td_api::make_object( td_->dialog_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), accent_color_id_object, description, - participant_count, std::move(member_user_ids), creates_join_request, is_public, is_verified, is_scam, is_fake); + participant_count, std::move(member_user_ids), std::move(subscription_info), creates_join_request, is_public, + is_verified, is_scam, is_fake); } void DialogInviteLinkManager::add_dialog_access_by_invite_link(DialogId dialog_id, const string &invite_link, @@ -957,37 +978,53 @@ void DialogInviteLinkManager::on_get_permanent_dialog_invite_link(DialogId dialo } void DialogInviteLinkManager::export_dialog_invite_link(DialogId dialog_id, string title, int32 expire_date, - int32 usage_limit, bool creates_join_request, bool is_permanent, + int32 usage_limit, bool creates_join_request, + StarSubscriptionPricing subscription_pricing, + bool is_subscription, bool is_permanent, Promise> &&promise) { - td_->user_manager_->get_me(PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, title = std::move(title), - expire_date, usage_limit, creates_join_request, is_permanent, - promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &DialogInviteLinkManager::export_dialog_invite_link_impl, dialog_id, std::move(title), - expire_date, usage_limit, creates_join_request, is_permanent, std::move(promise)); + if (is_subscription) { + if (subscription_pricing.is_empty()) { + return promise.set_error(Status::Error(400, "Invalid subscription pricing specified")); } - })); + } else { + CHECK(subscription_pricing.is_empty()); + } + td_->user_manager_->get_me(PromiseCreator::lambda( + [actor_id = actor_id(this), dialog_id, title = std::move(title), expire_date, usage_limit, creates_join_request, + subscription_pricing, is_permanent, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &DialogInviteLinkManager::export_dialog_invite_link_impl, dialog_id, std::move(title), + expire_date, usage_limit, creates_join_request, subscription_pricing, is_permanent, + std::move(promise)); + } + })); } void DialogInviteLinkManager::export_dialog_invite_link_impl( DialogId dialog_id, string title, int32 expire_date, int32 usage_limit, bool creates_join_request, - bool is_permanent, Promise> &&promise) { + StarSubscriptionPricing subscription_pricing, bool is_permanent, + Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); if (creates_join_request && usage_limit > 0) { return promise.set_error( Status::Error(400, "Member limit can't be specified for links requiring administrator approval")); } + if ((expire_date || usage_limit || creates_join_request) && !subscription_pricing.is_empty()) { + return promise.set_error( + Status::Error(400, "Subscription plan can't be specified for links with additional restrictions")); + } auto new_title = clean_name(std::move(title), MAX_INVITE_LINK_TITLE_LENGTH); td_->create_handler(std::move(promise)) - ->send(dialog_id, new_title, expire_date, usage_limit, creates_join_request, is_permanent); + ->send(dialog_id, new_title, expire_date, usage_limit, creates_join_request, subscription_pricing, is_permanent); } void DialogInviteLinkManager::edit_dialog_invite_link(DialogId dialog_id, const string &invite_link, string title, int32 expire_date, int32 usage_limit, bool creates_join_request, + bool is_subscription, Promise> &&promise) { TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); if (creates_join_request && usage_limit > 0) { @@ -1001,7 +1038,7 @@ void DialogInviteLinkManager::edit_dialog_invite_link(DialogId dialog_id, const auto new_title = clean_name(std::move(title), MAX_INVITE_LINK_TITLE_LENGTH); td_->create_handler(std::move(promise)) - ->send(dialog_id, invite_link, new_title, expire_date, usage_limit, creates_join_request); + ->send(dialog_id, invite_link, new_title, expire_date, usage_limit, creates_join_request, is_subscription); } void DialogInviteLinkManager::get_dialog_invite_link(DialogId dialog_id, const string &invite_link, @@ -1038,8 +1075,9 @@ void DialogInviteLinkManager::get_dialog_invite_links(DialogId dialog_id, UserId } void DialogInviteLinkManager::get_dialog_invite_link_users( - DialogId dialog_id, const string &invite_link, td_api::object_ptr offset_member, - int32 limit, Promise> &&promise) { + DialogId dialog_id, const string &invite_link, bool subscription_expired, + td_api::object_ptr offset_member, int32 limit, + Promise> &&promise) { TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id)); if (limit <= 0) { @@ -1058,7 +1096,7 @@ void DialogInviteLinkManager::get_dialog_invite_link_users( } td_->create_handler(std::move(promise)) - ->send(dialog_id, invite_link, offset_date, offset_user_id, limit); + ->send(dialog_id, invite_link, subscription_expired, offset_date, offset_user_id, limit); } void DialogInviteLinkManager::revoke_dialog_invite_link( diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h index b9f72711..5f003b85 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h @@ -9,6 +9,7 @@ #include "td/telegram/AccentColorId.h" #include "td/telegram/DialogId.h" #include "td/telegram/Photo.h" +#include "td/telegram/StarSubscriptionPricing.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -55,11 +56,12 @@ class DialogInviteLinkManager final : public Actor { void remove_dialog_access_by_invite_link(DialogId dialog_id); void export_dialog_invite_link(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit, - bool creates_join_request, bool is_permanent, + bool creates_join_request, StarSubscriptionPricing subscription_pricing, + bool is_subscription, bool is_permanent, Promise> &&promise); void edit_dialog_invite_link(DialogId dialog_id, const string &link, string title, int32 expire_date, - int32 usage_limit, bool creates_join_request, + int32 usage_limit, bool creates_join_request, bool is_subscription, Promise> &&promise); void get_dialog_invite_link(DialogId dialog_id, const string &invite_link, @@ -72,7 +74,7 @@ class DialogInviteLinkManager final : public Actor { const string &offset_invite_link, int32 limit, Promise> &&promise); - void get_dialog_invite_link_users(DialogId dialog_id, const string &invite_link, + void get_dialog_invite_link_users(DialogId dialog_id, const string &invite_link, bool subscription_expired, td_api::object_ptr offset_member, int32 limit, Promise> &&promise); @@ -97,8 +99,8 @@ class DialogInviteLinkManager final : public Actor { int32 get_dialog_accessible_by_invite_link_before_date(DialogId dialog_id) const; void export_dialog_invite_link_impl(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit, - bool creates_join_request, bool is_permanent, - Promise> &&promise); + bool creates_join_request, StarSubscriptionPricing subscription_pricing, + bool is_permanent, Promise> &&promise); Status can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only = false); @@ -113,7 +115,10 @@ class DialogInviteLinkManager final : public Actor { int32 participant_count = 0; vector participant_user_ids; string description; + StarSubscriptionPricing subscription_pricing; + int64 subscription_form_id; bool creates_join_request = false; + bool can_refulfill_subscription = false; bool is_chat = false; bool is_channel = false; bool is_public = false; diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp index fa8bb226..b78f3df6 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp @@ -433,7 +433,7 @@ DialogParticipantStatus DialogParticipantStatus::Administrator(AdministratorRigh bool can_be_edited) { uint64 flags = administrator_rights.flags_; if (flags == 0) { - return Member(); + return Member(0); } flags = flags | (static_cast(can_be_edited) * CAN_BE_EDITED); return DialogParticipantStatus( @@ -442,15 +442,16 @@ DialogParticipantStatus DialogParticipantStatus::Administrator(AdministratorRigh std::move(rank)); } -DialogParticipantStatus DialogParticipantStatus::Member() { - return DialogParticipantStatus(Type::Member, IS_MEMBER | RestrictedRights::ALL_RESTRICTED_RIGHTS, 0, string()); +DialogParticipantStatus DialogParticipantStatus::Member(int32 member_until_date) { + return DialogParticipantStatus(Type::Member, IS_MEMBER | RestrictedRights::ALL_RESTRICTED_RIGHTS, member_until_date, + string()); } DialogParticipantStatus DialogParticipantStatus::Restricted(RestrictedRights restricted_rights, bool is_member, int32 restricted_until_date, ChannelType channel_type) { uint64 flags = restricted_rights.flags_; if (flags == RestrictedRights::ALL_RESTRICTED_RIGHTS || channel_type == ChannelType::Broadcast) { - return is_member ? Member() : Left(); + return is_member ? Member(0) : Left(); } flags |= (static_cast(is_member) * IS_MEMBER); return DialogParticipantStatus(Type::Restricted, flags, fix_until_date(restricted_until_date), string()); @@ -494,7 +495,7 @@ DialogParticipantStatus::DialogParticipantStatus(bool is_member, return; } if (channel_type == ChannelType::Broadcast) { - *this = is_member ? Member() : Left(); + *this = is_member ? Member(0) : Left(); return; } @@ -522,7 +523,7 @@ tl_object_ptr DialogParticipantStatus::get_chat_member return td_api::make_object( rank_, can_be_edited(), get_administrator_rights().get_chat_administrator_rights_object()); case Type::Member: - return td_api::make_object(); + return td_api::make_object(until_date_); case Type::Restricted: return td_api::make_object( is_member(), until_date_, get_restricted_rights().get_chat_permissions_object()); @@ -596,7 +597,7 @@ void DialogParticipantStatus::update_restrictions() const { type_ = Type::Left; } flags_ |= RestrictedRights::ALL_RESTRICTED_RIGHTS; - } else if (type_ == Type::Banned) { + } else if (type_ == Type::Banned || type_ == Type::Member) { type_ = Type::Left; } else { UNREACHABLE(); @@ -637,7 +638,11 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant } return string_builder; case DialogParticipantStatus::Type::Member: - return string_builder << "Member"; + string_builder << "Member"; + if (status.until_date_ != 0) { + string_builder << " until " << status.until_date_; + } + return string_builder; case DialogParticipantStatus::Type::Restricted: string_builder << status.get_restricted_rights(); if (status.until_date_ == 0) { @@ -702,7 +707,7 @@ DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptrcan_be_edited_*/); } case td_api::chatMemberStatusMember::ID: - return DialogParticipantStatus::Member(); + return DialogParticipantStatus::Member(0); case td_api::chatMemberStatusRestricted::ID: { auto st = static_cast(status.get()); return DialogParticipantStatus::Restricted(RestrictedRights(st->permissions_, channel_type), st->is_member_, @@ -716,7 +721,7 @@ DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr(participant_ptr); *this = {DialogId(UserId(participant->user_id_)), UserId(participant->inviter_id_), participant->date_, - DialogParticipantStatus::Member()}; + DialogParticipantStatus::Member(0)}; break; } case telegram_api::chatParticipantCreator::ID: { @@ -767,13 +772,13 @@ DialogParticipant::DialogParticipant(tl_object_ptr(participant_ptr); *this = {DialogId(UserId(participant->user_id_)), UserId(), participant->date_, - DialogParticipantStatus::Member()}; + DialogParticipantStatus::Member(participant->subscription_until_date_)}; break; } case telegram_api::channelParticipantSelf::ID: { auto participant = move_tl_object_as(participant_ptr); *this = {DialogId(UserId(participant->user_id_)), UserId(participant->inviter_id_), participant->date_, - DialogParticipantStatus::Member()}; + DialogParticipantStatus::Member(participant->subscription_until_date_)}; break; } case telegram_api::channelParticipantCreator::ID: { diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipant.h b/lib/tgchat/ext/td/td/telegram/DialogParticipant.h index 5f96cdce..2f56fdc6 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipant.h +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipant.h @@ -321,7 +321,7 @@ class DialogParticipantStatus { enum class Type : int32 { Creator, Administrator, Member, Restricted, Left, Banned }; // all fields are logically const, but should be updated in update_restrictions() mutable Type type_; - mutable int32 until_date_; // restricted and banned only + mutable int32 until_date_; // member, restricted and banned only mutable uint64 flags_; string rank_; // creator and administrator only @@ -343,7 +343,7 @@ class DialogParticipantStatus { static DialogParticipantStatus Administrator(AdministratorRights administrator_rights, string &&rank, bool can_be_edited); - static DialogParticipantStatus Member(); + static DialogParticipantStatus Member(int32 member_until_date); static DialogParticipantStatus Restricted(RestrictedRights restricted_rights, bool is_member, int32 restricted_until_date, ChannelType channel_type); @@ -655,7 +655,7 @@ struct DialogParticipant { static DialogParticipant private_member(UserId user_id, UserId other_user_id) { auto inviter_user_id = other_user_id.is_valid() ? other_user_id : user_id; - return {DialogId(user_id), inviter_user_id, 0, DialogParticipantStatus::Member()}; + return {DialogId(user_id), inviter_user_id, 0, DialogParticipantStatus::Member(0)}; } bool is_valid() const; diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp index 767cb71d..b0c9ceb3 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp @@ -111,9 +111,9 @@ class GetChatJoinRequestsQuery final : public Td::ResultHandler { if (!query.empty()) { flags |= telegram_api::messages_getChatInviteImporters::Q_MASK; } - send_query(G()->net_query_creator().create( - telegram_api::messages_getChatInviteImporters(flags, false /*ignored*/, std::move(input_peer), invite_link, - query, offset_date, r_input_user.move_as_ok(), limit))); + send_query(G()->net_query_creator().create(telegram_api::messages_getChatInviteImporters( + flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), invite_link, query, offset_date, + r_input_user.move_as_ok(), limit))); } void on_result(BufferSlice packet) final { @@ -1383,7 +1383,7 @@ void DialogParticipantManager::on_update_bot_stopped(UserId user_id, int32 date, } DialogParticipant old_dialog_participant(DialogId(my_user_id), user_id, date, DialogParticipantStatus::Banned(0)); - DialogParticipant new_dialog_participant(DialogId(my_user_id), user_id, date, DialogParticipantStatus::Member()); + DialogParticipant new_dialog_participant(DialogId(my_user_id), user_id, date, DialogParticipantStatus::Member(0)); if (is_stopped) { std::swap(old_dialog_participant.status_, new_dialog_participant.status_); } @@ -2341,7 +2341,7 @@ void DialogParticipantManager::add_channel_participant( return promise.set_error(Status::Error(400, "Not enough rights to invite members to the supergroup chat")); } - speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), old_status); + speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(0), old_status); vector> input_users; input_users.push_back(std::move(input_user)); td_->create_handler(std::move(promise))->send(channel_id, {user_id}, std::move(input_users)); @@ -2395,7 +2395,7 @@ void DialogParticipantManager::add_channel_participants( } input_users.push_back(std::move(input_user)); - speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), + speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(0), DialogParticipantStatus::Left()); } @@ -2837,7 +2837,7 @@ void DialogParticipantManager::add_cached_channel_participants(ChannelId channel } if (!is_found) { is_participants_cache_changed = true; - participants.emplace_back(DialogId(user_id), inviter_user_id, date, DialogParticipantStatus::Member()); + participants.emplace_back(DialogId(user_id), inviter_user_id, date, DialogParticipantStatus::Member(0)); } } if (is_participants_cache_changed) { diff --git a/lib/tgchat/ext/td/td/telegram/DocumentsManager.cpp b/lib/tgchat/ext/td/td/telegram/DocumentsManager.cpp index b8e8a091..7a4174de 100644 --- a/lib/tgchat/ext/td/td/telegram/DocumentsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DocumentsManager.cpp @@ -729,6 +729,12 @@ void DocumentsManager::delete_document_thumbnail(FileId file_id) { document->thumbnail = PhotoSize(); } +Slice DocumentsManager::get_document_mime_type(FileId file_id) const { + auto document = get_document(file_id); + CHECK(document != nullptr); + return document->mime_type; +} + FileId DocumentsManager::dup_document(FileId new_id, FileId old_id) { const GeneralDocument *old_document = get_document(old_id); CHECK(old_document != nullptr); diff --git a/lib/tgchat/ext/td/td/telegram/DocumentsManager.h b/lib/tgchat/ext/td/td/telegram/DocumentsManager.h index 81eca522..4bae409d 100644 --- a/lib/tgchat/ext/td/td/telegram/DocumentsManager.h +++ b/lib/tgchat/ext/td/td/telegram/DocumentsManager.h @@ -19,6 +19,7 @@ #include "td/utils/buffer.h" #include "td/utils/common.h" +#include "td/utils/Slice.h" #include "td/utils/WaitFreeHashMap.h" #include @@ -106,6 +107,8 @@ class DocumentsManager { void delete_document_thumbnail(FileId file_id); + Slice get_document_mime_type(FileId file_id) const; + FileId dup_document(FileId new_id, FileId old_id); void merge_documents(FileId new_id, FileId old_id); diff --git a/lib/tgchat/ext/td/td/telegram/Global.h b/lib/tgchat/ext/td/td/telegram/Global.h index cd6cd9c2..a9ba54a4 100644 --- a/lib/tgchat/ext/td/td/telegram/Global.h +++ b/lib/tgchat/ext/td/td/telegram/Global.h @@ -63,9 +63,11 @@ class MessagesManager; class NetQueryDispatcher; class NotificationManager; class NotificationSettingsManager; +class OnlineManager; class OptionManager; class PasswordManager; class PeopleNearbyManager; +class PromoDataManager; class QuickReplyManager; class ReactionManager; class SavedMessagesManager; @@ -406,6 +408,13 @@ class Global final : public ActorContext { notification_settings_manager_ = notification_settings_manager; } + ActorId online_manager() const { + return online_manager_; + } + void set_online_manager(ActorId online_manager) { + online_manager_ = online_manager; + } + void set_option_manager(OptionManager *option_manager) { option_manager_ = option_manager; } @@ -425,6 +434,13 @@ class Global final : public ActorContext { people_nearby_manager_ = people_nearby_manager; } + ActorId promo_data_manager() const { + return promo_data_manager_; + } + void set_promo_data_manager(ActorId promo_data_manager) { + promo_data_manager_ = promo_data_manager; + } + ActorId quick_reply_manager() const { return quick_reply_manager_; } @@ -689,8 +705,10 @@ class Global final : public ActorContext { ActorId messages_manager_; ActorId notification_manager_; ActorId notification_settings_manager_; + ActorId online_manager_; ActorId password_manager_; ActorId people_nearby_manager_; + ActorId promo_data_manager_; ActorId quick_reply_manager_; ActorId reaction_manager_; ActorId saved_messages_manager_; diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp index f47882ca..6feead16 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp @@ -397,6 +397,20 @@ class LinkManager::InternalLinkBusinessChat final : public InternalLink { } }; +class LinkManager::InternalLinkBuyStars final : public InternalLink { + int64 star_count_; + string purpose_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(star_count_, purpose_); + } + + public: + explicit InternalLinkBuyStars(int64 star_count, const string &purpose) + : star_count_(clamp(star_count, static_cast(1), static_cast(1000000000000))), purpose_(purpose) { + } +}; + class LinkManager::InternalLinkChangePhoneNumber final : public InternalLink { td_api::object_ptr get_internal_link_type_object() const final { return td_api::make_object(); @@ -1545,6 +1559,12 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que // msg_url?url= // msg_url?url=&text= return get_internal_link_message_draft(get_arg("url"), get_arg("text")); + } else if (path.size() == 1 && path[0] == "stars_topup") { + // stars_topup?balance=&purpose= + if (has_arg("balance")) { + return td::make_unique(to_integer(url_query.get_arg("balance")), + url_query.get_arg("purpose").str()); + } } if (!path.empty() && !path[0].empty()) { return td::make_unique(PSTRING() << "tg://" << query); @@ -2095,6 +2115,16 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp return PSTRING() << get_t_me_url() << "m/" << url_encode(link->link_name_); } } + case td_api::internalLinkTypeBuyStars::ID: { + auto link = static_cast(type_ptr); + if (!is_internal) { + return Status::Error("HTTP link is unavailable for the link type"); + } + if (link->star_count_ <= 0) { + return Status::Error(400, "Invalid Telegram Star number provided"); + } + return PSTRING() << "tg://stars_topup?balance=" << link->star_count_ << "&purpose=" << url_encode(link->purpose_); + } case td_api::internalLinkTypeChangePhoneNumber::ID: if (!is_internal) { return Status::Error("HTTP link is unavailable for the link type"); @@ -2654,6 +2684,30 @@ Result LinkManager::get_background_url(const string &name, return url; } +td_api::object_ptr LinkManager::get_background_type_object(const string &link, + bool is_pattern) { + auto parsed_link = parse_internal_link(link); + if (parsed_link == nullptr) { + return nullptr; + } + auto parsed_object = parsed_link->get_internal_link_type_object(); + if (parsed_object->get_id() != td_api::internalLinkTypeBackground::ID) { + return nullptr; + } + auto background_name = + std::move(static_cast(parsed_object.get())->background_name_); + if (!BackgroundType::is_background_name_local(background_name)) { + BackgroundType type(false, is_pattern, nullptr); + type.apply_parameters_from_link(background_name); + return type.get_background_type_object(); + } + auto r_background_type = BackgroundType::get_local_background_type(background_name); + if (r_background_type.is_error()) { + return nullptr; + } + return r_background_type.ok().get_background_type_object(); +} + string LinkManager::get_dialog_filter_invite_link_slug(Slice invite_link) { auto link_info = get_link_info(invite_link); if (link_info.type_ != LinkType::Tg && link_info.type_ != LinkType::TMe) { diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.h b/lib/tgchat/ext/td/td/telegram/LinkManager.h index aa01809d..1f72d5ec 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.h +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.h @@ -84,6 +84,8 @@ class LinkManager final : public Actor { static Result get_background_url(const string &name, td_api::object_ptr background_type); + static td_api::object_ptr get_background_type_object(const string &link, bool is_pattern); + static string get_dialog_filter_invite_link_slug(Slice invite_link); static string get_dialog_filter_invite_link(Slice slug, bool is_internal); @@ -125,6 +127,7 @@ class LinkManager final : public Actor { class InternalLinkBotStart; class InternalLinkBotStartInGroup; class InternalLinkBusinessChat; + class InternalLinkBuyStars; class InternalLinkChangePhoneNumber; class InternalLinkConfirmPhone; class InternalLinkDefaultMessageAutoDeleteTimerSettings; diff --git a/lib/tgchat/ext/td/td/telegram/MediaArea.cpp b/lib/tgchat/ext/td/td/telegram/MediaArea.cpp index e4db7024..c20fab14 100644 --- a/lib/tgchat/ext/td/td/telegram/MediaArea.cpp +++ b/lib/tgchat/ext/td/td/telegram/MediaArea.cpp @@ -63,7 +63,7 @@ MediaArea::MediaArea(Td *td, telegram_api::object_ptr & reaction_type_ = ReactionType(area->reaction_); is_dark_ = area->dark_; is_flipped_ = area->flipped_; - if (coordinates_.is_valid() && !reaction_type_.is_empty()) { + if (coordinates_.is_valid() && !reaction_type_.is_empty() && !reaction_type_.is_paid_reaction()) { type_ = Type::Reaction; } else { LOG(ERROR) << "Receive " << to_string(area); @@ -182,7 +182,7 @@ MediaArea::MediaArea(Td *td, td_api::object_ptr &&input_ reaction_type_ = ReactionType(type->reaction_type_); is_dark_ = type->is_dark_; is_flipped_ = type->is_flipped_; - if (!reaction_type_.is_empty()) { + if (!reaction_type_.is_empty() && !reaction_type_.is_paid_reaction()) { type_ = Type::Reaction; } break; diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp index e1ee1355..8695b825 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp @@ -2759,6 +2759,14 @@ unique_ptr create_text_message_content(string text, vector create_photo_message_content(Photo photo) { + return make_unique(std::move(photo), FormattedText(), false); +} + +unique_ptr create_video_message_content(FileId file_id) { + return make_unique(file_id, FormattedText(), false); +} + unique_ptr create_contact_registered_message_content() { return make_unique(); } @@ -4044,8 +4052,8 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten return Status::Error(400, "Paid media can't be sent to secret chats"); } } else { - if (dialog_type != DialogType::Channel || - !td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + if (!td->auth_manager_->is_bot() && (dialog_type != DialogType::Channel || + !td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()))) { return Status::Error(400, "Paid media can be sent only in channel chats"); } } @@ -4333,6 +4341,12 @@ int32 get_message_content_index_mask(const MessageContent *content, const Td *td return get_message_content_text_index_mask(content) | get_message_content_media_index_mask(content, td, is_outgoing); } +vector> get_individual_message_contents(const MessageContent *content) { + CHECK(content->get_type() == MessageContentType::PaidMedia); + const auto *m = static_cast(content); + return transform(m->media, [](const MessageExtendedMedia &media) { return media.get_message_content(); }); +} + StickerType get_message_content_sticker_type(const Td *td, const MessageContent *content) { CHECK(content->get_type() == MessageContentType::Sticker); return td->stickers_manager_->get_sticker_type(static_cast(content)->file_id); @@ -6839,7 +6853,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const } if (type != MessageContentDupType::Forward) { for (auto &media : result->media) { - media = media.dup_to_send(td, result->media.size() > 1u); + media = media.dup_to_send(td, true); CHECK(!media.is_empty()); } } @@ -8060,6 +8074,11 @@ unique_ptr get_uploaded_message_content( return content; } +int64 get_message_content_star_count(const MessageContent *content) { + CHECK(content->get_type() == MessageContentType::PaidMedia); + return static_cast(content)->star_count; +} + int32 get_message_content_duration(const MessageContent *content, const Td *td) { CHECK(content != nullptr); switch (content->get_type()) { diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.h b/lib/tgchat/ext/td/td/telegram/MessageContent.h index 43de7151..edf734d8 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.h +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.h @@ -105,6 +105,10 @@ unique_ptr create_text_message_content(string text, vector create_photo_message_content(Photo photo); + +unique_ptr create_video_message_content(FileId file_id); + unique_ptr create_contact_registered_message_content(); unique_ptr create_screenshot_taken_message_content(); @@ -157,6 +161,8 @@ bool update_opened_message_content(MessageContent *content); int32 get_message_content_index_mask(const MessageContent *content, const Td *td, bool is_outgoing); +vector> get_individual_message_contents(const MessageContent *content); + StickerType get_message_content_sticker_type(const Td *td, const MessageContent *content); MessageId get_message_content_pinned_message_id(const MessageContent *content); @@ -273,6 +279,8 @@ const FormattedText *get_message_content_text(const MessageContent *content); const FormattedText *get_message_content_caption(const MessageContent *content); +int64 get_message_content_star_count(const MessageContent *content); + int32 get_message_content_duration(const MessageContent *content, const Td *td); int32 get_message_content_media_duration(const MessageContent *content, const Td *td); diff --git a/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.cpp b/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.cpp index 6f047310..eb740d57 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.cpp @@ -12,6 +12,7 @@ #include "td/telegram/DocumentsManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" +#include "td/telegram/MessageContent.h" #include "td/telegram/Photo.h" #include "td/telegram/PhotoSize.h" #include "td/telegram/StickersManager.h" @@ -258,6 +259,21 @@ void MessageExtendedMedia::delete_thumbnail(Td *td) { } } +unique_ptr MessageExtendedMedia::get_message_content() const { + switch (type_) { + case Type::Photo: + return create_photo_message_content(photo_); + case Type::Video: + return create_video_message_content(video_file_id_); + case Type::Empty: + case Type::Unsupported: + case Type::Preview: + default: + UNREACHABLE(); + return nullptr; + } +} + int32 MessageExtendedMedia::get_duration(const Td *td) const { if (!has_media_timestamp()) { return -1; diff --git a/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.h b/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.h index 80ba060e..613cb1be 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.h +++ b/lib/tgchat/ext/td/td/telegram/MessageExtendedMedia.h @@ -18,6 +18,8 @@ namespace td { +class MessageContent; + class Td; class MessageExtendedMedia { @@ -92,6 +94,8 @@ class MessageExtendedMedia { bool is_equal_but_different(const MessageExtendedMedia &other) const; + unique_ptr get_message_content() const; + int32 get_duration(const Td *td) const; FileId get_upload_file_id() const; diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp index 144ca7fd..f5c10fe1 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp @@ -14,6 +14,7 @@ #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StarManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" @@ -31,6 +32,7 @@ #include "td/utils/Status.h" #include +#include #include namespace td { @@ -152,6 +154,90 @@ class SendReactionQuery final : public Td::ResultHandler { } }; +class SendPaidReactionQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit SendPaidReactionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(MessageFullId message_full_id, int32 star_count, bool is_anonymous, int64 random_id) { + dialog_id_ = message_full_id.get_dialog_id(); + + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + + int32 flags = 0; + if (is_anonymous) { + flags |= telegram_api::messages_sendPaidReaction::PRIVATE_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::messages_sendPaidReaction(flags, false /*ignored*/, std::move(input_peer), + message_full_id.get_message_id().get_server_message_id().get(), + star_count, random_id), + {{dialog_id_}, {message_full_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 SendPaidReactionQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "MESSAGE_NOT_MODIFIED") { + return promise_.set_value(Unit()); + } + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SendPaidReactionQuery"); + promise_.set_error(std::move(status)); + } +}; + +class TogglePaidReactionPrivacyQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit TogglePaidReactionPrivacyQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(MessageFullId message_full_id, bool is_anonymous) { + dialog_id_ = message_full_id.get_dialog_id(); + + auto input_peer = td_->dialog_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_togglePaidReactionPrivacy( + std::move(input_peer), message_full_id.get_message_id().get_server_message_id().get(), is_anonymous), + {{dialog_id_}, {message_full_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_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "TogglePaidReactionPrivacyQuery"); + promise_.set_error(std::move(status)); + } +}; + class GetMessageReactionsListQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; @@ -364,6 +450,12 @@ void MessageReaction::unset_as_chosen() { fix_choose_count(); } +void MessageReaction::add_paid_reaction(int32 star_count) { + is_chosen_ = true; + CHECK(star_count <= std::numeric_limits::max() - choose_count_); + choose_count_ += star_count; +} + void MessageReaction::fix_choose_count() { choose_count_ = max(choose_count_, narrow_cast(recent_chooser_dialog_ids_.size())); } @@ -457,8 +549,12 @@ StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReac << unread_reaction.sender_dialog_id_ << ']'; } +bool MessageReactions::are_empty() const { + return reactions_.empty() && pending_paid_reactions_ == 0; +} + unique_ptr MessageReactions::get_message_reactions( - Td *td, tl_object_ptr &&reactions, bool is_bot) { + Td *td, telegram_api::object_ptr &&reactions, bool is_bot) { if (reactions == nullptr || is_bot) { return nullptr; } @@ -550,7 +646,12 @@ 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_type); + if (reaction_type == ReactionType::paid()) { + LOG_IF(ERROR, reaction_count->chosen_order_ != -1) + << "Receive paid reaction with order " << reaction_count->chosen_order_; + } else { + chosen_reaction_order.emplace_back(reaction_count->chosen_order_, reaction_type); + } } result->reactions_.push_back({std::move(reaction_type), reaction_count->count_, is_chosen, my_recent_chooser_dialog_id, std::move(recent_chooser_dialog_ids), @@ -561,6 +662,23 @@ unique_ptr MessageReactions::get_message_reactions( result->chosen_reaction_order_ = transform(chosen_reaction_order, [](const std::pair &order) { return order.second; }); } + bool was_me = false; + for (auto &top_reactor : reactions->top_reactors_) { + MessageReactor reactor(std::move(top_reactor)); + if (!reactor.is_valid() || (reactions->min_ && reactor.is_me())) { + LOG(ERROR) << "Receive " << reactor; + continue; + } + if (reactor.is_me()) { + if (was_me) { + LOG(ERROR) << "Receive duplicate " << reactor; + continue; + } + was_me = true; + } + result->top_reactors_.push_back(std::move(reactor)); + } + MessageReactor::fix_message_reactors(result->top_reactors_, true); return result; } @@ -582,7 +700,7 @@ const MessageReaction *MessageReactions::get_reaction(const ReactionType &reacti return nullptr; } -void MessageReactions::update_from(const MessageReactions &old_reactions) { +void MessageReactions::update_from(const MessageReactions &old_reactions, DialogId my_dialog_id) { if (is_min_ && !old_reactions.is_min_) { // chosen reactions were known, keep them is_min_ = false; @@ -601,6 +719,23 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { if (chosen_reaction_order_.size() == 1) { reset_to_empty(chosen_reaction_order_); } + + bool was_me = false; + for (auto &reactor : top_reactors_) { + if (reactor.fix_is_me(my_dialog_id)) { + was_me = true; + break; + } + } + if (!was_me) { + for (auto &reactor : old_reactions.top_reactors_) { + if (reactor.is_me()) { + // self paid reaction was known, keep it + top_reactors_.push_back(reactor); + MessageReactor::fix_message_reactors(top_reactors_, false); + } + } + } } for (const auto &old_reaction : old_reactions.reactions_) { if (old_reaction.is_chosen() && @@ -611,6 +746,8 @@ void MessageReactions::update_from(const MessageReactions &old_reactions) { } } } + pending_paid_reactions_ = old_reactions.pending_paid_reactions_; + pending_is_anonymous_ = old_reactions.pending_is_anonymous_; } bool MessageReactions::add_my_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, @@ -706,9 +843,32 @@ bool MessageReactions::do_remove_my_reaction(const ReactionType &reaction_type) return false; } +void MessageReactions::add_my_paid_reaction(Td *td, int32 star_count, bool is_anonymous) { + if (pending_paid_reactions_ > 1000000000 || star_count > 1000000000) { + LOG(ERROR) << "Pending paid reactions overflown"; + return; + } + td->star_manager_->add_owned_star_count(-star_count); + pending_paid_reactions_ += star_count; + pending_is_anonymous_ = is_anonymous; +} + +bool MessageReactions::drop_pending_paid_reactions(Td *td) { + if (pending_paid_reactions_ == 0) { + return false; + } + td->star_manager_->add_owned_star_count(pending_paid_reactions_); + pending_paid_reactions_ = 0; + pending_is_anonymous_ = false; + return true; +} + 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_reaction_type().is_paid_reaction() != rhs.get_reaction_type().is_paid_reaction()) { + return lhs.get_reaction_type().is_paid_reaction(); + } if (lhs.get_choose_count() != rhs.get_choose_count()) { return lhs.get_choose_count() > rhs.get_choose_count(); } @@ -737,7 +897,8 @@ void MessageReactions::fix_chosen_reaction() { return; } for (auto &reaction : reactions_) { - if (reaction.is_chosen() && !reaction.get_my_recent_chooser_dialog_id().is_valid()) { + if (!reaction.get_reaction_type().is_paid_reaction() && reaction.is_chosen() && + !reaction.get_my_recent_chooser_dialog_id().is_valid()) { reaction.add_my_recent_chooser_dialog_id(my_dialog_id); } } @@ -745,7 +906,8 @@ void MessageReactions::fix_chosen_reaction() { 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() && + if (!reaction.get_reaction_type().is_paid_reaction() && 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; } @@ -759,7 +921,7 @@ vector MessageReactions::get_chosen_reaction_types() const { vector reaction_order; for (const auto &reaction : reactions_) { - if (reaction.is_chosen()) { + if (!reaction.get_reaction_type().is_paid_reaction() && reaction.is_chosen()) { reaction_order.push_back(reaction.get_reaction_type()); } } @@ -803,12 +965,57 @@ bool MessageReactions::are_consistent_with_list( } } +vector MessageReactions::apply_reactor_pending_paid_reactions(DialogId my_dialog_id) const { + vector top_reactors; + bool was_me = false; + for (auto &reactor : top_reactors_) { + top_reactors.push_back(reactor); + if (reactor.is_me()) { + was_me = true; + top_reactors.back().add_count(pending_paid_reactions_, pending_is_anonymous_); + } + } + if (!was_me) { + top_reactors.emplace_back(my_dialog_id, pending_paid_reactions_, pending_is_anonymous_); + } + MessageReactor::fix_message_reactors(top_reactors, false); + return top_reactors; +} + td_api::object_ptr MessageReactions::get_message_reactions_object(Td *td, UserId my_user_id, UserId peer_user_id) const { auto reactions = transform(reactions_, [td, my_user_id, peer_user_id](const MessageReaction &reaction) { return reaction.get_message_reaction_object(td, my_user_id, peer_user_id); }); - return td_api::make_object(std::move(reactions), are_tags_); + auto reactors = + transform(top_reactors_, [td](const MessageReactor &reactor) { return reactor.get_paid_reactor_object(td); }); + if (pending_paid_reactions_ > 0) { + if (reactions_.empty() || !reactions_[0].reaction_type_.is_paid_reaction()) { + reactions.insert(reactions.begin(), + MessageReaction(ReactionType::paid(), pending_paid_reactions_, true, DialogId(), Auto(), Auto()) + .get_message_reaction_object(td, my_user_id, peer_user_id)); + } else { + reactions[0]->total_count_ += pending_paid_reactions_; + reactions[0]->is_chosen_ = true; + } + + // my_user_id == UserId() + auto top_reactors = apply_reactor_pending_paid_reactions(td->dialog_manager_->get_my_dialog_id()); + reactors = + transform(top_reactors, [td](const MessageReactor &reactor) { return reactor.get_paid_reactor_object(td); }); + } + return td_api::make_object(std::move(reactions), are_tags_, std::move(reactors), + can_get_added_reactions_); +} + +int32 MessageReactions::get_non_paid_reaction_count() const { + int32 result = 0; + for (const auto &reaction : reactions_) { + if (!reaction.reaction_type_.is_paid_reaction()) { + result++; + } + } + return result; } void MessageReactions::add_min_channels(Td *td) const { @@ -827,6 +1034,9 @@ void MessageReactions::add_dependencies(Dependencies &dependencies) const { dependencies.add_message_sender_dependencies(dialog_id); } } + for (const auto &reactor : top_reactors_) { + reactor.add_dependencies(dependencies); + } } bool MessageReactions::need_update_message_reactions(const MessageReactions *old_reactions, @@ -844,7 +1054,8 @@ bool MessageReactions::need_update_message_reactions(const MessageReactions *old return old_reactions->reactions_ != new_reactions->reactions_ || old_reactions->is_min_ != new_reactions->is_min_ || old_reactions->can_get_added_reactions_ != new_reactions->can_get_added_reactions_ || old_reactions->need_polling_ != new_reactions->need_polling_ || - old_reactions->are_tags_ != new_reactions->are_tags_; + old_reactions->are_tags_ != new_reactions->are_tags_ || + old_reactions->top_reactors_ != new_reactions->top_reactors_; } bool MessageReactions::need_update_unread_reactions(const MessageReactions *old_reactions, @@ -855,6 +1066,47 @@ bool MessageReactions::need_update_unread_reactions(const MessageReactions *old_ return new_reactions == nullptr || old_reactions->unread_reactions_ != new_reactions->unread_reactions_; } +void MessageReactions::send_paid_message_reaction(Td *td, MessageFullId message_full_id, int64 random_id, + Promise &&promise) { + if (pending_paid_reactions_ == 0) { + return promise.set_value(Unit()); + } + auto star_count = pending_paid_reactions_; + auto is_anonymous = pending_is_anonymous_; + top_reactors_ = apply_reactor_pending_paid_reactions(td->dialog_manager_->get_my_dialog_id()); + if (reactions_.empty() || !reactions_[0].reaction_type_.is_paid_reaction()) { + reactions_.insert(reactions_.begin(), + MessageReaction(ReactionType::paid(), star_count, true, DialogId(), Auto(), Auto())); + } else { + reactions_[0].add_paid_reaction(star_count); + } + pending_paid_reactions_ = 0; + pending_is_anonymous_ = false; + + td->create_handler(std::move(promise)) + ->send(message_full_id, star_count, is_anonymous, random_id); +} + +bool MessageReactions::toggle_paid_message_reaction_is_anonymous(Td *td, MessageFullId message_full_id, + bool is_anonymous, Promise &&promise) { + if (pending_paid_reactions_ != 0) { + pending_is_anonymous_ = is_anonymous; + } + for (auto &top_reactor : top_reactors_) { + if (top_reactor.is_me()) { + top_reactor.add_count(0, is_anonymous); + td->create_handler(std::move(promise))->send(message_full_id, is_anonymous); + return true; + } + } + if (pending_paid_reactions_ != 0) { + promise.set_value(Unit()); + return true; + } + promise.set_error(Status::Error(400, "Message has no paid reaction")); + return false; +} + StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions) { if (reactions.are_tags_) { return string_builder << "MessageTags{" << reactions.reactions_ << '}'; @@ -862,7 +1114,10 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions return string_builder << (reactions.is_min_ ? "Min" : "") << "MessageReactions{" << reactions.reactions_ << " with unread " << reactions.unread_reactions_ << ", reaction order " << reactions.chosen_reaction_order_ - << " and can_get_added_reactions = " << reactions.can_get_added_reactions_ << '}'; + << " and can_get_added_reactions = " << reactions.can_get_added_reactions_ + << " with paid reactions by " << reactions.top_reactors_ << " and " + << reactions.pending_paid_reactions_ << (reactions.pending_is_anonymous_ ? "anonymous " : "") + << " pending paid reactions}"; } StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &reactions) { @@ -903,7 +1158,7 @@ void set_message_reactions(Td *td, MessageFullId message_full_id, vectormessages_manager_->have_message_force(message_full_id, "get_message_added_reactions")) { return promise.set_error(Status::Error(400, "Message not found")); } + if (reaction_type.is_paid_reaction()) { + return promise.set_error(Status::Error(400, "Can't use the method for paid reaction")); + } auto message_id = message_full_id.get_message_id(); if (message_full_id.get_dialog_id().get_type() == DialogType::SecretChat || !message_id.is_valid() || diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.h b/lib/tgchat/ext/td/td/telegram/MessageReaction.h index a64f80f0..22ec2812 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.h +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.h @@ -10,6 +10,7 @@ #include "td/telegram/DialogId.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageReactor.h" #include "td/telegram/MinChannel.h" #include "td/telegram/ReactionType.h" #include "td/telegram/td_api.h" @@ -63,6 +64,8 @@ class MessageReaction { void unset_as_chosen(); + void add_paid_reaction(int32 star_count); + void add_my_recent_chooser_dialog_id(DialogId dialog_id); bool remove_my_recent_chooser_dialog_id(); @@ -153,6 +156,9 @@ struct MessageReactions { vector reactions_; vector unread_reactions_; vector chosen_reaction_order_; + vector top_reactors_; + int32 pending_paid_reactions_ = 0; + bool pending_is_anonymous_ = false; bool is_min_ = false; bool need_polling_ = true; bool can_get_added_reactions_ = false; @@ -160,21 +166,26 @@ struct MessageReactions { MessageReactions() = default; - static unique_ptr get_message_reactions(Td *td, - tl_object_ptr &&reactions, - bool is_bot); + bool are_empty() const; + + static unique_ptr get_message_reactions( + Td *td, telegram_api::object_ptr &&reactions, bool is_bot); MessageReaction *get_reaction(const ReactionType &reaction_type); const MessageReaction *get_reaction(const ReactionType &reaction_type) const; - void update_from(const MessageReactions &old_reactions); + void update_from(const MessageReactions &old_reactions, DialogId my_dialog_id); bool add_my_reaction(const ReactionType &reaction_type, bool is_big, DialogId my_dialog_id, bool have_recent_choosers, bool is_tag); bool remove_my_reaction(const ReactionType &reaction_type, DialogId my_dialog_id); + void add_my_paid_reaction(Td *td, int32 star_count, bool is_anonymous); + + bool drop_pending_paid_reactions(Td *td); + void sort_reactions(const FlatHashMap &active_reaction_pos); void fix_chosen_reaction(); @@ -190,6 +201,8 @@ struct MessageReactions { td_api::object_ptr get_message_reactions_object(Td *td, UserId my_user_id, UserId peer_user_id) const; + int32 get_non_paid_reaction_count() const; + void add_min_channels(Td *td) const; void add_dependencies(Dependencies &dependencies) const; @@ -200,6 +213,11 @@ struct MessageReactions { static bool need_update_unread_reactions(const MessageReactions *old_reactions, const MessageReactions *new_reactions); + void send_paid_message_reaction(Td *td, MessageFullId message_full_id, int64 random_id, Promise &&promise); + + bool toggle_paid_message_reaction_is_anonymous(Td *td, MessageFullId message_full_id, bool is_anonymous, + Promise &&promise); + template void store(StorerT &storer) const; @@ -208,6 +226,8 @@ struct MessageReactions { private: bool do_remove_my_reaction(const ReactionType &reaction_type); + + vector apply_reactor_pending_paid_reactions(DialogId my_dialog_id) const; }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions); diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.hpp b/lib/tgchat/ext/td/td/telegram/MessageReaction.hpp index 75941c11..588a2540 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.hpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.hpp @@ -7,6 +7,8 @@ #pragma once #include "td/telegram/MessageReaction.h" + +#include "td/telegram/MessageReactor.hpp" #include "td/telegram/MinChannel.hpp" #include "td/telegram/ReactionType.hpp" @@ -100,6 +102,7 @@ void MessageReactions::store(StorerT &storer) const { bool has_reactions = !reactions_.empty(); bool has_unread_reactions = !unread_reactions_.empty(); bool has_chosen_reaction_order = !chosen_reaction_order_.empty(); + bool has_top_reactors = !top_reactors_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_min_); STORE_FLAG(need_polling_); @@ -108,6 +111,7 @@ void MessageReactions::store(StorerT &storer) const { STORE_FLAG(has_reactions); STORE_FLAG(has_chosen_reaction_order); STORE_FLAG(are_tags_); + STORE_FLAG(has_top_reactors); END_STORE_FLAGS(); if (has_reactions) { td::store(reactions_, storer); @@ -118,6 +122,9 @@ void MessageReactions::store(StorerT &storer) const { if (has_chosen_reaction_order) { td::store(chosen_reaction_order_, storer); } + if (has_top_reactors) { + td::store(top_reactors_, storer); + } } template @@ -125,6 +132,7 @@ void MessageReactions::parse(ParserT &parser) { bool has_reactions; bool has_unread_reactions; bool has_chosen_reaction_order; + bool has_top_reactors; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_min_); PARSE_FLAG(need_polling_); @@ -133,6 +141,7 @@ void MessageReactions::parse(ParserT &parser) { PARSE_FLAG(has_reactions); PARSE_FLAG(has_chosen_reaction_order); PARSE_FLAG(are_tags_); + PARSE_FLAG(has_top_reactors); END_PARSE_FLAGS(); if (has_reactions) { td::parse(reactions_, parser); @@ -143,6 +152,9 @@ void MessageReactions::parse(ParserT &parser) { if (has_chosen_reaction_order) { td::parse(chosen_reaction_order_, parser); } + if (has_top_reactors) { + td::parse(top_reactors_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageReactor.cpp b/lib/tgchat/ext/td/td/telegram/MessageReactor.cpp new file mode 100644 index 00000000..b79c7727 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageReactor.cpp @@ -0,0 +1,102 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/MessageReactor.h" + +#include "td/telegram/Dependencies.h" +#include "td/telegram/MessageSender.h" + +#include "td/utils/logging.h" + +#include + +namespace td { + +MessageReactor::MessageReactor(telegram_api::object_ptr &&reactor) + : dialog_id_(reactor->peer_id_ == nullptr ? DialogId() : DialogId(reactor->peer_id_)) + , count_(reactor->count_) + , is_top_(reactor->top_) + , is_me_(reactor->my_) + , is_anonymous_(reactor->anonymous_) { +} + +td_api::object_ptr MessageReactor::get_paid_reactor_object(Td *td) const { + return td_api::make_object( + dialog_id_ == DialogId() ? nullptr : get_message_sender_object(td, dialog_id_, "paidReactor"), count_, is_top_, + is_me_, is_anonymous_); +} + +void MessageReactor::add_dependencies(Dependencies &dependencies) const { + dependencies.add_message_sender_dependencies(dialog_id_); +} + +bool MessageReactor::fix_is_me(DialogId my_dialog_id) { + if (dialog_id_ == my_dialog_id) { + is_me_ = true; + return true; + } + return false; +} + +void MessageReactor::fix_message_reactors(vector &reactors, bool need_warning) { + size_t TOP_REACTOR_COUNT = 3u; + if (reactors.size() > TOP_REACTOR_COUNT + 1) { + LOG(ERROR) << "Have too many " << reactors; + reactors.resize(TOP_REACTOR_COUNT + 1); + } + if (reactors.size() > TOP_REACTOR_COUNT && !reactors[TOP_REACTOR_COUNT].is_me()) { + LOG(ERROR) << "Receive unexpected " << reactors; + reactors.resize(TOP_REACTOR_COUNT); + } + if (need_warning) { + for (size_t i = 0; i < reactors.size(); i++) { + if (reactors[i].is_top_ != (i < TOP_REACTOR_COUNT)) { + LOG(ERROR) << "Receive incorrect top " << reactors; + break; + } + } + for (size_t i = 0; i + 1 < reactors.size(); i++) { + if (reactors[i].count_ < reactors[i + 1].count_) { + LOG(ERROR) << "Receive unordered " << reactors; + break; + } + } + } + bool was_me = false; + for (const auto &reactor : reactors) { + CHECK(reactor.is_valid()); + if (reactor.is_me()) { + CHECK(!was_me); + was_me = true; + } + } + std::sort(reactors.begin(), reactors.end()); + if (reactors.size() > TOP_REACTOR_COUNT && !reactors[TOP_REACTOR_COUNT].is_me()) { + reactors.resize(TOP_REACTOR_COUNT); + } + for (size_t i = 0; i < reactors.size(); i++) { + reactors[i].is_top_ = (i < TOP_REACTOR_COUNT); + } +} + +bool operator<(const MessageReactor &lhs, const MessageReactor &rhs) { + if (lhs.count_ != rhs.count_) { + return lhs.count_ > rhs.count_; + } + return lhs.dialog_id_.get() < rhs.dialog_id_.get(); +} + +bool operator==(const MessageReactor &lhs, const MessageReactor &rhs) { + return lhs.dialog_id_ == rhs.dialog_id_ && lhs.count_ == rhs.count_ && lhs.is_top_ == rhs.is_top_ && + lhs.is_me_ == rhs.is_me_ && lhs.is_anonymous_ == rhs.is_anonymous_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactor &reactor) { + return string_builder << "PaidReactor[" << reactor.dialog_id_ << " - " << reactor.count_ + << (reactor.is_me_ ? " by me" : "") << ']'; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageReactor.h b/lib/tgchat/ext/td/td/telegram/MessageReactor.h new file mode 100644 index 00000000..f0b889b7 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageReactor.h @@ -0,0 +1,82 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class Dependencies; + +class Td; + +class MessageReactor { + DialogId dialog_id_; + int32 count_ = 0; + bool is_top_ = false; + bool is_me_ = false; + bool is_anonymous_ = false; + + friend bool operator<(const MessageReactor &lhs, const MessageReactor &rhs); + + friend bool operator==(const MessageReactor &lhs, const MessageReactor &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactor &reactor); + + public: + MessageReactor() = default; + + explicit MessageReactor(telegram_api::object_ptr &&reactor); + + MessageReactor(DialogId dialog_id, int32 count, bool is_anonymous) + : dialog_id_(dialog_id), count_(count), is_me_(true), is_anonymous_(is_anonymous) { + } + + bool is_valid() const { + return count_ > 0 && (is_me_ ? dialog_id_.is_valid() : (dialog_id_.is_valid() || is_anonymous_) && is_top_); + } + + bool is_me() const { + return is_me_; + } + + bool fix_is_me(DialogId my_dialog_id); + + void add_count(int32 count, bool is_anonymous) { + count_ += count; + is_anonymous_ = is_anonymous; + } + + td_api::object_ptr get_paid_reactor_object(Td *td) const; + + void add_dependencies(Dependencies &dependencies) const; + + static void fix_message_reactors(vector &reactors, bool need_warning); + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator<(const MessageReactor &lhs, const MessageReactor &rhs); + +bool operator==(const MessageReactor &lhs, const MessageReactor &rhs); + +inline bool operator!=(const MessageReactor &lhs, const MessageReactor &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactor &reactor); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageReactor.hpp b/lib/tgchat/ext/td/td/telegram/MessageReactor.hpp new file mode 100644 index 00000000..d50f21ba --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageReactor.hpp @@ -0,0 +1,46 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/MessageReactor.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MessageReactor::store(StorerT &storer) const { + bool has_dialog_id = dialog_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_top_); + STORE_FLAG(is_me_); + STORE_FLAG(is_anonymous_); + STORE_FLAG(has_dialog_id); + END_STORE_FLAGS(); + if (has_dialog_id) { + td::store(dialog_id_, storer); + } + td::store(count_, storer); +} + +template +void MessageReactor::parse(ParserT &parser) { + bool has_dialog_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_top_); + PARSE_FLAG(is_me_); + PARSE_FLAG(is_anonymous_); + PARSE_FLAG(has_dialog_id); + END_PARSE_FLAGS(); + if (has_dialog_id) { + td::parse(dialog_id_, parser); + } + td::parse(count_, parser); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp index 0ebd7220..dc328a1c 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp @@ -69,6 +69,7 @@ #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSound.h" #include "td/telegram/NotificationType.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/Photo.h" #include "td/telegram/PollId.h" @@ -810,13 +811,13 @@ class SetChatAvailableReactionsQuery final : public Td::ResultHandler { if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } - int32 flags = 0; + int32 flags = telegram_api::messages_setChatAvailableReactions::PAID_ENABLED_MASK; if (available_reactions.reactions_limit_ != 0) { flags |= telegram_api::messages_setChatAvailableReactions::REACTIONS_LIMIT_MASK; } send_query(G()->net_query_creator().create(telegram_api::messages_setChatAvailableReactions( flags, std::move(input_peer), available_reactions.get_input_chat_reactions(), - available_reactions.reactions_limit_))); + available_reactions.reactions_limit_, available_reactions.paid_reactions_available_))); } void on_result(BufferSlice packet) final { @@ -5557,6 +5558,9 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) send_update_chat_read_inbox_timeout_.set_callback(on_send_update_chat_read_inbox_timeout_callback); send_update_chat_read_inbox_timeout_.set_callback_data(static_cast(this)); + + send_paid_reactions_timeout_.set_callback(on_send_paid_reactions_timeout_callback); + send_paid_reactions_timeout_.set_callback_data(static_cast(this)); } MessagesManager::~MessagesManager() { @@ -5700,6 +5704,51 @@ void MessagesManager::on_send_update_chat_read_inbox_timeout_callback(void *mess &MessagesManager::on_send_update_chat_read_inbox_timeout, DialogId(dialog_id_int)); } +void MessagesManager::on_send_paid_reactions_timeout_callback(void *messages_manager_ptr, int64 task_id) { + if (G()->close_flag()) { + return; + } + + auto messages_manager = static_cast(messages_manager_ptr); + send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_send_paid_reactions_timeout, + task_id); +} + +void MessagesManager::on_live_location_expire_timeout_callback(void *messages_manager_ptr) { + if (G()->close_flag()) { + return; + } + + auto messages_manager = static_cast(messages_manager_ptr); + send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_live_location_expire_timeout); +} + +void MessagesManager::on_live_location_expire_timeout() { + if (G()->close_flag() || !td_->auth_manager_->is_authorized()) { + return; + } + vector to_delete_message_full_ids; + for (const auto &message_full_id : active_live_location_message_full_ids_) { + auto m = get_message(message_full_id); + CHECK(m != nullptr); + auto live_period = get_message_content_live_location_period(m->content.get()); + if (live_period <= G()->unix_time() - m->date) { + to_delete_message_full_ids.push_back(message_full_id); + } + } + if (to_delete_message_full_ids.empty()) { + LOG(INFO) << "Have no messages to delete"; + schedule_active_live_location_expiration(); + } else { + for (auto message_full_id : to_delete_message_full_ids) { + bool is_deleted = delete_active_live_location(message_full_id); + CHECK(is_deleted); + } + send_update_active_live_location_messages(); + save_active_live_locations(); + } +} + BufferSlice MessagesManager::get_dialog_database_value(const Dialog *d) { // can't use log_event_store, because it tries to parse stored Dialog LogEventStorerCalcLength storer_calc_length; @@ -6518,7 +6567,7 @@ td_api::object_ptr MessagesManager::get_message_ DialogId dialog_id, const Message *m) const { bool is_visible_reply_info = is_visible_message_reply_info(dialog_id, m); bool has_reactions = - m->reactions != nullptr && !m->reactions->reactions_.empty() && is_visible_message_reactions(dialog_id, m); + m->reactions != nullptr && !m->reactions->are_empty() && is_visible_message_reactions(dialog_id, m); if (m->view_count == 0 && m->forward_count == 0 && !is_visible_reply_info && !has_reactions) { return nullptr; } @@ -6646,7 +6695,7 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int } if (has_reactions && reactions != nullptr) { if (m->reactions != nullptr) { - reactions->update_from(*m->reactions); + reactions->update_from(*m->reactions, td_->dialog_manager_->get_my_dialog_id()); } reactions->sort_reactions(active_reaction_pos_); reactions->fix_chosen_reaction(); @@ -6757,15 +6806,15 @@ bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int void MessagesManager::on_update_live_location_viewed(MessageFullId message_full_id) { LOG(DEBUG) << "Live location was viewed in " << message_full_id; if (!are_active_live_location_messages_loaded_) { - get_active_live_location_messages(PromiseCreator::lambda([actor_id = actor_id(this), message_full_id](Unit result) { - send_closure(actor_id, &MessagesManager::on_update_live_location_viewed, message_full_id); - })); + load_active_live_location_messages( + PromiseCreator::lambda([actor_id = actor_id(this), message_full_id](Unit result) { + send_closure(actor_id, &MessagesManager::on_update_live_location_viewed, message_full_id); + })); return; } - auto active_live_location_message_ids = get_active_live_location_messages(Auto()); - if (!td::contains(active_live_location_message_ids, message_full_id)) { - LOG(DEBUG) << "Can't find " << message_full_id << " in " << active_live_location_message_ids; + if (!td::contains(active_live_location_message_full_ids_, message_full_id)) { + LOG(DEBUG) << "Can't find " << message_full_id; return; } @@ -6775,7 +6824,7 @@ void MessagesManager::on_update_live_location_viewed(MessageFullId message_full_ void MessagesManager::on_update_some_live_location_viewed(Promise &&promise) { LOG(DEBUG) << "Some live location was viewed"; if (!are_active_live_location_messages_loaded_) { - get_active_live_location_messages( + load_active_live_location_messages( PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Unit result) mutable { send_closure(actor_id, &MessagesManager::on_update_some_live_location_viewed, std::move(promise)); })); @@ -6783,8 +6832,7 @@ void MessagesManager::on_update_some_live_location_viewed(Promise &&promis } // update all live locations, because it is unknown, which exactly was viewed - auto active_live_location_message_ids = get_active_live_location_messages(Auto()); - for (const auto &message_full_id : active_live_location_message_ids) { + for (const auto &message_full_id : active_live_location_message_full_ids_) { send_update_message_live_location_viewed(message_full_id); } @@ -7610,13 +7658,14 @@ void MessagesManager::on_update_dialog_notify_settings( void MessagesManager::on_update_dialog_available_reactions( DialogId dialog_id, telegram_api::object_ptr &&available_reactions, - int32 reactions_limit) { + int32 reactions_limit, bool paid_reactions_available) { Dialog *d = get_dialog_force(dialog_id, "on_update_dialog_available_reactions"); if (d == nullptr) { return; } - set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions), reactions_limit)); + set_dialog_available_reactions( + d, ChatReactions(std::move(available_reactions), reactions_limit, paid_reactions_available)); } void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) { @@ -7695,8 +7744,8 @@ void MessagesManager::hide_dialog_message_reactions(Dialog *d) { CHECK(!td_->auth_manager_->is_bot()); auto dialog_type = d->dialog_id.get_type(); CHECK(dialog_type == DialogType::Chat || dialog_type == DialogType::Channel); - auto message_ids = find_dialog_messages( - d, [](const Message *m) { return m->reactions != nullptr && !m->reactions->reactions_.empty(); }); + auto message_ids = + find_dialog_messages(d, [](const Message *m) { return m->reactions != nullptr && !m->reactions->are_empty(); }); for (auto message_id : message_ids) { Message *m = get_message(d, message_id); CHECK(m != nullptr); @@ -10930,6 +10979,7 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia clear_dialog_message_list(d, remove_from_dialog_list, last_message_date); } + bool was_live_location_deleted = false; vector deleted_message_ids; d->messages.foreach([&](const MessageId &message_id, unique_ptr &message) { CHECK(message_id == message->message_id); @@ -10940,7 +10990,9 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia LOG(INFO) << "Delete " << message_id; deleted_message_ids.push_back(message_id.get()); - delete_active_live_location(d->dialog_id, m); + if (delete_active_live_location({d->dialog_id, m->message_id})) { + was_live_location_deleted = true; + } remove_message_file_sources(d->dialog_id, m); on_message_deleted(d, m, is_permanently_deleted, "do_delete_all_dialog_messages"); @@ -10951,6 +11003,11 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia }); Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), d->messages, d->ordered_messages); + if (was_live_location_deleted) { + send_update_active_live_location_messages(); + save_active_live_locations(); + } + delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3"); if (d->notification_info != nullptr) { @@ -11892,7 +11949,7 @@ void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId } send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, d->notification_info->message_notification_group_.get_group_id(), NotificationId(), - d->last_read_inbox_message_id, total_count, Slice(source) == Slice("view_messages"), + d->last_read_inbox_message_id, total_count, Slice(source) == Slice("read_dialog_inbox"), Promise()); } @@ -12003,25 +12060,32 @@ void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { } auto it = dialog_viewed_messages_.find(dialog_id); - if (it == dialog_viewed_messages_.end() || !td_->is_online()) { + if (it == dialog_viewed_messages_.end() || !td_->online_manager_->is_online()) { return; } - auto &info = it->second; - CHECK(info != nullptr); + vector viewed_message_ids; + for (const auto &message_it : it->second->message_id_to_view_id) { + viewed_message_ids.push_back(message_it.first); + } + process_viewed_message(d, viewed_message_ids, false); +} + +void MessagesManager::process_viewed_message(Dialog *d, const vector &viewed_message_ids, bool is_first) { + auto dialog_id = d->dialog_id; vector reaction_message_ids; vector views_message_ids; vector extended_media_message_ids; int32 newest_message_date = 0; - for (auto &message_it : info->message_id_to_view_id) { - Message *m = get_message_force(d, message_it.first, "on_update_viewed_messages_timeout"); + for (auto message_id : viewed_message_ids) { + Message *m = get_message_force(d, message_id, "on_update_viewed_messages_timeout"); CHECK(m != nullptr); CHECK(m->message_id.is_valid()); CHECK(m->message_id.is_server()); if (need_poll_message_reactions(d, m)) { reaction_message_ids.push_back(m->message_id); } - if (m->view_count > 0 && !m->has_get_message_views_query) { + if (!is_first && m->view_count > 0 && !m->has_get_message_views_query) { m->has_get_message_views_query = true; views_message_ids.push_back(m->message_id); } @@ -12043,9 +12107,10 @@ void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) { if (!extended_media_message_ids.empty()) { td_->create_handler()->send(dialog_id, std::move(extended_media_message_ids)); } - - int divisor = 5 - min(max(G()->unix_time() - newest_message_date, 0) / UPDATE_VIEWED_MESSAGES_PERIOD, 4); - update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD / divisor); + if (td_->online_manager_->is_online()) { + int divisor = 5 - min(max(G()->unix_time() - newest_message_date, 0) / UPDATE_VIEWED_MESSAGES_PERIOD, 4); + update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD / divisor); + } } void MessagesManager::on_send_update_chat_read_inbox_timeout(DialogId dialog_id) { @@ -12058,6 +12123,42 @@ void MessagesManager::on_send_update_chat_read_inbox_timeout(DialogId dialog_id) } } +void MessagesManager::on_send_paid_reactions_timeout(int64 task_id) { + if (G()->close_flag()) { + return; + } + auto it = paid_reaction_tasks_.find(task_id); + if (it == paid_reaction_tasks_.end()) { + return; + } + auto message_full_id = it->second; + paid_reaction_tasks_.erase(it); + bool is_erased = paid_reaction_task_ids_.erase(message_full_id) > 0; + CHECK(is_erased); + + Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), "on_send_paid_reactions_timeout"); + CHECK(d != nullptr); + auto *m = get_message_force(d, message_full_id.get_message_id(), "on_send_paid_reactions_timeout"); + if (m == nullptr || m->reactions == nullptr) { + return; + } + if (!get_message_available_reactions(d, m, true, nullptr).is_allowed_reaction_type(ReactionType::paid())) { + if (m->reactions->drop_pending_paid_reactions(td_)) { + send_update_message_interaction_info(d->dialog_id, m); + on_message_changed(d, m, true, "on_send_paid_reactions_timeout"); + } + return; + } + + pending_reactions_[message_full_id].query_count++; + + int64 random_id = (static_cast(G()->unix_time()) << 32) | static_cast(Random::secure_uint32()); + auto promise = PromiseCreator::lambda([actor_id = actor_id(this), message_full_id](Result &&result) { + send_closure(actor_id, &MessagesManager::on_set_message_reactions, message_full_id, std::move(result), Auto()); + }); + m->reactions->send_paid_message_reaction(td_, message_full_id, random_id, std::move(promise)); +} + int32 MessagesManager::get_message_date(const tl_object_ptr &message_ptr) { switch (message_ptr->get_id()) { case telegram_api::messageEmpty::ID: @@ -12637,6 +12738,8 @@ void MessagesManager::init() { } } } + + load_active_live_location_messages(Promise()); } else if (!td_->auth_manager_->is_bot()) { G()->td_db()->get_binlog_pmc()->erase_by_prefix("pinned_dialog_ids"); G()->td_db()->get_binlog_pmc()->erase_by_prefix("last_server_dialog_date"); @@ -13822,7 +13925,9 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const } if (is_sent_message) { - try_add_active_live_location(dialog_id, m); + if (try_add_active_live_location(dialog_id, m)) { + send_update_active_live_location_messages(); + } // add_message_to_dialog will not update counts, because need_update == false update_message_count_by_index(d, +1, m); @@ -15250,7 +15355,10 @@ unique_ptr MessagesManager::do_delete_message(Dialog * delete_message_from_database(d, message_id, m, is_permanently_deleted, source); - delete_active_live_location(d->dialog_id, m); + if (delete_active_live_location({d->dialog_id, m->message_id})) { + send_update_active_live_location_messages(); + save_active_live_locations(); + } remove_message_file_sources(d->dialog_id, m); if (message_id == d->last_message_id) { @@ -17358,7 +17466,7 @@ void MessagesManager::get_message_properties(DialogId dialog_id, MessageId messa auto can_be_saved = can_save_message(dialog_id, m); auto can_be_edited = can_edit_message(dialog_id, m, false, is_bot); auto can_be_forwarded = can_be_saved && can_forward_message(dialog_id, m); - auto can_be_paid = get_invoice_message_id({dialog_id, m->message_id}).is_ok(); + auto can_be_paid = get_invoice_message_info({dialog_id, m->message_id}).is_ok(); auto can_be_pinned = can_pin_message(dialog_id, m).is_ok(); auto can_be_replied = message_id.is_valid() && !(message_id == MessageId(ServerMessageId(1)) && dialog_type == DialogType::Channel) && @@ -17368,7 +17476,6 @@ void MessagesManager::get_message_properties(DialogId dialog_id, MessageId messa auto can_be_replied_in_another_chat = can_be_forwarded && m->message_id.is_server(); auto can_be_shared_in_story = can_share_message_in_story(dialog_id, m); auto can_edit_scheduling_state = m->message_id.is_valid_scheduled() && m->message_id.is_scheduled_server(); - auto can_get_added_reactions = m->reactions != nullptr && m->reactions->can_get_added_reactions_; auto can_get_statistics = can_get_message_statistics(dialog_id, m); auto can_get_message_thread = get_top_thread_message_full_id(dialog_id, m, false).is_ok(); auto can_get_read_date = can_get_message_read_date(dialog_id, m).is_ok(); @@ -17390,9 +17497,9 @@ void MessagesManager::get_message_properties(DialogId dialog_id, MessageId messa promise.set_value(td_api::make_object( can_delete_for_self, can_delete_for_all_users, can_be_edited, can_be_forwarded, can_be_paid, can_be_pinned, can_be_replied, can_be_replied_in_another_chat, can_be_saved, can_be_shared_in_story, can_edit_scheduling_state, - can_get_added_reactions, can_get_embedding_code, can_get_link, can_get_media_timestamp_links, - can_get_message_thread, can_get_read_date, can_get_statistics, can_get_viewers, can_recognize_speech, - can_report_chat, can_report_reactions, can_report_supergroup_spam, can_set_fact_check, need_show_statistics)); + can_get_embedding_code, can_get_link, can_get_media_timestamp_links, can_get_message_thread, can_get_read_date, + can_get_statistics, can_get_viewers, can_recognize_speech, can_report_chat, can_report_reactions, + can_report_supergroup_spam, can_set_fact_check, need_show_statistics)); } bool MessagesManager::is_message_edited_recently(MessageFullId message_full_id, int32 seconds) { @@ -18893,7 +19000,6 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess vector read_content_message_ids; vector read_reaction_message_ids; vector new_viewed_message_ids; - vector viewed_reaction_message_ids; vector viewed_fact_check_message_ids; vector authentication_codes; vector screenshotted_secret_message_ids; @@ -18952,9 +19058,6 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess auto &view_id = info->message_id_to_view_id[message_id]; if (view_id == 0) { new_viewed_message_ids.push_back(message_id); - if (need_poll_message_reactions(d, m)) { - viewed_reaction_message_ids.push_back(message_id); - } } else { info->recently_viewed_messages.erase(view_id); } @@ -19014,9 +19117,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess info->message_id_to_view_id.erase(it->second); info->recently_viewed_messages.erase(it); } - if (!viewed_reaction_message_ids.empty()) { - queue_message_reactions_reload(dialog_id, viewed_reaction_message_ids); - } + process_viewed_message(d, new_viewed_message_ids, true); } if (!viewed_fact_check_message_ids.empty()) { CHECK(dialog_id.get_type() != DialogType::SecretChat); @@ -19029,7 +19130,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess td_->create_handler(std::move(promise)) ->send(dialog_id, std::move(viewed_fact_check_message_ids)); } - if (td_->is_online() && dialog_viewed_messages_.count(dialog_id) != 0) { + if (td_->online_manager_->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()) { @@ -19629,8 +19730,8 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * td_->dialog_manager_->get_dialog_profile_accent_color_id_object(d->dialog_id), td_->dialog_manager_->get_dialog_profile_background_custom_emoji_id(d->dialog_id).get(), td_->dialog_manager_->get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(), - get_message_object(d->dialog_id, get_message(d, d->last_message_id), source), get_chat_positions_object(d), - std::move(chat_lists), get_default_message_sender_object(d), block_list_id.get_block_list_object(), + get_message_object(d, d->last_message_id, source), get_chat_positions_object(d), std::move(chat_lists), + get_default_message_sender_object(d), block_list_id.get_block_list_object(), td_->dialog_manager_->get_dialog_has_protected_content(d->dialog_id), is_translatable, d->is_marked_as_unread, get_dialog_view_as_topics(d), get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_, td_->dialog_manager_->can_report_dialog(d->dialog_id), @@ -20639,6 +20740,11 @@ MessagesManager::FoundDialogMessages MessagesManager::search_dialog_messages( } } + if (tag.is_paid_reaction()) { + promise.set_value(Unit()); + return result; + } + do { random_id = Random::secure_int64(); } while (random_id == 0 || found_dialog_messages_.count(random_id) > 0); @@ -20831,46 +20937,22 @@ void MessagesManager::search_dialog_recent_location_messages(DialogId dialog_id, } } -vector MessagesManager::get_active_live_location_messages(Promise &&promise) { - if (!G()->use_message_database()) { +void MessagesManager::load_active_live_location_messages(Promise &&promise) { + if (!G()->use_message_database() || td_->auth_manager_->is_bot()) { are_active_live_location_messages_loaded_ = true; } - - if (!are_active_live_location_messages_loaded_) { - load_active_live_location_messages_queries_.push_back(std::move(promise)); - if (load_active_live_location_messages_queries_.size() == 1u) { - LOG(INFO) << "Trying to load active live location messages from database"; - G()->td_db()->get_sqlite_pmc()->get( - "di_active_live_location_messages", PromiseCreator::lambda([](string value) { - send_closure(G()->messages_manager(), - &MessagesManager::on_load_active_live_location_message_full_ids_from_database, - std::move(value)); - })); - } - return {}; + if (are_active_live_location_messages_loaded_) { + return promise.set_value(Unit()); } - - promise.set_value(Unit()); - vector result; - for (auto &message_full_id : active_live_location_message_full_ids_) { - auto m = get_message(message_full_id); - CHECK(m != nullptr); - CHECK(m->content->get_type() == MessageContentType::LiveLocation); - CHECK(!m->message_id.is_scheduled()); - - if (m->is_failed_to_send) { - continue; - } - - auto live_period = get_message_content_live_location_period(m->content.get()); - if (live_period <= G()->unix_time() - m->date) { // bool is_expired flag? - // live location is expired - continue; - } - result.push_back(message_full_id); + load_active_live_location_messages_queries_.push_back(std::move(promise)); + if (load_active_live_location_messages_queries_.size() == 1u) { + LOG(INFO) << "Trying to load active live location messages from database"; + G()->td_db()->get_sqlite_pmc()->get( + "di_active_live_location_messages", PromiseCreator::lambda([](string value) { + send_closure(G()->messages_manager(), + &MessagesManager::on_load_active_live_location_message_full_ids_from_database, std::move(value)); + })); } - - return result; } void MessagesManager::on_load_active_live_location_message_full_ids_from_database(string value) { @@ -20879,17 +20961,17 @@ void MessagesManager::on_load_active_live_location_message_full_ids_from_databas } if (value.empty()) { LOG(INFO) << "Active live location messages aren't found in the database"; - on_load_active_live_location_messages_finished(); - + are_active_live_location_messages_loaded_ = true; if (!active_live_location_message_full_ids_.empty()) { save_active_live_locations(); } + set_promises(load_active_live_location_messages_queries_); return; } LOG(INFO) << "Successfully loaded active live location messages list of size " << value.size() << " from database"; - auto new_message_full_ids = std::move(active_live_location_message_full_ids_); + const auto new_message_full_ids = std::move(active_live_location_message_full_ids_); vector old_message_full_ids; log_event_parse(old_message_full_ids, value).ensure(); @@ -20906,64 +20988,80 @@ void MessagesManager::on_load_active_live_location_message_full_ids_from_databas add_active_live_location(message_full_id); } - on_load_active_live_location_messages_finished(); - + are_active_live_location_messages_loaded_ = true; + if (new_message_full_ids.size() != active_live_location_message_full_ids_.size()) { + send_update_active_live_location_messages(); + } if (!new_message_full_ids.empty() || old_message_full_ids.size() != active_live_location_message_full_ids_.size()) { save_active_live_locations(); } -} - -void MessagesManager::on_load_active_live_location_messages_finished() { - are_active_live_location_messages_loaded_ = true; set_promises(load_active_live_location_messages_queries_); } -void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Message *m) { +bool MessagesManager::try_add_active_live_location(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); - if (td_->auth_manager_->is_bot()) { - return; - } - if (m->content->get_type() != MessageContentType::LiveLocation || m->message_id.is_scheduled() || - m->message_id.is_local() || m->via_bot_user_id.is_valid() || m->via_business_bot_user_id.is_valid() || - m->forward_info != nullptr) { - return; + if (td_->auth_manager_->is_bot() || m->content->get_type() != MessageContentType::LiveLocation || + m->message_id.is_scheduled() || m->message_id.is_local() || m->message_id.is_yet_unsent() || + m->via_bot_user_id.is_valid() || m->via_business_bot_user_id.is_valid() || m->forward_info != nullptr) { + return false; } auto live_period = get_message_content_live_location_period(m->content.get()); if (live_period <= G()->unix_time() - m->date + 1) { // bool is_expired flag? // live location is expired - return; + return false; } - add_active_live_location({dialog_id, m->message_id}); + return add_active_live_location({dialog_id, m->message_id}); } -void MessagesManager::add_active_live_location(MessageFullId message_full_id) { +bool MessagesManager::add_active_live_location(MessageFullId message_full_id) { if (td_->auth_manager_->is_bot()) { - return; + return false; } CHECK(message_full_id.get_message_id().is_valid()); if (!active_live_location_message_full_ids_.insert(message_full_id).second) { - return; + return false; } - // TODO add timer for live location expiration - - if (!G()->use_message_database()) { - return; + if (G()->use_message_database()) { + if (are_active_live_location_messages_loaded_) { + save_active_live_locations(); + } else if (load_active_live_location_messages_queries_.empty()) { + // load active live locations and save after that + load_active_live_location_messages(Auto()); + } } + return true; +} - if (are_active_live_location_messages_loaded_) { - save_active_live_locations(); - } else if (load_active_live_location_messages_queries_.empty()) { - // load active live locations and save after that - get_active_live_location_messages(Auto()); - } +bool MessagesManager::delete_active_live_location(MessageFullId message_full_id) { + return active_live_location_message_full_ids_.erase(message_full_id) != 0; } -bool MessagesManager::delete_active_live_location(DialogId dialog_id, const Message *m) { - CHECK(m != nullptr); - return active_live_location_message_full_ids_.erase(MessageFullId{dialog_id, m->message_id}) != 0; +void MessagesManager::schedule_active_live_location_expiration() { + if (active_live_location_message_full_ids_.empty()) { + live_location_expire_timeout_.cancel_timeout(); + } else { + double expires_in = std::numeric_limits::max(); + for (auto message_full_id : active_live_location_message_full_ids_) { + const auto *m = get_message(message_full_id); + CHECK(m != nullptr); + double live_period = get_message_content_live_location_period(m->content.get()); + if (live_period > 2e9) { + continue; + } + expires_in = min(expires_in, live_period + m->date - G()->unix_time()); + } + if (expires_in < 2e9) { + LOG(INFO) << "Schedule live location expiration in " << expires_in; + live_location_expire_timeout_.set_callback(std::move(on_live_location_expire_timeout_callback)); + live_location_expire_timeout_.set_callback_data(static_cast(this)); + live_location_expire_timeout_.set_timeout_in(expires_in); + } else { + LOG(INFO) << "Have no active expiring live locations"; + } + } } void MessagesManager::save_active_live_locations() { @@ -21298,7 +21396,7 @@ td_api::object_ptr MessagesManager::get_found_chat_me vector> result; result.reserve(found_dialog_messages.message_ids.size()); for (const auto &message_id : found_dialog_messages.message_ids) { - auto message = get_message_object(dialog_id, get_message_force(d, message_id, source), source); + auto message = get_message_object(d, message_id, source); if (message != nullptr) { result.push_back(std::move(message)); } @@ -21466,7 +21564,7 @@ void MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (message_id.is_valid() && (message_id == d->last_message_id || (*d->ordered_messages.get_const_iterator(message_id))->have_next())) { - return promise.set_value(get_message_object(dialog_id, get_message(d, message_id), "get_dialog_message_by_date")); + return promise.set_value(get_message_object(d, message_id, "get_dialog_message_by_date")); } if (G()->use_message_database() && d->last_database_message_id != MessageId()) { @@ -21551,8 +21649,7 @@ void MessagesManager::on_get_dialog_message_by_date_from_database( LOG(ERROR) << "Failed to find " << m->message_id << " in " << dialog_id << " by date " << date; message_id = m->message_id; } - promise.set_value( - get_message_object(dialog_id, get_message(d, message_id), "on_get_dialog_message_by_date_from_database")); + promise.set_value(get_message_object(d, message_id, "on_get_dialog_message_by_date_from_database")); return; } // TODO if m == nullptr, we need to just adjust it to the next non-nullptr message, not get from server @@ -21572,8 +21669,7 @@ void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, in auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (message_id.is_valid()) { - promise.set_value( - get_message_object(d->dialog_id, get_message(d, message_id), "get_dialog_message_by_date_from_server")); + promise.set_value(get_message_object(d, message_id, "get_dialog_message_by_date_from_server")); } else { promise.set_value(nullptr); } @@ -21607,8 +21703,7 @@ void MessagesManager::on_get_dialog_message_by_date(DialogId dialog_id, int32 da LOG(ERROR) << "Failed to find " << result.get_message_id() << " in " << dialog_id << " by date " << date; message_id = result.get_message_id(); } - return promise.set_value( - get_message_object(dialog_id, get_message(d, message_id), "on_get_dialog_message_by_date")); + return promise.set_value(get_message_object(d, message_id, "on_get_dialog_message_by_date")); } } } @@ -22498,7 +22593,7 @@ void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id bool MessagesManager::can_add_message_tag(DialogId dialog_id, const MessageReactions *reactions) const { return dialog_id == td_->dialog_manager_->get_my_dialog_id() && - (reactions == nullptr || reactions->reactions_.empty() || reactions->are_tags_); + (reactions == nullptr || reactions->are_empty() || reactions->are_tags_); } Result> MessagesManager::get_message_available_reactions( @@ -22558,10 +22653,12 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, reactions_uniq_max = active_reactions.reactions_limit_; } bool can_add_new_reactions = - m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; + m->reactions == nullptr || m->reactions->get_non_paid_reaction_count() < reactions_uniq_max; - if (!can_use_reactions || !can_add_new_reactions) { + if (!can_use_reactions) { active_reactions = ChatReactions(); + } else if (!can_add_new_reactions) { + active_reactions.ignore_non_paid_reaction_types(); } if (active_reactions.allow_all_regular_) { @@ -22600,10 +22697,12 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, DialogId MessagesManager::get_my_reaction_dialog_id(const Dialog *d) const { auto my_dialog_id = td_->dialog_manager_->get_my_dialog_id(); + if (td_->dialog_manager_->is_broadcast_channel(d->dialog_id)) { + return my_dialog_id; + } auto reaction_dialog_id = d->default_send_message_as_dialog_id.is_valid() ? d->default_send_message_as_dialog_id : my_dialog_id; - if (reaction_dialog_id == my_dialog_id && td_->dialog_manager_->is_anonymous_administrator(d->dialog_id, nullptr) && - !td_->dialog_manager_->is_broadcast_channel(d->dialog_id)) { + if (reaction_dialog_id == my_dialog_id && td_->dialog_manager_->is_anonymous_administrator(d->dialog_id, nullptr)) { // react as the supergroup return d->dialog_id; } @@ -22626,6 +22725,9 @@ void MessagesManager::add_message_reaction(MessageFullId message_full_id, Reacti if (!get_message_available_reactions(d, m, true, nullptr).is_allowed_reaction_type(reaction_type)) { return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); } + if (reaction_type.is_paid_reaction()) { + return promise.set_error(Status::Error(400, "Use addPaidMessageReaction instead to add the paid reaction")); + } bool have_recent_choosers = !td_->dialog_manager_->is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m); @@ -22666,7 +22768,7 @@ void MessagesManager::remove_message_reaction(MessageFullId message_full_id, Rea return promise.set_error(Status::Error(400, "Message not found")); } - if (reaction_type.is_empty()) { + if (reaction_type.is_empty() || reaction_type.is_paid_reaction()) { return promise.set_error(Status::Error(400, "Invalid reaction specified")); } @@ -22688,6 +22790,89 @@ void MessagesManager::remove_message_reaction(MessageFullId message_full_id, Rea } } +void MessagesManager::add_paid_message_reaction(MessageFullId message_full_id, int64 star_count, bool is_anonymous, + Promise &&promise) { + auto dialog_id = message_full_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id, "add_paid_message_reaction"); + if (d == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + Message *m = get_message_force(d, message_full_id.get_message_id(), "add_paid_message_reaction"); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (!get_message_available_reactions(d, m, true, nullptr).is_allowed_reaction_type(ReactionType::paid()) || + !td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return promise.set_error(Status::Error(400, "The reaction isn't available for the message")); + } + if (star_count <= 0 || star_count > td_->option_manager_->get_option_integer("paid_reaction_star_count_max")) { + return promise.set_error(Status::Error(400, "Invalid Telegram Star count specified")); + } + + if (m->reactions == nullptr) { + m->reactions = make_unique(); + m->available_reactions_generation = d->available_reactions_generation; + } + + LOG(INFO) << "Have message with " << *m->reactions; + m->reactions->add_my_paid_reaction(td_, narrow_cast(star_count), is_anonymous); + m->reactions->sort_reactions(active_reaction_pos_); + LOG(INFO) << "Update message reactions to " << *m->reactions; + + send_update_message_interaction_info(d->dialog_id, m); + on_message_changed(d, m, true, "add_paid_message_reaction"); + + auto &task_id = paid_reaction_task_ids_[message_full_id]; + if (task_id == 0) { + task_id = ++paid_reaction_task_id_; + paid_reaction_tasks_[task_id] = message_full_id; + } + send_paid_reactions_timeout_.set_timeout_in(task_id, 6.0); + promise.set_value(Unit()); +} + +void MessagesManager::remove_paid_message_reactions(MessageFullId message_full_id, Promise &&promise) { + auto it = paid_reaction_task_ids_.find(message_full_id); + if (it == paid_reaction_task_ids_.end()) { + return promise.set_value(Unit()); + } + auto task_id = it->second; + paid_reaction_task_ids_.erase(it); + bool is_erased = paid_reaction_tasks_.erase(task_id) > 0; + CHECK(is_erased); + + send_paid_reactions_timeout_.cancel_timeout(task_id); + + Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), "remove_paid_message_reaction"); + CHECK(d != nullptr); + auto *m = get_message_force(d, message_full_id.get_message_id(), "on_send_paid_reactions_timeout"); + if (m != nullptr && m->reactions != nullptr && m->reactions->drop_pending_paid_reactions(td_)) { + send_update_message_interaction_info(d->dialog_id, m); + on_message_changed(d, m, true, "on_send_paid_reactions_timeout"); + } + promise.set_value(Unit()); +} + +void MessagesManager::toggle_paid_message_reaction_is_anonymous(MessageFullId message_full_id, bool is_anonymous, + Promise &&promise) { + auto dialog_id = message_full_id.get_dialog_id(); + Dialog *d = get_dialog_force(dialog_id, "toggle_paid_message_reaction_is_anonymous"); + if (d == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + Message *m = get_message_force(d, message_full_id.get_message_id(), "toggle_paid_message_reaction_is_anonymous"); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (m->reactions == nullptr) { + return promise.set_error(Status::Error(400, "Message has no paid reactions")); + } + if (m->reactions->toggle_paid_message_reaction_is_anonymous(td_, message_full_id, is_anonymous, std::move(promise))) { + send_update_message_interaction_info(d->dialog_id, m); + on_message_changed(d, m, true, "toggle_paid_message_reaction_is_anonymous"); + } +} + void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise &&promise) { CHECK(m->reactions != nullptr); @@ -22852,8 +23037,9 @@ td_api::object_ptr MessagesManager::get_dialog_event_log_messag nullptr, nullptr, m->is_outgoing, m->is_pinned, m->is_from_offline, can_be_saved, true, m->is_channel_post, m->is_topic_message, false, m->date, edit_date, std::move(forward_info), std::move(import_info), std::move(interaction_info), Auto(), nullptr, nullptr, 0, 0, nullptr, 0.0, 0.0, via_bot_user_id, 0, - m->sender_boost_count, m->author_signature, 0, 0, get_restriction_reason_description(m->restriction_reasons), - std::move(content), std::move(reply_markup)); + m->sender_boost_count, m->author_signature, 0, 0, + get_restriction_reason_has_sensitive_content(m->restriction_reasons), + get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } td_api::object_ptr MessagesManager::get_business_message_object( @@ -22920,8 +23106,18 @@ td_api::object_ptr MessagesManager::get_business_message_messag nullptr, nullptr, m->is_outgoing, false, m->is_from_offline, can_be_saved, false, false, false, false, m->date, m->edit_date, std::move(forward_info), std::move(import_info), nullptr, Auto(), nullptr, std::move(reply_to), 0, 0, std::move(self_destruct_type), 0.0, 0.0, via_bot_user_id, via_business_bot_user_id, 0, string(), - m->media_album_id, m->effect_id.get(), get_restriction_reason_description(m->restriction_reasons), - std::move(content), std::move(reply_markup)); + m->media_album_id, m->effect_id.get(), get_restriction_reason_has_sensitive_content(m->restriction_reasons), + get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); +} + +td_api::object_ptr MessagesManager::get_message_object(Dialog *d, MessageId message_id, + const char *source) { + return get_message_object(d->dialog_id, get_message_force(d, message_id, source), source); +} + +td_api::object_ptr MessagesManager::get_message_object(const Dialog *d, MessageId message_id, + const char *source) const { + return get_message_object(d->dialog_id, get_message(d, message_id), source); } td_api::object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, @@ -23003,6 +23199,7 @@ td_api::object_ptr MessagesManager::get_message_object(DialogId td_->saved_messages_manager_->get_saved_messages_topic_id_object(m->saved_messages_topic_id), std::move(self_destruct_type), ttl_expires_in, auto_delete_in, via_bot_user_id, via_business_bot_user_id, m->sender_boost_count, m->author_signature, m->media_album_id, m->effect_id.get(), + get_restriction_reason_has_sensitive_content(m->restriction_reasons), get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } @@ -23011,9 +23208,8 @@ td_api::object_ptr MessagesManager::get_messages_object(int32 bool skip_not_found, const char *source) { Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); - auto message_objects = transform(message_ids, [this, dialog_id, d, source](MessageId message_id) { - return get_message_object(dialog_id, get_message_force(d, message_id, source), source); - }); + auto message_objects = transform( + message_ids, [this, d, source](MessageId message_id) { return get_message_object(d, message_id, source); }); return get_messages_object(total_count, std::move(message_objects), skip_not_found); } @@ -23121,13 +23317,8 @@ unique_ptr MessagesManager::create_message_to_send( auto message = make_unique(); auto *m = message.get(); bool is_channel_post = td_->dialog_manager_->is_broadcast_channel(dialog_id); - if (is_channel_post) { - // sender of the post can be hidden - if (!is_scheduled && td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { - m->author_signature = td_->user_manager_->get_user_title(my_id); - } - m->sender_dialog_id = dialog_id; - } else { + if (!is_channel_post || + (!is_scheduled && td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id()))) { if (send_as_dialog_id.is_valid()) { if (send_as_dialog_id.get_type() == DialogType::User) { m->sender_user_id = send_as_dialog_id.get_user_id(); @@ -23142,12 +23333,27 @@ unique_ptr MessagesManager::create_message_to_send( } m->has_explicit_sender = true; } else { - if (td_->dialog_manager_->is_anonymous_administrator(dialog_id, &m->author_signature)) { + if (!is_channel_post && td_->dialog_manager_->is_anonymous_administrator(dialog_id, &m->author_signature)) { m->sender_dialog_id = dialog_id; } else { m->sender_user_id = my_id; } } + } else { + m->sender_dialog_id = dialog_id; + } + if (is_channel_post && !is_scheduled && td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { + auto show_message_sender = td_->chat_manager_->get_channel_show_message_sender(dialog_id.get_channel_id()); + if (m->sender_dialog_id != dialog_id || !m->has_explicit_sender) { + m->author_signature = m->sender_dialog_id == dialog_id || m->sender_dialog_id == DialogId() || + (m->has_explicit_sender && !show_message_sender) + ? td_->user_manager_->get_user_title(my_id) + : td_->dialog_manager_->get_dialog_title(m->sender_dialog_id); + } + if (!show_message_sender) { + m->sender_user_id = UserId(); + m->sender_dialog_id = dialog_id; + } } m->message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date) : get_next_yet_unsent_message_id(d); @@ -23195,7 +23401,7 @@ unique_ptr MessagesManager::create_message_to_send( } } } - if (m->sender_user_id == my_id && dialog_type == DialogType::Channel) { + if (m->sender_user_id == my_id && dialog_type == DialogType::Channel && !is_channel_post) { m->sender_boost_count = td_->chat_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); } m->effect_id = MessageEffectId(options.effect_id); @@ -23599,9 +23805,10 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "get_dialog_send_message_as_dialog_ids")); - if (!d->default_send_message_as_dialog_id.is_valid()) { + if (!d->default_send_message_as_dialog_id.is_valid() || can_send_message(dialog_id).is_error()) { return promise.set_value(td_api::make_object()); } + // checked in on_update_dialog_default_send_message_as_dialog_id CHECK(dialog_id.get_type() == DialogType::Channel); if (td_->chat_manager_->are_created_public_broadcasts_inited()) { @@ -23612,6 +23819,13 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( auto sender = get_message_sender_object(td, dialog_id, "add_sender"); senders->senders_.push_back(td_api::make_object(std::move(sender), needs_premium)); }; + auto is_broadcast = td_->dialog_manager_->is_broadcast_channel(dialog_id); + if (is_broadcast) { + if (!td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { + return promise.set_value(td_api::make_object()); + } + add_sender(td_->dialog_manager_->get_my_dialog_id(), false); + } if (td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr)) { add_sender(dialog_id, false); } else { @@ -23628,9 +23842,12 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( auto linked_channel_id = td_->chat_manager_->get_channel_linked_channel_id( dialog_id.get_channel_id(), "get_dialog_send_message_as_dialog_ids"); for (auto channel_id : created_public_broadcasts) { + if (DialogId(channel_id) == dialog_id) { + continue; + } int64 score = td_->chat_manager_->get_channel_participant_count(channel_id); - bool needs_premium = - !is_premium && channel_id != linked_channel_id && !td_->chat_manager_->get_channel_is_verified(channel_id); + bool needs_premium = !is_premium && !is_broadcast && channel_id != linked_channel_id && + !td_->chat_manager_->get_channel_is_verified(channel_id); if (needs_premium) { score -= static_cast(1) << 40; } @@ -23666,19 +23883,21 @@ void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dial TRY_RESULT_PROMISE( promise, d, check_dialog_access(dialog_id, false, AccessRights::Read, "set_dialog_default_send_message_as_dialog_id")); - if (!d->default_send_message_as_dialog_id.is_valid()) { + if (!d->default_send_message_as_dialog_id.is_valid() || can_send_message(dialog_id).is_error()) { return promise.set_error(Status::Error(400, "Can't change message sender in the chat")); } // checked in on_update_dialog_default_send_message_as_dialog_id - CHECK(dialog_id.get_type() == DialogType::Channel && !td_->dialog_manager_->is_broadcast_channel(dialog_id)); + CHECK(dialog_id.get_type() == DialogType::Channel); - bool is_anonymous = td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr); + auto is_broadcast = td_->dialog_manager_->is_broadcast_channel(dialog_id); + auto is_anonymous = td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr); switch (message_sender_dialog_id.get_type()) { case DialogType::User: if (message_sender_dialog_id != td_->dialog_manager_->get_my_dialog_id()) { return promise.set_error(Status::Error(400, "Can't send messages as another user")); } - if (is_anonymous) { + if (is_anonymous && + (!is_broadcast || !td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id()))) { return promise.set_error(Status::Error(400, "Can't send messages as self")); } break; @@ -26403,7 +26622,7 @@ void MessagesManager::fix_forwarded_message(Message *m, DialogId to_dialog_id, c m->via_bot_user_id = forwarded_message->sender_user_id; } if (m->via_bot_user_id == td_->user_manager_->get_my_id()) { - // if via_bot_user_id is the current bot user, then there should be + // if via_bot_user_id is the current bot user, then there should be no via_bot m->via_bot_user_id = UserId(); } } @@ -27215,12 +27434,10 @@ Result MessagesManager::add_local_message( } else { return Status::Error(400, "The message must have a sender"); } - if (is_channel_post && sender_user_id.is_valid()) { + if (is_channel_post && sender_user_id.is_valid() && + !td_->chat_manager_->get_channel_show_message_sender(dialog_id.get_channel_id())) { return Status::Error(400, "Channel post can't have user as a sender"); } - if (is_channel_post && sender_dialog_id != dialog_id) { - return Status::Error(400, "Channel post must have the channel as a sender"); - } auto dialog_type = dialog_id.get_type(); auto my_id = td_->user_manager_->get_my_id(); @@ -27246,13 +27463,14 @@ Result MessagesManager::add_local_message( if (is_channel_post) { // sender of the post can be hidden if (td_->chat_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { - m->author_signature = td_->user_manager_->get_user_title(sender_user_id); + auto real_sender_user_id = sender_user_id.is_valid() ? sender_user_id : my_id; + m->author_signature = m->sender_dialog_id == dialog_id || m->sender_dialog_id == DialogId() + ? td_->user_manager_->get_user_title(real_sender_user_id) + : td_->dialog_manager_->get_dialog_title(m->sender_dialog_id); } - m->sender_dialog_id = sender_dialog_id; - } else { - m->sender_user_id = sender_user_id; - m->sender_dialog_id = sender_dialog_id; } + m->sender_user_id = sender_user_id; + m->sender_dialog_id = sender_dialog_id; m->date = G()->unix_time(); m->replied_message_info = RepliedMessageInfo(td_, input_reply_to); m->reply_to_story_full_id = input_reply_to.get_story_full_id(); @@ -27273,7 +27491,7 @@ Result MessagesManager::add_local_message( m->update_stickersets_order = false; m->view_count = 0; m->forward_count = 0; - if (m->sender_user_id == my_id && dialog_type == DialogType::Channel) { + if (m->sender_user_id == my_id && dialog_type == DialogType::Channel && !is_channel_post) { m->sender_boost_count = td_->chat_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); } m->content = std::move(message_content.content); @@ -28514,7 +28732,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f int32 min_delay_ms = 0; if (need_delay_message_content_notification(m->content.get(), td_->user_manager_->get_my_id())) { min_delay_ms = 3000; // 3 seconds - } else if (td_->is_online() && d->open_count > 0) { + } else if (td_->online_manager_->is_online() && d->open_count > 0) { min_delay_ms = 1000; // 1 second } auto ringtone_id = get_dialog_notification_ringtone_id(settings_dialog_id, settings_dialog); @@ -28752,6 +28970,21 @@ void MessagesManager::send_update_message_live_location_viewed(MessageFullId mes message_full_id.get_message_id().get())); } +td_api::object_ptr +MessagesManager::get_update_active_live_location_messages_object() const { + auto message_objects = transform(active_live_location_message_full_ids_, [this](MessageFullId message_full_id) { + const auto *m = get_message(message_full_id); + CHECK(m != nullptr); + return get_message_object(message_full_id.get_dialog_id(), m, "send_update_active_live_location_messages"); + }); + return td_api::make_object(std::move(message_objects)); +} + +void MessagesManager::send_update_active_live_location_messages() { + schedule_active_live_location_expiration(); + send_closure(G()->td(), &Td::send_update, get_update_active_live_location_messages_object()); +} + void MessagesManager::send_update_delete_messages(DialogId dialog_id, vector &&message_ids, bool is_permanent) const { if (message_ids.empty()) { @@ -29468,7 +29701,9 @@ MessageFullId MessagesManager::on_send_message_success(int64 random_id, MessageI return {}; } - try_add_active_live_location(dialog_id, m); + if (try_add_active_live_location(dialog_id, m)) { + send_update_active_live_location_messages(); + } update_reply_count_by_message(d, +1, m); update_forward_count(dialog_id, m); being_readded_message_id_ = MessageFullId(); @@ -30899,7 +31134,7 @@ void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogI return; } auto dialog_type = dialog_id.get_type(); - if (dialog_type != DialogType::Channel || td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + if (dialog_type != DialogType::Channel) { if (default_send_as_dialog_id != DialogId()) { LOG(ERROR) << "Receive message sender " << default_send_as_dialog_id << " in " << dialog_id; } @@ -31416,7 +31651,8 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { } CHECK(m->message_id.is_yet_unsent()); if (m->forward_info != nullptr || m->had_forward_info || m->is_copy || m->message_id.is_scheduled() || - m->sender_dialog_id.is_valid() || m->content->get_type() == MessageContentType::PaidMedia) { + m->sender_dialog_id.is_valid() || m->content->get_type() == MessageContentType::PaidMedia || + td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return; } @@ -32783,7 +33019,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq const int32 INDEX_MASK_MASK = ~(message_search_filter_index_mask(MessageSearchFilter::UnreadMention) | message_search_filter_index_mask(MessageSearchFilter::UnreadReaction)); auto old_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK; - bool was_deleted = delete_active_live_location(dialog_id, m); + bool was_deleted = delete_active_live_location({dialog_id, m->message_id}); auto old_file_ids = get_message_file_ids(m); bool need_send_update = update_message(d, m, std::move(message), true); @@ -32792,7 +33028,12 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } auto new_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK; if (was_deleted) { - try_add_active_live_location(dialog_id, m); + if (!try_add_active_live_location(dialog_id, m)) { + send_update_active_live_location_messages(); + save_active_live_locations(); + } else { + schedule_active_live_location_expiration(); + } } change_message_files(dialog_id, m, old_file_ids); if (need_send_update) { @@ -35814,7 +36055,8 @@ unique_ptr MessagesManager::parse_dialog(DialogId dialo break; } if (!d->need_drop_default_send_message_as_dialog_id && d->default_send_message_as_dialog_id.is_valid() && - dialog_type == DialogType::Channel && !td_->chat_manager_->is_channel_public(dialog_id.get_channel_id()) && + dialog_type == DialogType::Channel && !td_->dialog_manager_->is_broadcast_channel(d->dialog_id) && + !td_->chat_manager_->is_channel_public(dialog_id.get_channel_id()) && !td_->chat_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { LOG(INFO) << "Drop message sender in " << dialog_id; d->need_drop_default_send_message_as_dialog_id = true; @@ -38413,8 +38655,8 @@ void MessagesManager::stop_poll(MessageFullId message_full_id, td_api::object_pt stop_message_content_poll(td_, m->content.get(), message_full_id, std::move(new_reply_markup), std::move(promise)); } -Result MessagesManager::get_invoice_message_id(MessageFullId message_full_id) { - auto m = get_message_force(message_full_id, "get_invoice_message_id"); +Result MessagesManager::get_invoice_message_info(MessageFullId message_full_id) { + auto m = get_message_force(message_full_id, "get_invoice_message_info"); if (m == nullptr) { return Status::Error(400, "Message not found"); } @@ -38441,7 +38683,11 @@ Result MessagesManager::get_invoice_message_id(MessageFullId me } } - return m->message_id.get_server_message_id(); + InvoiceMessageInfo result; + result.server_message_id_ = m->message_id.get_server_message_id(); + result.star_count_ = + content_type != MessageContentType::PaidMedia ? 0 : get_message_content_star_count(m->content.get()); + return std::move(result); } Result MessagesManager::get_payment_successful_message_id(MessageFullId message_full_id) { @@ -38652,6 +38898,10 @@ void MessagesManager::get_current_state(vector> &&promise); - vector get_active_live_location_messages(Promise &&promise); + void load_active_live_location_messages(Promise &&promise); void get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise> &&promise); @@ -795,6 +796,14 @@ class MessagesManager final : public Actor { void remove_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, Promise &&promise); + void add_paid_message_reaction(MessageFullId message_full_id, int64 star_count, bool is_anonymous, + Promise &&promise); + + void remove_paid_message_reactions(MessageFullId message_full_id, Promise &&promise); + + void toggle_paid_message_reaction_is_anonymous(MessageFullId message_full_id, bool is_anonymous, + Promise &&promise); + td_api::object_ptr get_dialog_event_log_message_object( DialogId dialog_id, tl_object_ptr &&message, DialogId &sender_dialog_id); @@ -849,7 +858,7 @@ class MessagesManager final : public Actor { void on_update_dialog_available_reactions(DialogId dialog_id, telegram_api::object_ptr &&available_reactions, - int32 reactions_limit); + int32 reactions_limit, bool paid_reactions_available); void hide_dialog_action_bar(DialogId dialog_id); @@ -961,7 +970,11 @@ class MessagesManager final : public Actor { Result get_login_button_url(MessageFullId message_full_id, int64 button_id); - Result get_invoice_message_id(MessageFullId message_full_id); + struct InvoiceMessageInfo { + ServerMessageId server_message_id_; + int64 star_count_ = 0; + }; + Result get_invoice_message_info(MessageFullId message_full_id); Result get_payment_successful_message_id(MessageFullId message_full_id); @@ -2119,6 +2132,8 @@ class MessagesManager final : public Actor { void on_send_update_chat_read_inbox_timeout(DialogId dialog_id); + void on_send_paid_reactions_timeout(int64 task_id); + bool delete_newer_server_messages_at_the_end(Dialog *d, MessageId max_message_id); template @@ -2369,6 +2384,8 @@ class MessagesManager final : public Actor { void send_update_message_live_location_viewed(MessageFullId message_full_id); + void send_update_active_live_location_messages(); + void send_update_delete_messages(DialogId dialog_id, vector &&message_ids, bool is_permanent) const; void send_update_new_chat(Dialog *d, const char *source); @@ -2442,6 +2459,11 @@ class MessagesManager final : public Actor { td_api::object_ptr get_message_message_content_object(DialogId dialog_id, const Message *m) const; + td_api::object_ptr get_message_object(Dialog *d, MessageId message_id, const char *source); + + td_api::object_ptr get_message_object(const Dialog *d, MessageId message_id, + const char *source) const; + td_api::object_ptr get_message_object(DialogId dialog_id, const Message *m, const char *source) const; @@ -2452,6 +2474,8 @@ class MessagesManager final : public Actor { td_api::object_ptr get_business_message_message_object( telegram_api::object_ptr &&message); + td_api::object_ptr get_update_active_live_location_messages_object() const; + vector sort_dialogs_by_order(const vector &dialog_ids, int32 limit) const; static bool need_unread_counter(int64 dialog_order); @@ -2870,15 +2894,17 @@ class MessagesManager final : public Actor { MessageId offset_message_id, int32 limit, MessageSearchFilter filter, Promise> &&promise); + void process_viewed_message(Dialog *d, const vector &viewed_message_ids, bool is_first); + void on_load_active_live_location_message_full_ids_from_database(string value); - void on_load_active_live_location_messages_finished(); + bool try_add_active_live_location(DialogId dialog_id, const Message *m); - void try_add_active_live_location(DialogId dialog_id, const Message *m); + bool add_active_live_location(MessageFullId message_full_id); - void add_active_live_location(MessageFullId message_full_id); + bool delete_active_live_location(MessageFullId message_full_id); - bool delete_active_live_location(DialogId dialog_id, const Message *m); + void schedule_active_live_location_expiration(); void save_active_live_locations(); @@ -3040,6 +3066,12 @@ class MessagesManager final : public Actor { static void on_send_update_chat_read_inbox_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int); + static void on_send_paid_reactions_timeout_callback(void *messages_manager_ptr, int64 task_id); + + static void on_live_location_expire_timeout_callback(void *messages_manager_ptr); + + void on_live_location_expire_timeout(); + void load_secret_thumbnail(FileId thumbnail_file_id); void on_upload_media(FileId file_id, tl_object_ptr input_file, @@ -3371,6 +3403,9 @@ class MessagesManager final : public Actor { MultiTimeout preload_folder_dialog_list_timeout_{"PreloadFolderDialogListTimeout"}; MultiTimeout update_viewed_messages_timeout_{"UpdateViewedMessagesTimeout"}; MultiTimeout send_update_chat_read_inbox_timeout_{"SendUpdateChatReadInboxTimeout"}; + MultiTimeout send_paid_reactions_timeout_{"SendPaidReactionsTimeout"}; + + Timeout live_location_expire_timeout_; Hints dialogs_hints_; // search dialogs by title and usernames @@ -3505,6 +3540,10 @@ class MessagesManager final : public Actor { }; FlatHashMap pending_reactions_; + int64 paid_reaction_task_id_ = 0; + FlatHashMap paid_reaction_task_ids_; + FlatHashMap paid_reaction_tasks_; + FlatHashMap pending_read_reactions_; vector active_reaction_types_; diff --git a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp index f8e2a434..59a3a5f6 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp @@ -21,6 +21,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/ConnectionCreator.h" #include "td/telegram/net/DcId.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/Photo.h" #include "td/telegram/Photo.hpp" @@ -2789,7 +2790,7 @@ void NotificationManager::process_push_notification(string payload, Promiseis_online()) { + if (!td_->online_manager_->is_online()) { // reset online flag to false to immediately check all connections aliveness send_closure(G()->state_manager(), &StateManager::on_online, false); } diff --git a/lib/tgchat/ext/td/td/telegram/OnlineManager.cpp b/lib/tgchat/ext/td/td/telegram/OnlineManager.cpp new file mode 100644 index 00000000..96a3a9fc --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/OnlineManager.cpp @@ -0,0 +1,164 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/OnlineManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/StateManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/Random.h" +#include "td/utils/Status.h" + +namespace td { + +class UpdateStatusQuery final : public Td::ResultHandler { + bool is_offline_; + + public: + NetQueryRef send(bool is_offline) { + is_offline_ = is_offline; + auto net_query = G()->net_query_creator().create(telegram_api::account_updateStatus(is_offline)); + auto result = net_query.get_weak(); + send_query(std::move(net_query)); + return result; + } + + 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()); + } + + bool result = result_ptr.ok(); + LOG(INFO) << "Receive result for UpdateStatusQuery: " << result; + td_->online_manager_->on_update_status_success(!is_offline_); + } + + void on_error(Status status) final { + if (status.code() != NetQuery::Canceled && !G()->is_expected_error(status)) { + LOG(ERROR) << "Receive error for UpdateStatusQuery: " << status; + } + } +}; + +OnlineManager::OnlineManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void OnlineManager::tear_down() { + parent_.reset(); +} + +void OnlineManager::start_up() { + init(); +} + +void OnlineManager::init() { + if (is_online_) { + on_online_updated(false /*ignored*/, true); + } + if (td_->auth_manager_->is_bot()) { + set_is_bot_online(true); + } +} + +void OnlineManager::on_online_timeout_callback(void *online_manager_ptr) { + if (G()->close_flag()) { + return; + } + + auto online_manager = static_cast(online_manager_ptr); + send_closure_later(online_manager->actor_id(online_manager), &OnlineManager::on_online_updated, false, true); +} + +void OnlineManager::on_ping_server_timeout_callback(void *online_manager_ptr) { + if (G()->close_flag()) { + return; + } + + auto online_manager = static_cast(online_manager_ptr); + send_closure_later(online_manager->actor_id(online_manager), &OnlineManager::on_ping_server_timeout); +} + +void OnlineManager::on_online_updated(bool force, bool send_update) { + if (G()->close_flag() || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + if (force || is_online_) { + td_->user_manager_->set_my_online_status(is_online_, send_update, true); + if (!update_status_query_.empty()) { + LOG(INFO) << "Cancel previous update status query"; + cancel_query(update_status_query_); + } + update_status_query_ = td_->create_handler()->send(!is_online_); + } + if (is_online_) { + online_timeout_.set_callback(std::move(on_online_timeout_callback)); + online_timeout_.set_callback_data(static_cast(this)); + online_timeout_.set_timeout_in(static_cast(G()->get_option_integer("online_update_period_ms", 210000)) * + 1e-3); + } else { + online_timeout_.cancel_timeout(); + } +} + +void OnlineManager::on_update_status_success(bool is_online) { + if (is_online == is_online_) { + if (!update_status_query_.empty()) { + update_status_query_ = NetQueryRef(); + } + td_->user_manager_->set_my_online_status(is_online_, true, false); + } +} + +bool OnlineManager::is_online() const { + return is_online_; +} + +void OnlineManager::set_is_online(bool is_online) { + if (is_online == is_online_) { + return; + } + + is_online_ = is_online; + if (td_->auth_manager_ != nullptr) { // postpone if there is no AuthManager yet + on_online_updated(true, true); + } +} + +void OnlineManager::set_is_bot_online(bool is_bot_online) { + ping_server_timeout_.set_callback(std::move(on_ping_server_timeout_callback)); + ping_server_timeout_.set_callback_data(static_cast(this)); + ping_server_timeout_.set_timeout_in(PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5)); + + if (td_->option_manager_->get_option_integer("session_count") > 1) { + is_bot_online = false; + } + + if (is_bot_online == is_bot_online_) { + return; + } + + is_bot_online_ = is_bot_online; + send_closure(G()->state_manager(), &StateManager::on_online, is_bot_online_); +} + +void OnlineManager::on_ping_server_timeout() { + if (G()->close_flag() || td_->updates_manager_ == nullptr || !td_->auth_manager_->is_authorized()) { + return; + } + td_->updates_manager_->ping_server(); + set_is_bot_online(false); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/OnlineManager.h b/lib/tgchat/ext/td/td/telegram/OnlineManager.h new file mode 100644 index 00000000..fda4803e --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/OnlineManager.h @@ -0,0 +1,60 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/net/NetQuery.h" + +#include "td/actor/actor.h" +#include "td/actor/Timeout.h" + +#include "td/utils/common.h" + +namespace td { + +class Td; + +class OnlineManager final : public Actor { + public: + OnlineManager(Td *td, ActorShared<> parent); + + void init(); + + void on_online_updated(bool force, bool send_update); + + void on_update_status_success(bool is_online); + + bool is_online() const; + + void set_is_online(bool is_online); + + void set_is_bot_online(bool is_bot_online); + + private: + static constexpr int32 PING_SERVER_TIMEOUT = 300; + + void tear_down() final; + + void start_up() final; + + static void on_online_timeout_callback(void *online_manager_ptr); + + static void on_ping_server_timeout_callback(void *online_manager_ptr); + + void on_ping_server_timeout(); + + Td *td_; + ActorShared<> parent_; + + bool is_online_ = false; + bool is_bot_online_ = false; + NetQueryRef update_status_query_; + + Timeout online_timeout_; + Timeout ping_server_timeout_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp index 821af1ed..6ae5bbc1 100644 --- a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp @@ -21,6 +21,7 @@ #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/PeopleNearbyManager.h" #include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" @@ -155,6 +156,10 @@ OptionManager::OptionManager(Td *td) set_default_integer_option("story_link_area_count_max", 3); set_default_integer_option("paid_media_message_star_count_max", 10000); set_default_integer_option("bot_media_preview_count_max", 12); + set_default_integer_option("paid_reaction_star_count_max", 2500); + set_default_integer_option("subscription_star_count_max", 2500); + set_default_integer_option("usd_to_1000_star_rate", 1410); + set_default_integer_option("1000_star_to_usd_rate", 1200); if (options.isset("my_phone_number") || !options.isset("my_id")) { update_premium_options(); @@ -687,7 +692,7 @@ void OptionManager::get_option(const string &name, Promise(td_->is_online())); + return promise.set_value(td_api::make_object(td_->online_manager_->is_online())); } break; case 'u': @@ -709,7 +714,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.34"); + return td_api::make_object("1.8.35"); } break; } @@ -924,7 +929,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr(value.get())->value_; - td_->set_is_online(is_online); + td_->online_manager_->set_is_online(is_online); if (!is_bot) { send_closure(td_->state_manager_, &StateManager::on_online, is_online); } @@ -1046,7 +1051,7 @@ void OptionManager::get_current_state(vector> get_common_state(updates); updates.push_back(td_api::make_object( - "online", td_api::make_object(td_->is_online()))); + "online", td_api::make_object(td_->online_manager_->is_online()))); updates.push_back(td_api::make_object("unix_time", get_unix_time_option_value_object())); diff --git a/lib/tgchat/ext/td/td/telegram/Payments.cpp b/lib/tgchat/ext/td/td/telegram/Payments.cpp index db033671..915a3aa9 100644 --- a/lib/tgchat/ext/td/td/telegram/Payments.cpp +++ b/lib/tgchat/ext/td/td/telegram/Payments.cpp @@ -8,10 +8,12 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/DialogId.h" +#include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogManager.h" #include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" #include "td/telegram/InputInvoice.h" +#include "td/telegram/LinkManager.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" @@ -21,6 +23,7 @@ #include "td/telegram/Premium.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StarManager.h" +#include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" @@ -43,6 +46,7 @@ namespace { struct InputInvoiceInfo { DialogId dialog_id_; telegram_api::object_ptr input_invoice_; + int64 star_count_ = 0; }; Result get_input_invoice_info(Td *td, td_api::object_ptr &&input_invoice) { @@ -56,7 +60,7 @@ Result get_input_invoice_info(Td *td, td_api::object_ptr(input_invoice); DialogId dialog_id(invoice->chat_id_); MessageId message_id(invoice->message_id_); - TRY_RESULT(server_message_id, td->messages_manager_->get_invoice_message_id({dialog_id, message_id})); + TRY_RESULT(invoice_message_info, td->messages_manager_->get_invoice_message_info({dialog_id, message_id})); auto input_peer = td->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { @@ -64,8 +68,9 @@ Result get_input_invoice_info(Td *td, td_api::object_ptr(std::move(input_peer), server_message_id.get()); + result.input_invoice_ = telegram_api::make_object( + std::move(input_peer), invoice_message_info.server_message_id_.get()); + result.star_count_ = invoice_message_info.star_count_; break; } case td_api::inputInvoiceName::ID: { @@ -131,6 +136,8 @@ Result get_input_invoice_info(Td *td, td_api::object_ptrcurrency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::StarsSubscriptionLowBalance}, + Promise()); auto purpose = telegram_api::make_object(p->star_count_, p->currency_, p->amount_); result.input_invoice_ = telegram_api::make_object(std::move(purpose)); @@ -151,6 +158,18 @@ Result get_input_invoice_info(Td *td, td_api::object_ptr(std::move(purpose)); break; } + case td_api::telegramPaymentPurposeJoinChat::ID: { + auto p = static_cast(invoice->purpose_.get()); + if (!DialogInviteLink::is_valid_invite_link(p->invite_link_)) { + return Status::Error(400, "Invalid invite link"); + } + auto hash = LinkManager::get_dialog_invite_link_hash(p->invite_link_); + if (!clean_input_string(hash)) { + return Status::Error(400, "Invalid invite link"); + } + result.input_invoice_ = telegram_api::make_object(hash); + break; + } default: UNREACHABLE(); } @@ -656,6 +675,7 @@ class SendPaymentFormQuery final : public Td::ResultHandler { class SendStarPaymentFormQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; + int64 star_count_; public: explicit SendStarPaymentFormQuery(Promise> &&promise) @@ -664,13 +684,14 @@ class SendStarPaymentFormQuery final : public Td::ResultHandler { void send(InputInvoiceInfo &&input_invoice_info, int64 payment_form_id) { dialog_id_ = input_invoice_info.dialog_id_; + star_count_ = input_invoice_info.star_count_; send_query(G()->net_query_creator().create( telegram_api::payments_sendStarsForm(0, payment_form_id, std::move(input_invoice_info.input_invoice_)))); } void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); + auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } @@ -681,6 +702,9 @@ class SendStarPaymentFormQuery final : public Td::ResultHandler { switch (payment_result->get_id()) { case telegram_api::payments_paymentResult::ID: { auto result = telegram_api::move_object_as(payment_result); + if (star_count_ != 0) { + td_->star_manager_->add_owned_star_count(-star_count_); + } td_->updates_manager_->on_get_updates( std::move(result->updates_), PromiseCreator::lambda([promise = std::move(promise_)](Unit) mutable { promise.set_value(td_api::make_object(true, string())); diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.cpp b/lib/tgchat/ext/td/td/telegram/PollManager.cpp index 24f502bb..b04597f0 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PollManager.cpp @@ -18,6 +18,7 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/PollId.hpp" #include "td/telegram/PollManager.hpp" #include "td/telegram/StateManager.h" @@ -1356,7 +1357,7 @@ void PollManager::stop_local_poll(PollId poll_id) { } double PollManager::get_polling_timeout() const { - double result = td_->is_online() ? 60 : 30 * 60; + double result = td_->online_manager_->is_online() ? 60 : 30 * 60; return result * Random::fast(70, 100) * 0.01; } diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.hpp b/lib/tgchat/ext/td/td/telegram/PollManager.hpp index 88ba0354..681a6a1d 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/PollManager.hpp @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/MessageEntity.hpp" #include "td/telegram/MinChannel.hpp" #include "td/telegram/PollManager.h" #include "td/telegram/UserId.h" diff --git a/lib/tgchat/ext/td/td/telegram/Premium.cpp b/lib/tgchat/ext/td/td/telegram/Premium.cpp index a7523648..84428f4c 100644 --- a/lib/tgchat/ext/td/td/telegram/Premium.cpp +++ b/lib/tgchat/ext/td/td/telegram/Premium.cpp @@ -253,6 +253,7 @@ static Result> get_input_s if (!clean_input_string(p->currency_)) { return Status::Error(400, "Strings must be encoded in UTF-8"); } + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::StarsSubscriptionLowBalance}, Promise()); return telegram_api::make_object(p->star_count_, p->currency_, p->amount_); } diff --git a/lib/tgchat/ext/td/td/telegram/PromoDataManager.cpp b/lib/tgchat/ext/td/td/telegram/PromoDataManager.cpp new file mode 100644 index 00000000..7f22497e --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/PromoDataManager.cpp @@ -0,0 +1,148 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/PromoDataManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/DialogSource.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/Promise.h" + +namespace td { + +class GetPromoDataQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetPromoDataQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + // we don't poll promo data before authorization + send_query(G()->net_query_creator().create(telegram_api::help_getPromoData())); + } + + 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)); + } +}; + +PromoDataManager::PromoDataManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void PromoDataManager::tear_down() { + parent_.reset(); +} + +void PromoDataManager::start_up() { + init(); +} + +void PromoDataManager::init() { + if (G()->close_flag()) { + return; + } + if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + is_inited_ = true; + + reload_promo_data(); +} + +void PromoDataManager::reload_promo_data() { + if (reloading_promo_data_) { + need_reload_promo_data_ = true; + return; + } + schedule_get_promo_data(0); +} + +void PromoDataManager::schedule_get_promo_data(int32 expires_in) { + if (!is_inited_) { + return; + } + + expires_in = expires_in <= 0 ? 0 : clamp(expires_in, 60, 86400); + LOG(INFO) << "Schedule getPromoData in " << expires_in; + set_timeout_in(expires_in); +} + +void PromoDataManager::timeout_expired() { + if (G()->close_flag() || !is_inited_) { + return; + } + + reloading_promo_data_ = true; + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this)](Result> result) { + send_closure(actor_id, &PromoDataManager::on_get_promo_data, std::move(result), false); + }); + td_->create_handler(std::move(promise))->send(); +} + +void PromoDataManager::on_get_promo_data(Result> r_promo_data, + bool dummy) { + if (G()->close_flag()) { + return; + } + reloading_promo_data_ = false; + + if (r_promo_data.is_error()) { + LOG(ERROR) << "Receive error for GetPromoData: " << r_promo_data.error(); + return schedule_get_promo_data(60); + } + + auto promo_data_ptr = r_promo_data.move_as_ok(); + CHECK(promo_data_ptr != nullptr); + LOG(DEBUG) << "Receive " << to_string(promo_data_ptr); + int32 expires_at = 0; + switch (promo_data_ptr->get_id()) { + case telegram_api::help_promoDataEmpty::ID: { + auto promo = telegram_api::move_object_as(promo_data_ptr); + expires_at = promo->expires_; + td_->messages_manager_->remove_sponsored_dialog(); + break; + } + case telegram_api::help_promoData::ID: { + auto promo = telegram_api::move_object_as(promo_data_ptr); + expires_at = promo->expires_; + bool is_proxy = promo->proxy_; + td_->messages_manager_->on_get_sponsored_dialog( + std::move(promo->peer_), + is_proxy ? DialogSource::mtproto_proxy() + : DialogSource::public_service_announcement(promo->psa_type_, promo->psa_message_), + std::move(promo->users_), std::move(promo->chats_)); + break; + } + default: + UNREACHABLE(); + } + if (need_reload_promo_data_) { + need_reload_promo_data_ = false; + expires_at = 0; + } + schedule_get_promo_data(expires_at == 0 ? 0 : expires_at - G()->unix_time()); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PromoDataManager.h b/lib/tgchat/ext/td/td/telegram/PromoDataManager.h new file mode 100644 index 00000000..e749477d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/PromoDataManager.h @@ -0,0 +1,47 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/telegram_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" + +namespace td { + +class Td; + +class PromoDataManager final : public Actor { + public: + PromoDataManager(Td *td, ActorShared<> parent); + + void init(); + + void reload_promo_data(); + + private: + void tear_down() final; + + void start_up() final; + + void timeout_expired() final; + + void on_get_promo_data(Result> r_promo_data, bool dummy); + + void schedule_get_promo_data(int32 expires_in); + + Td *td_; + ActorShared<> parent_; + + bool is_inited_ = false; + bool reloading_promo_data_ = false; + bool need_reload_promo_data_ = false; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp index 949e2aa1..c7ee15b0 100644 --- a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp @@ -3335,7 +3335,7 @@ Result QuickReplyManager::process_input_message_content( return Status::Error(400, "Can't add poll as a quick reply"); } if (message_content_id == td_api::inputMessagePaidMedia::ID) { - return Status::Error(400, "Can't send paid media as business"); + return Status::Error(400, "Can't add paid media as a quick reply"); } if (message_content_id == td_api::inputMessageLocation::ID && static_cast(input_message_content.get())->live_period_ != 0) { diff --git a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp index 0c9e31f3..98255171 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp @@ -551,7 +551,8 @@ td_api::object_ptr ReactionManager::get_sorted_avail top_reactions = get_reaction_list(ReactionListType::Top).reaction_types_; } LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions - << " and recent reactions " << recent_reactions; + << " and recent reactions " << recent_reactions + << " and paid reaction = " << available_reactions.paid_reactions_available_; if (active_reactions.allow_all_custom_ && active_reactions.allow_all_regular_) { for (auto &reaction_type : recent_reactions) { if (reaction_type.is_custom_reaction()) { @@ -570,6 +571,11 @@ td_api::object_ptr ReactionManager::get_sorted_avail CHECK(!reaction_type.is_empty()); all_available_reaction_types.insert(reaction_type); } + if (available_reactions.paid_reactions_available_ || + (!available_reactions.reaction_types_.empty() && available_reactions.reaction_types_[0].is_paid_reaction())) { + all_available_reaction_types.insert(ReactionType::paid()); + top_reactions.insert(top_reactions.begin(), ReactionType::paid()); + } vector> top_reaction_objects; vector> recent_reaction_objects; @@ -664,6 +670,7 @@ void ReactionManager::add_recent_reaction(const ReactionType &reaction_type) { if (!reactions.empty() && reactions[0] == reaction_type) { return; } + CHECK(!reaction_type.is_paid_reaction()); add_to_top(reactions, MAX_RECENT_REACTIONS, reaction_type); @@ -964,8 +971,11 @@ void ReactionManager::set_default_reaction(ReactionType reaction_type, Promiseoption_manager_->get_option_string("default_reaction", "-") != reaction_type.get_string()) { @@ -1183,6 +1193,9 @@ void ReactionManager::set_saved_messages_tag_title(ReactionType reaction_type, s if (reaction_type.is_empty()) { return promise.set_error(Status::Error(400, "Reaction type must be non-empty")); } + if (reaction_type.is_paid_reaction()) { + return promise.set_error(Status::Error(400, "Invalid reaction specified")); + } title = clean_name(title, MAX_TAG_TITLE_LENGTH); auto *all_tags = get_saved_reaction_tags(SavedMessagesTopicId()); diff --git a/lib/tgchat/ext/td/td/telegram/ReactionType.cpp b/lib/tgchat/ext/td/td/telegram/ReactionType.cpp index d851985f..8f418560 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionType.cpp +++ b/lib/tgchat/ext/td/td/telegram/ReactionType.cpp @@ -13,6 +13,7 @@ #include "td/utils/base64.h" #include "td/utils/crypto.h" #include "td/utils/emoji.h" +#include "td/utils/logging.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/utf8.h" @@ -44,7 +45,7 @@ ReactionType::ReactionType(const telegram_api::object_ptr(reaction.get())->emoticon_; - if (is_custom_reaction()) { + if (is_custom_reaction() || is_paid_reaction()) { reaction_ = string(); } break; @@ -52,6 +53,9 @@ ReactionType::ReactionType(const telegram_api::object_ptr(reaction.get())->document_id_); break; + case telegram_api::reactionPaid::ID: + reaction_ = "$"; + break; default: UNREACHABLE(); break; @@ -69,7 +73,7 @@ ReactionType::ReactionType(const td_api::object_ptr &type) break; } reaction_ = emoji; - if (is_custom_reaction()) { + if (is_custom_reaction() || is_paid_reaction()) { reaction_ = string(); break; } @@ -79,12 +83,21 @@ ReactionType::ReactionType(const td_api::object_ptr &type) reaction_ = get_custom_emoji_string(static_cast(type.get())->custom_emoji_id_); break; + case td_api::reactionTypePaid::ID: + reaction_ = "$"; + break; default: UNREACHABLE(); break; } } +ReactionType ReactionType::paid() { + ReactionType reaction_type; + reaction_type.reaction_ = "$"; + return reaction_type; +} + vector ReactionType::get_reaction_types( const vector> &reactions) { return transform(reactions, [](const auto &reaction) { return ReactionType(reaction); }); @@ -102,9 +115,16 @@ vector> ReactionType::get_input } vector> ReactionType::get_reaction_types_object( - const vector &reaction_types) { - return transform(reaction_types, - [](const ReactionType &reaction_type) { return reaction_type.get_reaction_type_object(); }); + const vector &reaction_types, bool paid_reactions_available) { + vector> result; + result.reserve(reaction_types.size() + (paid_reactions_available ? 1 : 0)); + if (paid_reactions_available) { + result.push_back(paid().get_reaction_type_object()); + } + for (auto &reaction_type : reaction_types) { + result.push_back(reaction_type.get_reaction_type_object()); + } + return result; } telegram_api::object_ptr ReactionType::get_input_reaction() const { @@ -114,6 +134,9 @@ telegram_api::object_ptr ReactionType::get_input_reactio if (is_custom_reaction()) { return telegram_api::make_object(get_custom_emoji_id(reaction_)); } + if (is_paid_reaction()) { + return telegram_api::make_object(); + } return telegram_api::make_object(reaction_); } @@ -124,6 +147,9 @@ td_api::object_ptr ReactionType::get_reaction_type_object( if (is_custom_reaction()) { return td_api::make_object(get_custom_emoji_id(reaction_)); } + if (is_paid_reaction()) { + return td_api::make_object(); + } return td_api::make_object(reaction_); } @@ -146,12 +172,22 @@ bool ReactionType::is_custom_reaction() const { return reaction_[0] == '#'; } +bool ReactionType::is_paid_reaction() const { + return reaction_ == "$"; +} + bool ReactionType::is_active_reaction( const FlatHashMap &active_reaction_pos) const { - return !is_empty() && (is_custom_reaction() || active_reaction_pos.count(*this) > 0); + return !is_empty() && (is_custom_reaction() || is_paid_reaction() || active_reaction_pos.count(*this) > 0); } bool operator<(const ReactionType &lhs, const ReactionType &rhs) { + if (lhs.is_paid_reaction()) { + return !rhs.is_paid_reaction(); + } + if (rhs.is_paid_reaction()) { + return false; + } return lhs.reaction_ < rhs.reaction_; } @@ -166,6 +202,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const ReactionType &rea if (reaction_type.is_custom_reaction()) { return string_builder << "custom reaction " << get_custom_emoji_id(reaction_type.reaction_); } + if (reaction_type.is_paid_reaction()) { + return string_builder << "paid reaction"; + } return string_builder << "reaction " << reaction_type.reaction_; } @@ -177,6 +216,9 @@ int64 get_reaction_types_hash(const vector &reaction_types) { numbers.push_back(custom_emoji_id >> 32); numbers.push_back(custom_emoji_id & 0xFFFFFFFF); } else { + if (reaction_type.is_paid_reaction()) { + LOG(ERROR) << "Have paid reaction"; + } auto emoji = remove_emoji_selectors(reaction_type.get_string()); unsigned char hash[16]; md5(emoji, {hash, sizeof(hash)}); diff --git a/lib/tgchat/ext/td/td/telegram/ReactionType.h b/lib/tgchat/ext/td/td/telegram/ReactionType.h index 0bc0adc9..cfa49eb5 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionType.h +++ b/lib/tgchat/ext/td/td/telegram/ReactionType.h @@ -38,6 +38,8 @@ class ReactionType { explicit ReactionType(const td_api::object_ptr &type); + static ReactionType paid(); + static vector get_reaction_types( const vector> &reactions); @@ -47,7 +49,7 @@ class ReactionType { const vector &reaction_types); static vector> get_reaction_types_object( - const vector &reaction_types); + const vector &reaction_types, bool paid_reactions_available); telegram_api::object_ptr get_input_reaction() const; @@ -59,6 +61,8 @@ class ReactionType { bool is_custom_reaction() const; + bool is_paid_reaction() const; + bool is_active_reaction(const FlatHashMap &active_reaction_pos) const; bool is_empty() const { diff --git a/lib/tgchat/ext/td/td/telegram/RestrictionReason.cpp b/lib/tgchat/ext/td/td/telegram/RestrictionReason.cpp index 52aba3df..c187a70b 100644 --- a/lib/tgchat/ext/td/td/telegram/RestrictionReason.cpp +++ b/lib/tgchat/ext/td/td/telegram/RestrictionReason.cpp @@ -16,9 +16,9 @@ namespace td { -string get_restriction_reason_description(const vector &restriction_reasons) { +const RestrictionReason *get_restriction_reason(const vector &restriction_reasons, bool sensitive) { if (restriction_reasons.empty()) { - return string(); + return nullptr; } auto ignored_restriction_reasons = full_split(G()->get_option_string("ignored_restriction_reasons"), ','); @@ -44,8 +44,9 @@ string get_restriction_reason_description(const vector &restr // first find restriction for the current platform for (auto &restriction_reason : restriction_reasons) { if (restriction_reason.platform_ == platform && - !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) { - return restriction_reason.description_; + !td::contains(ignored_restriction_reasons, restriction_reason.reason_) && + restriction_reason.is_sensitive() == sensitive) { + return &restriction_reason; } } } @@ -54,8 +55,9 @@ string get_restriction_reason_description(const vector &restr // then find restriction for added platforms for (auto &restriction_reason : restriction_reasons) { if (td::contains(restriction_add_platforms, restriction_reason.platform_) && - !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) { - return restriction_reason.description_; + !td::contains(ignored_restriction_reasons, restriction_reason.reason_) && + restriction_reason.is_sensitive() == sensitive) { + return &restriction_reason; } } } @@ -63,12 +65,25 @@ string get_restriction_reason_description(const vector &restr // then find restriction for all platforms for (auto &restriction_reason : restriction_reasons) { if (restriction_reason.platform_ == "all" && - !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) { - return restriction_reason.description_; + !td::contains(ignored_restriction_reasons, restriction_reason.reason_) && + restriction_reason.is_sensitive() == sensitive) { + return &restriction_reason; } } - return string(); + return nullptr; +} + +bool get_restriction_reason_has_sensitive_content(const vector &restriction_reasons) { + return get_restriction_reason(restriction_reasons, true) != nullptr; +} + +string get_restriction_reason_description(const vector &restriction_reasons) { + const auto *restriction_reason = get_restriction_reason(restriction_reasons, false); + if (restriction_reason == nullptr) { + return string(); + } + return restriction_reason->description_; } vector get_restriction_reasons(Slice legacy_restriction_reason) { diff --git a/lib/tgchat/ext/td/td/telegram/RestrictionReason.h b/lib/tgchat/ext/td/td/telegram/RestrictionReason.h index 729f2391..391a0983 100644 --- a/lib/tgchat/ext/td/td/telegram/RestrictionReason.h +++ b/lib/tgchat/ext/td/td/telegram/RestrictionReason.h @@ -29,8 +29,15 @@ class RestrictionReason { return lhs.platform_ == rhs.platform_ && lhs.reason_ == rhs.reason_ && lhs.description_ == rhs.description_; } + friend const RestrictionReason *get_restriction_reason(const vector &restriction_reasons, + bool sensitive); + friend string get_restriction_reason_description(const vector &restriction_reasons); + bool is_sensitive() const { + return reason_ == "sensitive"; + } + public: RestrictionReason() = default; @@ -60,6 +67,8 @@ inline bool operator!=(const RestrictionReason &lhs, const RestrictionReason &rh return !(lhs == rhs); } +bool get_restriction_reason_has_sensitive_content(const vector &restriction_reasons); + string get_restriction_reason_description(const vector &restriction_reasons); vector get_restriction_reasons(Slice legacy_restriction_reason); diff --git a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp index 449e15b0..35fd5cae 100644 --- a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp @@ -12,6 +12,7 @@ #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessageContent.h" +#include "td/telegram/MessageContentType.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/net/NetQueryCreator.h" @@ -281,7 +282,7 @@ td_api::object_ptr SponsoredMessageManager::get_sponso return td_api::make_object( sponsored_message.local_id, sponsored_message.is_recommended, sponsored_message.can_be_reported, get_message_content_object(sponsored_message.content.get(), td_, dialog_id, false, 0, false, true, -1, false, - false), + true), std::move(sponsor), sponsored_message.title, sponsored_message.button_text, td_->theme_manager_->get_accent_color_id_object(sponsored_message.peer_color.accent_color_id_, AccentColorId()), sponsored_message.peer_color.background_custom_emoji_id_.get(), sponsored_message.additional_info); @@ -364,12 +365,28 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( std::move(sponsored_message->entities_), true, true, 0, false, "on_get_dialog_sponsored_messages"); MessageSelfDestructType ttl; - auto content = get_message_content(td_, std::move(message_text), nullptr, DialogId(), G()->unix_time(), true, - UserId(), &ttl, nullptr, "on_get_dialog_sponsored_messages"); + auto content = + get_message_content(td_, std::move(message_text), std::move(sponsored_message->media_), DialogId(), + G()->unix_time(), true, UserId(), &ttl, nullptr, "on_get_dialog_sponsored_messages"); if (!ttl.is_empty()) { LOG(ERROR) << "Receive sponsored message with " << ttl; continue; } + bool is_allowed_content_type = [&] { + switch (content->get_type()) { + case MessageContentType::Animation: + case MessageContentType::Photo: + case MessageContentType::Text: + case MessageContentType::Video: + return true; + default: + return false; + } + }(); + if (!is_allowed_content_type) { + LOG(ERROR) << "Receive sponsored message with " << content->get_type(); + continue; + } current_sponsored_message_id_ = current_sponsored_message_id_.get_next_message_id(MessageType::Local); if (!current_sponsored_message_id_.is_valid_sponsored()) { diff --git a/lib/tgchat/ext/td/td/telegram/StarManager.cpp b/lib/tgchat/ext/td/td/telegram/StarManager.cpp index b2c1bd6d..93143d6a 100644 --- a/lib/tgchat/ext/td/td/telegram/StarManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StarManager.cpp @@ -22,6 +22,7 @@ #include "td/telegram/PasswordManager.h" #include "td/telegram/Photo.h" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/StarSubscription.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" @@ -121,7 +122,7 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { : promise_(std::move(promise)) { } - void send(DialogId dialog_id, const string &offset, int32 limit, + void send(DialogId dialog_id, const string &subscription_id, const string &offset, int32 limit, td_api::object_ptr &&direction) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); @@ -129,6 +130,9 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { return on_error(Status::Error(400, "Have no access to the chat")); } int32 flags = 0; + if (!subscription_id.empty()) { + flags |= telegram_api::payments_getStarsTransactions::SUBSCRIPTION_ID_MASK; + } if (direction != nullptr) { switch (direction->get_id()) { case td_api::starTransactionDirectionIncoming::ID: @@ -144,8 +148,9 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { if (td_->auth_manager_->is_bot()) { flags |= telegram_api::payments_getStarsTransactions::ASCENDING_MASK; } - send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTransactions( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), offset, limit))); + send_query(G()->net_query_creator().create( + telegram_api::payments_getStarsTransactions(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, + subscription_id, std::move(input_peer), offset, limit))); } void send(DialogId dialog_id, const string &transaction_id, bool is_refund) { @@ -175,7 +180,7 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { } auto result = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for GetStarsTransactionsQuery: " << to_string(result); + LOG(INFO) << "Receive result for GetStarsTransactionsQuery: " << to_string(result); td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); @@ -201,6 +206,19 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive Star transaction with bot payload"; } } + auto get_paid_media_object = [&](DialogId dialog_id) -> vector> { + auto extended_media = transform(std::move(transaction->extended_media_), [td = td_, dialog_id](auto &&media) { + return MessageExtendedMedia(td, std::move(media), dialog_id); + }); + for (auto &media : extended_media) { + media.append_file_ids(td_, file_ids); + } + auto extended_media_objects = transform(std::move(extended_media), [td = td_](auto &&media) { + return media.get_message_extended_media_object(td); + }); + transaction->extended_media_.clear(); + return extended_media_objects; + }; auto partner = [&]() -> td_api::object_ptr { switch (transaction->peer_->get_id()) { case telegram_api::starsTransactionPeerUnsupported::ID: @@ -261,40 +279,70 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { td_->stickers_manager_->get_premium_gift_sticker_object( StarManager::get_months_by_star_count(star_count))); } + if (!transaction->extended_media_.empty()) { // TODO + return td_api::make_object( + td_->user_manager_->get_user_id_object(user_id, "starTransactionPartnerBusiness"), + get_paid_media_object(DialogId(user_id))); + } LOG(ERROR) << "Receive Telegram Star transaction with " << user_id; return td_api::make_object(); } + if ((product_info == nullptr && bot_payload.empty()) || !transaction->extended_media_.empty()) { + if (G()->is_test_dc()) { + bot_payload.clear(); + } + return td_api::make_object( + td_->user_manager_->get_user_id_object(user_id, "starTransactionPartnerBot"), + td_api::make_object( + get_paid_media_object(DialogId(user_id)))); + } SCOPE_EXIT { bot_payload.clear(); }; return td_api::make_object( - td_->user_manager_->get_user_id_object(user_id, "starTransactionPartnerBot"), std::move(product_info), - bot_payload); + td_->user_manager_->get_user_id_object(user_id, "starTransactionPartnerBot"), + td_api::make_object(std::move(product_info), + bot_payload)); } if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + if (transaction->subscription_period_ > 0) { + SCOPE_EXIT { + transaction->subscription_period_ = 0; + }; + td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); + return td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionPartnerChannel"), + td_api::make_object(transaction->subscription_period_)); + } + if (transaction->reaction_) { + SCOPE_EXIT { + transaction->msg_id_ = 0; + transaction->reaction_ = false; + }; + auto message_id = MessageId(ServerMessageId(transaction->msg_id_)); + if (message_id != MessageId() && !message_id.is_valid()) { + LOG(ERROR) << "Receive " << message_id << " in " << to_string(transaction); + message_id = MessageId(); + } + td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); + return td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionPartnerChannel"), + td_api::make_object(message_id.get())); + } + SCOPE_EXIT { transaction->msg_id_ = 0; - transaction->extended_media_.clear(); }; auto message_id = MessageId(ServerMessageId(transaction->msg_id_)); if (message_id != MessageId() && !message_id.is_valid()) { LOG(ERROR) << "Receive " << message_id << " in " << to_string(transaction); message_id = MessageId(); } - auto extended_media = - transform(std::move(transaction->extended_media_), [td = td_, dialog_id](auto &&media) { - return MessageExtendedMedia(td, std::move(media), dialog_id); - }); - for (auto &media : extended_media) { - media.append_file_ids(td_, file_ids); - } - auto extended_media_objects = transform(std::move(extended_media), [td = td_](auto &&media) { - return media.get_message_extended_media_object(td); - }); td_->dialog_manager_->force_create_dialog(dialog_id, "starsTransactionPeer", true); return td_api::make_object( td_->dialog_manager_->get_chat_id_object(dialog_id, "starTransactionPartnerChannel"), - message_id.get(), std::move(extended_media_objects)); + td_api::make_object(message_id.get(), + get_paid_media_object(dialog_id))); } LOG(ERROR) << "Receive Telegram Star transaction with " << dialog_id; return td_api::make_object(); @@ -325,6 +373,15 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { if (transaction->gift_) { LOG(ERROR) << "Receive gift with " << to_string(star_transaction); } + if (transaction->subscription_period_ != 0) { + LOG(ERROR) << "Receive subscription period with " << to_string(star_transaction); + } + if (transaction->reaction_) { + LOG(ERROR) << "Receive reaction with " << to_string(star_transaction); + } + if (!transaction->extended_media_.empty()) { + LOG(ERROR) << "Receive paid media with " << to_string(star_transaction); + } } if (!file_ids.empty()) { auto file_source_id = @@ -335,6 +392,9 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { } transactions.push_back(std::move(star_transaction)); } + if (!td_->auth_manager_->is_bot() && dialog_id_ == td_->dialog_manager_->get_my_dialog_id()) { + td_->star_manager_->on_update_owned_star_count(star_count); + } promise_.set_value( td_api::make_object(star_count, std::move(transactions), result->next_offset_)); @@ -346,6 +406,111 @@ class GetStarsTransactionsQuery final : public Td::ResultHandler { } }; +class GetStarsSubscriptionsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStarsSubscriptionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(bool only_expiring, const string &offset) { + int32 flags = 0; + if (only_expiring) { + flags |= telegram_api::payments_getStarsSubscriptions::MISSING_BALANCE_MASK; + } + send_query(G()->net_query_creator().create(telegram_api::payments_getStarsSubscriptions( + flags, false /*ignored*/, telegram_api::make_object(), offset))); + } + + 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 GetStarsSubscriptionsQuery: " << to_string(result); + + td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsSubscriptionsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsSubscriptionsQuery"); + + vector> subscriptions; + for (auto &subscription : result->subscriptions_) { + StarSubscription star_subscription(std::move(subscription)); + if (!star_subscription.is_valid()) { + LOG(ERROR) << "Receive invalid subscription " << star_subscription; + } else { + subscriptions.push_back(star_subscription.get_star_subscription_object(td_)); + } + } + auto star_count = StarManager::get_star_count(result->balance_, true); + if (!td_->auth_manager_->is_bot()) { + td_->star_manager_->on_update_owned_star_count(star_count); + } + promise_.set_value(td_api::make_object( + star_count, std::move(subscriptions), StarManager::get_star_count(result->subscriptions_missing_balance_), + result->subscriptions_next_offset_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ChangeStarsSubscriptionQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ChangeStarsSubscriptionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &subscription_id, bool is_canceled) { + send_query(G()->net_query_creator().create(telegram_api::payments_changeStarsSubscription( + telegram_api::payments_changeStarsSubscription::CANCELED_MASK, + telegram_api::make_object(), subscription_id, is_canceled))); + } + + 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 FulfillStarsSubscriptionQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit FulfillStarsSubscriptionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &subscription_id) { + send_query(G()->net_query_creator().create(telegram_api::payments_fulfillStarsSubscription( + telegram_api::make_object(), subscription_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 { + promise_.set_error(std::move(status)); + } +}; + class RefundStarsChargeQuery final : public Td::ResultHandler { Promise promise_; @@ -509,6 +674,26 @@ void StarManager::tear_down() { parent_.reset(); } +td_api::object_ptr StarManager::get_update_owned_star_count_object() const { + CHECK(is_owned_star_count_inited_); + return td_api::make_object(owned_star_count_); +} + +void StarManager::on_update_owned_star_count(int64 star_count) { + if (is_owned_star_count_inited_ && star_count == owned_star_count_) { + return; + } + is_owned_star_count_inited_ = true; + owned_star_count_ = star_count; + send_closure(G()->td(), &Td::send_update, get_update_owned_star_count_object()); +} + +void StarManager::add_owned_star_count(int64 star_count) { + if (is_owned_star_count_inited_) { + on_update_owned_star_count(star_count + owned_star_count_); + } +} + Status StarManager::can_manage_stars(DialogId dialog_id, bool allow_self) const { switch (dialog_id.get_type()) { case DialogType::User: { @@ -552,34 +737,49 @@ void StarManager::get_star_gift_payment_options(UserId user_id, td_->create_handler(std::move(promise))->send(std::move(input_user)); } -void StarManager::get_star_transactions(td_api::object_ptr owner_id, const string &offset, - int32 limit, td_api::object_ptr &&direction, +void StarManager::get_star_transactions(td_api::object_ptr owner_id, + const string &subscription_id, const string &offset, int32 limit, + td_api::object_ptr &&direction, Promise> &&promise) { TRY_RESULT_PROMISE(promise, dialog_id, get_message_sender_dialog_id(td_, owner_id, true, false)); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); if (limit < 0) { return promise.set_error(Status::Error(400, "Limit must be non-negative")); } - td_->stickers_manager_->load_premium_gift_sticker_set( - PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, offset, limit, direction = std::move(direction), - promise = std::move(promise)](Result &&result) mutable { + td_->stickers_manager_->load_premium_gift_sticker_set(PromiseCreator::lambda( + [actor_id = actor_id(this), dialog_id, subscription_id, offset, limit, direction = std::move(direction), + promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { - send_closure(actor_id, &StarManager::do_get_star_transactions, dialog_id, offset, limit, std::move(direction), - std::move(promise)); + send_closure(actor_id, &StarManager::do_get_star_transactions, dialog_id, subscription_id, offset, limit, + std::move(direction), std::move(promise)); } })); } -void StarManager::do_get_star_transactions(DialogId dialog_id, const string &offset, int32 limit, +void StarManager::do_get_star_transactions(DialogId dialog_id, const string &subscription_id, const string &offset, + int32 limit, td_api::object_ptr &&direction, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_STATUS_PROMISE(promise, can_manage_stars(dialog_id, true)); td_->create_handler(std::move(promise)) - ->send(dialog_id, offset, limit, std::move(direction)); + ->send(dialog_id, subscription_id, offset, limit, std::move(direction)); +} + +void StarManager::get_star_subscriptions(bool only_expiring, const string &offset, + Promise> &&promise) { + td_->create_handler(std::move(promise))->send(only_expiring, offset); +} + +void StarManager::edit_star_subscriptions(const string &subscription_id, bool is_canceled, Promise &&promise) { + td_->create_handler(std::move(promise))->send(subscription_id, is_canceled); +} + +void StarManager::reuse_star_subscriptions(const string &subscription_id, Promise &&promise) { + td_->create_handler(std::move(promise))->send(subscription_id); } void StarManager::refund_star_payment(UserId user_id, const string &telegram_payment_charge_id, @@ -696,4 +896,10 @@ int32 StarManager::get_months_by_star_count(int64 star_count) { return star_count <= 1000 ? 3 : (star_count < 2500 ? 6 : 12); } +void StarManager::get_current_state(vector> &updates) const { + if (is_owned_star_count_inited_) { + updates.push_back(get_update_owned_star_count_object()); + } +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StarManager.h b/lib/tgchat/ext/td/td/telegram/StarManager.h index 1e834fe2..42410207 100644 --- a/lib/tgchat/ext/td/td/telegram/StarManager.h +++ b/lib/tgchat/ext/td/td/telegram/StarManager.h @@ -27,14 +27,26 @@ class StarManager final : public Actor { public: StarManager(Td *td, ActorShared<> parent); + void on_update_owned_star_count(int64 star_count); + + void add_owned_star_count(int64 star_count); + void get_star_payment_options(Promise> &&promise); void get_star_gift_payment_options(UserId user_id, Promise> &&promise); - void get_star_transactions(td_api::object_ptr owner_id, const string &offset, int32 limit, + void get_star_transactions(td_api::object_ptr owner_id, const string &subscription_id, + const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise); + void get_star_subscriptions(bool only_expiring, const string &offset, + Promise> &&promise); + + void edit_star_subscriptions(const string &subscription_id, bool is_canceled, Promise &&promise); + + void reuse_star_subscriptions(const string &subscription_id, Promise &&promise); + void refund_star_payment(UserId user_id, const string &telegram_payment_charge_id, Promise &&promise); void get_star_revenue_statistics(const td_api::object_ptr &owner_id, bool is_dark, @@ -56,12 +68,14 @@ class StarManager final : public Actor { static int32 get_months_by_star_count(int64 star_count); + void get_current_state(vector> &updates) const; + private: void tear_down() final; Status can_manage_stars(DialogId dialog_id, bool allow_self = false) const; - void do_get_star_transactions(DialogId dialog_id, const string &offset, int32 limit, + void do_get_star_transactions(DialogId dialog_id, const string &subscription_id, const string &offset, int32 limit, td_api::object_ptr &&direction, Promise> &&promise); @@ -69,9 +83,14 @@ class StarManager final : public Actor { DialogId dialog_id, int64 star_count, telegram_api::object_ptr input_check_password, Promise &&promise); + td_api::object_ptr get_update_owned_star_count_object() const; + Td *td_; ActorShared<> parent_; + bool is_owned_star_count_inited_ = false; + int64 owned_star_count_ = 0; + FlatHashMap, DialogIdHash> star_transaction_file_source_ids_[2]; }; diff --git a/lib/tgchat/ext/td/td/telegram/StarSubscription.cpp b/lib/tgchat/ext/td/td/telegram/StarSubscription.cpp new file mode 100644 index 00000000..a0b4f78a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StarSubscription.cpp @@ -0,0 +1,41 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/StarSubscription.h" + +#include "td/telegram/DialogManager.h" +#include "td/telegram/LinkManager.h" +#include "td/telegram/Td.h" + +namespace td { + +StarSubscription::StarSubscription(telegram_api::object_ptr &&subscription) + : id_(std::move(subscription->id_)) + , dialog_id_(subscription->peer_) + , until_date_(subscription->until_date_) + , can_reuse_(subscription->can_refulfill_) + , is_canceled_(subscription->canceled_) + , missing_balance_(subscription->missing_balance_) + , invite_hash_(std::move(subscription->chat_invite_hash_)) + , pricing_(std::move(subscription->pricing_)) { +} + +td_api::object_ptr StarSubscription::get_star_subscription_object(Td *td) const { + td->dialog_manager_->force_create_dialog(dialog_id_, "starSubscription", true); + return td_api::make_object( + id_, td->dialog_manager_->get_chat_id_object(dialog_id_, "starSubscription"), until_date_, can_reuse_, + is_canceled_, missing_balance_, LinkManager::get_dialog_invite_link(invite_hash_, false), + pricing_.get_star_subscription_pricing_object()); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscription &subscription) { + return string_builder << (subscription.is_canceled_ ? "canceled " : "") + << (subscription.missing_balance_ ? "expiring " : "") << "subscription " << subscription.id_ + << " to " << subscription.dialog_id_ << '/' << subscription.invite_hash_ << " until " + << subscription.until_date_ << " for " << subscription.pricing_; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StarSubscription.h b/lib/tgchat/ext/td/td/telegram/StarSubscription.h new file mode 100644 index 00000000..5238cce3 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StarSubscription.h @@ -0,0 +1,47 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/StarSubscriptionPricing.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; + +class StarSubscription { + string id_; + DialogId dialog_id_; + int32 until_date_ = 0; + bool can_reuse_ = false; + bool is_canceled_ = false; + bool missing_balance_ = false; + string invite_hash_; + StarSubscriptionPricing pricing_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscription &subscription); + + public: + StarSubscription() = default; + + explicit StarSubscription(telegram_api::object_ptr &&subscription); + + bool is_valid() const { + return !id_.empty() && dialog_id_.is_valid() && until_date_ >= 0 && !pricing_.is_empty(); + } + + td_api::object_ptr get_star_subscription_object(Td *td) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscription &subscription); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.cpp b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.cpp new file mode 100644 index 00000000..9a434b0c --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.cpp @@ -0,0 +1,58 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/StarSubscriptionPricing.h" + +#include "td/telegram/StarManager.h" + +namespace td { + +StarSubscriptionPricing::StarSubscriptionPricing( + telegram_api::object_ptr &&pricing) { + if (pricing != nullptr) { + period_ = pricing->period_; + amount_ = StarManager::get_star_count(pricing->amount_); + } +} + +StarSubscriptionPricing::StarSubscriptionPricing(td_api::object_ptr &&pricing) { + if (pricing != nullptr) { + period_ = pricing->period_; + amount_ = pricing->star_count_; + if (amount_ > 1000000000) { + amount_ = 0; + } + } +} + +td_api::object_ptr StarSubscriptionPricing::get_star_subscription_pricing_object() + const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(period_, amount_); +} + +telegram_api::object_ptr +StarSubscriptionPricing::get_input_stars_subscription_pricing() const { + if (is_empty()) { + return nullptr; + } + return telegram_api::make_object(period_, amount_); +} + +bool operator==(const StarSubscriptionPricing &lhs, const StarSubscriptionPricing &rhs) { + return lhs.period_ == rhs.period_ && lhs.amount_ == rhs.amount_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscriptionPricing &pricing) { + if (pricing.is_empty()) { + return string_builder << "no subscription"; + } + return string_builder << "subscription for " << pricing.period_ << " days for " << pricing.amount_ << " stars"; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.h b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.h new file mode 100644 index 00000000..350e32ff --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.h @@ -0,0 +1,55 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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 StarSubscriptionPricing { + int32 period_ = 0; + int64 amount_ = 0; + + friend bool operator==(const StarSubscriptionPricing &lhs, const StarSubscriptionPricing &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscriptionPricing &pricing); + + public: + StarSubscriptionPricing() = default; + + explicit StarSubscriptionPricing(telegram_api::object_ptr &&pricing); + + explicit StarSubscriptionPricing(td_api::object_ptr &&pricing); + + bool is_empty() const { + return period_ <= 0 || amount_ <= 0; + } + + td_api::object_ptr get_star_subscription_pricing_object() const; + + telegram_api::object_ptr get_input_stars_subscription_pricing() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const StarSubscriptionPricing &lhs, const StarSubscriptionPricing &rhs); + +inline bool operator!=(const StarSubscriptionPricing &lhs, const StarSubscriptionPricing &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const StarSubscriptionPricing &pricing); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.hpp b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.hpp new file mode 100644 index 00000000..e09eb6ef --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/StarSubscriptionPricing.hpp @@ -0,0 +1,32 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/StarSubscriptionPricing.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void StarSubscriptionPricing::store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + td::store(period_, storer); + td::store(amount_, storer); +} + +template +void StarSubscriptionPricing::parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + td::parse(period_, parser); + td::parse(amount_, parser); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/StoryContent.cpp b/lib/tgchat/ext/td/td/telegram/StoryContent.cpp index 4df1ac52..b2c89915 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryContent.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryContent.cpp @@ -494,7 +494,7 @@ td_api::object_ptr get_story_content_object(Td *td, const } } -FileId get_story_content_any_file_id(const Td *td, const StoryContent *content) { +FileId get_story_content_any_file_id(const StoryContent *content) { switch (content->get_type()) { case StoryContentType::Photo: return get_photo_any_file_id(static_cast(content)->photo_); diff --git a/lib/tgchat/ext/td/td/telegram/StoryContent.h b/lib/tgchat/ext/td/td/telegram/StoryContent.h index ffd2b3d3..6669e743 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryContent.h +++ b/lib/tgchat/ext/td/td/telegram/StoryContent.h @@ -66,7 +66,7 @@ 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); +FileId get_story_content_any_file_id(const StoryContent *content); vector get_story_content_file_ids(const Td *td, const StoryContent *content); diff --git a/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp b/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp index ecc8c35d..5b52529b 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp @@ -55,8 +55,8 @@ StoryInteractionInfo::StoryInteractionInfo(Td *td, telegram_api::object_ptr added_reaction_types; for (auto &reaction : story_views->reactions_) { ReactionType reaction_type(reaction->reaction_); - if (reaction_type.is_empty()) { - LOG(ERROR) << "Receive empty " << to_string(reaction); + if (reaction_type.is_empty() || reaction_type.is_paid_reaction()) { + LOG(ERROR) << "Receive " << to_string(reaction); continue; } if (!added_reaction_types.insert(reaction_type).second) { @@ -81,6 +81,7 @@ void StoryInteractionInfo::add_dependencies(Dependencies &dependencies) const { void StoryInteractionInfo::set_chosen_reaction_type(const ReactionType &new_reaction_type, const ReactionType &old_reaction_type) { if (!old_reaction_type.is_empty()) { + CHECK(!old_reaction_type.is_paid_reaction()); for (auto it = reaction_counts_.begin(); it != reaction_counts_.end(); ++it) { if (it->first == old_reaction_type) { it->second--; @@ -92,6 +93,7 @@ void StoryInteractionInfo::set_chosen_reaction_type(const ReactionType &new_reac } } if (!new_reaction_type.is_empty()) { + CHECK(!new_reaction_type.is_paid_reaction()); bool is_found = false; for (auto it = reaction_counts_.begin(); it != reaction_counts_.end(); ++it) { if (it->first == old_reaction_type) { diff --git a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp index 7d6e9509..d1bafb0f 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp @@ -19,7 +19,7 @@ #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/MessageEntity.hpp" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/NotificationId.h" @@ -310,6 +310,7 @@ class SendStoryReactionQuery final : public Td::ResultHandler { if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } + CHECK(!reaction_type.is_paid_reaction()); int32 flags = 0; if (!reaction_type.is_empty() && add_to_recent) { @@ -411,6 +412,7 @@ class GetStoryReactionsListQuery final : public Td::ResultHandler { if (input_peer == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } + CHECK(!reaction_type.is_paid_reaction()); int32 flags = 0; if (!reaction_type.is_empty()) { @@ -1238,10 +1240,6 @@ class StoryManager::EditStoryQuery final : public Td::ResultHandler { vector> input_media_areas; if (edited_story->edit_media_areas_) { input_media_areas = MediaArea::get_input_media_areas(td_, edited_story->areas_); - } else if (content != nullptr) { - input_media_areas = MediaArea::get_input_media_areas(td_, story->areas_); - } - if (!input_media_areas.empty()) { flags |= telegram_api::stories_editStory::MEDIA_AREAS_MASK; } vector> entities; @@ -2894,7 +2892,7 @@ void StoryManager::on_story_replied(StoryFullId story_full_id, UserId replier_us } bool StoryManager::has_suggested_reaction(const Story *story, const ReactionType &reaction_type) { - if (reaction_type.is_empty()) { + if (reaction_type.is_empty() || reaction_type.is_paid_reaction()) { return false; } CHECK(story != nullptr); @@ -2914,6 +2912,9 @@ bool StoryManager::can_use_story_reaction(const Story *story, const ReactionType } return false; } + if (reaction_type.is_paid_reaction()) { + return false; + } return td_->reaction_manager_->is_active_reaction(reaction_type); } @@ -3279,6 +3280,9 @@ void StoryManager::get_dialog_story_interactions(StoryFullId story_full_id, Reac if (!story_full_id.get_story_id().is_server()) { return promise.set_value(td_api::make_object()); } + if (reaction_type.is_paid_reaction()) { + return promise.set_error(Status::Error(400, "Stories can't have paid reactions")); + } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), story_full_id, promise = std::move(promise)]( @@ -4579,6 +4583,10 @@ void StoryManager::on_update_story_chosen_reaction_type(DialogId owner_dialog_id if (!td_->dialog_manager_->have_dialog_info_force(owner_dialog_id, "on_update_story_chosen_reaction_type")) { return; } + if (chosen_reaction_type.is_paid_reaction()) { + LOG(ERROR) << "Receive paid reaction for " << story_id << " in " << owner_dialog_id; + return; + } StoryFullId story_full_id{owner_dialog_id, story_id}; auto pending_reaction_it = being_set_story_reactions_.find(story_full_id); if (pending_reaction_it != being_set_story_reactions_.end()) { @@ -5249,7 +5257,7 @@ void StoryManager::do_send_story(unique_ptr &&pending_story, vecto 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); + FileId file_id = get_story_content_any_file_id(content); CHECK(file_id.is_valid()); LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; @@ -5598,7 +5606,7 @@ void StoryManager::edit_story_cover(DialogId owner_dialog_id, StoryId story_id, } td_->create_handler(std::move(promise)) - ->send(owner_dialog_id, story_id, main_frame_timestamp, get_story_content_any_file_id(td_, story->content_.get()), + ->send(owner_dialog_id, story_id, main_frame_timestamp, get_story_content_any_file_id(story->content_.get()), std::move(input_media)); } diff --git a/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp b/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp index e0a4bd0c..9dd0a89b 100644 --- a/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp +++ b/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp @@ -50,6 +50,8 @@ SuggestedAction::SuggestedAction(Slice action_str) { init(Type::BirthdaySetup); } else if (action_str == Slice("PREMIUM_GRACE")) { init(Type::PremiumGrace); + } else if (action_str == Slice("STARS_SUBSCRIPTION_LOW_BALANCE")) { + init(Type::StarsSubscriptionLowBalance); } } @@ -111,6 +113,9 @@ SuggestedAction::SuggestedAction(const td_api::object_ptr SuggestedAction::get_suggested_actio return td_api::make_object(); case Type::PremiumGrace: return td_api::make_object( - G()->get_option_string("premium_manage_subscription_url", "https://T.me/premiumbot?start=status")); + G()->get_option_string("premium_manage_subscription_url", "https://t.me/premiumbot?start=status")); + case Type::StarsSubscriptionLowBalance: + return td_api::make_object(); default: UNREACHABLE(); return nullptr; @@ -243,6 +252,7 @@ void dismiss_suggested_action(SuggestedAction action, Promise &&promise) { case SuggestedAction::Type::GiftPremiumForChristmas: case SuggestedAction::Type::BirthdaySetup: case SuggestedAction::Type::PremiumGrace: + case SuggestedAction::Type::StarsSubscriptionLowBalance: return send_closure_later(G()->config_manager(), &ConfigManager::dismiss_suggested_action, std::move(action), std::move(promise)); case SuggestedAction::Type::ConvertToGigagroup: diff --git a/lib/tgchat/ext/td/td/telegram/SuggestedAction.h b/lib/tgchat/ext/td/td/telegram/SuggestedAction.h index ac25eaf3..ca4833c6 100644 --- a/lib/tgchat/ext/td/td/telegram/SuggestedAction.h +++ b/lib/tgchat/ext/td/td/telegram/SuggestedAction.h @@ -29,7 +29,8 @@ struct SuggestedAction { RestorePremium, GiftPremiumForChristmas, BirthdaySetup, - PremiumGrace + PremiumGrace, + StarsSubscriptionLowBalance }; Type type_ = Type::Empty; DialogId dialog_id_; diff --git a/lib/tgchat/ext/td/td/telegram/SynchronousRequests.cpp b/lib/tgchat/ext/td/td/telegram/SynchronousRequests.cpp new file mode 100644 index 00000000..53a37996 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SynchronousRequests.cpp @@ -0,0 +1,400 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/SynchronousRequests.h" + +#include "td/telegram/CountryInfoManager.h" +#include "td/telegram/DialogFilter.h" +#include "td/telegram/JsonValue.h" +#include "td/telegram/LanguagePackManager.h" +#include "td/telegram/Logging.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageQuote.h" +#include "td/telegram/misc.h" +#include "td/telegram/NotificationManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/QuickReplyManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/td_api.hpp" +#include "td/telegram/ThemeManager.h" + +#include "td/utils/filesystem.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/MimeType.h" +#include "td/utils/PathView.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Status.h" +#include "td/utils/utf8.h" + +#include + +namespace td { + +td_api::object_ptr SynchronousRequests::run_request(td_api::object_ptr &&function) { + if (function == nullptr) { + return td_api::make_object(400, "Request is empty"); + } + + auto function_id = function->get_id(); + bool need_logging = [function_id] { + switch (function_id) { + case td_api::parseTextEntities::ID: + case td_api::parseMarkdown::ID: + case td_api::getMarkdownText::ID: + case td_api::searchStringsByPrefix::ID: + case td_api::checkQuickReplyShortcutName::ID: + case td_api::getCountryFlagEmoji::ID: + case td_api::getFileMimeType::ID: + case td_api::getFileExtension::ID: + case td_api::cleanFileName::ID: + case td_api::getChatFolderDefaultIconName::ID: + case td_api::getJsonValue::ID: + case td_api::getJsonString::ID: + case td_api::getThemeParametersJsonString::ID: + case td_api::testReturnError::ID: + return true; + default: + return false; + } + }(); + + if (need_logging) { + VLOG(td_requests) << "Receive static request: " << to_string(function); + } + + td_api::object_ptr response; + downcast_call(*function, [&response](auto &request) { response = SynchronousRequests::do_request(request); }); + LOG_CHECK(response != nullptr) << function_id; + + if (need_logging) { + VLOG(td_requests) << "Sending result for static request: " << to_string(response); + } + return response; +} + +bool SynchronousRequests::is_synchronous_request(const td_api::Function *function) { + switch (function->get_id()) { + case td_api::searchQuote::ID: + case td_api::getTextEntities::ID: + case td_api::parseTextEntities::ID: + case td_api::parseMarkdown::ID: + case td_api::getMarkdownText::ID: + case td_api::searchStringsByPrefix::ID: + case td_api::checkQuickReplyShortcutName::ID: + case td_api::getCountryFlagEmoji::ID: + case td_api::getFileMimeType::ID: + case td_api::getFileExtension::ID: + case td_api::cleanFileName::ID: + case td_api::getLanguagePackString::ID: + case td_api::getPhoneNumberInfoSync::ID: + case td_api::getChatFolderDefaultIconName::ID: + case td_api::getJsonValue::ID: + case td_api::getJsonString::ID: + case td_api::getThemeParametersJsonString::ID: + case td_api::getPushReceiverId::ID: + case td_api::setLogStream::ID: + case td_api::getLogStream::ID: + case td_api::setLogVerbosityLevel::ID: + case td_api::getLogVerbosityLevel::ID: + case td_api::getLogTags::ID: + case td_api::setLogTagVerbosityLevel::ID: + case td_api::getLogTagVerbosityLevel::ID: + case td_api::addLogMessage::ID: + case td_api::testReturnError::ID: + return true; + case td_api::getOption::ID: + return OptionManager::is_synchronous_option(static_cast(function)->name_); + default: + return false; + } +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::searchQuote &request) { + if (request.text_ == nullptr || request.quote_ == nullptr) { + return make_error(400, "Text and quote must be non-empty"); + } + if (!check_utf8(request.text_->text_) || !check_utf8(request.quote_->text_)) { + return make_error(400, "Strings must be encoded in UTF-8"); + } + auto r_text_entities = get_message_entities(nullptr, std::move(request.text_->entities_), false); + if (r_text_entities.is_error()) { + return make_error(400, r_text_entities.error().message()); + } + auto r_quote_entities = get_message_entities(nullptr, std::move(request.quote_->entities_), false); + if (r_quote_entities.is_error()) { + return make_error(400, r_quote_entities.error().message()); + } + auto position = MessageQuote::search_quote({std::move(request.text_->text_), r_text_entities.move_as_ok()}, + {std::move(request.quote_->text_), r_quote_entities.move_as_ok()}, + request.quote_position_); + if (position == -1) { + return make_error(404, "Not Found"); + } + return td_api::make_object(position); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getTextEntities &request) { + if (!check_utf8(request.text_)) { + return make_error(400, "Text must be encoded in UTF-8"); + } + auto text_entities = find_entities(request.text_, false, false); + return td_api::make_object( + get_text_entities_object(nullptr, text_entities, false, std::numeric_limits::max())); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::parseTextEntities &request) { + if (!check_utf8(request.text_)) { // must not use clean_input_string, because \r may be used as a separator + return make_error(400, "Text must be encoded in UTF-8"); + } + if (request.parse_mode_ == nullptr) { + return make_error(400, "Parse mode must be non-empty"); + } + + auto r_entities = [&]() -> Result> { + if (utf8_length(request.text_) > 65536) { + return Status::Error("Text is too long"); + } + switch (request.parse_mode_->get_id()) { + case td_api::textParseModeHTML::ID: + return parse_html(request.text_); + case td_api::textParseModeMarkdown::ID: { + auto version = static_cast(request.parse_mode_.get())->version_; + if (version == 0 || version == 1) { + return parse_markdown(request.text_); + } + if (version == 2) { + return parse_markdown_v2(request.text_); + } + return Status::Error("Wrong Markdown version specified"); + } + default: + UNREACHABLE(); + return Status::Error(500, "Unknown parse mode"); + } + }(); + if (r_entities.is_error()) { + return make_error(400, PSLICE() << "Can't parse entities: " << r_entities.error().message()); + } + + return td_api::make_object(std::move(request.text_), + get_text_entities_object(nullptr, r_entities.ok(), false, -1)); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::parseMarkdown &request) { + if (request.text_ == nullptr) { + return make_error(400, "Text must be non-empty"); + } + + auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_), true); + if (r_entities.is_error()) { + return make_error(400, r_entities.error().message()); + } + auto entities = r_entities.move_as_ok(); + auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true); + if (status.is_error()) { + return make_error(400, status.message()); + } + + auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)}); + fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).ensure(); + return get_formatted_text_object(nullptr, parsed_text, false, std::numeric_limits::max()); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getOption &request) { + if (!is_synchronous_request(&request)) { + return make_error(400, "The option can't be get synchronously"); + } + return OptionManager::get_option_synchronously(request.name_); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::getMarkdownText &request) { + if (request.text_ == nullptr) { + return make_error(400, "Text must be non-empty"); + } + + auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_)); + if (r_entities.is_error()) { + return make_error(400, r_entities.error().message()); + } + auto entities = r_entities.move_as_ok(); + auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true); + if (status.is_error()) { + return make_error(400, status.message()); + } + + return get_formatted_text_object(nullptr, get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), + false, std::numeric_limits::max()); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::searchStringsByPrefix &request) { + if (!clean_input_string(request.query_)) { + return make_error(400, "Strings must be encoded in UTF-8"); + } + for (auto &str : request.strings_) { + if (!clean_input_string(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 SynchronousRequests::do_request(const td_api::checkQuickReplyShortcutName &request) { + // don't check name UTF-8 correctness + auto status = QuickReplyManager::check_shortcut_name(request.name_); + if (status.is_ok()) { + return td_api::make_object(); + } + return make_error(200, status.message()); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getCountryFlagEmoji &request) { + // don't check country code UTF-8 correctness + return td_api::make_object(CountryInfoManager::get_country_flag_emoji(request.country_code_)); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getFileMimeType &request) { + // don't check file name UTF-8 correctness + return td_api::make_object(MimeType::from_extension(PathView(request.file_name_).extension())); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getFileExtension &request) { + // don't check MIME type UTF-8 correctness + return td_api::make_object(MimeType::to_extension(request.mime_type_)); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::cleanFileName &request) { + // don't check file name UTF-8 correctness + return td_api::make_object(clean_filename(request.file_name_)); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getLanguagePackString &request) { + return LanguagePackManager::get_language_pack_string( + request.language_pack_database_path_, request.localization_target_, request.language_pack_id_, request.key_); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::getPhoneNumberInfoSync &request) { + // don't check language_code/phone number UTF-8 correctness + return CountryInfoManager::get_phone_number_info_sync(request.language_code_, + std::move(request.phone_number_prefix_)); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getPushReceiverId &request) { + // don't check push payload UTF-8 correctness + auto r_push_receiver_id = NotificationManager::get_push_receiver_id(request.payload_); + if (r_push_receiver_id.is_error()) { + VLOG(notifications) << "Failed to get push notification receiver from \"" << format::escaped(request.payload_) + << '"'; + return make_error(r_push_receiver_id.error().code(), r_push_receiver_id.error().message()); + } + return td_api::make_object(r_push_receiver_id.ok()); +} + +td_api::object_ptr SynchronousRequests::do_request( + const td_api::getChatFolderDefaultIconName &request) { + if (request.folder_ == nullptr) { + return make_error(400, "Chat folder must be non-empty"); + } + if (!check_utf8(request.folder_->title_)) { + return make_error(400, "Chat folder title must be encoded in UTF-8"); + } + if (request.folder_->icon_ != nullptr && !check_utf8(request.folder_->icon_->name_)) { + return make_error(400, "Chat folder icon name must be encoded in UTF-8"); + } + return td_api::make_object(DialogFilter::get_default_icon_name(request.folder_.get())); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::getJsonValue &request) { + if (!check_utf8(request.json_)) { + return make_error(400, "JSON has invalid encoding"); + } + auto result = get_json_value(request.json_); + if (result.is_error()) { + return make_error(400, result.error().message()); + } else { + return result.move_as_ok(); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getJsonString &request) { + return td_api::make_object(get_json_string(request.json_value_.get())); +} + +td_api::object_ptr SynchronousRequests::do_request( + const td_api::getThemeParametersJsonString &request) { + return td_api::make_object(ThemeManager::get_theme_parameters_json_string(request.theme_)); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::setLogStream &request) { + auto result = Logging::set_current_stream(std::move(request.log_stream_)); + if (result.is_ok()) { + return td_api::make_object(); + } else { + return make_error(400, result.message()); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getLogStream &request) { + auto result = Logging::get_current_stream(); + if (result.is_ok()) { + return result.move_as_ok(); + } else { + return make_error(400, result.error().message()); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::setLogVerbosityLevel &request) { + auto result = Logging::set_verbosity_level(static_cast(request.new_verbosity_level_)); + if (result.is_ok()) { + return td_api::make_object(); + } else { + return make_error(400, result.message()); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getLogVerbosityLevel &request) { + return td_api::make_object(Logging::get_verbosity_level()); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getLogTags &request) { + return td_api::make_object(Logging::get_tags()); +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::setLogTagVerbosityLevel &request) { + auto result = Logging::set_tag_verbosity_level(request.tag_, static_cast(request.new_verbosity_level_)); + if (result.is_ok()) { + return td_api::make_object(); + } else { + return make_error(400, result.message()); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::getLogTagVerbosityLevel &request) { + auto result = Logging::get_tag_verbosity_level(request.tag_); + if (result.is_ok()) { + return td_api::make_object(result.ok()); + } else { + return make_error(400, result.error().message()); + } +} + +td_api::object_ptr SynchronousRequests::do_request(const td_api::addLogMessage &request) { + Logging::add_message(request.verbosity_level_, request.text_); + return td_api::make_object(); +} + +td_api::object_ptr SynchronousRequests::do_request(td_api::testReturnError &request) { + if (request.error_ == nullptr) { + return td_api::make_object(404, "Not Found"); + } + + return std::move(request.error_); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SynchronousRequests.h b/lib/tgchat/ext/td/td/telegram/SynchronousRequests.h new file mode 100644 index 00000000..472c23d9 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SynchronousRequests.h @@ -0,0 +1,89 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +namespace td { + +class SynchronousRequests { + public: + static td_api::object_ptr run_request(td_api::object_ptr &&function); + + static bool is_synchronous_request(const td_api::Function *function); + + private: + static td_api::object_ptr make_error(int32 code, Slice error) { + return td_api::make_object(code, error.str()); + } + + template + static td_api::object_ptr do_request(const T &request) { + return td_api::make_object(400, "The method can't be executed synchronously"); + } + + static td_api::object_ptr do_request(const td_api::getOption &request); + + static td_api::object_ptr do_request(td_api::searchQuote &request); + + static td_api::object_ptr do_request(const td_api::getTextEntities &request); + + static td_api::object_ptr do_request(td_api::parseTextEntities &request); + + static td_api::object_ptr do_request(td_api::parseMarkdown &request); + + static td_api::object_ptr do_request(td_api::getMarkdownText &request); + + static td_api::object_ptr do_request(td_api::searchStringsByPrefix &request); + + static td_api::object_ptr do_request(const td_api::checkQuickReplyShortcutName &request); + + static td_api::object_ptr do_request(const td_api::getCountryFlagEmoji &request); + + static td_api::object_ptr do_request(const td_api::getFileMimeType &request); + + static td_api::object_ptr do_request(const td_api::getFileExtension &request); + + static td_api::object_ptr do_request(const td_api::cleanFileName &request); + + static td_api::object_ptr do_request(const td_api::getLanguagePackString &request); + + static td_api::object_ptr do_request(td_api::getPhoneNumberInfoSync &request); + + static td_api::object_ptr do_request(const td_api::getPushReceiverId &request); + + static td_api::object_ptr do_request(const td_api::getChatFolderDefaultIconName &request); + + static td_api::object_ptr do_request(td_api::getJsonValue &request); + + static td_api::object_ptr do_request(const td_api::getJsonString &request); + + static td_api::object_ptr do_request(const td_api::getThemeParametersJsonString &request); + + static td_api::object_ptr do_request(td_api::setLogStream &request); + + static td_api::object_ptr do_request(const td_api::getLogStream &request); + + static td_api::object_ptr do_request(const td_api::setLogVerbosityLevel &request); + + static td_api::object_ptr do_request(const td_api::getLogVerbosityLevel &request); + + static td_api::object_ptr do_request(const td_api::getLogTags &request); + + static td_api::object_ptr do_request(const td_api::setLogTagVerbosityLevel &request); + + static td_api::object_ptr do_request(const td_api::getLogTagVerbosityLevel &request); + + static td_api::object_ptr do_request(const td_api::addLogMessage &request); + + static td_api::object_ptr do_request(td_api::testReturnError &request); +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Td.cpp b/lib/tgchat/ext/td/td/telegram/Td.cpp index 7d20c8d2..ffbe4986 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.cpp +++ b/lib/tgchat/ext/td/td/telegram/Td.cpp @@ -40,6 +40,7 @@ #include "td/telegram/ChatManager.h" #include "td/telegram/CommonDialogManager.h" #include "td/telegram/ConfigManager.h" +#include "td/telegram/ConnectionStateManager.h" #include "td/telegram/CountryInfoManager.h" #include "td/telegram/CustomEmojiId.h" #include "td/telegram/DeviceTokenManager.h" @@ -47,7 +48,6 @@ #include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogBoostLinkInfo.h" #include "td/telegram/DialogEventLog.h" -#include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilterId.h" #include "td/telegram/DialogFilterManager.h" #include "td/telegram/DialogId.h" @@ -58,7 +58,6 @@ #include "td/telegram/DialogParticipant.h" #include "td/telegram/DialogParticipantFilter.h" #include "td/telegram/DialogParticipantManager.h" -#include "td/telegram/DialogSource.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/DownloadManager.h" #include "td/telegram/DownloadManagerCallback.h" @@ -85,7 +84,6 @@ #include "td/telegram/LanguagePackManager.h" #include "td/telegram/LinkManager.h" #include "td/telegram/Location.h" -#include "td/telegram/Logging.h" #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageEffectId.h" #include "td/telegram/MessageEntity.h" @@ -93,7 +91,6 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessageImportManager.h" #include "td/telegram/MessageLinkInfo.h" -#include "td/telegram/MessageQuote.h" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessageSearchFilter.h" #include "td/telegram/MessageSender.h" @@ -110,7 +107,6 @@ #include "td/telegram/net/NetStatsManager.h" #include "td/telegram/net/NetType.h" #include "td/telegram/net/Proxy.h" -#include "td/telegram/net/PublicRsaKeySharedMain.h" #include "td/telegram/net/TempAuthKeyWatchdog.h" #include "td/telegram/NotificationGroupId.h" #include "td/telegram/NotificationId.h" @@ -118,6 +114,7 @@ #include "td/telegram/NotificationObjectId.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSettingsScope.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/Payments.h" @@ -127,6 +124,7 @@ #include "td/telegram/PollManager.h" #include "td/telegram/Premium.h" #include "td/telegram/PrivacyManager.h" +#include "td/telegram/PromoDataManager.h" #include "td/telegram/PublicDialogType.h" #include "td/telegram/QuickReplyManager.h" #include "td/telegram/ReactionManager.h" @@ -144,6 +142,7 @@ #include "td/telegram/SentEmailCode.h" #include "td/telegram/SponsoredMessageManager.h" #include "td/telegram/StarManager.h" +#include "td/telegram/StarSubscriptionPricing.h" #include "td/telegram/StateManager.h" #include "td/telegram/StatisticsManager.h" #include "td/telegram/StickerFormat.h" @@ -157,9 +156,11 @@ #include "td/telegram/StoryManager.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Support.h" +#include "td/telegram/SynchronousRequests.h" #include "td/telegram/td_api.hpp" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/TermsOfServiceManager.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/TimeZoneManager.h" #include "td/telegram/TopDialogCategory.h" @@ -178,31 +179,15 @@ #include "td/db/binlog/BinlogEvent.h" -#include "td/mtproto/DhCallback.h" -#include "td/mtproto/Handshake.h" -#include "td/mtproto/HandshakeActor.h" -#include "td/mtproto/RawConnection.h" -#include "td/mtproto/RSA.h" -#include "td/mtproto/TransportType.h" - #include "td/actor/actor.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" -#include "td/utils/filesystem.h" -#include "td/utils/format.h" -#include "td/utils/MimeType.h" #include "td/utils/misc.h" -#include "td/utils/PathView.h" -#include "td/utils/port/IPAddress.h" -#include "td/utils/port/SocketFd.h" #include "td/utils/port/uname.h" -#include "td/utils/Random.h" #include "td/utils/Slice.h" -#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/Timer.h" -#include "td/utils/utf8.h" #include #include @@ -226,33 +211,6 @@ void Td::ResultHandler::send_query(NetQueryPtr query) { G()->net_query_dispatcher().dispatch(std::move(query)); } -class GetPromoDataQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit GetPromoDataQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send() { - // we don't poll promo data before authorization - send_query(G()->net_query_creator().create(telegram_api::help_getPromoData())); - } - - 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 GetRecentMeUrlsQuery final : public Td::ResultHandler { Promise> promise_; @@ -435,37 +393,6 @@ class SetBotUpdatesStatusQuery final : public Td::ResultHandler { } }; -class UpdateStatusQuery final : public Td::ResultHandler { - bool is_offline_; - - public: - NetQueryRef send(bool is_offline) { - is_offline_ = is_offline; - auto net_query = G()->net_query_creator().create(telegram_api::account_updateStatus(is_offline)); - auto result = net_query.get_weak(); - send_query(std::move(net_query)); - return result; - } - - 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()); - } - - bool result = result_ptr.ok(); - LOG(INFO) << "Receive result for UpdateStatusQuery: " << result; - td_->on_update_status_success(!is_offline_); - } - - void on_error(Status status) final { - if (status.code() != NetQuery::Canceled && !G()->is_expected_error(status)) { - LOG(ERROR) << "Receive error for UpdateStatusQuery: " << status; - } - status.ignore(); - } -}; - class TestNetworkQuery final : public Td::ResultHandler { Promise promise_; @@ -493,114 +420,6 @@ class TestNetworkQuery final : public Td::ResultHandler { } }; -class TestProxyRequest final : public RequestOnceActor { - Proxy proxy_; - int16 dc_id_; - double timeout_; - ActorOwn<> child_; - Promise<> promise_; - - auto get_transport() { - return mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, dc_id_, proxy_.secret()}; - } - - void do_run(Promise &&promise) final { - set_timeout_in(timeout_); - - promise_ = std::move(promise); - IPAddress ip_address; - auto status = ip_address.init_host_port(proxy_.server(), proxy_.port()); - if (status.is_error()) { - return promise_.set_error(Status::Error(400, status.public_message())); - } - auto r_socket_fd = SocketFd::open(ip_address); - if (r_socket_fd.is_error()) { - return promise_.set_error(Status::Error(400, r_socket_fd.error().public_message())); - } - - auto dc_options = ConnectionCreator::get_default_dc_options(false); - IPAddress mtproto_ip_address; - for (auto &dc_option : dc_options.dc_options) { - if (dc_option.get_dc_id().get_raw_id() == dc_id_) { - mtproto_ip_address = dc_option.get_ip_address(); - break; - } - } - - auto connection_promise = - PromiseCreator::lambda([actor_id = actor_id(this)](Result r_data) mutable { - send_closure(actor_id, &TestProxyRequest::on_connection_data, std::move(r_data)); - }); - - child_ = ConnectionCreator::prepare_connection(ip_address, r_socket_fd.move_as_ok(), proxy_, mtproto_ip_address, - get_transport(), "Test", "TestPingDC2", nullptr, {}, false, - std::move(connection_promise)); - } - - void on_connection_data(Result r_data) { - if (r_data.is_error()) { - return promise_.set_error(r_data.move_as_error()); - } - class HandshakeContext final : public mtproto::AuthKeyHandshakeContext { - public: - mtproto::DhCallback *get_dh_callback() final { - return nullptr; - } - mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final { - return public_rsa_key_.get(); - } - - private: - std::shared_ptr public_rsa_key_ = PublicRsaKeySharedMain::create(false); - }; - auto handshake = make_unique(dc_id_, 3600); - auto data = r_data.move_as_ok(); - auto raw_connection = - mtproto::RawConnection::create(data.ip_address, std::move(data.buffered_socket_fd), get_transport(), nullptr); - child_ = create_actor( - "HandshakeActor", std::move(handshake), std::move(raw_connection), make_unique(), 10.0, - PromiseCreator::lambda([actor_id = actor_id(this)](Result> raw_connection) { - send_closure(actor_id, &TestProxyRequest::on_handshake_connection, std::move(raw_connection)); - }), - PromiseCreator::lambda( - [actor_id = actor_id(this)](Result> handshake) mutable { - send_closure(actor_id, &TestProxyRequest::on_handshake, std::move(handshake)); - })); - } - void on_handshake_connection(Result> r_raw_connection) { - if (r_raw_connection.is_error()) { - return promise_.set_error(Status::Error(400, r_raw_connection.move_as_error().public_message())); - } - } - void on_handshake(Result> r_handshake) { - if (!promise_) { - return; - } - if (r_handshake.is_error()) { - return promise_.set_error(Status::Error(400, r_handshake.move_as_error().public_message())); - } - - auto handshake = r_handshake.move_as_ok(); - if (!handshake->is_ready_for_finish()) { - promise_.set_error(Status::Error(400, "Handshake is not ready")); - } - promise_.set_value(Unit()); - } - - void timeout_expired() final { - send_error(Status::Error(400, "Timeout expired")); - stop(); - } - - public: - TestProxyRequest(ActorShared td, uint64 request_id, Proxy proxy, int32 dc_id, double timeout) - : RequestOnceActor(std::move(td), request_id) - , proxy_(std::move(proxy)) - , dc_id_(static_cast(dc_id)) - , timeout_(timeout) { - } -}; - class GetMeRequest final : public RequestActor<> { UserId user_id_; @@ -1439,24 +1258,6 @@ class SearchChatMessagesRequest final : public RequestActor<> { } }; -class GetActiveLiveLocationMessagesRequest final : public RequestActor<> { - vector message_full_ids_; - - void do_run(Promise &&promise) final { - message_full_ids_ = td_->messages_manager_->get_active_live_location_messages(std::move(promise)); - } - - void do_send_result() final { - send_result(td_->messages_manager_->get_messages_object(-1, message_full_ids_, true, - "GetActiveLiveLocationMessagesRequest")); - } - - public: - GetActiveLiveLocationMessagesRequest(ActorShared td, uint64 request_id) - : RequestActor(std::move(td), request_id) { - } -}; - class GetChatScheduledMessagesRequest final : public RequestActor<> { DialogId dialog_id_; @@ -2369,37 +2170,6 @@ void Td::on_alarm_timeout_callback(void *td_ptr, int64 alarm_id) { } void Td::on_alarm_timeout(int64 alarm_id) { - if (alarm_id == ONLINE_ALARM_ID) { - on_online_updated(false, true); - return; - } - if (alarm_id == PING_SERVER_ALARM_ID) { - if (!close_flag_ && updates_manager_ != nullptr && auth_manager_->is_authorized()) { - updates_manager_->ping_server(); - set_is_bot_online(false); - } - return; - } - if (alarm_id == TERMS_OF_SERVICE_ALARM_ID) { - if (!close_flag_ && !auth_manager_->is_bot()) { - get_terms_of_service( - this, PromiseCreator::lambda([actor_id = actor_id(this)](Result> result) { - send_closure(actor_id, &Td::on_get_terms_of_service, std::move(result), false); - })); - } - return; - } - if (alarm_id == PROMO_DATA_ALARM_ID) { - if (!close_flag_ && !auth_manager_->is_bot()) { - reloading_promo_data_ = true; - auto promise = PromiseCreator::lambda( - [actor_id = actor_id(this)](Result> result) { - send_closure(actor_id, &Td::on_get_promo_data, std::move(result), false); - }); - create_handler(std::move(promise))->send(); - } - return; - } if (close_flag_ >= 2) { // pending_alarms_ was already cleared return; @@ -2412,162 +2182,6 @@ void Td::on_alarm_timeout(int64 alarm_id) { send_result(request_id, make_tl_object()); } -void Td::on_online_updated(bool force, bool send_update) { - if (close_flag_ >= 2 || !auth_manager_->is_authorized() || auth_manager_->is_bot()) { - return; - } - if (force || is_online_) { - user_manager_->set_my_online_status(is_online_, send_update, true); - if (!update_status_query_.empty()) { - LOG(INFO) << "Cancel previous update status query"; - cancel_query(update_status_query_); - } - update_status_query_ = create_handler()->send(!is_online_); - } - if (is_online_) { - alarm_timeout_.set_timeout_in( - ONLINE_ALARM_ID, static_cast(G()->get_option_integer("online_update_period_ms", 210000)) * 1e-3); - } else { - alarm_timeout_.cancel_timeout(ONLINE_ALARM_ID); - } -} - -void Td::on_update_status_success(bool is_online) { - if (is_online == is_online_) { - if (!update_status_query_.empty()) { - update_status_query_ = NetQueryRef(); - } - user_manager_->set_my_online_status(is_online_, true, false); - } -} - -td_api::object_ptr Td::get_update_terms_of_service_object() const { - auto terms_of_service = pending_terms_of_service_.get_terms_of_service_object(); - if (terms_of_service == nullptr) { - return nullptr; - } - return td_api::make_object(pending_terms_of_service_.get_id().str(), - std::move(terms_of_service)); -} - -void Td::on_get_terms_of_service(Result> result, bool dummy) { - int32 expires_in = 0; - if (result.is_error()) { - expires_in = Random::fast(10, 60); - } else { - auto terms = result.move_as_ok(); - pending_terms_of_service_ = std::move(terms.second); - auto update = get_update_terms_of_service_object(); - if (update == nullptr) { - expires_in = min(max(terms.first, G()->unix_time() + 3600) - G()->unix_time(), 86400); - } else { - send_update(std::move(update)); - } - } - if (expires_in > 0) { - schedule_get_terms_of_service(expires_in); - } -} - -void Td::schedule_get_terms_of_service(int32 expires_in) { - if (expires_in == 0) { - // drop pending Terms of Service after successful accept - pending_terms_of_service_ = TermsOfService(); - } - if (!close_flag_ && !auth_manager_->is_bot()) { - alarm_timeout_.set_timeout_in(TERMS_OF_SERVICE_ALARM_ID, expires_in); - } -} - -void Td::on_get_promo_data(Result> r_promo_data, bool dummy) { - if (G()->close_flag()) { - return; - } - reloading_promo_data_ = false; - - if (r_promo_data.is_error()) { - LOG(ERROR) << "Receive error for GetPromoData: " << r_promo_data.error(); - return schedule_get_promo_data(60); - } - - auto promo_data_ptr = r_promo_data.move_as_ok(); - CHECK(promo_data_ptr != nullptr); - LOG(DEBUG) << "Receive " << to_string(promo_data_ptr); - int32 expires_at = 0; - switch (promo_data_ptr->get_id()) { - case telegram_api::help_promoDataEmpty::ID: { - auto promo = telegram_api::move_object_as(promo_data_ptr); - expires_at = promo->expires_; - messages_manager_->remove_sponsored_dialog(); - break; - } - case telegram_api::help_promoData::ID: { - auto promo = telegram_api::move_object_as(promo_data_ptr); - expires_at = promo->expires_; - bool is_proxy = promo->proxy_; - messages_manager_->on_get_sponsored_dialog( - std::move(promo->peer_), - is_proxy ? DialogSource::mtproto_proxy() - : DialogSource::public_service_announcement(promo->psa_type_, promo->psa_message_), - std::move(promo->users_), std::move(promo->chats_)); - break; - } - default: - UNREACHABLE(); - } - if (need_reload_promo_data_) { - need_reload_promo_data_ = false; - expires_at = 0; - } - schedule_get_promo_data(expires_at == 0 ? 0 : expires_at - G()->unix_time()); -} - -void Td::reload_promo_data() { - if (reloading_promo_data_) { - need_reload_promo_data_ = true; - return; - } - schedule_get_promo_data(0); -} - -void Td::schedule_get_promo_data(int32 expires_in) { - expires_in = expires_in <= 0 ? 0 : clamp(expires_in, 60, 86400); - if (!close_flag_ && auth_manager_->is_authorized() && !auth_manager_->is_bot()) { - LOG(INFO) << "Schedule getPromoData in " << expires_in; - alarm_timeout_.set_timeout_in(PROMO_DATA_ALARM_ID, expires_in); - } -} - -bool Td::is_online() const { - return is_online_; -} - -void Td::set_is_online(bool is_online) { - if (is_online == is_online_) { - return; - } - - is_online_ = is_online; - if (auth_manager_ != nullptr) { // postpone if there is no AuthManager yet - on_online_updated(true, true); - } -} - -void Td::set_is_bot_online(bool is_bot_online) { - alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID, PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5)); - - if (G()->get_option_integer("session_count") > 1) { - is_bot_online = false; - } - - if (is_bot_online == is_bot_online_) { - return; - } - - is_bot_online_ = is_bot_online; - send_closure(G()->state_manager(), &StateManager::on_online, is_bot_online_); -} - bool Td::ignore_background_updates() const { return can_ignore_background_updates_ && option_manager_->get_option_boolean("ignore_background_updates"); } @@ -2601,43 +2215,6 @@ bool Td::is_authentication_request(int32 id) { } } -bool Td::is_synchronous_request(const td_api::Function *function) { - switch (function->get_id()) { - case td_api::searchQuote::ID: - case td_api::getTextEntities::ID: - case td_api::parseTextEntities::ID: - case td_api::parseMarkdown::ID: - case td_api::getMarkdownText::ID: - case td_api::searchStringsByPrefix::ID: - case td_api::checkQuickReplyShortcutName::ID: - case td_api::getCountryFlagEmoji::ID: - case td_api::getFileMimeType::ID: - case td_api::getFileExtension::ID: - case td_api::cleanFileName::ID: - case td_api::getLanguagePackString::ID: - case td_api::getPhoneNumberInfoSync::ID: - case td_api::getChatFolderDefaultIconName::ID: - case td_api::getJsonValue::ID: - case td_api::getJsonString::ID: - case td_api::getThemeParametersJsonString::ID: - case td_api::getPushReceiverId::ID: - case td_api::setLogStream::ID: - case td_api::getLogStream::ID: - case td_api::setLogVerbosityLevel::ID: - case td_api::getLogVerbosityLevel::ID: - case td_api::getLogTags::ID: - case td_api::setLogTagVerbosityLevel::ID: - case td_api::getLogTagVerbosityLevel::ID: - case td_api::addLogMessage::ID: - case td_api::testReturnError::ID: - return true; - case td_api::getOption::ID: - return OptionManager::is_synchronous_option(static_cast(function)->name_); - default: - return false; - } -} - bool Td::is_preinitialization_request(int32 id) { switch (id) { case td_api::getCurrentState::ID: @@ -2730,17 +2307,6 @@ vector> Td::get_fake_current_state() const { return updates; } -DbKey Td::as_db_key(string key) { - // Database will still be effectively not encrypted, but - // 1. SQLite database will be protected from corruption, because that's how sqlcipher works - // 2. security through obscurity - // 3. no need for reencryption of SQLite database - if (key.empty()) { - return DbKey::raw_key("cucumber"); - } - return DbKey::raw_key(std::move(key)); -} - void Td::request(uint64 id, tl_object_ptr function) { if (id == 0) { LOG(ERROR) << "Ignore request with ID == 0: " << to_string(function); @@ -2753,7 +2319,7 @@ void Td::request(uint64 id, tl_object_ptr function) { VLOG(td_requests) << "Receive request " << id << ": " << to_string(function); request_set_.emplace(id, function->get_id()); - if (is_synchronous_request(function.get())) { + if (SynchronousRequests::is_synchronous_request(function.get())) { // send response synchronously return send_result(id, static_request(std::move(function))); } @@ -2761,7 +2327,7 @@ void Td::request(uint64 id, tl_object_ptr function) { run_request(id, std::move(function)); } -void Td::run_request(uint64 id, tl_object_ptr function) { +void Td::run_request(uint64 id, td_api::object_ptr function) { if (set_parameters_request_id_ > 0) { pending_set_parameters_requests_.emplace_back(id, std::move(function)); return; @@ -2813,7 +2379,7 @@ void Td::run_request(uint64 id, tl_object_ptr function) { } default: if (is_preinitialization_request(function_id)) { - break; + return do_run_request(id, std::move(function)); } if (is_preauthentication_request(function_id)) { pending_preauthentication_requests_.emplace_back(id, std::move(function)); @@ -2822,65 +2388,28 @@ void Td::run_request(uint64 id, tl_object_ptr function) { return send_error_impl( id, make_error(400, "Initialization parameters are needed: call setTdlibParameters first")); } - break; + UNREACHABLE(); } case State::Close: - if (destroy_flag_) { + return send_error_impl(id, make_error(destroy_flag_ ? 401 : 500, + destroy_flag_ ? CSlice("Unauthorized") : CSlice("Request aborted"))); + case State::Run: + if (!auth_manager_->is_authorized() && !is_preauthentication_request(function_id) && + !is_preinitialization_request(function_id) && !is_authentication_request(function_id)) { return send_error_impl(id, make_error(401, "Unauthorized")); - } else { - return send_error_impl(id, make_error(500, "Request aborted")); } - case State::Run: - break; + return do_run_request(id, std::move(function)); + default: + UNREACHABLE(); } +} - if ((auth_manager_ == nullptr || !auth_manager_->is_authorized()) && !is_preauthentication_request(function_id) && - !is_preinitialization_request(function_id) && !is_authentication_request(function_id)) { - return send_error_impl(id, make_error(401, "Unauthorized")); - } +void Td::do_run_request(uint64 id, td_api::object_ptr &&function) { downcast_call(*function, [this, id](auto &request) { this->on_request(id, request); }); } td_api::object_ptr Td::static_request(td_api::object_ptr function) { - if (function == nullptr) { - return td_api::make_object(400, "Request is empty"); - } - - auto function_id = function->get_id(); - bool need_logging = [function_id] { - switch (function_id) { - case td_api::parseTextEntities::ID: - case td_api::parseMarkdown::ID: - case td_api::getMarkdownText::ID: - case td_api::searchStringsByPrefix::ID: - case td_api::checkQuickReplyShortcutName::ID: - case td_api::getCountryFlagEmoji::ID: - case td_api::getFileMimeType::ID: - case td_api::getFileExtension::ID: - case td_api::cleanFileName::ID: - case td_api::getChatFolderDefaultIconName::ID: - case td_api::getJsonValue::ID: - case td_api::getJsonString::ID: - case td_api::getThemeParametersJsonString::ID: - case td_api::testReturnError::ID: - return true; - default: - return false; - } - }(); - - if (need_logging) { - VLOG(td_requests) << "Receive static request: " << to_string(function); - } - - td_api::object_ptr response; - downcast_call(*function, [&response](auto &request) { response = Td::do_static_request(request); }); - LOG_CHECK(response != nullptr) << function_id; - - if (need_logging) { - VLOG(td_requests) << "Sending result for static request: " << to_string(response); - } - return response; + return SynchronousRequests::run_request(std::move(function)); } void Td::add_handler(uint64 id, std::shared_ptr handler) { @@ -2913,7 +2442,7 @@ void Td::on_update(telegram_api::object_ptr updates, uint updates_manager_->on_update_from_auth_key_id(auth_key_id); updates_manager_->on_get_updates(std::move(updates), Promise()); if (auth_manager_->is_bot() && auth_manager_->is_authorized()) { - set_is_bot_online(true); + online_manager_->set_is_bot_online(true); } } } @@ -2941,19 +2470,6 @@ void Td::on_result(NetQueryPtr query) { } } -void Td::on_connection_state_changed(ConnectionState new_state) { - if (G()->close_flag()) { - return; - } - if (new_state == connection_state_) { - LOG(ERROR) << "State manager sends update about unchanged state " << static_cast(new_state); - return; - } - connection_state_ = new_state; - - send_closure(actor_id(this), &Td::send_update, get_update_connection_state_object(connection_state_)); -} - void Td::start_up() { uint64 check_endianness = 0x0706050403020100; auto check_endianness_raw = reinterpret_cast(&check_endianness); @@ -3039,8 +2555,9 @@ void Td::dec_actor_refcnt() { reset_manager(business_manager_, "BusinessManager"); reset_manager(callback_queries_manager_, "CallbackQueriesManager"); reset_manager(channel_recommendation_manager_, "ChannelRecommendationManager"); - reset_manager(common_dialog_manager_, "CommonDialogManager"); reset_manager(chat_manager_, "ChatManager"); + reset_manager(common_dialog_manager_, "CommonDialogManager"); + reset_manager(connection_state_manager_, "ConnectionStateManager"); reset_manager(country_info_manager_, "CountryInfoManager"); reset_manager(dialog_action_manager_, "DialogActionManager"); reset_manager(dialog_filter_manager_, "DialogFilterManager"); @@ -3061,10 +2578,12 @@ void Td::dec_actor_refcnt() { reset_manager(messages_manager_, "MessagesManager"); reset_manager(notification_manager_, "NotificationManager"); reset_manager(notification_settings_manager_, "NotificationSettingsManager"); + reset_manager(online_manager_, "OnlineManager"); reset_manager(people_nearby_manager_, "PeopleNearbyManager"); reset_manager(phone_number_manager_, "PhoneNumberManager"); reset_manager(poll_manager_, "PollManager"); reset_manager(privacy_manager_, "PrivacyManager"); + reset_manager(promo_data_manager_, "PromoDataManager"); reset_manager(quick_reply_manager_, "QuickReplyManager"); reset_manager(reaction_manager_, "ReactionManager"); reset_manager(saved_messages_manager_, "SavedMessagesManager"); @@ -3073,6 +2592,7 @@ void Td::dec_actor_refcnt() { reset_manager(statistics_manager_, "StatisticsManager"); reset_manager(stickers_manager_, "StickersManager"); reset_manager(story_manager_, "StoryManager"); + reset_manager(terms_of_service_manager_, "TermsOfServiceManager"); reset_manager(theme_manager_, "ThemeManager"); reset_manager(time_zone_manager_, "TimeZoneManager"); reset_manager(top_dialog_manager_, "TopDialogManager"); @@ -3173,13 +2693,6 @@ void Td::clear() { state_manager_.reset(); LOG(DEBUG) << "StateManager was cleared" << timer; clear_requests(); - if (is_online_) { - is_online_ = false; - alarm_timeout_.cancel_timeout(ONLINE_ALARM_ID); - } - alarm_timeout_.cancel_timeout(PING_SERVER_ALARM_ID); - alarm_timeout_.cancel_timeout(TERMS_OF_SERVICE_ALARM_ID); - alarm_timeout_.cancel_timeout(PROMO_DATA_ALARM_ID); auto reset_actor = [&timer](ActorOwn actor) { if (!actor.empty()) { @@ -3218,8 +2731,9 @@ void Td::clear() { reset_actor(ActorOwn(std::move(business_connection_manager_actor_))); reset_actor(ActorOwn(std::move(business_manager_actor_))); reset_actor(ActorOwn(std::move(channel_recommendation_manager_actor_))); - reset_actor(ActorOwn(std::move(common_dialog_manager_actor_))); reset_actor(ActorOwn(std::move(chat_manager_actor_))); + reset_actor(ActorOwn(std::move(common_dialog_manager_actor_))); + reset_actor(ActorOwn(std::move(connection_state_manager_actor_))); reset_actor(ActorOwn(std::move(country_info_manager_actor_))); reset_actor(ActorOwn(std::move(dialog_action_manager_actor_))); reset_actor(ActorOwn(std::move(dialog_filter_manager_actor_))); @@ -3239,10 +2753,12 @@ void Td::clear() { reset_actor(ActorOwn(std::move(messages_manager_actor_))); reset_actor(ActorOwn(std::move(notification_manager_actor_))); reset_actor(ActorOwn(std::move(notification_settings_manager_actor_))); + reset_actor(ActorOwn(std::move(online_manager_actor_))); reset_actor(ActorOwn(std::move(people_nearby_manager_actor_))); reset_actor(ActorOwn(std::move(phone_number_manager_actor_))); reset_actor(ActorOwn(std::move(poll_manager_actor_))); reset_actor(ActorOwn(std::move(privacy_manager_actor_))); + reset_actor(ActorOwn(std::move(promo_data_manager_actor_))); reset_actor(ActorOwn(std::move(quick_reply_manager_actor_))); reset_actor(ActorOwn(std::move(reaction_manager_actor_))); reset_actor(ActorOwn(std::move(saved_messages_manager_actor_))); @@ -3251,6 +2767,7 @@ void Td::clear() { reset_actor(ActorOwn(std::move(statistics_manager_actor_))); reset_actor(ActorOwn(std::move(stickers_manager_actor_))); reset_actor(ActorOwn(std::move(story_manager_actor_))); + reset_actor(ActorOwn(std::move(terms_of_service_manager_actor_))); reset_actor(ActorOwn(std::move(theme_manager_actor_))); reset_actor(ActorOwn(std::move(time_zone_manager_actor_))); reset_actor(ActorOwn(std::move(top_dialog_manager_actor_))); @@ -3339,7 +2856,7 @@ template void Td::complete_pending_preauthentication_requests(const T &func) { for (auto &request : pending_preauthentication_requests_) { if (request.second != nullptr && func(request.second->get_id())) { - downcast_call(*request.second, [this, id = request.first](auto &request) { this->on_request(id, request); }); + do_run_request(request.first, std::move(request.second)); request.second = nullptr; } } @@ -3471,13 +2988,6 @@ void Td::init(Parameters parameters, Result r_opened_datab option_manager_->on_td_inited(); - if (is_online_) { - on_online_updated(true, true); - } - if (auth_manager_->is_bot()) { - set_is_bot_online(true); - } - process_binlog_events(std::move(events)); VLOG(td_init) << "Ping datacenter"; @@ -3485,8 +2995,6 @@ void Td::init(Parameters parameters, Result r_opened_datab country_info_manager_->get_current_country_code(Promise()); } else { updates_manager_->get_difference("init"); - schedule_get_terms_of_service(0); - reload_promo_data(); } complete_pending_preauthentication_requests([](int32 id) { return true; }); @@ -3567,20 +3075,7 @@ void Td::process_binlog_events(TdDb::OpenedDatabase &&events) { void Td::init_options_and_network() { VLOG(td_init) << "Create StateManager"; - class StateManagerCallback final : public StateManager::Callback { - public: - explicit StateManagerCallback(ActorShared td) : td_(std::move(td)) { - } - bool on_state(ConnectionState state) final { - send_closure(td_, &Td::on_connection_state_changed, state); - return td_.is_alive(); - } - - private: - ActorShared td_; - }; state_manager_ = create_actor("State manager", create_reference()); - send_closure(state_manager_, &StateManager::add_callback, make_unique(create_reference())); G()->set_state_manager(state_manager_.get()); VLOG(td_init) << "Create OptionManager"; @@ -3612,6 +3107,11 @@ void Td::init_options_and_network() { VLOG(td_init) << "Create ConfigManager"; config_manager_ = create_actor("ConfigManager", create_reference()); G()->set_config_manager(config_manager_.get()); + + VLOG(td_init) << "Create OnlineManager"; + online_manager_ = make_unique(this, create_reference()); + online_manager_actor_ = register_actor("OnlineManager", online_manager_.get()); + G()->set_online_manager(online_manager_actor_.get()); } void Td::init_file_manager() { @@ -3723,11 +3223,13 @@ void Td::init_managers() { channel_recommendation_manager_ = make_unique(this, create_reference()); channel_recommendation_manager_actor_ = register_actor("ChannelRecommendationManager", channel_recommendation_manager_.get()); - common_dialog_manager_ = make_unique(this, create_reference()); - common_dialog_manager_actor_ = register_actor("CommonDialogManager", common_dialog_manager_.get()); chat_manager_ = make_unique(this, create_reference()); chat_manager_actor_ = register_actor("ChatManager", chat_manager_.get()); G()->set_chat_manager(chat_manager_actor_.get()); + common_dialog_manager_ = make_unique(this, create_reference()); + common_dialog_manager_actor_ = register_actor("CommonDialogManager", common_dialog_manager_.get()); + connection_state_manager_ = make_unique(this, create_reference()); + connection_state_manager_actor_ = register_actor("ConnectionStateManager", connection_state_manager_.get()); country_info_manager_ = make_unique(this, create_reference()); country_info_manager_actor_ = register_actor("CountryInfoManager", country_info_manager_.get()); dialog_action_manager_ = make_unique(this, create_reference()); @@ -3787,6 +3289,9 @@ void Td::init_managers() { 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()); + promo_data_manager_ = make_unique(this, create_reference()); + promo_data_manager_actor_ = register_actor("PromoDataManager", promo_data_manager_.get()); + G()->set_promo_data_manager(promo_data_manager_actor_.get()); quick_reply_manager_ = make_unique(this, create_reference()); quick_reply_manager_actor_ = register_actor("QuickReplyManager", quick_reply_manager_.get()); G()->set_quick_reply_manager(quick_reply_manager_actor_.get()); @@ -3810,6 +3315,8 @@ void Td::init_managers() { story_manager_ = make_unique(this, create_reference()); story_manager_actor_ = register_actor("StoryManager", story_manager_.get()); G()->set_story_manager(story_manager_actor_.get()); + terms_of_service_manager_ = make_unique(this, create_reference()); + terms_of_service_manager_actor_ = register_actor("TermsOfServiceManager", terms_of_service_manager_.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()); @@ -4002,7 +3509,7 @@ Result> Td::get_parameters( result.first.api_hash_ = std::move(parameters->api_hash_); result.first.use_secret_chats_ = parameters->use_secret_chats_; - result.second.encryption_key_ = as_db_key(std::move(parameters->database_encryption_key_)); + result.second.encryption_key_ = TdDb::as_db_key(std::move(parameters->database_encryption_key_)); result.second.database_directory_ = std::move(parameters->database_directory_); result.second.files_directory_ = std::move(parameters->files_directory_); result.second.is_test_dc_ = parameters->use_test_dc_; @@ -4050,7 +3557,7 @@ void Td::on_request(uint64 id, const td_api::setTdlibParameters &request) { void Td::on_request(uint64 id, td_api::setDatabaseEncryptionKey &request) { CREATE_OK_REQUEST_PROMISE(); - G()->td_db()->get_binlog()->change_key(as_db_key(std::move(request.new_encryption_key_)), std::move(promise)); + G()->td_db()->get_binlog()->change_key(TdDb::as_db_key(std::move(request.new_encryption_key_)), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getAuthorizationState &request) { @@ -4167,7 +3674,7 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { updates.push_back(td_api::make_object(std::move(state))); } - updates.push_back(get_update_connection_state_object(connection_state_)); + connection_state_manager_->get_current_state(updates); if (auth_manager_->is_authorized()) { user_manager_->get_current_state(updates); @@ -4210,16 +3717,15 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { business_connection_manager_->get_current_state(updates); + terms_of_service_manager_->get_current_state(updates); + + star_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; } - auto update_terms_of_service = get_update_terms_of_service_object(); - if (update_terms_of_service != nullptr) { - updates.push_back(std::move(update_terms_of_service)); - } - // send response synchronously to prevent "Request aborted" or other changes of the current state send_result(id, td_api::make_object(std::move(updates))); } @@ -4762,6 +4268,7 @@ void Td::on_request(uint64 id, td_api::getStorageStatisticsFast &request) { }); send_closure(storage_manager_, &StorageManager::get_storage_stats_fast, std::move(query_promise)); } + void Td::on_request(uint64 id, const td_api::getDatabaseStatistics &request) { CREATE_REQUEST_PROMISE(); auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { @@ -5375,11 +4882,6 @@ void Td::on_request(uint64 id, const td_api::searchChatRecentLocationMessages &r std::move(promise)); } -void Td::on_request(uint64 id, const td_api::getActiveLiveLocationMessages &request) { - CHECK_IS_USER(); - CREATE_NO_ARGS_REQUEST(GetActiveLiveLocationMessagesRequest); -} - void Td::on_request(uint64 id, const td_api::getChatMessageByDate &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -5468,6 +4970,27 @@ void Td::on_request(uint64 id, const td_api::addMessageReaction &request) { request.update_recent_reactions_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::addPaidMessageReaction &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->add_paid_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)}, + request.star_count_, request.is_anonymous_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::removePendingPaidMessageReactions &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->remove_paid_message_reactions({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::togglePaidMessageReactionIsAnonymous &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->toggle_paid_message_reaction_is_anonymous( + {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.is_anonymous_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::removeMessageReaction &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -7255,16 +6778,24 @@ void Td::on_request(uint64 id, const td_api::getChatAdministrators &request) { void Td::on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request) { CREATE_REQUEST_PROMISE(); - dialog_invite_link_manager_->export_dialog_invite_link(DialogId(request.chat_id_), string(), 0, 0, false, true, - std::move(promise)); + dialog_invite_link_manager_->export_dialog_invite_link(DialogId(request.chat_id_), string(), 0, 0, false, + StarSubscriptionPricing(), false, true, std::move(promise)); } void Td::on_request(uint64 id, td_api::createChatInviteLink &request) { CLEAN_INPUT_STRING(request.name_); CREATE_REQUEST_PROMISE(); - dialog_invite_link_manager_->export_dialog_invite_link(DialogId(request.chat_id_), std::move(request.name_), - request.expiration_date_, request.member_limit_, - request.creates_join_request_, false, std::move(promise)); + dialog_invite_link_manager_->export_dialog_invite_link( + DialogId(request.chat_id_), std::move(request.name_), request.expiration_date_, request.member_limit_, + request.creates_join_request_, StarSubscriptionPricing(), false, false, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::createChatSubscriptionInviteLink &request) { + CLEAN_INPUT_STRING(request.name_); + CREATE_REQUEST_PROMISE(); + dialog_invite_link_manager_->export_dialog_invite_link( + DialogId(request.chat_id_), std::move(request.name_), 0, 0, false, + StarSubscriptionPricing(std::move(request.subscription_pricing_)), true, false, std::move(promise)); } void Td::on_request(uint64 id, td_api::editChatInviteLink &request) { @@ -7273,7 +6804,15 @@ void Td::on_request(uint64 id, td_api::editChatInviteLink &request) { CREATE_REQUEST_PROMISE(); dialog_invite_link_manager_->edit_dialog_invite_link( DialogId(request.chat_id_), request.invite_link_, std::move(request.name_), request.expiration_date_, - request.member_limit_, request.creates_join_request_, std::move(promise)); + request.member_limit_, request.creates_join_request_, false, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editChatSubscriptionInviteLink &request) { + CLEAN_INPUT_STRING(request.name_); + CLEAN_INPUT_STRING(request.invite_link_); + CREATE_REQUEST_PROMISE(); + dialog_invite_link_manager_->edit_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, + std::move(request.name_), 0, 0, false, true, std::move(promise)); } void Td::on_request(uint64 id, td_api::getChatInviteLink &request) { @@ -7303,9 +6842,9 @@ void Td::on_request(uint64 id, td_api::getChatInviteLinkMembers &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.invite_link_); CREATE_REQUEST_PROMISE(); - dialog_invite_link_manager_->get_dialog_invite_link_users(DialogId(request.chat_id_), request.invite_link_, - std::move(request.offset_member_), request.limit_, - std::move(promise)); + dialog_invite_link_manager_->get_dialog_invite_link_users( + DialogId(request.chat_id_), request.invite_link_, request.only_with_expired_subscription_, + std::move(request.offset_member_), request.limit_, std::move(promise)); } void Td::on_request(uint64 id, td_api::getChatJoinRequests &request) { @@ -8220,7 +7759,7 @@ void Td::on_request(uint64 id, const td_api::toggleSupergroupSignMessages &reque CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); chat_manager_->toggle_channel_sign_messages(ChannelId(request.supergroup_id_), request.sign_messages_, - std::move(promise)); + request.show_message_sender_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request) { @@ -9582,10 +9121,32 @@ void Td::on_request(uint64 id, const td_api::getStarGiftPaymentOptions &request) } void Td::on_request(uint64 id, td_api::getStarTransactions &request) { + CLEAN_INPUT_STRING(request.subscription_id_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + star_manager_->get_star_transactions(std::move(request.owner_id_), request.subscription_id_, request.offset_, + request.limit_, std::move(request.direction_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getStarSubscriptions &request) { + CHECK_IS_USER(); CLEAN_INPUT_STRING(request.offset_); CREATE_REQUEST_PROMISE(); - star_manager_->get_star_transactions(std::move(request.owner_id_), request.offset_, request.limit_, - std::move(request.direction_), std::move(promise)); + star_manager_->get_star_subscriptions(request.only_expiring_, request.offset_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editStarSubscription &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.subscription_id_); + CREATE_OK_REQUEST_PROMISE(); + star_manager_->edit_star_subscriptions(request.subscription_id_, request.is_canceled_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::reuseStarSubscription &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.subscription_id_); + CREATE_OK_REQUEST_PROMISE(); + star_manager_->reuse_star_subscriptions(request.subscription_id_, std::move(promise)); } void Td::on_request(uint64 id, td_api::canPurchaseFromStore &request) { @@ -9619,15 +9180,8 @@ void Td::on_request(uint64 id, const td_api::getBusinessFeatures &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([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 { - send_closure(actor_id, &Td::send_result, id, td_api::make_object()); - send_closure(actor_id, &Td::schedule_get_terms_of_service, 0); - } - }); - accept_terms_of_service(this, std::move(request.terms_of_service_id_), std::move(promise)); + CREATE_OK_REQUEST_PROMISE(); + terms_of_service_manager_->accept_terms_of_service(std::move(request.terms_of_service_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getCountries &request) { @@ -9881,288 +9435,6 @@ void Td::on_request(uint64 id, const td_api::addLogMessage &request) { UNREACHABLE(); } -td_api::object_ptr Td::do_static_request(td_api::searchQuote &request) { - if (request.text_ == nullptr || request.quote_ == nullptr) { - return make_error(400, "Text and quote must be non-empty"); - } - if (!check_utf8(request.text_->text_) || !check_utf8(request.quote_->text_)) { - return make_error(400, "Strings must be encoded in UTF-8"); - } - auto r_text_entities = get_message_entities(nullptr, std::move(request.text_->entities_), false); - if (r_text_entities.is_error()) { - return make_error(400, r_text_entities.error().message()); - } - auto r_quote_entities = get_message_entities(nullptr, std::move(request.quote_->entities_), false); - if (r_quote_entities.is_error()) { - return make_error(400, r_quote_entities.error().message()); - } - auto position = MessageQuote::search_quote({std::move(request.text_->text_), r_text_entities.move_as_ok()}, - {std::move(request.quote_->text_), r_quote_entities.move_as_ok()}, - request.quote_position_); - if (position == -1) { - return make_error(404, "Not Found"); - } - return td_api::make_object(position); -} - -td_api::object_ptr Td::do_static_request(const td_api::getTextEntities &request) { - if (!check_utf8(request.text_)) { - return make_error(400, "Text must be encoded in UTF-8"); - } - auto text_entities = find_entities(request.text_, false, false); - return make_tl_object( - get_text_entities_object(nullptr, text_entities, false, std::numeric_limits::max())); -} - -td_api::object_ptr Td::do_static_request(td_api::parseTextEntities &request) { - if (!check_utf8(request.text_)) { // must not use clean_input_string, because \r may be used as a separator - return make_error(400, "Text must be encoded in UTF-8"); - } - if (request.parse_mode_ == nullptr) { - return make_error(400, "Parse mode must be non-empty"); - } - - auto r_entities = [&]() -> Result> { - if (utf8_length(request.text_) > 65536) { - return Status::Error("Text is too long"); - } - switch (request.parse_mode_->get_id()) { - case td_api::textParseModeHTML::ID: - return parse_html(request.text_); - case td_api::textParseModeMarkdown::ID: { - auto version = static_cast(request.parse_mode_.get())->version_; - if (version == 0 || version == 1) { - return parse_markdown(request.text_); - } - if (version == 2) { - return parse_markdown_v2(request.text_); - } - return Status::Error("Wrong Markdown version specified"); - } - default: - UNREACHABLE(); - return Status::Error(500, "Unknown parse mode"); - } - }(); - if (r_entities.is_error()) { - return make_error(400, PSLICE() << "Can't parse entities: " << r_entities.error().message()); - } - - return make_tl_object(std::move(request.text_), - get_text_entities_object(nullptr, r_entities.ok(), false, -1)); -} - -td_api::object_ptr Td::do_static_request(td_api::parseMarkdown &request) { - if (request.text_ == nullptr) { - return make_error(400, "Text must be non-empty"); - } - - auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_), true); - if (r_entities.is_error()) { - return make_error(400, r_entities.error().message()); - } - auto entities = r_entities.move_as_ok(); - auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true); - if (status.is_error()) { - return make_error(400, status.message()); - } - - auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)}); - fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).ensure(); - return get_formatted_text_object(nullptr, parsed_text, false, std::numeric_limits::max()); -} - -td_api::object_ptr Td::do_static_request(const td_api::getOption &request) { - if (!is_synchronous_request(&request)) { - return make_error(400, "The option can't be get synchronously"); - } - return OptionManager::get_option_synchronously(request.name_); -} - -td_api::object_ptr Td::do_static_request(td_api::getMarkdownText &request) { - if (request.text_ == nullptr) { - return make_error(400, "Text must be non-empty"); - } - - auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_)); - if (r_entities.is_error()) { - return make_error(400, r_entities.error().message()); - } - auto entities = r_entities.move_as_ok(); - auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true); - if (status.is_error()) { - return make_error(400, status.message()); - } - - return get_formatted_text_object(nullptr, get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), - false, std::numeric_limits::max()); -} - -td_api::object_ptr Td::do_static_request(td_api::searchStringsByPrefix &request) { - if (!clean_input_string(request.query_)) { - return make_error(400, "Strings must be encoded in UTF-8"); - } - for (auto &str : request.strings_) { - if (!clean_input_string(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::checkQuickReplyShortcutName &request) { - // don't check name UTF-8 correctness - auto status = QuickReplyManager::check_shortcut_name(request.name_); - if (status.is_ok()) { - return td_api::make_object(); - } - return make_error(200, status.message()); -} - -td_api::object_ptr Td::do_static_request(const td_api::getCountryFlagEmoji &request) { - // don't check country code UTF-8 correctness - return td_api::make_object(CountryInfoManager::get_country_flag_emoji(request.country_code_)); -} - -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())); -} - -td_api::object_ptr Td::do_static_request(const td_api::getFileExtension &request) { - // don't check MIME type UTF-8 correctness - return make_tl_object(MimeType::to_extension(request.mime_type_)); -} - -td_api::object_ptr Td::do_static_request(const td_api::cleanFileName &request) { - // don't check file name UTF-8 correctness - return make_tl_object(clean_filename(request.file_name_)); -} - -td_api::object_ptr Td::do_static_request(const td_api::getLanguagePackString &request) { - return LanguagePackManager::get_language_pack_string( - request.language_pack_database_path_, request.localization_target_, request.language_pack_id_, request.key_); -} - -td_api::object_ptr Td::do_static_request(td_api::getPhoneNumberInfoSync &request) { - // don't check language_code/phone number UTF-8 correctness - return CountryInfoManager::get_phone_number_info_sync(request.language_code_, - std::move(request.phone_number_prefix_)); -} - -td_api::object_ptr Td::do_static_request(const td_api::getPushReceiverId &request) { - // don't check push payload UTF-8 correctness - auto r_push_receiver_id = NotificationManager::get_push_receiver_id(request.payload_); - if (r_push_receiver_id.is_error()) { - VLOG(notifications) << "Failed to get push notification receiver from \"" << format::escaped(request.payload_) - << '"'; - return make_error(r_push_receiver_id.error().code(), r_push_receiver_id.error().message()); - } - return td_api::make_object(r_push_receiver_id.ok()); -} - -td_api::object_ptr Td::do_static_request(const td_api::getChatFolderDefaultIconName &request) { - if (request.folder_ == nullptr) { - return make_error(400, "Chat folder must be non-empty"); - } - if (!check_utf8(request.folder_->title_)) { - return make_error(400, "Chat folder title must be encoded in UTF-8"); - } - if (request.folder_->icon_ != nullptr && !check_utf8(request.folder_->icon_->name_)) { - return make_error(400, "Chat folder icon name must be encoded in UTF-8"); - } - return td_api::make_object(DialogFilter::get_default_icon_name(request.folder_.get())); -} - -td_api::object_ptr Td::do_static_request(td_api::getJsonValue &request) { - if (!check_utf8(request.json_)) { - return make_error(400, "JSON has invalid encoding"); - } - auto result = get_json_value(request.json_); - if (result.is_error()) { - return make_error(400, result.error().message()); - } else { - return result.move_as_ok(); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::getJsonString &request) { - return td_api::make_object(get_json_string(request.json_value_.get())); -} - -td_api::object_ptr Td::do_static_request(const td_api::getThemeParametersJsonString &request) { - return td_api::make_object(ThemeManager::get_theme_parameters_json_string(request.theme_)); -} - -td_api::object_ptr Td::do_static_request(td_api::setLogStream &request) { - auto result = Logging::set_current_stream(std::move(request.log_stream_)); - if (result.is_ok()) { - return td_api::make_object(); - } else { - return make_error(400, result.message()); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::getLogStream &request) { - auto result = Logging::get_current_stream(); - if (result.is_ok()) { - return result.move_as_ok(); - } else { - return make_error(400, result.error().message()); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::setLogVerbosityLevel &request) { - auto result = Logging::set_verbosity_level(static_cast(request.new_verbosity_level_)); - if (result.is_ok()) { - return td_api::make_object(); - } else { - return make_error(400, result.message()); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::getLogVerbosityLevel &request) { - return td_api::make_object(Logging::get_verbosity_level()); -} - -td_api::object_ptr Td::do_static_request(const td_api::getLogTags &request) { - return td_api::make_object(Logging::get_tags()); -} - -td_api::object_ptr Td::do_static_request(const td_api::setLogTagVerbosityLevel &request) { - auto result = Logging::set_tag_verbosity_level(request.tag_, static_cast(request.new_verbosity_level_)); - if (result.is_ok()) { - return td_api::make_object(); - } else { - return make_error(400, result.message()); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::getLogTagVerbosityLevel &request) { - auto result = Logging::get_tag_verbosity_level(request.tag_); - if (result.is_ok()) { - return td_api::make_object(result.ok()); - } else { - return make_error(400, result.error().message()); - } -} - -td_api::object_ptr Td::do_static_request(const td_api::addLogMessage &request) { - Logging::add_message(request.verbosity_level_, request.text_); - return td_api::make_object(); -} - -td_api::object_ptr Td::do_static_request(td_api::testReturnError &request) { - if (request.error_ == nullptr) { - return td_api::make_object(404, "Not Found"); - } - - return std::move(request.error_); -} - // test void Td::on_request(uint64 id, const td_api::testNetwork &request) { CREATE_OK_REQUEST_PROMISE(); @@ -10174,7 +9446,9 @@ void Td::on_request(uint64 id, td_api::testProxy &request) { if (r_proxy.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_proxy.move_as_error()); } - CREATE_REQUEST(TestProxyRequest, r_proxy.move_as_ok(), request.dc_id_, request.timeout_); + CREATE_OK_REQUEST_PROMISE(); + send_closure(G()->connection_creator(), &ConnectionCreator::test_proxy, r_proxy.move_as_ok(), request.dc_id_, + request.timeout_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::testGetDifference &request) { diff --git a/lib/tgchat/ext/td/td/telegram/Td.h b/lib/tgchat/ext/td/td/telegram/Td.h index 2d3f9f2a..8a812a8c 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.h +++ b/lib/tgchat/ext/td/td/telegram/Td.h @@ -6,7 +6,6 @@ // #pragma once -#include "td/telegram/ConnectionState.h" #include "td/telegram/files/FileId.h" #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQuery.h" @@ -15,9 +14,6 @@ #include "td/telegram/TdCallback.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" -#include "td/telegram/TermsOfService.h" - -#include "td/db/DbKey.h" #include "td/actor/actor.h" #include "td/actor/MultiTimeout.h" @@ -54,6 +50,7 @@ class ChannelRecommendationManager; class ChatManager; class CommonDialogManager; class ConfigManager; +class ConnectionStateManager; class CountryInfoManager; class DeviceTokenManager; class DialogActionManager; @@ -78,12 +75,14 @@ class MessagesManager; class NetStatsManager; class NotificationManager; class NotificationSettingsManager; +class OnlineManager; class OptionManager; class PasswordManager; class PeopleNearbyManager; class PhoneNumberManager; class PollManager; class PrivacyManager; +class PromoDataManager; class QuickReplyManager; class ReactionManager; class SavedMessagesManager; @@ -96,6 +95,7 @@ class StatisticsManager; class StickersManager; class StorageManager; class StoryManager; +class TermsOfServiceManager; class ThemeManager; class TimeZoneManager; class TopDialogManager; @@ -138,24 +138,10 @@ class Td final : public Actor { void destroy(); - void schedule_get_terms_of_service(int32 expires_in); - - void reload_promo_data(); - void on_update(telegram_api::object_ptr updates, uint64 auth_key_id); void on_result(NetQueryPtr query); - void on_online_updated(bool force, bool send_update); - - void on_update_status_success(bool is_online); - - bool is_online() const; - - void set_is_online(bool is_online); - - void set_is_bot_online(bool is_bot_online); - bool can_ignore_background_updates() const { return can_ignore_background_updates_; } @@ -194,6 +180,8 @@ class Td final : public Actor { ActorOwn chat_manager_actor_; unique_ptr common_dialog_manager_; ActorOwn common_dialog_manager_actor_; + unique_ptr connection_state_manager_; + ActorOwn connection_state_manager_actor_; unique_ptr country_info_manager_; ActorOwn country_info_manager_actor_; unique_ptr dialog_action_manager_; @@ -232,14 +220,18 @@ class Td final : public Actor { ActorOwn notification_manager_actor_; unique_ptr notification_settings_manager_; ActorOwn notification_settings_manager_actor_; - unique_ptr poll_manager_; - ActorOwn poll_manager_actor_; - unique_ptr privacy_manager_; - ActorOwn privacy_manager_actor_; + unique_ptr online_manager_; + ActorOwn online_manager_actor_; unique_ptr people_nearby_manager_; ActorOwn people_nearby_manager_actor_; unique_ptr phone_number_manager_; ActorOwn phone_number_manager_actor_; + unique_ptr poll_manager_; + ActorOwn poll_manager_actor_; + unique_ptr privacy_manager_; + ActorOwn privacy_manager_actor_; + unique_ptr promo_data_manager_; + ActorOwn promo_data_manager_actor_; unique_ptr quick_reply_manager_; ActorOwn quick_reply_manager_actor_; unique_ptr reaction_manager_; @@ -256,6 +248,8 @@ class Td final : public Actor { ActorOwn stickers_manager_actor_; unique_ptr story_manager_; ActorOwn story_manager_actor_; + unique_ptr terms_of_service_manager_; + ActorOwn terms_of_service_manager_actor_; unique_ptr theme_manager_; ActorOwn theme_manager_actor_; unique_ptr time_zone_manager_; @@ -335,15 +329,9 @@ class Td final : public Actor { static td_api::object_ptr static_request(td_api::object_ptr function); private: - static constexpr int64 ONLINE_ALARM_ID = 0; - static constexpr int64 PING_SERVER_ALARM_ID = -1; - static constexpr int32 PING_SERVER_TIMEOUT = 300; - static constexpr int64 TERMS_OF_SERVICE_ALARM_ID = -2; - static constexpr int64 PROMO_DATA_ALARM_ID = -3; - - void on_connection_state_changed(ConnectionState new_state); + void run_request(uint64 id, td_api::object_ptr function); - void run_request(uint64 id, tl_object_ptr function); + void do_run_request(uint64 id, td_api::object_ptr &&function); void send_result(uint64 id, tl_object_ptr object); void send_error(uint64 id, Status error); @@ -369,8 +357,6 @@ class Td final : public Actor { MtprotoHeader::Options options_; - ConnectionState connection_state_ = ConnectionState::Empty; - std::unordered_multimap request_set_; int actor_refcnt_ = 0; int request_actor_refcnt_ = 0; @@ -387,19 +373,10 @@ class Td final : public Actor { bool can_ignore_background_updates_ = false; - bool reloading_promo_data_ = false; - bool need_reload_promo_data_ = false; - - bool is_online_ = false; - bool is_bot_online_ = false; - NetQueryRef update_status_query_; - int64 alarm_id_ = 1; FlatHashMap pending_alarms_; MultiTimeout alarm_timeout_{"AlarmTimeout"}; - TermsOfService pending_terms_of_service_; - struct DownloadInfo { int64 offset = -1; int64 limit = -1; @@ -410,6 +387,7 @@ class Td final : public Actor { vector>> pending_preauthentication_requests_; vector>> pending_set_parameters_requests_; + vector>> pending_init_requests_; template @@ -420,13 +398,8 @@ class Td final : public Actor { vector> get_fake_current_state() const; static void on_alarm_timeout_callback(void *td_ptr, int64 alarm_id); - void on_alarm_timeout(int64 alarm_id); - - td_api::object_ptr get_update_terms_of_service_object() const; - - void on_get_terms_of_service(Result> result, bool dummy); - void on_get_promo_data(Result> r_promo_data, bool dummy); + void on_alarm_timeout(int64 alarm_id); template friend class RequestActor; // uses send_result/send_error @@ -451,8 +424,6 @@ class Td final : public Actor { std::shared_ptr old_context_; - void schedule_get_promo_data(int32 expires_in); - static int *get_log_verbosity_level(Slice name); template @@ -470,8 +441,6 @@ class Td final : public Actor { static bool is_authentication_request(int32 id); - static bool is_synchronous_request(const td_api::Function *function); - static bool is_preinitialization_request(int32 id); static bool is_preauthentication_request(int32 id); @@ -821,8 +790,6 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request); - void on_request(uint64 id, const td_api::getActiveLiveLocationMessages &request); - void on_request(uint64 id, const td_api::getChatMessageByDate &request); void on_request(uint64 id, const td_api::getChatSparseMessagePositions &request); @@ -843,6 +810,12 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::addMessageReaction &request); + void on_request(uint64 id, const td_api::addPaidMessageReaction &request); + + void on_request(uint64 id, const td_api::removePendingPaidMessageReactions &request); + + void on_request(uint64 id, const td_api::togglePaidMessageReactionIsAnonymous &request); + void on_request(uint64 id, const td_api::removeMessageReaction &request); void on_request(uint64 id, const td_api::setMessageReactions &request); @@ -1303,8 +1276,12 @@ class Td final : public Actor { void on_request(uint64 id, td_api::createChatInviteLink &request); + void on_request(uint64 id, td_api::createChatSubscriptionInviteLink &request); + void on_request(uint64 id, td_api::editChatInviteLink &request); + void on_request(uint64 id, td_api::editChatSubscriptionInviteLink &request); + void on_request(uint64 id, td_api::getChatInviteLink &request); void on_request(uint64 id, const td_api::getChatInviteLinkCounts &request); @@ -1905,6 +1882,12 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getStarTransactions &request); + void on_request(uint64 id, td_api::getStarSubscriptions &request); + + void on_request(uint64 id, td_api::editStarSubscription &request); + + void on_request(uint64 id, td_api::reuseStarSubscription &request); + void on_request(uint64 id, td_api::canPurchaseFromStore &request); void on_request(uint64 id, td_api::assignAppStoreTransaction &request); @@ -2020,41 +2003,6 @@ class Td final : public Actor { void on_request(uint64 id, td_api::testCallVectorString &request); void on_request(uint64 id, td_api::testCallVectorStringObject &request); - template - static td_api::object_ptr do_static_request(const T &request) { - return td_api::make_object(400, "The method can't be executed synchronously"); - } - static td_api::object_ptr do_static_request(const td_api::getOption &request); - static td_api::object_ptr do_static_request(td_api::searchQuote &request); - static td_api::object_ptr do_static_request(const td_api::getTextEntities &request); - static td_api::object_ptr do_static_request(td_api::parseTextEntities &request); - static td_api::object_ptr do_static_request(td_api::parseMarkdown &request); - 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::checkQuickReplyShortcutName &request); - static td_api::object_ptr do_static_request(const td_api::getCountryFlagEmoji &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); - static td_api::object_ptr do_static_request(const td_api::getLanguagePackString &request); - static td_api::object_ptr do_static_request(td_api::getPhoneNumberInfoSync &request); - static td_api::object_ptr do_static_request(const td_api::getPushReceiverId &request); - static td_api::object_ptr do_static_request(const td_api::getChatFolderDefaultIconName &request); - static td_api::object_ptr do_static_request(td_api::getJsonValue &request); - static td_api::object_ptr do_static_request(const td_api::getJsonString &request); - static td_api::object_ptr do_static_request(const td_api::getThemeParametersJsonString &request); - static td_api::object_ptr do_static_request(td_api::setLogStream &request); - static td_api::object_ptr do_static_request(const td_api::getLogStream &request); - static td_api::object_ptr do_static_request(const td_api::setLogVerbosityLevel &request); - static td_api::object_ptr do_static_request(const td_api::getLogVerbosityLevel &request); - static td_api::object_ptr do_static_request(const td_api::getLogTags &request); - static td_api::object_ptr do_static_request(const td_api::setLogTagVerbosityLevel &request); - static td_api::object_ptr do_static_request(const td_api::getLogTagVerbosityLevel &request); - static td_api::object_ptr do_static_request(const td_api::addLogMessage &request); - static td_api::object_ptr do_static_request(td_api::testReturnError &request); - - static DbKey as_db_key(string key); - struct Parameters { int32 api_id_ = 0; string api_hash_; diff --git a/lib/tgchat/ext/td/td/telegram/TdDb.cpp b/lib/tgchat/ext/td/td/telegram/TdDb.cpp index 3cf446ca..5998610c 100644 --- a/lib/tgchat/ext/td/td/telegram/TdDb.cpp +++ b/lib/tgchat/ext/td/td/telegram/TdDb.cpp @@ -662,6 +662,13 @@ Status TdDb::check_parameters(Parameters ¶meters) { return Status::OK(); } +DbKey TdDb::as_db_key(string key) { + if (key.empty()) { + return DbKey::raw_key("cucumber"); + } + return DbKey::raw_key(std::move(key)); +} + void TdDb::change_key(DbKey key, Promise<> promise) { get_binlog()->change_key(std::move(key), std::move(promise)); } diff --git a/lib/tgchat/ext/td/td/telegram/TdDb.h b/lib/tgchat/ext/td/td/telegram/TdDb.h index 7e43a698..70813923 100644 --- a/lib/tgchat/ext/td/td/telegram/TdDb.h +++ b/lib/tgchat/ext/td/td/telegram/TdDb.h @@ -148,6 +148,8 @@ class TdDb { StoryDbSyncInterface *get_story_db_sync(); StoryDbAsyncInterface *get_story_db_async(); + static DbKey as_db_key(string key); + void change_key(DbKey key, Promise<> promise); void with_db_path(const std::function &callback); diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp b/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp index 0e88221f..2faff600 100644 --- a/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp +++ b/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp @@ -6,88 +6,8 @@ // #include "td/telegram/TermsOfService.h" -#include "td/telegram/Global.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" -#include "td/utils/Status.h" - namespace td { -class GetTermsOfServiceUpdateQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit GetTermsOfServiceUpdateQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send() { - // we don't poll terms of service before authorization - send_query(G()->net_query_creator().create(telegram_api::help_getTermsOfServiceUpdate())); - } - - 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(); - switch (result->get_id()) { - case telegram_api::help_termsOfServiceUpdateEmpty::ID: { - auto update = move_tl_object_as(result); - promise_.set_value(std::make_pair(update->expires_, TermsOfService())); - break; - } - case telegram_api::help_termsOfServiceUpdate::ID: { - auto update = move_tl_object_as(result); - promise_.set_value(std::make_pair(update->expires_, TermsOfService(std::move(update->terms_of_service_)))); - break; - } - default: - UNREACHABLE(); - } - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class AcceptTermsOfServiceQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit AcceptTermsOfServiceQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(const string &terms_of_service_id) { - send_query(G()->net_query_creator().create(telegram_api::help_acceptTermsOfService( - telegram_api::make_object(terms_of_service_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.ok(); - if (!result) { - LOG(ERROR) << "Failed to accept terms of service"; - } - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - TermsOfService::TermsOfService(telegram_api::object_ptr terms) { if (terms == nullptr) { return; @@ -103,12 +23,13 @@ TermsOfService::TermsOfService(telegram_api::object_ptrpopup_; } -void get_terms_of_service(Td *td, Promise> promise) { - td->create_handler(std::move(promise))->send(); -} +td_api::object_ptr TermsOfService::get_terms_of_service_object() const { + if (id_.empty()) { + return nullptr; + } -void accept_terms_of_service(Td *td, string &&terms_of_service_id, Promise &&promise) { - td->create_handler(std::move(promise))->send(terms_of_service_id); + return td_api::make_object(get_formatted_text_object(nullptr, text_, true, -1), min_user_age_, + show_popup_); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfService.h b/lib/tgchat/ext/td/td/telegram/TermsOfService.h index 43235774..929d3a07 100644 --- a/lib/tgchat/ext/td/td/telegram/TermsOfService.h +++ b/lib/tgchat/ext/td/td/telegram/TermsOfService.h @@ -7,21 +7,14 @@ #pragma once #include "td/telegram/MessageEntity.h" -#include "td/telegram/MessageEntity.hpp" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/utils/common.h" -#include "td/utils/Promise.h" #include "td/utils/Slice.h" -#include "td/utils/tl_helpers.h" - -#include namespace td { -class Td; - class TermsOfService { string id_; FormattedText text_; @@ -35,40 +28,13 @@ class TermsOfService { return id_; } - td_api::object_ptr get_terms_of_service_object() const { - if (id_.empty()) { - return nullptr; - } - - return td_api::make_object(get_formatted_text_object(nullptr, text_, true, -1), - min_user_age_, show_popup_); - } + td_api::object_ptr get_terms_of_service_object() const; template - void store(StorerT &storer) const { - using td::store; - BEGIN_STORE_FLAGS(); - STORE_FLAG(show_popup_); - END_STORE_FLAGS(); - store(id_, storer); - store(text_, storer); - store(min_user_age_, storer); - } + void store(StorerT &storer) const; template - void parse(ParserT &parser) { - using td::parse; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(show_popup_); - END_PARSE_FLAGS(); - parse(id_, parser); - parse(text_, parser); - parse(min_user_age_, parser); - } + void parse(ParserT &parser); }; -void get_terms_of_service(Td *td, Promise> promise); - -void accept_terms_of_service(Td *td, string &&terms_of_service_id, Promise &&promise); - } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfService.hpp b/lib/tgchat/ext/td/td/telegram/TermsOfService.hpp new file mode 100644 index 00000000..e106c39f --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/TermsOfService.hpp @@ -0,0 +1,40 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/TermsOfService.h" + +#include "td/telegram/MessageEntity.hpp" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void TermsOfService::store(StorerT &storer) const { + using td::store; + BEGIN_STORE_FLAGS(); + STORE_FLAG(show_popup_); + END_STORE_FLAGS(); + store(id_, storer); + store(text_, storer); + store(min_user_age_, storer); +} + +template +void TermsOfService::parse(ParserT &parser) { + using td::parse; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(show_popup_); + END_PARSE_FLAGS(); + parse(id_, parser); + parse(text_, parser); + parse(min_user_age_, parser); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.cpp b/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.cpp new file mode 100644 index 00000000..5e1a9123 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.cpp @@ -0,0 +1,201 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/TermsOfServiceManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/Global.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" +#include "td/utils/misc.h" +#include "td/utils/Random.h" +#include "td/utils/Status.h" + +namespace td { + +class GetTermsOfServiceUpdateQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetTermsOfServiceUpdateQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + // we don't poll terms of service before authorization + send_query(G()->net_query_creator().create(telegram_api::help_getTermsOfServiceUpdate())); + } + + 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(); + switch (result->get_id()) { + case telegram_api::help_termsOfServiceUpdateEmpty::ID: { + auto update = move_tl_object_as(result); + promise_.set_value(std::make_pair(update->expires_, TermsOfService())); + break; + } + case telegram_api::help_termsOfServiceUpdate::ID: { + auto update = move_tl_object_as(result); + promise_.set_value(std::make_pair(update->expires_, TermsOfService(std::move(update->terms_of_service_)))); + break; + } + default: + UNREACHABLE(); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class AcceptTermsOfServiceQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit AcceptTermsOfServiceQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &terms_of_service_id) { + send_query(G()->net_query_creator().create(telegram_api::help_acceptTermsOfService( + telegram_api::make_object(terms_of_service_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.ok(); + if (!result) { + LOG(ERROR) << "Failed to accept terms of service"; + } + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +TermsOfServiceManager::TermsOfServiceManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void TermsOfServiceManager::tear_down() { + parent_.reset(); +} + +void TermsOfServiceManager::start_up() { + init(); +} + +void TermsOfServiceManager::init() { + if (G()->close_flag()) { + return; + } + if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { + return; + } + is_inited_ = true; + + schedule_get_terms_of_service(0); +} + +void TermsOfServiceManager::schedule_get_terms_of_service(int32 expires_in) { + if (G()->close_flag() || !is_inited_) { + return; + } + + set_timeout_in(expires_in); +} + +void TermsOfServiceManager::timeout_expired() { + if (G()->close_flag() || !is_inited_) { + return; + } + + get_terms_of_service( + PromiseCreator::lambda([actor_id = actor_id(this)](Result> result) { + send_closure(actor_id, &TermsOfServiceManager::on_get_terms_of_service, std::move(result), false); + })); +} + +td_api::object_ptr TermsOfServiceManager::get_update_terms_of_service_object() const { + auto terms_of_service = pending_terms_of_service_.get_terms_of_service_object(); + if (terms_of_service == nullptr) { + return nullptr; + } + return td_api::make_object(pending_terms_of_service_.get_id().str(), + std::move(terms_of_service)); +} + +void TermsOfServiceManager::on_get_terms_of_service(Result> result, bool dummy) { + if (G()->close_flag()) { + return; + } + CHECK(is_inited_); + + int32 expires_in = 0; + if (result.is_error()) { + expires_in = Random::fast(10, 60); + } else { + auto terms = result.move_as_ok(); + pending_terms_of_service_ = std::move(terms.second); + auto update = get_update_terms_of_service_object(); + if (update == nullptr) { + expires_in = clamp(terms.first - G()->unix_time(), 3600, 86400); + } else { + send_closure(G()->td(), &Td::send_update, std::move(update)); + } + } + if (expires_in > 0) { + schedule_get_terms_of_service(expires_in); + } +} + +void TermsOfServiceManager::get_terms_of_service(Promise> promise) { + td_->create_handler(std::move(promise))->send(); +} + +void TermsOfServiceManager::accept_terms_of_service(string &&terms_of_service_id, Promise &&promise) { + 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, &TermsOfServiceManager::on_accept_terms_of_service, std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(terms_of_service_id); +} + +void TermsOfServiceManager::on_accept_terms_of_service(Promise &&promise) { + pending_terms_of_service_ = TermsOfService(); + promise.set_value(Unit()); + schedule_get_terms_of_service(0); +} + +void TermsOfServiceManager::get_current_state(vector> &updates) const { + if (!is_inited_) { + return; + } + + auto update_terms_of_service_object = get_update_terms_of_service_object(); + if (update_terms_of_service_object != nullptr) { + updates.push_back(std::move(update_terms_of_service_object)); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.h b/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.h new file mode 100644 index 00000000..aef96d8d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/TermsOfServiceManager.h @@ -0,0 +1,59 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 +// +// 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/TermsOfService.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +#include + +namespace td { + +class Td; + +class TermsOfServiceManager final : public Actor { + public: + TermsOfServiceManager(Td *td, ActorShared<> parent); + + void init(); + + void accept_terms_of_service(string &&terms_of_service_id, Promise &&promise); + + void get_current_state(vector> &updates) const; + + private: + void tear_down() final; + + void start_up() final; + + void timeout_expired() final; + + void schedule_get_terms_of_service(int32 expires_in); + + void get_terms_of_service(Promise> promise); + + td_api::object_ptr get_update_terms_of_service_object() const; + + void on_get_terms_of_service(Result> result, bool dummy); + + void on_accept_terms_of_service(Promise &&promise); + + Td *td_; + ActorShared<> parent_; + + TermsOfService pending_terms_of_service_; + + bool is_inited_ = false; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp index 4ab388a1..e9cd82dc 100644 --- a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp @@ -48,6 +48,7 @@ #include "td/telegram/NotificationManager.h" #include "td/telegram/NotificationSettingsManager.h" #include "td/telegram/NotificationSettingsScope.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/OrderInfo.h" #include "td/telegram/PeopleNearbyManager.h" @@ -2240,7 +2241,7 @@ void UpdatesManager::try_reload_data_static(void *td) { void UpdatesManager::try_reload_data() { if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || running_get_difference_ || - !td_->is_online()) { + !td_->online_manager_->is_online()) { return; } @@ -3011,7 +3012,7 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up update->invite_ != nullptr && update->invite_->get_id() == telegram_api::chatInvitePublicJoinRequests::ID; td_->dialog_participant_manager_->on_update_chat_participant( ChatId(update->chat_id_), UserId(update->actor_id_), update->date_, - DialogInviteLink(std::move(update->invite_), true, "updateChatParticipant"), via_join_request, + DialogInviteLink(std::move(update->invite_), true, true, "updateChatParticipant"), via_join_request, std::move(update->prev_participant_), std::move(update->new_participant_)); break; } @@ -3021,7 +3022,7 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up update->invite_ != nullptr && update->invite_->get_id() == telegram_api::chatInvitePublicJoinRequests::ID; td_->dialog_participant_manager_->on_update_channel_participant( ChannelId(update->channel_id_), UserId(update->actor_id_), update->date_, - DialogInviteLink(std::move(update->invite_), true, "updateChannelParticipant"), via_join_request, + DialogInviteLink(std::move(update->invite_), true, true, "updateChannelParticipant"), via_join_request, update->via_chatlist_, std::move(update->prev_participant_), std::move(update->new_participant_)); break; } @@ -3029,7 +3030,7 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up auto update = move_tl_object_as(update_ptr); td_->dialog_participant_manager_->on_update_chat_invite_requester( DialogId(update->peer_), UserId(update->user_id_), std::move(update->about_), update->date_, - DialogInviteLink(std::move(update->invite_), true, "updateBotChatInviteRequester")); + DialogInviteLink(std::move(update->invite_), true, true, "updateBotChatInviteRequester")); break; } case telegram_api::updateBotChatBoost::ID: { @@ -3058,8 +3059,8 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up td_api::make_object( td_->dialog_manager_->get_chat_id_object(dialog_id, "updateMessageReaction"), message_id.get(), get_message_sender_object(td_, actor_dialog_id, "updateMessageReaction"), date, - ReactionType::get_reaction_types_object(old_reaction_types), - ReactionType::get_reaction_types_object(new_reaction_types))); + ReactionType::get_reaction_types_object(old_reaction_types, false), + ReactionType::get_reaction_types_object(new_reaction_types, false))); break; } case telegram_api::updateBotMessageReactions::ID: { @@ -4582,8 +4583,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - send_closure(G()->td(), &Td::send_update, - td_api::make_object(StarManager::get_star_count(update->balance_, true))); + td_->star_manager_->on_update_owned_star_count(StarManager::get_star_count(update->balance_, true)); promise.set_value(Unit()); } diff --git a/lib/tgchat/ext/td/td/telegram/UserManager.cpp b/lib/tgchat/ext/td/td/telegram/UserManager.cpp index e15fd506..ad034f8e 100644 --- a/lib/tgchat/ext/td/td/telegram/UserManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/UserManager.cpp @@ -43,6 +43,7 @@ #include "td/telegram/misc.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/OnlineManager.h" #include "td/telegram/OptionManager.h" #include "td/telegram/PeerColor.h" #include "td/telegram/PeopleNearbyManager.h" @@ -1077,12 +1078,14 @@ class UpdateBirthdayQuery final : public Td::ResultHandler { class UpdatePersonalChannelQuery final : public Td::ResultHandler { Promise promise_; + ChannelId channel_id_; public: explicit UpdatePersonalChannelQuery(Promise &&promise) : promise_(std::move(promise)) { } void send(ChannelId channel_id) { + channel_id_ = channel_id; telegram_api::object_ptr input_channel; if (channel_id == ChannelId()) { input_channel = telegram_api::make_object(); @@ -1109,6 +1112,9 @@ class UpdatePersonalChannelQuery final : public Td::ResultHandler { } void on_error(Status status) final { + if (channel_id_.is_valid()) { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdatePersonalChannelQuery"); + } promise_.set_error(std::move(status)); } }; @@ -1678,6 +1684,7 @@ void UserManager::UserFull::store(StorerT &storer) const { bool has_birthdate = !birthdate.is_empty(); bool has_personal_channel_id = personal_channel_id.is_valid(); bool has_flags2 = true; + bool has_privacy_policy_url = !privacy_policy_url.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_about); STORE_FLAG(is_blocked); @@ -1713,6 +1720,7 @@ void UserManager::UserFull::store(StorerT &storer) const { if (has_flags2) { BEGIN_STORE_FLAGS(); STORE_FLAG(has_preview_medias); + STORE_FLAG(has_privacy_policy_url); END_STORE_FLAGS(); } if (has_about) { @@ -1766,6 +1774,9 @@ void UserManager::UserFull::store(StorerT &storer) const { if (has_personal_channel_id) { store(personal_channel_id, storer); } + if (has_privacy_policy_url) { + store(privacy_policy_url, storer); + } } template @@ -1788,6 +1799,7 @@ void UserManager::UserFull::parse(ParserT &parser) { bool has_birthdate; bool has_personal_channel_id; bool has_flags2; + bool has_privacy_policy_url = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_about); PARSE_FLAG(is_blocked); @@ -1823,6 +1835,7 @@ void UserManager::UserFull::parse(ParserT &parser) { if (has_flags2) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_preview_medias); + PARSE_FLAG(has_privacy_policy_url); END_PARSE_FLAGS(); } if (has_about) { @@ -1876,6 +1889,9 @@ void UserManager::UserFull::parse(ParserT &parser) { if (has_personal_channel_id) { parse(personal_channel_id, parser); } + if (has_privacy_policy_url) { + parse(privacy_policy_url, parser); + } } template @@ -2026,7 +2042,7 @@ UserManager::UserManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::m was_online_local_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_local")); was_online_remote_ = to_integer(G()->td_db()->get_binlog_pmc()->get("my_was_online_remote")); auto unix_time = G()->unix_time(); - if (was_online_local_ >= unix_time && !td_->is_online()) { + if (was_online_local_ >= unix_time && !td_->online_manager_->is_online()) { was_online_local_ = unix_time - 1; } } @@ -2220,7 +2236,7 @@ UserId UserManager::add_channel_bot_user() { UserManager::MyOnlineStatusInfo UserManager::get_my_online_status() const { MyOnlineStatusInfo status_info; - status_info.is_online_local = td_->is_online(); + status_info.is_online_local = td_->online_manager_->is_online(); status_info.is_online_remote = was_online_remote_ > G()->unix_time(); status_info.was_online_local = was_online_local_; status_info.was_online_remote = was_online_remote_; @@ -3147,7 +3163,7 @@ void UserManager::on_update_user_online(User *u, UserId user_id, u->is_online_status_changed = true; } if (is_offline) { - td_->on_online_updated(false, false); + td_->online_manager_->on_online_updated(false, false); } } else if (old_is_online != new_is_online) { u->is_online_status_changed = true; @@ -3271,13 +3287,8 @@ void UserManager::on_update_user_full_common_chat_count(UserFull *user_full, Use } } -void UserManager::on_update_user_location(UserId user_id, DialogLocation &&location) { - LOG(INFO) << "Receive " << location << " for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - +void UserManager::on_update_my_user_location(DialogLocation &&location) { + auto user_id = get_my_id(); UserFull *user_full = get_user_full_force(user_id, "on_update_user_location"); if (user_full == nullptr) { return; @@ -3293,13 +3304,8 @@ void UserManager::on_update_user_full_location(UserFull *user_full, UserId user_ } } -void UserManager::on_update_user_work_hours(UserId user_id, BusinessWorkHours &&work_hours) { - LOG(INFO) << "Receive " << work_hours << " for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - +void UserManager::on_update_my_user_work_hours(BusinessWorkHours &&work_hours) { + auto user_id = get_my_id(); UserFull *user_full = get_user_full_force(user_id, "on_update_user_work_hours"); if (user_full == nullptr) { return; @@ -3315,13 +3321,8 @@ void UserManager::on_update_user_full_work_hours(UserFull *user_full, UserId use } } -void UserManager::on_update_user_away_message(UserId user_id, BusinessAwayMessage &&away_message) { - LOG(INFO) << "Receive " << away_message << " for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - +void UserManager::on_update_my_user_away_message(BusinessAwayMessage &&away_message) { + auto user_id = get_my_id(); UserFull *user_full = get_user_full_force(user_id, "on_update_user_away_message"); if (user_full == nullptr) { return; @@ -3342,13 +3343,8 @@ void UserManager::on_update_user_full_away_message(UserFull *user_full, UserId u } } -void UserManager::on_update_user_greeting_message(UserId user_id, BusinessGreetingMessage &&greeting_message) { - LOG(INFO) << "Receive " << greeting_message << " for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - +void UserManager::on_update_my_user_greeting_message(BusinessGreetingMessage &&greeting_message) { + auto user_id = get_my_id(); UserFull *user_full = get_user_full_force(user_id, "on_update_user_greeting_message"); if (user_full == nullptr) { return; @@ -3369,13 +3365,8 @@ void UserManager::on_update_user_full_greeting_message(UserFull *user_full, User } } -void UserManager::on_update_user_intro(UserId user_id, BusinessIntro &&intro) { - LOG(INFO) << "Receive " << intro << " for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - +void UserManager::on_update_my_user_intro(BusinessIntro &&intro) { + auto user_id = get_my_id(); UserFull *user_full = get_user_full_force(user_id, "on_update_user_intro"); if (user_full == nullptr) { return; @@ -5254,6 +5245,7 @@ void UserManager::set_personal_channel(DialogId dialog_id, Promise &&promi if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return promise.set_error(Status::Error(400, "Chat can't be set as a personal chat")); } + channel_id = dialog_id.get_channel_id(); } auto query_promise = PromiseCreator::lambda( [actor_id = actor_id(this), channel_id, promise = std::move(promise)](Result result) mutable { @@ -6964,6 +6956,11 @@ void UserManager::on_get_user_full(telegram_api::object_ptrbot_info_->commands_)); on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_)); on_update_user_full_has_preview_medias(user_full, user_id, std::move(user->bot_info_->has_preview_medias_)); + + if (user_full->privacy_policy_url != user->bot_info_->privacy_policy_url_) { + user_full->privacy_policy_url = std::move(user->bot_info_->privacy_policy_url_); + user_full->is_changed = true; + } } if (user_full->description != description) { user_full->description = std::move(description); @@ -7242,6 +7239,7 @@ void UserManager::drop_user_full(UserId user_id) { user_full->birthdate = {}; user_full->sponsored_enabled = false; user_full->has_preview_medias = false; + user_full->privacy_policy_url = string(); user_full->is_changed = true; update_user_full(user_full, user_id, "drop_user_full"); @@ -7974,7 +7972,7 @@ td_api::object_ptr UserManager::get_user_full_info_object( user_full->about, user_full->description, get_photo_object(td_->file_manager_.get(), user_full->description_photo), td_->animations_manager_->get_animation_object(user_full->description_animation_file_id), - std::move(menu_button), std::move(commands), + std::move(menu_button), std::move(commands), user_full->privacy_policy_url, user_full->group_administrator_rights == AdministratorRights() ? nullptr : user_full->group_administrator_rights.get_chat_administrator_rights_object(), diff --git a/lib/tgchat/ext/td/td/telegram/UserManager.h b/lib/tgchat/ext/td/td/telegram/UserManager.h index 4273f719..69482185 100644 --- a/lib/tgchat/ext/td/td/telegram/UserManager.h +++ b/lib/tgchat/ext/td/td/telegram/UserManager.h @@ -138,15 +138,15 @@ class UserManager final : public Actor { void on_update_user_common_chat_count(UserId user_id, int32 common_chat_count); - void on_update_user_location(UserId user_id, DialogLocation &&location); + void on_update_my_user_location(DialogLocation &&location); - void on_update_user_work_hours(UserId user_id, BusinessWorkHours &&work_hours); + void on_update_my_user_work_hours(BusinessWorkHours &&work_hours); - void on_update_user_away_message(UserId user_id, BusinessAwayMessage &&away_message); + void on_update_my_user_away_message(BusinessAwayMessage &&away_message); - void on_update_user_greeting_message(UserId user_id, BusinessGreetingMessage &&greeting_message); + void on_update_my_user_greeting_message(BusinessGreetingMessage &&greeting_message); - void on_update_user_intro(UserId user_id, BusinessIntro &&intro); + void on_update_my_user_intro(BusinessIntro &&intro); void on_update_user_commands(UserId user_id, vector> &&bot_commands); @@ -587,6 +587,7 @@ class UserManager final : public Actor { unique_ptr menu_button; vector commands; + string privacy_policy_url; AdministratorRights group_administrator_rights; AdministratorRights broadcast_administrator_rights; diff --git a/lib/tgchat/ext/td/td/telegram/Version.h b/lib/tgchat/ext/td/td/telegram/Version.h index e89da052..de2fbe96 100644 --- a/lib/tgchat/ext/td/td/telegram/Version.h +++ b/lib/tgchat/ext/td/td/telegram/Version.h @@ -10,7 +10,7 @@ namespace td { -constexpr int32 MTPROTO_LAYER = 185; +constexpr int32 MTPROTO_LAYER = 186; enum class Version : int32 { Initial, // 0 diff --git a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp index 50cb3fb2..9d18781e 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp @@ -2491,4 +2491,19 @@ vector> get_page_blocks_object( return get_page_blocks_object(page_blocks, &context); } +bool WebPageBlock::are_allowed_album_block_types(const vector> &page_blocks) { + for (const auto &block : page_blocks) { + switch (block->get_type()) { + case Type::Title: + case Type::AuthorDate: + case Type::Collage: + case Type::Slideshow: + continue; + default: + return false; + } + } + return true; +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/WebPageBlock.h b/lib/tgchat/ext/td/td/telegram/WebPageBlock.h index f8574da0..061670ac 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPageBlock.h +++ b/lib/tgchat/ext/td/td/telegram/WebPageBlock.h @@ -87,6 +87,8 @@ class WebPageBlock { virtual void append_file_ids(const Td *td, vector &file_ids) const = 0; virtual td_api::object_ptr get_page_block_object(Context *context) const = 0; + + static bool are_allowed_album_block_types(const vector> &page_blocks); }; void store(const unique_ptr &block, LogEventStorerCalcLength &storer); diff --git a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp index 66873155..ccb16dde 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp @@ -21,6 +21,7 @@ #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/Global.h" +#include "td/telegram/LinkManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessagesManager.h" @@ -245,6 +246,8 @@ class WebPagesManager::WebPage { int32 duration_ = 0; string author_; bool has_large_media_ = false; + mutable bool is_album_ = false; + mutable bool is_album_checked_ = false; Document document_; vector documents_; ThemeSettings theme_settings_; @@ -1347,76 +1350,121 @@ bool WebPagesManager::have_web_page(WebPageId web_page_id) const { return get_web_page(web_page_id) != nullptr; } -td_api::object_ptr WebPagesManager::get_link_preview_type_object( - const WebPage *web_page) const { - if (begins_with(web_page->type_, "telegram_")) { - Slice type = Slice(web_page->type_).substr(9); - if (type == "album") { - if (web_page->instant_view_.is_empty_ || !web_page->instant_view_.is_loaded_) { - LOG(ERROR) << "Have no instant view in Telegram album for " << web_page->url_; - return td_api::make_object(); +bool WebPagesManager::can_web_page_be_album(const WebPage *web_page) { + if (web_page->type_ == "telegram_album") { + return true; + } + auto site_name = to_lower(web_page->site_name_); + return site_name == "instagram" || site_name == "twitter" || site_name == "x"; +} + +bool WebPagesManager::is_web_page_album(const WebPage *web_page) { + if (!web_page->is_album_checked_) { + web_page->is_album_checked_ = true; + if (web_page->type_ == "telegram_album") { + web_page->is_album_ = true; + } else if (can_web_page_be_album(web_page) && !web_page->instant_view_.is_empty_) { + if (!web_page->instant_view_.is_loaded_) { + LOG(ERROR) << "Have no instant view for " << web_page->url_; + } else { + web_page->is_album_ = WebPageBlock::are_allowed_album_block_types(web_page->instant_view_.page_blocks_); } - vector> media; - string caption; - for (auto &block_object : get_page_blocks_object(web_page->instant_view_.page_blocks_, td_, Slice(), Slice())) { - switch (block_object->get_id()) { - case td_api::pageBlockTitle::ID: - case td_api::pageBlockAuthorDate::ID: - continue; - case td_api::pageBlockCollage::ID: { - auto *collage = static_cast(block_object.get()); - for (auto &block : collage->page_blocks_) { - switch (block->get_id()) { - case td_api::pageBlockPhoto::ID: { - auto photo = std::move(static_cast(block.get())->photo_); - if (photo == nullptr) { - LOG(ERROR) << "Receive pageBlockPhoto without photo"; - } else { - media.push_back(td_api::make_object(std::move(photo))); - } - break; - } - case td_api::pageBlockVideo::ID: { - auto video = std::move(static_cast(block.get())->video_); - if (video == nullptr) { - LOG(ERROR) << "Receive pageBlockVideo without video"; - } else { - media.push_back(td_api::make_object(std::move(video))); - } - break; - } - default: - LOG(ERROR) << "Receive " << to_string(block_object); - break; - } - } - if (collage->caption_->text_->get_id() == td_api::richTextPlain::ID) { - caption = std::move(static_cast(collage->caption_->text_.get())->text_); - } else { - LOG(ERROR) << "Receive instead of caption text: " << to_string(collage->caption_->text_); - } - break; + } + } + return web_page->is_album_; +} + +td_api::object_ptr WebPagesManager::get_link_preview_type_album_object( + const WebPageInstantView &instant_view) const { + if (instant_view.is_empty_ || !instant_view.is_loaded_) { + LOG(ERROR) << "Have no instant view in Telegram album for " << instant_view.url_; + return td_api::make_object(); + } + vector> media; + string caption_text; + auto process_album = [&media, &caption_text](vector> page_blocks, + td_api::object_ptr caption) { + for (auto &block : page_blocks) { + switch (block->get_id()) { + case td_api::pageBlockPhoto::ID: { + auto photo = std::move(static_cast(block.get())->photo_); + if (photo == nullptr) { + LOG(ERROR) << "Receive pageBlockPhoto without photo"; + } else { + media.push_back(td_api::make_object(std::move(photo))); } - default: - LOG(ERROR) << "Receive " << to_string(block_object); - break; + break; + } + case td_api::pageBlockVideo::ID: { + auto video = std::move(static_cast(block.get())->video_); + if (video == nullptr) { + LOG(ERROR) << "Receive pageBlockVideo without video"; + } else { + media.push_back(td_api::make_object(std::move(video))); + } + break; } + default: + LOG(ERROR) << "Receive " << to_string(block); + break; } - if (!media.empty()) { - return td_api::make_object(std::move(media), caption); + } + if (caption != nullptr && caption->text_ != nullptr && caption->text_->get_id() == td_api::richTextPlain::ID) { + caption_text = std::move(static_cast(caption->text_.get())->text_); + } else { + LOG(ERROR) << "Receive instead of caption text: " << to_string(caption); + } + }; + + for (auto &block_object : get_page_blocks_object(instant_view.page_blocks_, td_, Slice(), Slice())) { + switch (block_object->get_id()) { + case td_api::pageBlockTitle::ID: + case td_api::pageBlockAuthorDate::ID: + break; + case td_api::pageBlockCollage::ID: { + auto *collage = static_cast(block_object.get()); + process_album(std::move(collage->page_blocks_), std::move(collage->caption_)); + break; } - LOG(ERROR) << "Have no media in Telegram album for " << web_page->url_; - return td_api::make_object(); + case td_api::pageBlockSlideshow::ID: { + auto *collage = static_cast(block_object.get()); + process_album(std::move(collage->page_blocks_), std::move(collage->caption_)); + break; + } + default: + LOG(ERROR) << "Receive " << to_string(block_object); + break; } + } + if (!media.empty()) { + return td_api::make_object(std::move(media), caption_text); + } + LOG(ERROR) << "Have no media in Telegram album for " << instant_view.url_; + return td_api::make_object(); +} + +td_api::object_ptr WebPagesManager::get_link_preview_type_object( + const WebPage *web_page) const { + if (is_web_page_album(web_page)) { + return get_link_preview_type_album_object(web_page->instant_view_); + } + if (begins_with(web_page->type_, "telegram_")) { + Slice type = Slice(web_page->type_).substr(9); if (type == "background") { LOG_IF(ERROR, !web_page->photo_.is_empty()) << "Receive photo for " << web_page->url_; LOG_IF(ERROR, web_page->document_.type != Document::Type::Unknown && web_page->document_.type != Document::Type::General) << "Receive wrong document for " << web_page->url_; + bool is_pattern = false; + if (web_page->document_.type == Document::Type::General) { + auto mime_type = td_->documents_manager_->get_document_mime_type(web_page->document_.file_id); + is_pattern = mime_type == "image/png" || mime_type == "application/x-tgwallpattern"; + } return td_api::make_object( web_page->document_.type == Document::Type::General ? td_->documents_manager_->get_document_object(web_page->document_.file_id, PhotoFormat::Png) - : nullptr); + : nullptr, + LinkManager::get_background_type_object(web_page->url_, is_pattern)); } if (type == "bot") { LOG_IF(ERROR, web_page->document_.type != Document::Type::Unknown) @@ -1723,7 +1771,7 @@ td_api::object_ptr WebPagesManager::get_link_preview_object return nullptr; } int32 instant_view_version = [web_page] { - if (web_page->instant_view_.is_empty_ || web_page->type_ == "telegram_album") { + if (web_page->instant_view_.is_empty_ || is_web_page_album(web_page)) { return 0; } if (web_page->instant_view_.is_v2_) { @@ -2292,7 +2340,7 @@ void WebPagesManager::on_load_web_page_from_database(WebPageId web_page_id, stri update_web_page(std::move(result), web_page_id, true, true); const WebPage *web_page = get_web_page(web_page_id); - if (web_page != nullptr && web_page->type_ == "telegram_album" && !web_page->instant_view_.is_empty_ && + if (web_page != nullptr && can_web_page_be_album(web_page) && !web_page->instant_view_.is_empty_ && !web_page->instant_view_.is_loaded_) { LOG(INFO) << "Forcely load instant view of " << web_page_id; on_load_web_page_instant_view_from_database( diff --git a/lib/tgchat/ext/td/td/telegram/WebPagesManager.h b/lib/tgchat/ext/td/td/telegram/WebPagesManager.h index 8b477885..df4c85b2 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPagesManager.h +++ b/lib/tgchat/ext/td/td/telegram/WebPagesManager.h @@ -138,6 +138,9 @@ class WebPagesManager final : public Actor { void get_web_page_instant_view_impl(WebPageId web_page_id, bool force_full, Promise &&promise); + td_api::object_ptr get_link_preview_type_album_object( + const WebPageInstantView &instant_view) const; + td_api::object_ptr get_link_preview_type_object(const WebPage *web_page) const; td_api::object_ptr get_web_page_instant_view_object( @@ -193,6 +196,10 @@ class WebPagesManager final : public Actor { vector get_web_page_file_ids(const WebPage *web_page) const; + static bool can_web_page_be_album(const WebPage *web_page); + + static bool is_web_page_album(const WebPage *web_page); + Td *td_; ActorShared<> parent_; WaitFreeHashMap, WebPageIdHash> web_pages_; diff --git a/lib/tgchat/ext/td/td/telegram/cli.cpp b/lib/tgchat/ext/td/td/telegram/cli.cpp index 2735a813..1ea91980 100644 --- a/lib/tgchat/ext/td/td/telegram/cli.cpp +++ b/lib/tgchat/ext/td/td/telegram/cli.cpp @@ -458,7 +458,7 @@ class CliClient final : public Actor { static char get_delimiter(Slice str) { FlatHashSet chars; for (auto c : trim(str)) { - if (!is_alnum(c) && c != '_' && c != '-' && c != '@' && c != '.' && c != '/' && c != '\0' && + if (!is_alnum(c) && c != '_' && c != '-' && c != '@' && c != '.' && c != '/' && c != '\0' && c != '$' && static_cast(c) <= 127) { chars.insert(c); } @@ -693,6 +693,10 @@ class CliClient final : public Actor { return to_integer(trim(str)); } + static vector as_file_ids(Slice str) { + return transform(autosplit(str), as_file_id); + } + static td_api::object_ptr as_input_file_id(Slice str) { return td_api::make_object(as_file_id(str)); } @@ -788,6 +792,9 @@ class CliClient final : public Actor { if (type.empty()) { return nullptr; } + if (type == "$") { + return td_api::make_object(); + } auto r_custom_emoji_id = to_integer_safe(type); if (r_custom_emoji_id.is_ok()) { return td_api::make_object(r_custom_emoji_id.ok()); @@ -1011,12 +1018,16 @@ class CliClient final : public Actor { int64 chat_id = 0; int64 message_id = 0; string invoice_name; + string invite_link; operator td_api::object_ptr() const { - if (invoice_name.empty()) { - return td_api::make_object(chat_id, message_id); - } else { + if (!invite_link.empty()) { + return td_api::make_object( + td_api::make_object(invite_link)); + } else if (!invoice_name.empty()) { return td_api::make_object(invoice_name); + } else { + return td_api::make_object(chat_id, message_id); } } }; @@ -1024,6 +1035,8 @@ class CliClient final : public Actor { void get_args(string &args, InputInvoice &arg) const { if (args.size() > 1 && (args[0] == '#' || args[0] == '$')) { arg.invoice_name = args.substr(1); + } else if (args[0] == '+' || begins_with(args, "https://t.me/+")) { + arg.invite_link = args; } else { string chat_id; string message_id; @@ -2017,6 +2030,8 @@ class CliClient final : public Actor { return td_api::make_object(); } else if (category == "inline") { return td_api::make_object(); + } else if (category == "app") { + return td_api::make_object(); } else if (category == "call") { return td_api::make_object(); } else if (category == "forward") { @@ -2762,10 +2777,6 @@ class CliClient final : public Actor { send_request(td_api::make_object()); } else if (op == "dsc") { send_request(td_api::make_object()); - // } else if (op == "stlsr") { - // send_request(td_api::make_object()); - // } else if (op == "gtwps") { - // send_request(td_api::make_object()); } else if (op == "rsp") { UserId user_id; string telegram_payment_charge_id; @@ -2959,6 +2970,24 @@ class CliClient final : public Actor { auto reaction_types = transform(autosplit_str(reactions), as_reaction_type); send_request(td_api::make_object(chat_id, message_id, std::move(reaction_types), op == "reactbotbig")); + } else if (op == "apmr" || op == "apmra") { + ChatId chat_id; + MessageId message_id; + int64 star_count; + get_args(args, chat_id, message_id, star_count); + send_request(td_api::make_object(chat_id, message_id, star_count, op == "apmra")); + } else if (op == "rppmr") { + ChatId chat_id; + MessageId message_id; + get_args(args, chat_id, message_id); + send_request(td_api::make_object(chat_id, message_id)); + } else if (op == "tpmria") { + ChatId chat_id; + MessageId message_id; + bool is_anonymous; + get_args(args, chat_id, message_id, is_anonymous); + send_request( + td_api::make_object(chat_id, message_id, is_anonymous)); } else if (op == "gmars") { ChatId chat_id; MessageId message_id; @@ -3441,17 +3470,32 @@ class CliClient final : public Actor { send_request(td_api::make_object(user_id)); } else if (op == "gsta" || op == "gsti" || op == "gsto") { string owner_id; + string subscription_id; string offset; string limit; - get_args(args, owner_id, offset, limit); + get_args(args, owner_id, subscription_id, offset, limit); td_api::object_ptr direction; if (op == "gsti") { direction = td_api::make_object(); } else if (op == "gsto") { direction = td_api::make_object(); } - send_request(td_api::make_object(as_message_sender(owner_id), std::move(direction), - offset, as_limit(limit))); + send_request(td_api::make_object(as_message_sender(owner_id), subscription_id, + std::move(direction), offset, as_limit(limit))); + } else if (op == "gssu") { + bool only_expiring; + string offset; + get_args(args, only_expiring, offset); + send_request(td_api::make_object(only_expiring, offset)); + } else if (op == "ess") { + string subscription_id; + bool is_canceled; + get_args(args, subscription_id, is_canceled); + send_request(td_api::make_object(subscription_id, is_canceled)); + } else if (op == "rss") { + string subscription_id; + get_args(args, subscription_id); + send_request(td_api::make_object(subscription_id)); } else if (op == "cpfs" || op == "cpfsb") { UserId user_id; string currency; @@ -4335,6 +4379,14 @@ class CliClient final : public Actor { get_args(args, chat_id, name, expiration_date, member_limit, creates_join_request); send_request(td_api::make_object(chat_id, name, expiration_date, member_limit, creates_join_request)); + } else if (op == "ccsil") { + ChatId chat_id; + string name; + int32 period; + int64 star_count; + get_args(args, chat_id, name, period, star_count); + send_request(td_api::make_object( + chat_id, name, td_api::make_object(period, star_count))); } else if (op == "ecil") { ChatId chat_id; string invite_link; @@ -4345,6 +4397,12 @@ class CliClient final : public Actor { get_args(args, chat_id, invite_link, name, expiration_date, member_limit, creates_join_request); send_request(td_api::make_object(chat_id, invite_link, name, expiration_date, member_limit, creates_join_request)); + } else if (op == "ecsil") { + ChatId chat_id; + string invite_link; + string name; + get_args(args, chat_id, invite_link, name); + send_request(td_api::make_object(chat_id, invite_link, name)); } else if (op == "rcil") { ChatId chat_id; string invite_link; @@ -4368,7 +4426,7 @@ class CliClient final : public Actor { get_args(args, chat_id, creator_user_id, offset_date, offset_invite_link, limit); send_request(td_api::make_object(chat_id, creator_user_id, op == "gcilr", offset_date, offset_invite_link, as_limit(limit))); - } else if (op == "gcilm") { + } else if (op == "gcilm" || op == "gcilme") { ChatId chat_id; string invite_link; UserId offset_user_id; @@ -4376,7 +4434,7 @@ class CliClient final : public Actor { string limit; get_args(args, chat_id, invite_link, offset_user_id, offset_date, limit); send_request(td_api::make_object( - chat_id, invite_link, + chat_id, invite_link, op == "gcilme", td_api::make_object(offset_user_id, offset_date, false, 0), as_limit(limit))); } else if (op == "gcjr") { ChatId chat_id; @@ -4635,8 +4693,7 @@ class CliClient final : public Actor { get_args(args, chat_id, photo, caption, rules, areas, active_period, sticker_file_ids, protect_content); send_request(td_api::make_object( chat_id, - td_api::make_object(as_input_file(photo), - to_integers(sticker_file_ids)), + td_api::make_object(as_input_file(photo), as_file_ids(sticker_file_ids)), areas, as_caption(caption), rules, active_period ? active_period : 86400, get_reposted_story_full_id(), op == "sspp", protect_content)); } else if (op == "ssv" || op == "ssvp") { @@ -4652,8 +4709,8 @@ class CliClient final : public Actor { get_args(args, chat_id, video, caption, rules, areas, active_period, duration, sticker_file_ids, protect_content); send_request(td_api::make_object( chat_id, - td_api::make_object( - as_input_file(video), to_integers(sticker_file_ids), duration, 0.5, true), + td_api::make_object(as_input_file(video), as_file_ids(sticker_file_ids), + duration, 0.5, true), areas, as_caption(caption), rules, active_period ? active_period : 86400, get_reposted_story_full_id(), op == "ssvp", protect_content)); } else if (op == "esc") { @@ -4672,11 +4729,10 @@ class CliClient final : public Actor { InputStoryAreas areas; string sticker_file_ids; get_args(args, story_sender_chat_id, story_id, photo, caption, areas, sticker_file_ids); - send_request( - td_api::make_object(story_sender_chat_id, story_id, - td_api::make_object( - as_input_file(photo), to_integers(sticker_file_ids)), - areas, as_caption(caption))); + send_request(td_api::make_object( + story_sender_chat_id, story_id, + td_api::make_object(as_input_file(photo), as_file_ids(sticker_file_ids)), + areas, as_caption(caption))); } else if (op == "esv") { ChatId story_sender_chat_id; StoryId story_id; @@ -4688,8 +4744,8 @@ class CliClient final : public Actor { get_args(args, story_sender_chat_id, story_id, video, caption, duration, sticker_file_ids); send_request(td_api::make_object( story_sender_chat_id, story_id, - td_api::make_object( - as_input_file(video), to_integers(sticker_file_ids), duration, 0.0, false), + td_api::make_object(as_input_file(video), as_file_ids(sticker_file_ids), + duration, 0.0, false), areas, as_caption(caption))); } else if (op == "esco") { ChatId story_sender_chat_id; @@ -5319,8 +5375,6 @@ class CliClient final : public Actor { MessageThreadId message_thread_id; get_args(args, chat_id, message_thread_id); send_request(td_api::make_object(chat_id, message_thread_id)); - } else if (op == "gallm") { - send_request(td_api::make_object()); } else if (op == "sbsm") { UserId bot_user_id; ChatId chat_id; @@ -5575,10 +5629,9 @@ class CliClient final : public Actor { string caption; string sticker_file_ids; get_args(args, chat_id, photo, caption, sticker_file_ids); - send_message(chat_id, - td_api::make_object( - as_input_file(photo), nullptr, to_integers(sticker_file_ids), 0, 0, as_caption(caption), - show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, td_api::make_object( + as_input_file(photo), nullptr, as_file_ids(sticker_file_ids), 0, 0, as_caption(caption), + show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "spg") { ChatId chat_id; string photo_path; @@ -5663,7 +5716,7 @@ class CliClient final : public Actor { if (trim(video_path).empty()) { video_path = sticker_file_ids_str; } else { - sticker_file_ids = to_integers(sticker_file_ids_str); + sticker_file_ids = as_file_ids(sticker_file_ids_str); } send_message(chat_id, td_api::make_object( @@ -6297,9 +6350,10 @@ class CliClient final : public Actor { } else if (op == "tsgsm") { string supergroup_id; bool sign_messages; - get_args(args, supergroup_id, sign_messages); - send_request( - td_api::make_object(as_supergroup_id(supergroup_id), sign_messages)); + bool show_message_sender; + get_args(args, supergroup_id, sign_messages, show_message_sender); + send_request(td_api::make_object(as_supergroup_id(supergroup_id), + sign_messages, show_message_sender)); } else if (op == "tsgjtsm") { string supergroup_id; bool join_to_send_message; @@ -6629,15 +6683,15 @@ class CliClient final : public Actor { string language_code; string file_ids; get_args(args, bot_user_id, language_code, file_ids); - send_request(td_api::make_object(bot_user_id, language_code, - to_integers(file_ids))); + send_request( + td_api::make_object(bot_user_id, language_code, as_file_ids(file_ids))); } else if (op == "dbmp") { UserId bot_user_id; string language_code; string file_ids; get_args(args, bot_user_id, language_code, file_ids); - send_request(td_api::make_object(bot_user_id, language_code, - to_integers(file_ids))); + send_request( + td_api::make_object(bot_user_id, language_code, as_file_ids(file_ids))); } else if (op == "gbi") { UserId bot_user_id; string language_code; diff --git a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp index 5d0473cb..bc33ef27 100644 --- a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.cpp @@ -14,13 +14,16 @@ #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/net/NetType.h" +#include "td/telegram/net/PublicRsaKeySharedMain.h" +#include "td/telegram/PromoDataManager.h" #include "td/telegram/StateManager.h" -#include "td/telegram/Td.h" #include "td/telegram/TdDb.h" +#include "td/mtproto/DhCallback.h" +#include "td/mtproto/HandshakeActor.h" #include "td/mtproto/Ping.h" #include "td/mtproto/ProxySecret.h" -#include "td/mtproto/RawConnection.h" +#include "td/mtproto/RSA.h" #include "td/mtproto/TlsInit.h" #include "td/net/GetHostByNameActor.h" @@ -28,12 +31,13 @@ #include "td/net/Socks5.h" #include "td/net/TransparentProxy.h" +#include "td/actor/SleepActor.h" + #include "td/utils/algorithm.h" #include "td/utils/base64.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" -#include "td/utils/port/IPAddress.h" #include "td/utils/Random.h" #include "td/utils/ScopeGuard.h" #include "td/utils/SliceBuilder.h" @@ -386,6 +390,138 @@ void ConnectionCreator::ping_proxy_buffered_socket_fd(IPAddress ip_address, Buff create_reference(token))}; } +void ConnectionCreator::test_proxy(Proxy &&proxy, int32 dc_id, double timeout, Promise &&promise) { + auto start_time = Time::now(); + + IPAddress ip_address; + auto status = ip_address.init_host_port(proxy.server(), proxy.port()); + if (status.is_error()) { + return promise.set_error(Status::Error(400, status.public_message())); + } + auto r_socket_fd = SocketFd::open(ip_address); + if (r_socket_fd.is_error()) { + return promise.set_error(Status::Error(400, r_socket_fd.error().public_message())); + } + + auto dc_options = get_default_dc_options(false); + IPAddress mtproto_ip_address; + for (auto &dc_option : dc_options.dc_options) { + if (dc_option.get_dc_id().get_raw_id() == dc_id) { + mtproto_ip_address = dc_option.get_ip_address(); + break; + } + } + if (!mtproto_ip_address.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid datacenter identifier specified")); + } + + auto request_id = ++test_proxy_request_id_; + auto request = make_unique(); + request->proxy_ = std::move(proxy); + request->dc_id_ = static_cast(dc_id); + request->promise_ = std::move(promise); + + auto connection_promise = + PromiseCreator::lambda([actor_id = actor_id(this), request_id](Result r_data) { + send_closure(actor_id, &ConnectionCreator::on_test_proxy_connection_data, request_id, std::move(r_data)); + }); + request->child_ = prepare_connection(ip_address, r_socket_fd.move_as_ok(), request->proxy_, mtproto_ip_address, + request->get_transport(), "Test", "TestPingDC2", nullptr, {}, false, + std::move(connection_promise)); + + test_proxy_requests_.emplace(request_id, std::move(request)); + + create_actor("TestProxyTimeoutActor", timeout + start_time - Time::now(), + PromiseCreator::lambda([actor_id = actor_id(this), request_id](Result result) { + send_closure(actor_id, &ConnectionCreator::on_test_proxy_timeout, request_id); + })) + .release(); +} + +void ConnectionCreator::on_test_proxy_connection_data(uint64 request_id, Result r_data) { + auto it = test_proxy_requests_.find(request_id); + if (it == test_proxy_requests_.end()) { + return; + } + auto *request = it->second.get(); + if (r_data.is_error()) { + auto promise = std::move(request->promise_); + test_proxy_requests_.erase(it); + return promise.set_error(r_data.move_as_error()); + } + + class HandshakeContext final : public mtproto::AuthKeyHandshakeContext { + public: + mtproto::DhCallback *get_dh_callback() final { + return nullptr; + } + mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final { + return public_rsa_key_.get(); + } + + private: + std::shared_ptr public_rsa_key_ = PublicRsaKeySharedMain::create(false); + }; + auto handshake = make_unique(request->dc_id_, 3600); + auto data = r_data.move_as_ok(); + auto raw_connection = mtproto::RawConnection::create(data.ip_address, std::move(data.buffered_socket_fd), + request->get_transport(), nullptr); + request->child_ = create_actor( + "HandshakeActor", std::move(handshake), std::move(raw_connection), make_unique(), 10.0, + PromiseCreator::lambda( + [actor_id = actor_id(this), request_id](Result> raw_connection) { + send_closure(actor_id, &ConnectionCreator::on_test_proxy_handshake_connection, request_id, + std::move(raw_connection)); + }), + PromiseCreator::lambda( + [actor_id = actor_id(this), request_id](Result> handshake) { + send_closure(actor_id, &ConnectionCreator::on_test_proxy_handshake, request_id, std::move(handshake)); + })); +} + +void ConnectionCreator::on_test_proxy_handshake_connection( + uint64 request_id, Result> r_raw_connection) { + if (r_raw_connection.is_error()) { + auto it = test_proxy_requests_.find(request_id); + if (it == test_proxy_requests_.end()) { + return; + } + auto promise = std::move(it->second->promise_); + test_proxy_requests_.erase(it); + return promise.set_error(Status::Error(400, r_raw_connection.move_as_error().public_message())); + } +} + +void ConnectionCreator::on_test_proxy_handshake(uint64 request_id, + Result> r_handshake) { + auto it = test_proxy_requests_.find(request_id); + if (it == test_proxy_requests_.end()) { + return; + } + auto promise = std::move(it->second->promise_); + test_proxy_requests_.erase(it); + + if (r_handshake.is_error()) { + return promise.set_error(Status::Error(400, r_handshake.move_as_error().public_message())); + } + auto handshake = r_handshake.move_as_ok(); + if (!handshake->is_ready_for_finish()) { + return promise.set_error(Status::Error(400, "Handshake is not ready")); + } + promise.set_value(Unit()); +} + +void ConnectionCreator::on_test_proxy_timeout(uint64 request_id) { + auto it = test_proxy_requests_.find(request_id); + if (it == test_proxy_requests_.end()) { + return; + } + auto promise = std::move(it->second->promise_); + test_proxy_requests_.erase(it); + + promise.set_error(Status::Error(400, "Timeout expired")); +} + void ConnectionCreator::set_active_proxy_id(int32 proxy_id, bool from_binlog) { active_proxy_id_ = proxy_id; if (proxy_id == 0) { @@ -423,7 +559,7 @@ void ConnectionCreator::enable_proxy_impl(int32 proxy_id) { void ConnectionCreator::disable_proxy_impl() { if (active_proxy_id_ == 0) { send_closure(G()->messages_manager(), &MessagesManager::remove_sponsored_dialog); - send_closure(G()->td(), &Td::reload_promo_data); + send_closure(G()->promo_data_manager(), &PromoDataManager::reload_promo_data); return; } CHECK(proxies_.count(active_proxy_id_) == 1); @@ -458,7 +594,7 @@ void ConnectionCreator::on_proxy_changed(bool from_db) { if (active_proxy_id_ == 0 || !from_db) { send_closure(G()->messages_manager(), &MessagesManager::remove_sponsored_dialog); } - send_closure(G()->td(), &Td::reload_promo_data); + send_closure(G()->promo_data_manager(), &PromoDataManager::reload_promo_data); loop(); } diff --git a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h index d43bd92f..37b7447f 100644 --- a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h +++ b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h @@ -15,6 +15,7 @@ #include "td/mtproto/AuthData.h" #include "td/mtproto/ConnectionManager.h" +#include "td/mtproto/Handshake.h" #include "td/mtproto/RawConnection.h" #include "td/mtproto/TransportType.h" @@ -79,22 +80,7 @@ class ConnectionCreator final : public NetQueryCallback { void get_proxy_link(int32 proxy_id, Promise promise); void ping_proxy(int32 proxy_id, Promise promise); - struct ConnectionData { - IPAddress ip_address; - BufferedFd buffered_socket_fd; - mtproto::ConnectionManager::ConnectionToken connection_token; - unique_ptr stats_callback; - }; - - static DcOptions get_default_dc_options(bool is_test); - - static ActorOwn<> prepare_connection(IPAddress ip_address, SocketFd socket_fd, const Proxy &proxy, - const IPAddress &mtproto_ip_address, - const mtproto::TransportType &transport_type, Slice actor_name_prefix, - Slice debug_str, - unique_ptr stats_callback, - ActorShared<> parent, bool use_connection_token, - Promise promise); + void test_proxy(Proxy &&proxy, int32 dc_id, double timeout, Promise &&promise); private: ActorShared<> parent_; @@ -185,6 +171,26 @@ class ConnectionCreator final : public NetQueryCallback { }; std::map ping_main_dc_requests_; + struct ConnectionData { + IPAddress ip_address; + BufferedFd buffered_socket_fd; + mtproto::ConnectionManager::ConnectionToken connection_token; + unique_ptr stats_callback; + }; + + struct TestProxyRequest { + Proxy proxy_; + int16 dc_id_; + ActorOwn<> child_; + Promise promise_; + + mtproto::TransportType get_transport() const { + return mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, dc_id_, proxy_.secret()}; + } + }; + uint64 test_proxy_request_id_ = 0; + FlatHashMap> test_proxy_requests_; + uint64 next_token() { return ++current_token_; } @@ -237,12 +243,21 @@ class ConnectionCreator final : public NetQueryCallback { IPAddress mtproto_ip_address; bool check_mode{false}; }; + Result find_connection(const Proxy &proxy, const IPAddress &proxy_ip_address, DcId dc_id, + bool allow_media_only, FindConnectionExtra &extra); + + static DcOptions get_default_dc_options(bool is_test); static Result get_transport_type(const Proxy &proxy, const DcOptionsSet::ConnectionInfo &info); - Result find_connection(const Proxy &proxy, const IPAddress &proxy_ip_address, DcId dc_id, - bool allow_media_only, FindConnectionExtra &extra); + static ActorOwn<> prepare_connection(IPAddress ip_address, SocketFd socket_fd, const Proxy &proxy, + const IPAddress &mtproto_ip_address, + const mtproto::TransportType &transport_type, Slice actor_name_prefix, + Slice debug_str, + unique_ptr stats_callback, + ActorShared<> parent, bool use_connection_token, + Promise promise); ActorId get_dns_resolver(); @@ -252,6 +267,15 @@ class ConnectionCreator final : public NetQueryCallback { mtproto::TransportType transport_type, string debug_str, Promise promise); void on_ping_main_dc_result(uint64 token, Result result); + + void on_test_proxy_connection_data(uint64 request_id, Result r_data); + + void on_test_proxy_handshake_connection(uint64 request_id, + Result> r_raw_connection); + + void on_test_proxy_handshake(uint64 request_id, Result> r_handshake); + + void on_test_proxy_timeout(uint64 request_id); }; } // namespace td diff --git a/lib/tgchat/ext/td/tdactor/CMakeLists.txt b/lib/tgchat/ext/td/tdactor/CMakeLists.txt index 664a0899..2a7c68f5 100644 --- a/lib/tgchat/ext/td/tdactor/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdactor/CMakeLists.txt @@ -6,7 +6,6 @@ if (NOT DEFINED CMAKE_INSTALL_LIBDIR) set(CMAKE_INSTALL_LIBDIR "lib") endif() -#SOURCE SETS set(TDACTOR_SOURCE td/actor/ConcurrentScheduler.cpp td/actor/impl/Scheduler.cpp @@ -43,10 +42,6 @@ set(TDACTOR_TEST_SOURCE PARENT_SCOPE ) -#RULES - -#LIBRARIES - add_library(tdactor STATIC ${TDACTOR_SOURCE}) target_include_directories(tdactor PUBLIC $) target_link_libraries(tdactor PUBLIC tdutils) diff --git a/lib/tgchat/ext/td/tddb/CMakeLists.txt b/lib/tgchat/ext/td/tddb/CMakeLists.txt index a76fc6e4..85573170 100644 --- a/lib/tgchat/ext/td/tddb/CMakeLists.txt +++ b/lib/tgchat/ext/td/tddb/CMakeLists.txt @@ -6,7 +6,6 @@ if (NOT DEFINED CMAKE_INSTALL_LIBDIR) set(CMAKE_INSTALL_LIBDIR "lib") endif() -#SOURCE SETS set(TDDB_SOURCE td/db/binlog/Binlog.cpp td/db/binlog/BinlogEvent.cpp diff --git a/lib/tgchat/ext/td/tdnet/CMakeLists.txt b/lib/tgchat/ext/td/tdnet/CMakeLists.txt index d490f87f..0e35c972 100644 --- a/lib/tgchat/ext/td/tdnet/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdnet/CMakeLists.txt @@ -11,7 +11,6 @@ if (NOT OPENSSL_FOUND) find_package(ZLIB REQUIRED) endif() -#SOURCE SETS set(TDNET_SOURCE td/net/GetHostByNameActor.cpp td/net/HttpChunkedByteFlow.cpp @@ -59,9 +58,6 @@ if (APPLE_WATCH) set_source_files_properties(td/net/DarwinHttp.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) endif() -#RULES -#LIBRARIES - add_library(tdnet STATIC ${TDNET_SOURCE}) target_include_directories(tdnet PUBLIC $) target_include_directories(tdnet SYSTEM PRIVATE $) diff --git a/lib/tgchat/ext/td/tdtl/CMakeLists.txt b/lib/tgchat/ext/td/tdtl/CMakeLists.txt index 8b139ddc..5d3399d2 100644 --- a/lib/tgchat/ext/td/tdtl/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdtl/CMakeLists.txt @@ -2,7 +2,6 @@ if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2")) message(FATAL_ERROR "CMake >= 3.0.2 is required") endif() -#SOURCE SETS set(TDTL_SOURCE td/tl/tl_config.cpp td/tl/tl_core.cpp @@ -27,5 +26,3 @@ set(TDTL_SOURCE add_library(tdtl STATIC ${TDTL_SOURCE}) target_include_directories(tdtl PUBLIC $) -#TODO -#target_compile_options(tdtl PRIVATE "-std=c++03") diff --git a/lib/tgchat/ext/td/tdutils/CMakeLists.txt b/lib/tgchat/ext/td/tdutils/CMakeLists.txt index 00fb3c30..9783a5d9 100644 --- a/lib/tgchat/ext/td/tdutils/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdutils/CMakeLists.txt @@ -42,7 +42,6 @@ configure_file(td/utils/config.h.in td/utils/config.h @ONLY) add_subdirectory(generate) -# TDUTILS set_source_files_properties(${TDMIME_AUTO} PROPERTIES GENERATED TRUE) if (CLANG OR GCC) set_property(SOURCE ${TDMIME_AUTO} APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-conversion") @@ -349,7 +348,6 @@ set(TDUTILS_TEST_SOURCE PARENT_SCOPE ) -#LIBRARIES add_library(tdutils STATIC ${TDUTILS_SOURCE}) if (NOT CMAKE_CROSSCOMPILING AND TDUTILS_MIME_TYPE) diff --git a/lib/tgchat/ext/td/test/CMakeLists.txt b/lib/tgchat/ext/td/test/CMakeLists.txt index a45b04ad..da7d45c3 100644 --- a/lib/tgchat/ext/td/test/CMakeLists.txt +++ b/lib/tgchat/ext/td/test/CMakeLists.txt @@ -2,7 +2,6 @@ if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2")) message(FATAL_ERROR "CMake >= 3.0.2 is required") endif() -#SOURCE SETS set(TD_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/country_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db.cpp @@ -36,7 +35,6 @@ set(TESTS_MAIN #target_link_libraries(all_tests PRIVATE tdcore tdclient) if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN) - #Tests if (OPENSSL_FOUND) add_executable(test-crypto EXCLUDE_FROM_ALL crypto.cpp) target_include_directories(test-crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) diff --git a/lib/tgchat/ext/td/test/link.cpp b/lib/tgchat/ext/td/test/link.cpp index cddbc046..4225a237 100644 --- a/lib/tgchat/ext/td/test/link.cpp +++ b/lib/tgchat/ext/td/test/link.cpp @@ -224,6 +224,10 @@ static auto business_chat(const td::string &link_name) { return td::td_api::make_object(link_name); } +static auto buy_stars(td::int64 star_count, const td::string &purpose) { + return td::td_api::make_object(star_count, purpose); +} + static auto change_phone_number() { return td::td_api::make_object(); } @@ -1327,6 +1331,12 @@ TEST(Link, parse_internal_link_part4) { parse_internal_link("tg://settings/language", language_settings()); parse_internal_link("tg://settings/privacy", privacy_and_security_settings()); + parse_internal_link("tg://stars_topup", unknown_deep_link("tg://stars_topup")); + parse_internal_link("tg://stars_topup?balance=", unknown_deep_link("tg://stars_topup?balance=")); + parse_internal_link("tg://stars_topup?balance=test", buy_stars(1, "")); + parse_internal_link("tg://stars_topup?balance=10&purpose=%30test", buy_stars(10, "0test")); + parse_internal_link("tg://stars_topup?balance=100000000000&purpose=subs", buy_stars(100000000000, "subs")); + parse_internal_link("username.t.me////0/a//s/as?start=", bot_start("username", "")); parse_internal_link("username.t.me?start=as", bot_start("username", "as")); parse_internal_link("username.t.me", public_chat("username")); diff --git a/lib/tgchat/src/tgchat.cpp b/lib/tgchat/src/tgchat.cpp index 8bed94a4..8a068969 100644 --- a/lib/tgchat/src/tgchat.cpp +++ b/lib/tgchat/src/tgchat.cpp @@ -41,7 +41,7 @@ // #define SIMULATED_SPONSORED_MESSAGES -static const int s_TdlibDate = 20240730; +static const int s_TdlibDate = 20240814; namespace detail { diff --git a/src/nchat.1 b/src/nchat.1 index 8031a6ae..869213ce 100644 --- a/src/nchat.1 +++ b/src/nchat.1 @@ -1,5 +1,5 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man. -.TH NCHAT "1" "August 2024" "nchat 5.2.1" "User Commands" +.TH NCHAT "1" "August 2024" "nchat 5.2.2" "User Commands" .SH NAME nchat \- ncurses chat .SH SYNOPSIS