From 2a5119ffc325db2ef6dc90d59c828bef9879824a Mon Sep 17 00:00:00 2001 From: Kristofer Berggren Date: Tue, 4 Jun 2024 19:10:42 +0800 Subject: [PATCH] update tdlib to 1.8.30 from tdlib/td@fab354a --- lib/common/src/version.h | 2 +- lib/tgchat/ext/td/CHANGELOG.md | 4 +- .../ext/td/CMake/GeneratePkgConfig.cmake | 1 + lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake | 2 + lib/tgchat/ext/td/CMake/iOS.cmake | 21 + lib/tgchat/ext/td/CMakeLists.txt | 177 +- lib/tgchat/ext/td/README.md | 4 +- lib/tgchat/ext/td/SplitSource.php | 23 +- lib/tgchat/ext/td/benchmark/CMakeLists.txt | 2 +- lib/tgchat/ext/td/benchmark/bench_misc.cpp | 2 +- lib/tgchat/ext/td/build.html | 39 +- lib/tgchat/ext/td/example/README.md | 4 +- .../ext/td/example/android/CMakeLists.txt | 13 +- lib/tgchat/ext/td/example/android/Dockerfile | 7 +- lib/tgchat/ext/td/example/android/README.md | 10 +- .../ext/td/example/android/build-openssl.sh | 3 + .../ext/td/example/android/build-tdlib.sh | 68 +- .../ext/td/example/android/fetch-sdk.sh | 8 +- lib/tgchat/ext/td/example/cpp/CMakeLists.txt | 2 +- .../td/example/ios/Python-Apple-support.patch | 36 +- lib/tgchat/ext/td/example/ios/README.md | 6 +- .../ext/td/example/ios/build-openssl.sh | 2 +- lib/tgchat/ext/td/example/ios/build.sh | 4 +- .../ext/td/example/uwp/Telegram.Td.UWP.nuspec | 2 +- .../ext/td/example/uwp/extension.vsixmanifest | 2 +- lib/tgchat/ext/td/td/generate/CMakeLists.txt | 6 +- .../DotnetTlDocumentationGenerator.php | 5 +- .../DoxygenTlDocumentationGenerator.php | 6 +- .../ext/td/td/generate/scheme/td_api.tl | 1373 +- .../ext/td/td/generate/scheme/telegram_api.tl | 218 +- .../ext/td/td/generate/tl_writer_dotnet.h | 8 +- .../ext/td/td/generate/tl_writer_td.cpp | 2 +- .../ext/td/td/mtproto/SessionConnection.cpp | 2 +- .../ext/td/td/telegram/AccountManager.cpp | 18 +- .../ext/td/td/telegram/AttachMenuManager.cpp | 73 +- lib/tgchat/ext/td/td/telegram/AuthManager.cpp | 27 +- lib/tgchat/ext/td/td/telegram/AuthManager.h | 3 +- .../ext/td/td/telegram/AutosaveManager.cpp | 19 +- .../ext/td/td/telegram/BackgroundInfo.h | 5 +- .../ext/td/td/telegram/BackgroundManager.cpp | 57 +- .../ext/td/td/telegram/BackgroundManager.h | 20 + .../ext/td/td/telegram/BackgroundType.cpp | 10 + .../ext/td/td/telegram/BackgroundType.h | 7 + lib/tgchat/ext/td/td/telegram/Birthdate.cpp | 69 + lib/tgchat/ext/td/td/telegram/Birthdate.h | 69 + lib/tgchat/ext/td/td/telegram/Birthdate.hpp | 25 + .../ext/td/td/telegram/BoostManager.cpp | 96 +- lib/tgchat/ext/td/td/telegram/BotCommand.cpp | 6 +- .../ext/td/td/telegram/BotCommandScope.cpp | 19 +- .../ext/td/td/telegram/BotInfoManager.cpp | 32 +- .../ext/td/td/telegram/BotMenuButton.cpp | 6 +- .../td/td/telegram/BusinessAwayMessage.cpp | 8 +- .../ext/td/td/telegram/BusinessAwayMessage.h | 3 + .../td/td/telegram/BusinessBotManageBar.cpp | 94 + .../ext/td/td/telegram/BusinessBotManageBar.h | 86 + .../ext/td/td/telegram/BusinessChatLink.cpp | 53 + .../ext/td/td/telegram/BusinessChatLink.h | 54 + .../td/td/telegram/BusinessConnectedBot.cpp | 6 +- .../ext/td/td/telegram/BusinessConnectionId.h | 79 + .../td/telegram/BusinessConnectionManager.cpp | 1088 + .../td/telegram/BusinessConnectionManager.h | 180 + .../td/telegram/BusinessGreetingMessage.cpp | 8 +- .../td/td/telegram/BusinessGreetingMessage.h | 3 + .../ext/td/td/telegram/BusinessInfo.cpp | 39 +- lib/tgchat/ext/td/td/telegram/BusinessInfo.h | 10 + .../ext/td/td/telegram/BusinessInfo.hpp | 11 + .../ext/td/td/telegram/BusinessIntro.cpp | 94 + lib/tgchat/ext/td/td/telegram/BusinessIntro.h | 62 + .../ext/td/td/telegram/BusinessIntro.hpp | 63 + .../ext/td/td/telegram/BusinessManager.cpp | 353 +- .../ext/td/td/telegram/BusinessManager.h | 21 + .../ext/td/td/telegram/BusinessRecipients.cpp | 100 +- .../ext/td/td/telegram/BusinessRecipients.h | 10 +- .../ext/td/td/telegram/BusinessRecipients.hpp | 10 + .../ext/td/td/telegram/BusinessWorkHours.cpp | 74 +- .../ext/td/td/telegram/BusinessWorkHours.h | 6 + lib/tgchat/ext/td/td/telegram/CallActor.cpp | 26 +- lib/tgchat/ext/td/td/telegram/CallActor.h | 1 + .../td/td/telegram/CallbackQueriesManager.cpp | 35 +- .../telegram/ChannelRecommendationManager.cpp | 541 + .../telegram/ChannelRecommendationManager.h | 108 + lib/tgchat/ext/td/td/telegram/ChatManager.cpp | 8925 ++++++++ lib/tgchat/ext/td/td/telegram/ChatManager.h | 949 + .../ext/td/td/telegram/ChatReactions.cpp | 29 +- lib/tgchat/ext/td/td/telegram/ChatReactions.h | 12 +- .../ext/td/td/telegram/ChatReactions.hpp | 10 + lib/tgchat/ext/td/td/telegram/Client.cpp | 7 + .../td/td/telegram/CommonDialogManager.cpp | 15 +- .../ext/td/td/telegram/ConfigManager.cpp | 230 +- lib/tgchat/ext/td/td/telegram/ConfigManager.h | 23 +- lib/tgchat/ext/td/td/telegram/Contact.cpp | 20 +- lib/tgchat/ext/td/td/telegram/Contact.h | 10 +- .../ext/td/td/telegram/ContactsManager.cpp | 17435 ---------------- .../ext/td/td/telegram/ContactsManager.h | 1990 -- .../ext/td/td/telegram/Dependencies.cpp | 13 +- .../ext/td/td/telegram/DeviceTokenManager.cpp | 4 +- .../ext/td/td/telegram/DialogAction.cpp | 1 + .../ext/td/td/telegram/DialogActionBar.cpp | 21 +- .../td/td/telegram/DialogActionManager.cpp | 53 +- .../ext/td/td/telegram/DialogActionManager.h | 5 +- .../td/td/telegram/DialogAdministrator.cpp | 8 +- .../ext/td/td/telegram/DialogAdministrator.h | 5 +- .../ext/td/td/telegram/DialogEventLog.cpp | 58 +- .../ext/td/td/telegram/DialogFilter.cpp | 56 +- .../td/td/telegram/DialogFilterManager.cpp | 35 +- .../ext/td/td/telegram/DialogInviteLink.cpp | 10 +- .../ext/td/td/telegram/DialogInviteLink.h | 4 +- .../td/telegram/DialogInviteLinkManager.cpp | 170 +- .../td/td/telegram/DialogInviteLinkManager.h | 3 + .../ext/td/td/telegram/DialogManager.cpp | 587 +- lib/tgchat/ext/td/td/telegram/DialogManager.h | 32 +- .../ext/td/td/telegram/DialogParticipant.cpp | 4 +- .../td/telegram/DialogParticipantFilter.cpp | 6 +- .../td/telegram/DialogParticipantManager.cpp | 545 +- .../td/td/telegram/DialogParticipantManager.h | 36 +- .../ext/td/td/telegram/DraftMessage.cpp | 22 +- .../ext/td/td/telegram/DraftMessage.hpp | 3 +- lib/tgchat/ext/td/td/telegram/EmojiGroup.cpp | 44 +- lib/tgchat/ext/td/td/telegram/EmojiGroup.h | 6 +- lib/tgchat/ext/td/td/telegram/EmojiGroup.hpp | 25 +- .../ext/td/td/telegram/EmojiGroupType.cpp | 4 + .../ext/td/td/telegram/EmojiGroupType.h | 4 +- lib/tgchat/ext/td/td/telegram/FactCheck.cpp | 67 + lib/tgchat/ext/td/td/telegram/FactCheck.h | 65 + lib/tgchat/ext/td/td/telegram/FactCheck.hpp | 55 + .../td/td/telegram/FileReferenceManager.cpp | 21 +- .../td/td/telegram/FileReferenceManager.hpp | 11 +- .../ext/td/td/telegram/ForumTopicManager.cpp | 59 +- lib/tgchat/ext/td/td/telegram/Game.cpp | 8 +- lib/tgchat/ext/td/td/telegram/Game.h | 4 +- lib/tgchat/ext/td/td/telegram/GameManager.cpp | 32 +- .../ext/td/td/telegram/GiveawayParameters.cpp | 7 +- lib/tgchat/ext/td/td/telegram/Global.cpp | 5 + lib/tgchat/ext/td/td/telegram/Global.h | 47 +- .../ext/td/td/telegram/GroupCallManager.cpp | 74 +- .../ext/td/td/telegram/HashtagHints.cpp | 29 +- lib/tgchat/ext/td/td/telegram/HashtagHints.h | 8 +- .../td/td/telegram/InlineQueriesManager.cpp | 66 +- .../td/td/telegram/InputBusinessChatLink.cpp | 50 + .../td/td/telegram/InputBusinessChatLink.h | 36 + .../ext/td/td/telegram/InputInvoice.cpp | 20 +- lib/tgchat/ext/td/td/telegram/InputInvoice.h | 3 +- .../ext/td/td/telegram/InputMessageText.cpp | 4 +- .../td/td/telegram/LanguagePackManager.cpp | 77 +- lib/tgchat/ext/td/td/telegram/LinkManager.cpp | 170 +- lib/tgchat/ext/td/td/telegram/LinkManager.h | 3 +- lib/tgchat/ext/td/td/telegram/Location.cpp | 4 +- lib/tgchat/ext/td/td/telegram/MediaArea.cpp | 5 +- .../ext/td/td/telegram/MessageContent.cpp | 344 +- .../ext/td/td/telegram/MessageContent.h | 7 + .../ext/td/td/telegram/MessageContentType.cpp | 18 + .../ext/td/td/telegram/MessageContentType.h | 5 +- .../ext/td/td/telegram/MessageCopyOptions.h | 4 +- .../ext/td/td/telegram/MessageEntity.cpp | 505 +- lib/tgchat/ext/td/td/telegram/MessageEntity.h | 38 +- .../ext/td/td/telegram/MessageForwardInfo.cpp | 4 +- .../td/td/telegram/MessageImportManager.cpp | 30 +- .../td/td/telegram/MessageInputReplyTo.cpp | 53 +- .../ext/td/td/telegram/MessageInputReplyTo.h | 25 +- .../td/td/telegram/MessageInputReplyTo.hpp | 43 +- .../ext/td/td/telegram/MessageOrigin.cpp | 11 +- .../ext/td/td/telegram/MessageQuote.cpp | 235 + lib/tgchat/ext/td/td/telegram/MessageQuote.h | 85 + .../ext/td/td/telegram/MessageQuote.hpp | 50 + .../ext/td/td/telegram/MessageReaction.cpp | 31 +- .../ext/td/td/telegram/MessageReplyInfo.cpp | 9 +- .../td/td/telegram/MessageSearchOffset.cpp | 69 + .../ext/td/td/telegram/MessageSearchOffset.h | 30 + .../ext/td/td/telegram/MessageSender.cpp | 25 +- .../ext/td/td/telegram/MessageViewer.cpp | 14 +- lib/tgchat/ext/td/td/telegram/MessageViewer.h | 6 +- .../ext/td/td/telegram/MessagesInfo.cpp | 7 +- .../ext/td/td/telegram/MessagesManager.cpp | 3175 +-- .../ext/td/td/telegram/MessagesManager.h | 211 +- .../ext/td/td/telegram/MissingInvitee.cpp | 57 + .../ext/td/td/telegram/MissingInvitee.h | 54 + .../td/td/telegram/NotificationManager.cpp | 32 +- .../telegram/NotificationSettingsManager.cpp | 206 +- .../td/telegram/NotificationSettingsManager.h | 22 + .../ext/td/td/telegram/NotificationSound.cpp | 9 +- .../ext/td/td/telegram/NotificationSound.h | 4 +- .../ext/td/td/telegram/OptionManager.cpp | 67 +- lib/tgchat/ext/td/td/telegram/Payments.cpp | 374 +- lib/tgchat/ext/td/td/telegram/Payments.h | 6 + .../td/td/telegram/PeopleNearbyManager.cpp | 450 + .../ext/td/td/telegram/PeopleNearbyManager.h | 105 + .../ext/td/td/telegram/PhoneNumberManager.cpp | 438 +- .../ext/td/td/telegram/PhoneNumberManager.h | 68 +- lib/tgchat/ext/td/td/telegram/PhotoSize.cpp | 10 + lib/tgchat/ext/td/td/telegram/PollManager.cpp | 108 +- lib/tgchat/ext/td/td/telegram/PollManager.h | 14 +- lib/tgchat/ext/td/td/telegram/PollManager.hpp | 77 +- lib/tgchat/ext/td/td/telegram/Premium.cpp | 327 +- lib/tgchat/ext/td/td/telegram/Premium.h | 9 + .../ext/td/td/telegram/PrivacyManager.cpp | 8 +- .../ext/td/td/telegram/PublicDialogType.h | 2 +- .../ext/td/td/telegram/QuickReplyManager.cpp | 2046 +- .../ext/td/td/telegram/QuickReplyManager.h | 158 +- .../ext/td/td/telegram/QuickReplyShortcutId.h | 4 + .../ext/td/td/telegram/ReactionManager.cpp | 298 +- .../ext/td/td/telegram/ReactionManager.h | 80 +- .../ext/td/td/telegram/ReactionManager.hpp | 98 + .../telegram/ReactionNotificationSettings.cpp | 75 + .../telegram/ReactionNotificationSettings.h | 60 + .../telegram/ReactionNotificationSettings.hpp | 45 + .../td/telegram/ReactionNotificationsFrom.cpp | 98 + .../td/telegram/ReactionNotificationsFrom.h | 52 + .../td/telegram/ReactionNotificationsFrom.hpp | 29 + .../ext/td/td/telegram/RecentDialogList.cpp | 15 +- .../ext/td/td/telegram/RepliedMessageInfo.cpp | 79 +- .../ext/td/td/telegram/RepliedMessageInfo.h | 6 +- .../ext/td/td/telegram/RepliedMessageInfo.hpp | 46 +- lib/tgchat/ext/td/td/telegram/ReplyMarkup.cpp | 38 +- lib/tgchat/ext/td/td/telegram/ReplyMarkup.h | 12 +- .../td/td/telegram/RequestedDialogType.cpp | 56 +- .../ext/td/td/telegram/RequestedDialogType.h | 11 +- .../td/td/telegram/SavedMessagesManager.cpp | 7 +- .../td/td/telegram/SavedMessagesTopicId.cpp | 6 +- .../ext/td/td/telegram/SecretChatActor.cpp | 2 +- .../ext/td/td/telegram/SecretChatActor.h | 2 +- .../ext/td/td/telegram/SecretChatsManager.cpp | 8 +- .../ext/td/td/telegram/SecureManager.cpp | 6 +- .../ext/td/td/telegram/SendCodeHelper.cpp | 83 +- .../ext/td/td/telegram/SendCodeHelper.h | 19 +- .../ext/td/td/telegram/SharedDialog.cpp | 83 + lib/tgchat/ext/td/td/telegram/SharedDialog.h | 71 + .../ext/td/td/telegram/SharedDialog.hpp | 70 + .../td/telegram/SponsoredMessageManager.cpp | 304 +- .../td/td/telegram/SponsoredMessageManager.h | 3 + .../ext/td/td/telegram/StatisticsManager.cpp | 309 +- .../ext/td/td/telegram/StatisticsManager.h | 15 + .../ext/td/td/telegram/StickersManager.cpp | 385 +- .../ext/td/td/telegram/StickersManager.h | 48 +- .../ext/td/td/telegram/StickersManager.hpp | 60 +- lib/tgchat/ext/td/td/telegram/StoryId.h | 9 + .../td/td/telegram/StoryInteractionInfo.cpp | 6 +- .../ext/td/td/telegram/StoryManager.cpp | 236 +- lib/tgchat/ext/td/td/telegram/StoryManager.h | 6 +- .../ext/td/td/telegram/SuggestedAction.cpp | 34 +- .../ext/td/td/telegram/SuggestedAction.h | 14 +- .../ext/td/td/telegram/SuggestedAction.hpp | 50 + lib/tgchat/ext/td/td/telegram/Support.cpp | 10 +- lib/tgchat/ext/td/td/telegram/Td.cpp | 955 +- lib/tgchat/ext/td/td/telegram/Td.h | 149 +- lib/tgchat/ext/td/td/telegram/TdDb.cpp | 2 + .../ext/td/td/telegram/TermsOfService.cpp | 14 +- .../ext/td/td/telegram/ThemeManager.cpp | 5 +- lib/tgchat/ext/td/td/telegram/ThemeManager.h | 2 +- .../ext/td/td/telegram/TimeZoneManager.cpp | 11 + .../ext/td/td/telegram/TimeZoneManager.h | 2 + .../ext/td/td/telegram/TopDialogManager.cpp | 26 +- .../td/td/telegram/TranscriptionManager.cpp | 2 +- .../ext/td/td/telegram/TranslationManager.cpp | 19 +- .../ext/td/td/telegram/UpdatesManager.cpp | 289 +- .../ext/td/td/telegram/UpdatesManager.h | 25 +- lib/tgchat/ext/td/td/telegram/UserManager.cpp | 8082 +++++++ lib/tgchat/ext/td/td/telegram/UserManager.h | 1121 + .../ext/td/td/telegram/UserPrivacySetting.cpp | 10 + .../ext/td/td/telegram/UserPrivacySetting.h | 1 + .../td/td/telegram/UserPrivacySettingRule.cpp | 37 +- .../td/td/telegram/UserPrivacySettingRule.h | 7 +- lib/tgchat/ext/td/td/telegram/Version.h | 3 +- lib/tgchat/ext/td/td/telegram/WebApp.cpp | 6 - lib/tgchat/ext/td/td/telegram/WebApp.h | 3 - .../ext/td/td/telegram/WebPageBlock.cpp | 18 +- .../ext/td/td/telegram/WebPagesManager.cpp | 73 +- lib/tgchat/ext/td/td/telegram/cli.cpp | 638 +- .../td/td/telegram/files/FileDownloader.cpp | 8 +- .../ext/td/td/telegram/files/FileManager.cpp | 49 +- .../ext/td/td/telegram/files/FileManager.h | 3 + .../ext/td/td/telegram/logevent/LogEvent.h | 2 + .../td/td/telegram/net/ConnectionCreator.h | 2 +- .../ext/td/td/telegram/net/DcAuthManager.cpp | 7 +- .../ext/td/td/telegram/net/DcOptionsSet.h | 6 +- .../ext/td/td/telegram/net/NetQuery.cpp | 81 + lib/tgchat/ext/td/td/telegram/net/NetQuery.h | 78 +- .../td/td/telegram/net/NetQueryCreator.cpp | 34 +- .../ext/td/td/telegram/net/NetQueryCreator.h | 9 +- .../td/td/telegram/net/NetQueryDelayer.cpp | 23 +- .../td/td/telegram/net/NetQueryDispatcher.cpp | 88 +- .../td/td/telegram/net/NetQueryDispatcher.h | 7 +- .../td/td/telegram/net/NetQueryVerifier.cpp | 77 + .../ext/td/td/telegram/net/NetQueryVerifier.h | 39 + lib/tgchat/ext/td/td/telegram/net/Session.cpp | 6 +- lib/tgchat/ext/td/td/tl/TlObject.h | 4 +- .../ext/td/tdactor/td/actor/PromiseFuture.h | 18 +- .../td/tdactor/td/actor/impl/Scheduler-decl.h | 47 +- .../td/tdactor/td/actor/impl/Scheduler.cpp | 30 +- .../ext/td/tdactor/td/actor/impl/Scheduler.h | 67 +- .../ext/td/tdactor/test/actors_main.cpp | 9 +- .../ext/td/tddb/td/db/binlog/BinlogEvent.cpp | 4 +- .../ext/td/tddb/td/db/binlog/binlog_dump.cpp | 4 +- lib/tgchat/ext/td/tdtl/td/tl/tl_writer.cpp | 2 +- lib/tgchat/ext/td/tdutils/CMakeLists.txt | 2 + .../td/tdutils/td/utils/ConcurrentHashTable.h | 2 +- .../ext/td/tdutils/td/utils/FlatHashMap.h | 2 +- .../td/tdutils/td/utils/FlatHashMapChunks.h | 8 +- .../ext/td/tdutils/td/utils/FlatHashSet.h | 2 +- .../ext/td/tdutils/td/utils/FlatHashTable.h | 4 +- .../ext/td/tdutils/td/utils/HashTableUtils.h | 4 +- .../ext/td/tdutils/td/utils/HttpDate.cpp | 92 + lib/tgchat/ext/td/tdutils/td/utils/HttpDate.h | 34 + .../ext/td/tdutils/td/utils/JsonBuilder.cpp | 7 + .../ext/td/tdutils/td/utils/JsonBuilder.h | 7 +- lib/tgchat/ext/td/tdutils/td/utils/MapNode.h | 10 +- .../ext/td/tdutils/td/utils/MpmcQueue.h | 4 +- lib/tgchat/ext/td/tdutils/td/utils/SetNode.h | 10 +- .../tdutils/td/utils/port/ServerSocketFd.cpp | 8 + .../td/tdutils/td/utils/port/ServerSocketFd.h | 3 + .../ext/td/tdutils/td/utils/port/SocketFd.cpp | 8 + .../ext/td/tdutils/td/utils/port/SocketFd.h | 3 + .../td/tdutils/td/utils/port/UdpSocketFd.cpp | 56 +- .../td/tdutils/td/utils/port/UdpSocketFd.h | 2 - .../tdutils/td/utils/port/detail/NativeFd.cpp | 41 + .../tdutils/td/utils/port/detail/NativeFd.h | 5 + .../ext/td/tdutils/td/utils/port/platform.h | 2 + .../ext/td/tdutils/td/utils/port/uname.cpp | 2 + .../ext/td/tdutils/test/hashset_benchmark.cpp | 2 +- lib/tgchat/ext/td/tdutils/test/misc.cpp | 5 +- lib/tgchat/ext/td/test/CMakeLists.txt | 2 +- lib/tgchat/ext/td/test/http.cpp | 8 + lib/tgchat/ext/td/test/link.cpp | 51 +- lib/tgchat/ext/td/test/message_entities.cpp | 44 +- lib/tgchat/ext/td/test/mtproto.cpp | 1 + lib/tgchat/ext/td/test/online.cpp | 21 +- lib/tgchat/ext/td/test/secret.cpp | 2 +- lib/tgchat/src/tgchat.cpp | 91 +- src/nchat.1 | 2 +- 328 files changed, 38014 insertions(+), 25570 deletions(-) create mode 100644 lib/tgchat/ext/td/td/telegram/Birthdate.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/Birthdate.h create mode 100644 lib/tgchat/ext/td/td/telegram/Birthdate.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.h create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessChatLink.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessChatLink.h create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessConnectionId.h create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessIntro.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessIntro.h create mode 100644 lib/tgchat/ext/td/td/telegram/BusinessIntro.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/ChatManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/ChatManager.h delete mode 100644 lib/tgchat/ext/td/td/telegram/ContactsManager.cpp delete mode 100644 lib/tgchat/ext/td/td/telegram/ContactsManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/FactCheck.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/FactCheck.h create mode 100644 lib/tgchat/ext/td/td/telegram/FactCheck.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.h create mode 100644 lib/tgchat/ext/td/td/telegram/MessageQuote.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/MessageQuote.h create mode 100644 lib/tgchat/ext/td/td/telegram/MessageQuote.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/MessageSearchOffset.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/MessageSearchOffset.h create mode 100644 lib/tgchat/ext/td/td/telegram/MissingInvitee.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/MissingInvitee.h create mode 100644 lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.h create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.h create mode 100644 lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/SharedDialog.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/SharedDialog.h create mode 100644 lib/tgchat/ext/td/td/telegram/SharedDialog.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/SuggestedAction.hpp create mode 100644 lib/tgchat/ext/td/td/telegram/UserManager.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/UserManager.h create mode 100644 lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.cpp create mode 100644 lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.h create mode 100644 lib/tgchat/ext/td/tdutils/td/utils/HttpDate.cpp create mode 100644 lib/tgchat/ext/td/tdutils/td/utils/HttpDate.h diff --git a/lib/common/src/version.h b/lib/common/src/version.h index 2499d06e..dada2e31 100644 --- a/lib/common/src/version.h +++ b/lib/common/src/version.h @@ -7,4 +7,4 @@ #pragma once -#define NCHAT_VERSION "4.85" +#define NCHAT_VERSION "4.86" diff --git a/lib/tgchat/ext/td/CHANGELOG.md b/lib/tgchat/ext/td/CHANGELOG.md index 7f3cd334..f590b3ef 100644 --- a/lib/tgchat/ext/td/CHANGELOG.md +++ b/lib/tgchat/ext/td/CHANGELOG.md @@ -1087,9 +1087,9 @@ Changes in 1.4.0 (1 May 2019): the class `scopeNotificationSettings`. - Added the class `PushMessageContent` describing the content of a notification, received through a push notification. - - Added the class `NotificationType` describing a type of a notification. + - Added the class `NotificationType` describing a type of notification. - Added the class `notification` containing information about a notification. - - Added the class `NotificationGroupType` describing a type of a notification group. + - Added the class `NotificationGroupType` describing a type of notification group. - Added the class `notificationGroup` describing a state of a notification group. - Added the methods `removeNotification` and `removeNotificationGroup` for handling notifications removal by the user. diff --git a/lib/tgchat/ext/td/CMake/GeneratePkgConfig.cmake b/lib/tgchat/ext/td/CMake/GeneratePkgConfig.cmake index afbe06ac..a2800e10 100644 --- a/lib/tgchat/ext/td/CMake/GeneratePkgConfig.cmake +++ b/lib/tgchat/ext/td/CMake/GeneratePkgConfig.cmake @@ -27,6 +27,7 @@ function(get_relative_link OUTPUT PATH) set(${OUTPUT} "${LINK}" PARENT_SCOPE) endfunction() +# TODO: support interface libraries in dependencies function(generate_pkgconfig TARGET DESCRIPTION) # message("Generating pkg-config for ${TARGET}") get_filename_component(PREFIX "${CMAKE_INSTALL_PREFIX}" REALPATH) diff --git a/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake b/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake index 81cee4d2..ff5991cf 100644 --- a/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake +++ b/lib/tgchat/ext/td/CMake/TdSetUpCompiler.cmake @@ -62,6 +62,8 @@ function(td_set_up_compiler) set(TD_LINKER_FLAGS "-Wl,-z,ignore") elseif (EMSCRIPTEN) set(TD_LINKER_FLAGS "-Wl,--gc-sections") + elseif (ANDROID) + set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL -Wl,--icf=safe") else() set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL") endif() diff --git a/lib/tgchat/ext/td/CMake/iOS.cmake b/lib/tgchat/ext/td/CMake/iOS.cmake index bb2a1295..4ef76bd0 100644 --- a/lib/tgchat/ext/td/CMake/iOS.cmake +++ b/lib/tgchat/ext/td/CMake/iOS.cmake @@ -114,6 +114,23 @@ elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR") set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-tvsimulator") set (APPLE_TV True) +elseif (IOS_PLATFORM STREQUAL "VISIONOS") + set (IOS_PLATFORM_LOCATION "XROS.platform") + set (XCODE_IOS_PLATFORM xros) + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xros") + + set (APPLE_VISION True) +elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR") + set (SIMULATOR_FLAG true) + set (IOS_PLATFORM_LOCATION "XRSimulator.platform") + set (XCODE_IOS_PLATFORM xros-simulator) + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xrsimulator") + + set (APPLE_VISION True) else (IOS_PLATFORM STREQUAL "OS") message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, SIMULATOR, or WATCHOS.") endif () @@ -212,6 +229,10 @@ if (NOT DEFINED IOS_ARCH) set (IOS_ARCH "arm64") elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR") set (IOS_ARCH "x86_64;arm64") + elseif (IOS_PLATFORM STREQUAL "VISIONOS") + set (IOS_ARCH "arm64") + elseif (IOS_PLATFORM STREQUAL "VISIONSIMULATOR") + set (IOS_ARCH "x86_64;arm64") endif() endif() message (STATUS "The iOS architectures: ${IOS_ARCH}") diff --git a/lib/tgchat/ext/td/CMakeLists.txt b/lib/tgchat/ext/td/CMakeLists.txt index c297f19d..2edd6621 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.26 LANGUAGES CXX C) +project(TDLib VERSION 1.8.30 LANGUAGES CXX C) if (NOT DEFINED CMAKE_MODULE_PATH) set(CMAKE_MODULE_PATH "") @@ -239,13 +239,9 @@ set(TL_TD_API_SOURCE td/tl/TlObject.h ) +set_source_files_properties(${TL_MTPROTO_AUTO_SOURCE} PROPERTIES GENERATED TRUE) + set_source_files_properties(${TL_TD_AUTO_SOURCE} PROPERTIES GENERATED TRUE) -set(TL_TD_SCHEME_SOURCE - ${TL_TD_AUTO_SOURCE} - td/tl/TlObject.h - td/tl/tl_object_parse.h - td/tl/tl_object_store.h -) set_source_files_properties(${TL_TD_JSON_AUTO_SOURCE} PROPERTIES GENERATED TRUE) set(TL_TD_JSON_SOURCE @@ -264,7 +260,7 @@ set(TL_DOTNET_SCHEME_SOURCE td/tl/tl_dotnet_object.h ) -set(TDLIB_SOURCE +set(TD_MTPROTO_SOURCE td/mtproto/AuthData.cpp td/mtproto/ConnectionManager.cpp td/mtproto/DhHandshake.cpp @@ -285,6 +281,44 @@ set(TDLIB_SOURCE td/mtproto/Transport.cpp td/mtproto/utils.cpp + td/mtproto/AuthData.h + td/mtproto/AuthKey.h + td/mtproto/ConnectionManager.h + td/mtproto/CryptoStorer.h + td/mtproto/DhCallback.h + td/mtproto/DhHandshake.h + td/mtproto/Handshake.h + td/mtproto/HandshakeActor.h + td/mtproto/HandshakeConnection.h + td/mtproto/HttpTransport.h + td/mtproto/IStreamTransport.h + td/mtproto/KDF.h + td/mtproto/MessageId.h + td/mtproto/MtprotoQuery.h + td/mtproto/NoCryptoStorer.h + td/mtproto/PacketInfo.h + td/mtproto/PacketStorer.h + td/mtproto/Ping.h + td/mtproto/PingConnection.h + td/mtproto/ProxySecret.h + td/mtproto/RawConnection.h + td/mtproto/RSA.h + td/mtproto/SessionConnection.h + td/mtproto/TcpTransport.h + td/mtproto/TlsInit.h + td/mtproto/TlsReaderByteFlow.h + td/mtproto/Transport.h + td/mtproto/TransportType.h + td/mtproto/utils.h + + ${TL_MTPROTO_AUTO_SOURCE} + + td/tl/TlObject.h + td/tl/tl_object_parse.h + td/tl/tl_object_store.h +) + +set(TDLIB_SOURCE_PART1 td/telegram/AccountManager.cpp td/telegram/AnimationsManager.cpp td/telegram/Application.cpp @@ -296,6 +330,7 @@ set(TDLIB_SOURCE td/telegram/BackgroundInfo.cpp td/telegram/BackgroundManager.cpp td/telegram/BackgroundType.cpp + td/telegram/Birthdate.cpp td/telegram/BoostManager.cpp td/telegram/BotCommand.cpp td/telegram/BotCommandScope.cpp @@ -303,9 +338,13 @@ set(TDLIB_SOURCE td/telegram/BotMenuButton.cpp td/telegram/BusinessAwayMessage.cpp td/telegram/BusinessAwayMessageSchedule.cpp + td/telegram/BusinessBotManageBar.cpp + td/telegram/BusinessChatLink.cpp td/telegram/BusinessConnectedBot.cpp + td/telegram/BusinessConnectionManager.cpp td/telegram/BusinessGreetingMessage.cpp td/telegram/BusinessInfo.cpp + td/telegram/BusinessIntro.cpp td/telegram/BusinessManager.cpp td/telegram/BusinessRecipients.cpp td/telegram/BusinessWorkHours.cpp @@ -314,13 +353,14 @@ set(TDLIB_SOURCE td/telegram/CallDiscardReason.cpp td/telegram/CallManager.cpp td/telegram/ChannelParticipantFilter.cpp + td/telegram/ChannelRecommendationManager.cpp + td/telegram/ChatManager.cpp td/telegram/ChatReactions.cpp td/telegram/ClientActor.cpp td/telegram/CommonDialogManager.cpp td/telegram/ConfigManager.cpp td/telegram/ConnectionState.cpp td/telegram/Contact.cpp - td/telegram/ContactsManager.cpp td/telegram/CountryInfoManager.cpp td/telegram/DelayDispatcher.cpp td/telegram/Dependencies.cpp @@ -355,6 +395,7 @@ set(TDLIB_SOURCE td/telegram/EmojiGroup.cpp td/telegram/EmojiGroupType.cpp td/telegram/EmojiStatus.cpp + td/telegram/FactCheck.cpp td/telegram/FileReferenceManager.cpp td/telegram/files/FileBitmask.cpp td/telegram/files/FileDb.cpp @@ -391,6 +432,7 @@ set(TDLIB_SOURCE td/telegram/GroupCallVideoPayload.cpp td/telegram/HashtagHints.cpp td/telegram/InlineQueriesManager.cpp + td/telegram/InputBusinessChatLink.cpp td/telegram/InputDialogId.cpp td/telegram/InputGroupCallId.cpp td/telegram/InputInvoice.cpp @@ -413,10 +455,12 @@ set(TDLIB_SOURCE td/telegram/MessageImportManager.cpp td/telegram/MessageInputReplyTo.cpp td/telegram/MessageOrigin.cpp + td/telegram/MessageQuote.cpp td/telegram/MessageReaction.cpp td/telegram/MessageReplyHeader.cpp td/telegram/MessageReplyInfo.cpp td/telegram/MessageSearchFilter.cpp + td/telegram/MessageSearchOffset.cpp td/telegram/MessageSelfDestructType.cpp td/telegram/MessageSender.cpp td/telegram/MessagesInfo.cpp @@ -426,6 +470,9 @@ set(TDLIB_SOURCE td/telegram/MessageTtl.cpp td/telegram/MessageViewer.cpp td/telegram/misc.cpp + td/telegram/MissingInvitee.cpp +) +set(TDLIB_SOURCE_PART2 td/telegram/net/AuthDataShared.cpp td/telegram/net/ConnectionCreator.cpp td/telegram/net/DcAuthManager.cpp @@ -437,6 +484,7 @@ set(TDLIB_SOURCE td/telegram/net/NetQueryDelayer.cpp td/telegram/net/NetQueryDispatcher.cpp td/telegram/net/NetQueryStats.cpp + td/telegram/net/NetQueryVerifier.cpp td/telegram/net/NetStatsManager.cpp td/telegram/net/Proxy.cpp td/telegram/net/PublicRsaKeySharedCdn.cpp @@ -459,6 +507,7 @@ set(TDLIB_SOURCE td/telegram/PasswordManager.cpp td/telegram/Payments.cpp td/telegram/PeerColor.cpp + td/telegram/PeopleNearbyManager.cpp td/telegram/PhoneNumberManager.cpp td/telegram/Photo.cpp td/telegram/PhotoSize.cpp @@ -472,6 +521,8 @@ set(TDLIB_SOURCE td/telegram/QuickReplyManager.cpp td/telegram/ReactionListType.cpp td/telegram/ReactionManager.cpp + td/telegram/ReactionNotificationSettings.cpp + td/telegram/ReactionNotificationsFrom.cpp td/telegram/ReactionType.cpp td/telegram/RecentDialogList.cpp td/telegram/RepliedMessageInfo.cpp @@ -492,6 +543,7 @@ set(TDLIB_SOURCE td/telegram/SendCodeHelper.cpp td/telegram/SentEmailCode.cpp td/telegram/SequenceDispatcher.cpp + td/telegram/SharedDialog.cpp td/telegram/SpecialStickerSetType.cpp td/telegram/SponsoredMessageManager.cpp td/telegram/StateManager.cpp @@ -525,6 +577,7 @@ set(TDLIB_SOURCE td/telegram/TranscriptionManager.cpp td/telegram/TranslationManager.cpp td/telegram/UpdatesManager.cpp + td/telegram/UserManager.cpp td/telegram/Usernames.cpp td/telegram/UserPrivacySetting.cpp td/telegram/UserPrivacySettingRule.cpp @@ -536,36 +589,6 @@ set(TDLIB_SOURCE td/telegram/WebPageBlock.cpp td/telegram/WebPagesManager.cpp - td/mtproto/AuthData.h - td/mtproto/AuthKey.h - td/mtproto/ConnectionManager.h - td/mtproto/CryptoStorer.h - td/mtproto/DhCallback.h - td/mtproto/DhHandshake.h - td/mtproto/Handshake.h - td/mtproto/HandshakeActor.h - td/mtproto/HandshakeConnection.h - td/mtproto/HttpTransport.h - td/mtproto/IStreamTransport.h - td/mtproto/KDF.h - td/mtproto/MessageId.h - td/mtproto/MtprotoQuery.h - td/mtproto/NoCryptoStorer.h - td/mtproto/PacketInfo.h - td/mtproto/PacketStorer.h - td/mtproto/Ping.h - td/mtproto/PingConnection.h - td/mtproto/ProxySecret.h - td/mtproto/RawConnection.h - td/mtproto/RSA.h - td/mtproto/SessionConnection.h - td/mtproto/TcpTransport.h - td/mtproto/TlsInit.h - td/mtproto/TlsReaderByteFlow.h - td/mtproto/Transport.h - td/mtproto/TransportType.h - td/mtproto/utils.h - td/telegram/AccentColorId.h td/telegram/AccessRights.h td/telegram/AccountManager.h @@ -581,6 +604,7 @@ set(TDLIB_SOURCE td/telegram/BackgroundInfo.h td/telegram/BackgroundManager.h td/telegram/BackgroundType.h + td/telegram/Birthdate.h td/telegram/BlockListId.h td/telegram/BoostManager.h td/telegram/BotCommand.h @@ -589,9 +613,14 @@ set(TDLIB_SOURCE td/telegram/BotMenuButton.h td/telegram/BusinessAwayMessage.h td/telegram/BusinessAwayMessageSchedule.h + td/telegram/BusinessBotManageBar.h + td/telegram/BusinessChatLink.h td/telegram/BusinessConnectedBot.h + td/telegram/BusinessConnectionId.h + td/telegram/BusinessConnectionManager.h td/telegram/BusinessGreetingMessage.h td/telegram/BusinessInfo.h + td/telegram/BusinessIntro.h td/telegram/BusinessManager.h td/telegram/BusinessRecipients.h td/telegram/BusinessWorkHours.h @@ -603,15 +632,16 @@ set(TDLIB_SOURCE td/telegram/ChainId.h td/telegram/ChannelId.h td/telegram/ChannelParticipantFilter.h + td/telegram/ChannelRecommendationManager.h td/telegram/ChannelType.h td/telegram/ChatId.h + td/telegram/ChatManager.h td/telegram/ChatReactions.h td/telegram/ClientActor.h td/telegram/CommonDialogManager.h td/telegram/ConfigManager.h td/telegram/ConnectionState.h td/telegram/Contact.h - td/telegram/ContactsManager.h td/telegram/CountryInfoManager.h td/telegram/CustomEmojiId.h td/telegram/DelayDispatcher.h @@ -654,6 +684,7 @@ set(TDLIB_SOURCE td/telegram/EmojiGroupType.h td/telegram/EmojiStatus.h td/telegram/EncryptedFile.h + td/telegram/FactCheck.h td/telegram/FileReferenceManager.h td/telegram/files/FileBitmask.h td/telegram/files/FileData.h @@ -700,6 +731,7 @@ set(TDLIB_SOURCE td/telegram/GroupCallVideoPayload.h td/telegram/HashtagHints.h td/telegram/InlineQueriesManager.h + td/telegram/InputBusinessChatLink.h td/telegram/InputDialogId.h td/telegram/InputGroupCallId.h td/telegram/InputInvoice.h @@ -728,10 +760,12 @@ set(TDLIB_SOURCE td/telegram/MessageInputReplyTo.h td/telegram/MessageLinkInfo.h td/telegram/MessageOrigin.h + td/telegram/MessageQuote.h td/telegram/MessageReaction.h td/telegram/MessageReplyHeader.h td/telegram/MessageReplyInfo.h td/telegram/MessageSearchFilter.h + td/telegram/MessageSearchOffset.h td/telegram/MessageSelfDestructType.h td/telegram/MessageSender.h td/telegram/MessagesInfo.h @@ -743,6 +777,7 @@ set(TDLIB_SOURCE td/telegram/MessageViewer.h td/telegram/MinChannel.h td/telegram/misc.h + td/telegram/MissingInvitee.h td/telegram/net/AuthDataShared.h td/telegram/net/AuthKeyState.h td/telegram/net/ConnectionCreator.h @@ -758,6 +793,7 @@ set(TDLIB_SOURCE td/telegram/net/NetQueryDelayer.h td/telegram/net/NetQueryDispatcher.h td/telegram/net/NetQueryStats.h + td/telegram/net/NetQueryVerifier.h td/telegram/net/NetStatsManager.h td/telegram/net/NetType.h td/telegram/net/Proxy.h @@ -790,6 +826,7 @@ set(TDLIB_SOURCE td/telegram/PasswordManager.h td/telegram/Payments.h td/telegram/PeerColor.h + td/telegram/PeopleNearbyManager.h td/telegram/PhoneNumberManager.h td/telegram/Photo.h td/telegram/PhotoFormat.h @@ -809,6 +846,8 @@ set(TDLIB_SOURCE td/telegram/QuickReplyShortcutId.h td/telegram/ReactionListType.h td/telegram/ReactionManager.h + td/telegram/ReactionNotificationSettings.h + td/telegram/ReactionNotificationsFrom.h td/telegram/ReactionType.h td/telegram/ReactionUnavailabilityReason.h td/telegram/RecentDialogList.h @@ -836,6 +875,7 @@ set(TDLIB_SOURCE td/telegram/SequenceDispatcher.h td/telegram/ServerMessageId.h td/telegram/SetWithPosition.h + td/telegram/SharedDialog.h td/telegram/SpecialStickerSetType.h td/telegram/SponsoredMessageManager.h td/telegram/StateManager.h @@ -876,6 +916,7 @@ set(TDLIB_SOURCE td/telegram/UniqueId.h td/telegram/UpdatesManager.h td/telegram/UserId.h + td/telegram/UserManager.h td/telegram/Usernames.h td/telegram/UserPrivacySetting.h td/telegram/UserPrivacySettingRule.h @@ -894,11 +935,13 @@ set(TDLIB_SOURCE td/telegram/AuthManager.hpp td/telegram/BackgroundInfo.hpp td/telegram/BackgroundType.hpp + td/telegram/Birthdate.hpp td/telegram/BusinessAwayMessage.hpp td/telegram/BusinessAwayMessageSchedule.hpp td/telegram/BusinessConnectedBot.hpp td/telegram/BusinessGreetingMessage.hpp td/telegram/BusinessInfo.hpp + td/telegram/BusinessIntro.hpp td/telegram/BusinessRecipients.hpp td/telegram/BusinessWorkHours.hpp td/telegram/ChatReactions.hpp @@ -909,6 +952,7 @@ set(TDLIB_SOURCE td/telegram/DocumentsManager.hpp td/telegram/DraftMessage.hpp td/telegram/EmojiGroup.hpp + td/telegram/FactCheck.hpp td/telegram/FileReferenceManager.hpp td/telegram/files/FileData.hpp td/telegram/files/FileId.hpp @@ -930,6 +974,7 @@ set(TDLIB_SOURCE td/telegram/MessageForwardInfo.hpp td/telegram/MessageInputReplyTo.hpp td/telegram/MessageOrigin.hpp + td/telegram/MessageQuote.hpp td/telegram/MessageReaction.hpp td/telegram/MessageReplyInfo.hpp td/telegram/MinChannel.hpp @@ -942,6 +987,8 @@ set(TDLIB_SOURCE td/telegram/PollManager.hpp td/telegram/PremiumGiftOption.hpp td/telegram/ReactionManager.hpp + td/telegram/ReactionNotificationSettings.hpp + td/telegram/ReactionNotificationsFrom.hpp td/telegram/ReactionType.hpp td/telegram/RepliedMessageInfo.hpp td/telegram/ReplyMarkup.hpp @@ -949,22 +996,32 @@ set(TDLIB_SOURCE td/telegram/ScopeNotificationSettings.hpp td/telegram/SecureValue.hpp td/telegram/SendCodeHelper.hpp + td/telegram/SharedDialog.hpp td/telegram/StickerMaskPosition.hpp td/telegram/StickerPhotoSize.hpp td/telegram/StickersManager.hpp td/telegram/StoryForwardInfo.hpp td/telegram/StoryInteractionInfo.hpp td/telegram/StoryStealthMode.hpp + td/telegram/SuggestedAction.hpp td/telegram/TranscriptionInfo.hpp td/telegram/VideoNotesManager.hpp td/telegram/VideosManager.hpp td/telegram/VoiceNotesManager.hpp td/telegram/WebApp.hpp - ${TL_TD_SCHEME_SOURCE} + ${TL_TD_AUTO_SOURCE} + + td/tl/TlObject.h + td/tl/tl_object_parse.h + td/tl/tl_object_store.h ${CMAKE_CURRENT_BINARY_DIR}/td/telegram/GitCommitHash.cpp ) +set(TDLIB_SOURCE + ${TDLIB_SOURCE_PART1} + ${TDLIB_SOURCE_PART2} +) set(MEMPROF_SOURCE memprof/memprof.cpp @@ -1015,19 +1072,40 @@ if (NOT CMAKE_CROSSCOMPILING) add_dependencies(tdapi tl_generate_common) endif() -# tdcore - mostly internal TDLib interface. One should use tdactor for interactions with it. -add_library(tdcore STATIC ${TDLIB_SOURCE}) -target_include_directories(tdcore PUBLIC $ $) -target_include_directories(tdcore SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) -target_link_libraries(tdcore PUBLIC tdapi tdactor tdutils tdnet tddb PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES}) +add_library(tdmtproto STATIC ${TD_MTPROTO_SOURCE}) +target_include_directories(tdmtproto PUBLIC $ $) +target_include_directories(tdmtproto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) +target_link_libraries(tdmtproto PUBLIC tdactor tdnet tdutils PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES}) if (WIN32) if (MINGW) - target_link_libraries(tdcore PRIVATE ws2_32 mswsock crypt32) + target_link_libraries(tdmtproto PRIVATE ws2_32 mswsock crypt32) else() - target_link_libraries(tdcore PRIVATE ws2_32 Mswsock Crypt32) + target_link_libraries(tdmtproto PRIVATE ws2_32 Mswsock Crypt32) endif() endif() +# tdcore - mostly internal TDLib interface. One should use tdactor for interactions with it. +if (MSVC AND TD_ENABLE_LTO) + add_library(tdcore_part1 STATIC ${TDLIB_SOURCE_PART1}) + target_include_directories(tdcore_part1 PUBLIC $ $) + target_link_libraries(tdcore_part1 PUBLIC tdapi tdnet tddb tdactor tdutils PRIVATE tdmtproto) + + add_library(tdcore_part2 STATIC ${TDLIB_SOURCE_PART2}) + target_include_directories(tdcore_part2 PUBLIC $ $) + target_link_libraries(tdcore_part2 PUBLIC tdapi tdnet tddb tdactor tdutils PRIVATE tdmtproto) + + add_library(tdcore INTERFACE) + target_link_libraries(tdcore INTERFACE tdcore_part1 tdcore_part2) + + set(TD_CORE_PART_TARGETS tdcore_part1 tdcore_part2) +else() + add_library(tdcore STATIC ${TDLIB_SOURCE}) + target_include_directories(tdcore PUBLIC $ $) + target_link_libraries(tdcore PUBLIC tdapi tdnet tddb tdactor tdutils PRIVATE tdmtproto) + + set(TD_CORE_PART_TARGETS) +endif() + if (NOT CMAKE_CROSSCOMPILING) add_dependencies(tdcore tl_generate_common) if (TD_ENABLE_JNI) @@ -1166,7 +1244,7 @@ add_library(Td::TdJson ALIAS TdJson) add_library(Td::TdJsonStatic ALIAS TdJsonStatic) set(INSTALL_TARGETS tdjson TdJson) -set(INSTALL_STATIC_TARGETS tdjson_static TdJsonStatic tdjson_private tdcore) +set(INSTALL_STATIC_TARGETS tdjson_static TdJsonStatic tdjson_private "${TD_CORE_PART_TARGETS}" tdcore tdmtproto) if (BUILD_SHARED_LIBS) set(INSTALL_TARGETS ${INSTALL_TARGETS} tdclient TdStatic tdapi) else() @@ -1197,6 +1275,7 @@ generate_pkgconfig(tddb "Telegram Library - Database") if (MEMPROF) # generate_pkgconfig(memprof "memprof - simple library for memory usage profiling") endif() +generate_pkgconfig(tdmtproto "Telegram Library - MTProto implementation") generate_pkgconfig(tdcore "Telegram Library - Core") generate_pkgconfig(tdclient "Telegram Library - C++ Interface") if (TD_ENABLE_DOTNET) diff --git a/lib/tgchat/ext/td/README.md b/lib/tgchat/ext/td/README.md index d0fbd51e..589bfab2 100644 --- a/lib/tgchat/ext/td/README.md +++ b/lib/tgchat/ext/td/README.md @@ -18,7 +18,7 @@ TDLib (Telegram Database library) is a cross-platform library for building [Tele `TDLib` has many advantages. Notably `TDLib` is: -* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort. +* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, visionOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort. * **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally, it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings. * **Easy to use**: `TDLib` takes care of all network implementation details, encryption and local data storage. * **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 24000 active bots simultaneously. @@ -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.26 REQUIRED) +find_package(Td 1.8.30 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 b9132f03..19e27f4b 100644 --- a/lib/tgchat/ext/td/SplitSource.php +++ b/lib/tgchat/ext/td/SplitSource.php @@ -306,13 +306,18 @@ function ($matches) use ($needed_std_headers) { 'BackgroundId' => 'BackgroundId', 'background_manager[_(-](?![.]get[(][)])|BackgroundManager' => 'BackgroundManager', 'BackgroundType' => 'BackgroundType', + 'Birthdate' => 'Birthdate', 'BotMenuButton|[a-z_]*_menu_button' => 'BotMenuButton', 'boost_manager[_(-](?![.]get[(][)])|BoostManager' => 'BoostManager', 'bot_info_manager[_(-](?![.]get[(][)])|BotInfoManager' => 'BotInfoManager', 'BusinessAwayMessage' => 'BusinessAwayMessage', + 'BusinessChatLink' => 'BusinessChatLink', 'BusinessConnectedBot' => 'BusinessConnectedBot', + 'BusinessConnectionId' => 'BusinessConnectionId', + 'business_connection_manager[_(-](?![.]get[(][)])|BusinessConnectionManager' => 'BusinessConnectionManager', 'BusinessGreetingMessage' => 'BusinessGreetingMessage', 'BusinessInfo|business_info' => 'BusinessInfo', + 'BusinessIntro' => 'BusinessIntro', 'business_manager[_(-](?![.]get[(][)])|BusinessManager' => 'BusinessManager', 'BusinessRecipients' => 'BusinessRecipients', 'BusinessWorkHours' => 'BusinessWorkHours', @@ -320,9 +325,10 @@ function ($matches) use ($needed_std_headers) { 'CallId' => 'CallId', 'call_manager[_(-](?![.]get[(][)])|CallManager' => 'CallManager', 'ChannelId' => 'ChannelId', + 'channel_recommendation_manager[_(-](?![.]get[(][)])|ChannelRecommendationManager' => 'ChannelRecommendationManager', 'ChatId' => 'ChatId', + 'chat_manager[_(-](?![.]get[(][)])|ChatManager([^ ;.]| [^*])' => 'ChatManager', 'common_dialog_manager[_(-](?![.]get[(][)])|CommonDialogManager' => 'CommonDialogManager', - 'contacts_manager[_(-](?![.]get[(][)])|ContactsManager([^ ;.]| [^*])' => 'ContactsManager', 'country_info_manager[_(-](?![.]get[(][)])|CountryInfoManager' => 'CountryInfoManager', 'CustomEmojiId' => 'CustomEmojiId', 'device_token_manager[_(-](?![.]get[(][)])|DeviceTokenManager' => 'DeviceTokenManager', @@ -345,6 +351,7 @@ function ($matches) use ($needed_std_headers) { 'EmailVerification' => 'EmailVerification', 'EmojiGroup' => 'EmojiGroup', 'EmojiStatus|[a-z_]*_emoji_status' => 'EmojiStatus', + 'FactCheck' => 'FactCheck', 'file_reference_manager[_(-](?![.]get[(][)])|FileReferenceManager|file_references[)]' => 'FileReferenceManager', 'file_manager[_(-](?![.]get[(][)])|FileManager([^ ;.]| [^*])|update_file[)]' => 'files/FileManager', 'FolderId' => 'FolderId', @@ -356,6 +363,7 @@ function ($matches) use ($needed_std_headers) { 'group_call_manager[_(-](?![.]get[(][)])|GroupCallManager' => 'GroupCallManager', 'hashtag_hints[_(-](?![.]get[(][)])|HashtagHints' => 'HashtagHints', 'inline_queries_manager[_(-](?![.]get[(][)])|InlineQueriesManager' => 'InlineQueriesManager', + 'InputBusinessChatLink' => 'InputBusinessChatLink', 'language_pack_manager[_(-]|LanguagePackManager' => 'LanguagePackManager', 'link_manager[_(-](?![.]get[(][)])|LinkManager' => 'LinkManager', 'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper', @@ -365,16 +373,20 @@ function ($matches) use ($needed_std_headers) { 'MessageId' => 'MessageId', 'message_import_manager[_(-](?![.]get[(][)])|MessageImportManager' => 'MessageImportManager', 'MessageLinkInfo' => 'MessageLinkInfo', + 'MessageQuote' => 'MessageQuote', 'MessageReaction|UnreadMessageReaction|[a-z_]*message[a-z_]*reaction' => 'MessageReaction', + 'MessageSearchOffset' => 'MessageSearchOffset', '[a-z_]*_message_sender' => 'MessageSender', 'messages_manager[_(-](?![.]get[(][)])|MessagesManager' => 'MessagesManager', 'MessageThreadInfo' => 'MessageThreadInfo', 'MessageTtl' => 'MessageTtl', + 'MissingInvitee' => 'MissingInvitee', 'notification_manager[_(-](?![.]get[(][)])|NotificationManager|notifications[)]' => 'NotificationManager', 'notification_settings_manager[_(-](?![.]get[(][)])|NotificationSettingsManager' => 'NotificationSettingsManager', 'option_manager[_(-](?![.]get[(][)])|OptionManager' => 'OptionManager', 'password_manager[_(-](?![.]get[(][)])|PasswordManager' => 'PasswordManager', - '[a-z_]*phone_number_manager[_(-](?![.]get[(][)])|PhoneNumberManager' => 'PhoneNumberManager', + 'people_nearby_manager[_(-](?![.]get[(][)])|PeopleNearbyManager' => 'PeopleNearbyManager', + 'phone_number_manager[_(-](?![.]get[(][)])|PhoneNumberManager' => 'PhoneNumberManager', 'PhotoSizeSource' => 'PhotoSizeSource', 'poll_manager[_(-](?![.]get[(][)])|PollManager' => 'PollManager', 'privacy_manager[_(-](?![.]get[(][)])|PrivacyManager' => 'PrivacyManager', @@ -382,6 +394,8 @@ function ($matches) use ($needed_std_headers) { 'quick_reply_manager[_(-](?![.]get[(][)])|QuickReplyManager' => 'QuickReplyManager', 'ReactionListType|[a-z_]*_reaction_list_type' => 'ReactionListType', 'reaction_manager[_(-](?![.]get[(][)])|ReactionManager' => 'ReactionManager', + 'ReactionNotificationSettings' => 'ReactionNotificationSettings', + 'ReactionNotificationsFrom' => 'ReactionNotificationsFrom', 'ReactionType|[a-z_]*_reaction_type' => 'ReactionType', 'RequestActor|RequestOnceActor' => 'RequestActor', 'saved_messages_manager[_(-](?![.]get[(][)])|SavedMessagesManager' => 'SavedMessagesManager', @@ -390,6 +404,7 @@ function ($matches) use ($needed_std_headers) { 'secret_chats_manager[_(-]|SecretChatsManager' => 'SecretChatsManager', 'secure_manager[_(-](?![.]get[(][)])|SecureManager' => 'SecureManager', 'SentEmailCode' => 'SentEmailCode', + 'SharedDialog' => 'SharedDialog', 'sponsored_message_manager[_(-](?![.]get[(][)])|SponsoredMessageManager' => 'SponsoredMessageManager', 'state_manager[_(-](?![.]get[(][)])|StateManager' => 'StateManager', 'statistics_manager[_(-](?![.]get[(][)])|StatisticsManager' => 'StatisticsManager', @@ -411,6 +426,7 @@ function ($matches) use ($needed_std_headers) { 'transcription_manager[_(-](?![.]get[(][)])|TranscriptionManager' => 'TranscriptionManager', 'updates_manager[_(-](?![.]get[(][)])|UpdatesManager|get_difference[)]|updateSentMessage|dummyUpdate' => 'UpdatesManager', 'UserId' => 'UserId', + 'user_manager[_(-](?![.]get[(][)])|UserManager([^ ;.]| [^*])' => 'UserManager', 'video_notes_manager[_(-](?![.]get[(][)])|VideoNotesManager' => 'VideoNotesManager', 'videos_manager[_(-](?![.]get[(][)])|VideosManager' => 'VideosManager', 'voice_notes_manager[_(-](?![.]get[(][)])|VoiceNotesManager' => 'VoiceNotesManager', @@ -456,11 +472,12 @@ function ($matches) { } $undo = in_array('--undo', $argv) || in_array('-u', $argv); -$files = array('td/telegram/ContactsManager' => 20, +$files = array('td/telegram/ChatManager' => 10, 'td/telegram/MessagesManager' => 50, 'td/telegram/NotificationManager' => 10, 'td/telegram/StickersManager' => 10, 'td/telegram/Td' => 50, + 'td/telegram/UserManager' => 10, 'td/generate/auto/td/telegram/td_api' => 10, 'td/generate/auto/td/telegram/td_api_json' => 10, 'td/generate/auto/td/telegram/telegram_api' => 10); diff --git a/lib/tgchat/ext/td/benchmark/CMakeLists.txt b/lib/tgchat/ext/td/benchmark/CMakeLists.txt index 53fbdede..07654b97 100644 --- a/lib/tgchat/ext/td/benchmark/CMakeLists.txt +++ b/lib/tgchat/ext/td/benchmark/CMakeLists.txt @@ -38,7 +38,7 @@ add_executable(bench_http_reader bench_http_reader.cpp) target_link_libraries(bench_http_reader PRIVATE tdnet tdutils) add_executable(bench_handshake bench_handshake.cpp) -target_link_libraries(bench_handshake PRIVATE tdcore tdutils) +target_link_libraries(bench_handshake PRIVATE tdmtproto tdutils) add_executable(bench_db bench_db.cpp) target_link_libraries(bench_db PRIVATE tdactor tddb tdutils) diff --git a/lib/tgchat/ext/td/benchmark/bench_misc.cpp b/lib/tgchat/ext/td/benchmark/bench_misc.cpp index d6ddb620..bfde3ab7 100644 --- a/lib/tgchat/ext/td/benchmark/bench_misc.cpp +++ b/lib/tgchat/ext/td/benchmark/bench_misc.cpp @@ -117,7 +117,7 @@ BENCH(TlToStringMessage, "TL to_string message") { td::vector{10000, 20000, 30000, 50000, 70000, 90000, 120000, 150000, 180000, 220000})); } x->content_ = td::td_api::make_object( - std::move(photo), td::td_api::make_object(), false, false); + std::move(photo), td::td_api::make_object(), false, false, false); std::size_t res = 0; for (int i = 0; i < n; i++) { diff --git a/lib/tgchat/ext/td/build.html b/lib/tgchat/ext/td/build.html index b45ed814..4c198bd1 100644 --- a/lib/tgchat/ext/td/build.html +++ b/lib/tgchat/ext/td/build.html @@ -248,6 +248,7 @@ +

@@ -368,10 +369,10 @@ case 'C#': return ['Windows (through C++/CLI)', 'Universal Windows Platform (through C++/CX)', 'Windows (.NET Core)', 'Linux (.NET Core)', 'macOS (.NET Core)', 'FreeBSD (.NET Core)']; case 'Dart': - return ['Android', 'iOS', 'Windows', 'Linux', 'macOS', 'tvOS', 'watchOS']; + return ['Android', 'iOS', 'Windows', 'Linux', 'macOS', 'tvOS', 'visionOS', 'watchOS']; case 'Swift': case 'Objective-C': - return ['Windows', 'Linux', 'macOS', 'iOS', 'tvOS', 'watchOS']; + return ['Windows', 'Linux', 'macOS', 'iOS', 'tvOS', 'visionOS', 'watchOS']; default: return ['Windows', 'Linux', 'macOS', 'FreeBSD', 'OpenBSD', 'NetBSD']; } @@ -474,7 +475,7 @@ if (os.includes('Android')) { return 'Android'; } - if (os.includes('iOS') || os.includes('tvOS') || os.includes('watchOS')) { + if (os.includes('iOS') || os.includes('tvOS') || os.includes('visionOS') || os.includes('watchOS')) { return 'iOS'; } @@ -496,7 +497,7 @@ case 'iOS': return 'JSON'; case 'tdclient': - return 'a simple and convenient C++11-interface'; + return 'simple and convenient C++11'; case 'JNI': case 'Android': return 'native ' + target + ''; @@ -589,8 +590,8 @@ var low_memory = false; if (os_linux || os_freebsd || os_netbsd) { low_memory = document.getElementById('buildLowMemoryCheckbox').checked; - document.getElementById('buildLowMemoryText').innerHTML = 'I have less than ' + (use_clang ? '1.5' : '3.5') +' GB of RAM.' + - (low_memory ? ' Now you will need only ' + (use_clang ? '0.5' : '1') +' GB of RAM to build TDLib.' : ''); + document.getElementById('buildLowMemoryText').innerHTML = 'I have less than ' + (use_clang ? '2' : '4') +' GB of RAM.' + + (low_memory ? ' Now you will need only ' + (use_clang ? '0.8' : '1.5') +' GB of RAM to build TDLib.' : ''); document.getElementById('buildLowMemoryDiv').style.display = 'block'; } else { if (os_openbsd) { @@ -641,7 +642,7 @@ var use_vcpkg = os_windows; var use_lto = false; - if (!use_msvc && language !== 'Go' && language !== 'Java' && language !== 'Kotlin' && (os_mac || (os_linux && (linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22' || linux_distro === 'Other')))) { + if (!use_msvc && language !== 'Go' && language !== 'Java' && language !== 'Kotlin' && (os_mac || (os_linux && (linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22' || linux_distro === 'Ubuntu 24' || linux_distro === 'Other')))) { document.getElementById('buildLtoDiv').style.display = 'block'; use_lto = document.getElementById('buildLtoCheckbox').checked; } else { @@ -728,7 +729,7 @@ if (os_windows) { pre_text.push('Download and install JDK.'); } else if (os_linux && linux_distro === 'Alpine') { - pre_text.push('Add community repository for your Alpine version (not edge) in /etc/apk/repositories. For example, you can do it through vim, preliminary installing it via "apk add --update vim".'); + pre_text.push('Add community repository for your Alpine version (not edge) in /etc/apk/repositories. For example, you can do it through vim, preliminary installing it via "apk add vim".'); } } if (os_linux && linux_distro === 'Other') { @@ -802,6 +803,19 @@ return '-10'; case 'Ubuntu 22': return '-14'; + case 'Ubuntu 24': + return '-18'; + default: + return ''; // use default version + } + } + + function getLibcplusplusVersionSuffix() { + switch (linux_distro) { + case 'Ubuntu 20': + case 'Ubuntu 22': + case 'Ubuntu 24': + return getClangVersionSuffix(); default: return ''; // use default version } @@ -827,7 +841,7 @@ if (target === 'JNI') { packages += ' openjdk8'; } - commands.push(sudo + 'apk add --update ' + packages); + commands.push(sudo + 'apk add ' + packages); break; case 'CentOS 7': case 'CentOS 8': @@ -855,6 +869,7 @@ case 'Ubuntu 18': case 'Ubuntu 20': case 'Ubuntu 22': + case 'Ubuntu 24': if (linux_distro.includes('Debian') && !use_root) { commands.push('su -'); } @@ -878,9 +893,9 @@ packages += ' default-jdk'; } if (use_clang) { - packages += ' clang' + getClangVersionSuffix() + ' libc++-dev'; - if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22') { - packages += ' libc++abi-dev'; + packages += ' clang' + getClangVersionSuffix() + ' libc++' + getLibcplusplusVersionSuffix() + '-dev'; + if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22' || linux_distro === 'Ubuntu 24') { + packages += ' libc++abi' + getLibcplusplusVersionSuffix() + '-dev'; } } else { packages += ' g++'; diff --git a/lib/tgchat/ext/td/example/README.md b/lib/tgchat/ext/td/example/README.md index 7a29c3d1..74f26b2c 100644 --- a/lib/tgchat/ext/td/example/README.md +++ b/lib/tgchat/ext/td/example/README.md @@ -140,7 +140,7 @@ or [MeeGram](https://github.com/qtinsider/meegram2) - a Telegram client for Noki TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically. -See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS. +See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS. See [TDLibKit](https://github.com/Swiftgram/TDLibKit), [tdlib-swift](https://github.com/modestman/tdlib-swift), or [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects. @@ -153,7 +153,7 @@ See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for a TDLib can be used from the Objective-C programming language through [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically. -See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS. +See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS. ## Using TDLib in Object Pascal projects with Delphi and Lazarus diff --git a/lib/tgchat/ext/td/example/android/CMakeLists.txt b/lib/tgchat/ext/td/example/android/CMakeLists.txt index a31b1fc9..71b2cb58 100644 --- a/lib/tgchat/ext/td/example/android/CMakeLists.txt +++ b/lib/tgchat/ext/td/example/android/CMakeLists.txt @@ -2,10 +2,19 @@ cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR) project(TdAndroid VERSION 1.0 LANGUAGES CXX) -option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON) - set(TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..) +if (TD_ANDROID_JSON) + if (CMAKE_CROSSCOMPILING) + string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz") + list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}") + endif() + add_subdirectory(${TD_DIR} td) + return() +endif() + +option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON) + if (CMAKE_CROSSCOMPILING) set(CMAKE_MODULE_PATH "${TD_DIR}/CMake") diff --git a/lib/tgchat/ext/td/example/android/Dockerfile b/lib/tgchat/ext/td/example/android/Dockerfile index 310e8f7e..3c782ca6 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:22.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/* @@ -15,10 +15,11 @@ RUN ./build-openssl.sh SDK "$ANDROID_NDK_VERSION" openssl "$OPENSSL_VERSION" ADD "https://api.github.com/repos/tdlib/td/git/refs/heads/master" version.json ARG COMMIT_HASH=master RUN git clone https://github.com/tdlib/td.git && cd td && git checkout "$COMMIT_HASH" -RUN cd td && git merge-base --is-ancestor bcd89728c3b93b67448b93b4863dc5bd4e122a4c "$COMMIT_HASH" +RUN cd td && git merge-base --is-ancestor f28fa6a860af8a88a330d22fe34628eba6d69d89 "$COMMIT_HASH" ARG ANDROID_STL=c++_static -RUN td/example/android/build-tdlib.sh SDK "$ANDROID_NDK_VERSION" openssl "$ANDROID_STL" && rm -rf td/example/android/build-* +ARG TDLIB_INTERFACE=Java +RUN td/example/android/build-tdlib.sh SDK "$ANDROID_NDK_VERSION" openssl "$ANDROID_STL" "$TDLIB_INTERFACE" && rm -rf td/example/android/build-* FROM scratch diff --git a/lib/tgchat/ext/td/example/android/README.md b/lib/tgchat/ext/td/example/android/README.md index c32ec409..d61a2e14 100644 --- a/lib/tgchat/ext/td/example/android/README.md +++ b/lib/tgchat/ext/td/example/android/README.md @@ -9,9 +9,9 @@ You need a Bash shell on Linux, macOS, or Windows with some common tools, a C++ * Run the script `./fetch-sdk.sh` to download Android SDK to a local directory. * Run the script `./build-openssl.sh` to download and build OpenSSL for Android. * Run the script `./build-tdlib.sh` to build TDLib for Android. -* The built libraries are now located in the `tdlib/libs` directory, corresponding Java code is located in the `tdlib/java` directory, and standalone Java documentation can be found in the `tdlib/javadoc` directory. You can also use archives `tdlib/tdlib.zip` and `tdlib/tdlib-debug.zip`, which contain all aforementioned data. +* The built libraries are now located in the `tdlib/libs` directory. If [Java](https://github.com/tdlib/td#using-java) interface was built, then corresponding Java code is located in the `tdlib/java` directory, and standalone Java documentation can be found in the `tdlib/javadoc` directory. You can also use archives `tdlib/tdlib.zip` and `tdlib/tdlib-debug.zip`, which contain all aforementioned data. -If you already have installed Android SDK and NDK, you can skip the second step and specify existing Android SDK root path and Android NDK version as the first and the second parameters to the subsequent scripts. Make sure that the SDK includes android-33 platform and CMake 3.22.1. +If you already have installed Android SDK and NDK, you can skip the second step and specify existing Android SDK root path and Android NDK version as the first and the second parameters to the subsequent scripts. Make sure that the SDK includes android-34 platform and CMake 3.22.1. If you already have prebuilt OpenSSL, you can skip the third step and specify path to the prebuild OpenSSL as the third parameter to the script `./build-tdlib.sh`. @@ -21,4 +21,8 @@ You can specify different OpenSSL version as the fourth parameter to the script You can build TDLib against shared standard C++ library by specifying "c++_shared" as the fourth parameter to the script `./build-tdlib.sh`. This can reduce total application size if you have a lot of other C++ code and want it to use the same shared library. -Alternatively, you can use Docker to build TDLib for Android. Use `docker build --output tdlib .` to build the latest TDLib commit from Github, or `docker build --build-arg COMMIT_HASH= --output tdlib .` to build specific commit. The output archives will be placed in the directory "tdlib" as specified. +You can build TDLib with [JSON interface](https://github.com/tdlib/td#using-json) instead of [Java](https://github.com/tdlib/td#using-java) interface by passing "JSON" as the fifth parameter to the script `./build-tdlib.sh`. + +You can pass an empty string instead of any script parameter to use its default value. For example, you can use the command `./build-tdlib.sh '' '' '' '' 'JSON'` to build TDLib with [JSON interface](https://github.com/tdlib/td#using-json) using default values for other parameters. + +Alternatively, you can use Docker to build TDLib for Android. Use `docker build --output tdlib .` to build the latest TDLib commit from Github, or `docker build --build-arg COMMIT_HASH= --output tdlib .` to build specific commit. The output archives will be placed in the directory "tdlib" as specified. Additionally, you can specify build arguments "TDLIB_INTERFACE", "ANDROID_NDK_VERSION", "OPENSSL_VERSION", and "ANDROID_STL" to the provided Dockerfile. For example, use `docker build --build-arg TDLIB_INTERFACE=JSON --output tdlib .` to build the latest TDLib with JSON interface. diff --git a/lib/tgchat/ext/td/example/android/build-openssl.sh b/lib/tgchat/ext/td/example/android/build-openssl.sh index e4b82e06..b0e35ba9 100755 --- a/lib/tgchat/ext/td/example/android/build-openssl.sh +++ b/lib/tgchat/ext/td/example/android/build-openssl.sh @@ -49,6 +49,9 @@ ANDROID_API64=21 if [[ ${ANDROID_NDK_VERSION%%.*} -ge 24 ]] ; then ANDROID_API32=19 fi +if [[ ${ANDROID_NDK_VERSION%%.*} -ge 26 ]] ; then + ANDROID_API32=21 +fi for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do if [[ $ABI == "x86" ]] ; then diff --git a/lib/tgchat/ext/td/example/android/build-tdlib.sh b/lib/tgchat/ext/td/example/android/build-tdlib.sh index 1ff9ae7a..3a257989 100755 --- a/lib/tgchat/ext/td/example/android/build-tdlib.sh +++ b/lib/tgchat/ext/td/example/android/build-tdlib.sh @@ -4,12 +4,18 @@ ANDROID_SDK_ROOT=${1:-SDK} ANDROID_NDK_VERSION=${2:-23.2.8568313} OPENSSL_INSTALL_DIR=${3:-third-party/openssl} ANDROID_STL=${4:-c++_static} +TDLIB_INTERFACE=${5:-Java} if [ "$ANDROID_STL" != "c++_static" ] && [ "$ANDROID_STL" != "c++_shared" ] ; then echo 'Error: ANDROID_STL must be either "c++_static" or "c++_shared".' exit 1 fi +if [ "$TDLIB_INTERFACE" != "Java" ] && [ "$TDLIB_INTERFACE" != "JSON" ] ; then + echo 'Error: TDLIB_INTERFACE must be either "Java" or "JSON".' + exit 1 +fi + source ./check-environment.sh || exit 1 if [ ! -d "$ANDROID_SDK_ROOT" ] ; then @@ -26,45 +32,57 @@ ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$ ANDROID_NDK_ROOT="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")" PATH=$ANDROID_SDK_ROOT/cmake/3.22.1/bin:$PATH +TDLIB_INTERFACE_OPTION=$([ "$TDLIB_INTERFACE" == "JSON" ] && echo "-DTD_ANDROID_JSON=ON" || echo "") cd $(dirname $0) -echo "Downloading annotation Java package..." -rm -f android.jar annotation-1.4.0.jar || exit 1 -$WGET https://maven.google.com/androidx/annotation/annotation/1.4.0/annotation-1.4.0.jar || exit 1 - echo "Generating TDLib source files..." -mkdir -p build-native || exit 1 -cd build-native -cmake .. || exit 1 +mkdir -p build-native-$TDLIB_INTERFACE || exit 1 +cd build-native-$TDLIB_INTERFACE +cmake $TDLIB_INTERFACE_OPTION .. || exit 1 cmake --build . --target prepare_cross_compiling || exit 1 -cmake --build . --target tl_generate_java || exit 1 cd .. -php AddIntDef.php org/drinkless/tdlib/TdApi.java || exit 1 -echo "Copying Java source files..." rm -rf tdlib || exit 1 -mkdir -p tdlib/java/org/drinkless/tdlib || exit 1 -cp -p {../../example,tdlib}/java/org/drinkless/tdlib/Client.java || exit 1 -mv {,tdlib/java/}org/drinkless/tdlib/TdApi.java || exit 1 -rm -rf org || exit 1 -echo "Generating Javadoc documentation..." -cp "$ANDROID_SDK_ROOT/platforms/android-33/android.jar" . || exit 1 -JAVADOC_SEPARATOR=$([ "$OS_NAME" == "win" ] && echo ";" || echo ":") -javadoc -d tdlib/javadoc -encoding UTF-8 -charset UTF-8 -classpath "android.jar${JAVADOC_SEPARATOR}annotation-1.4.0.jar" -quiet -sourcepath tdlib/java org.drinkless.tdlib || exit 1 -rm android.jar annotation-1.4.0.jar || exit 1 +if [ "$TDLIB_INTERFACE" == "Java" ] ; then + echo "Downloading annotation Java package..." + rm -f android.jar annotation-1.4.0.jar || exit 1 + $WGET https://maven.google.com/androidx/annotation/annotation/1.4.0/annotation-1.4.0.jar || exit 1 + + echo "Generating Java source files..." + cmake --build build-native-$TDLIB_INTERFACE --target tl_generate_java || exit 1 + php AddIntDef.php org/drinkless/tdlib/TdApi.java || exit 1 + mkdir -p tdlib/java/org/drinkless/tdlib || exit 1 + cp -p {../../example,tdlib}/java/org/drinkless/tdlib/Client.java || exit 1 + mv {,tdlib/java/}org/drinkless/tdlib/TdApi.java || exit 1 + rm -rf org || exit 1 + + echo "Generating Javadoc documentation..." + cp "$ANDROID_SDK_ROOT/platforms/android-34/android.jar" . || exit 1 + JAVADOC_SEPARATOR=$([ "$OS_NAME" == "win" ] && echo ";" || echo ":") + javadoc -d tdlib/javadoc -encoding UTF-8 -charset UTF-8 -classpath "android.jar${JAVADOC_SEPARATOR}annotation-1.4.0.jar" -quiet -sourcepath tdlib/java org.drinkless.tdlib || exit 1 + rm android.jar annotation-1.4.0.jar || exit 1 +fi echo "Building TDLib..." for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do - mkdir -p build-$ABI || exit 1 - cd build-$ABI - cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake" -DOPENSSL_ROOT_DIR="$OPENSSL_INSTALL_DIR/$ABI" -DCMAKE_BUILD_TYPE=RelWithDebInfo -GNinja -DANDROID_ABI=$ABI -DANDROID_STL=$ANDROID_STL -DANDROID_PLATFORM=android-16 .. || exit 1 - cmake --build . || exit 1 + mkdir -p tdlib/libs/$ABI/ || exit 1 + + mkdir -p build-$ABI-$TDLIB_INTERFACE || exit 1 + cd build-$ABI-$TDLIB_INTERFACE + cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake" -DOPENSSL_ROOT_DIR="$OPENSSL_INSTALL_DIR/$ABI" -DCMAKE_BUILD_TYPE=RelWithDebInfo -GNinja -DANDROID_ABI=$ABI -DANDROID_STL=$ANDROID_STL -DANDROID_PLATFORM=android-16 $TDLIB_INTERFACE_OPTION .. || exit 1 + if [ "$TDLIB_INTERFACE" == "Java" ] ; then + cmake --build . --target tdjni || exit 1 + cp -p libtd*.so* ../tdlib/libs/$ABI/ || exit 1 + fi + if [ "$TDLIB_INTERFACE" == "JSON" ] ; then + cmake --build . --target tdjson || exit 1 + cp -p td/libtdjson.so ../tdlib/libs/$ABI/libtdjson.so.debug || exit 1 + "$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" --strip-debug --strip-unneeded ../tdlib/libs/$ABI/libtdjson.so.debug -o ../tdlib/libs/$ABI/libtdjson.so || exit 1 + fi cd .. - mkdir -p tdlib/libs/$ABI/ || exit 1 - cp -p build-$ABI/libtd*.so* tdlib/libs/$ABI/ || exit 1 if [[ "$ANDROID_STL" == "c++_shared" ]] ; then if [[ "$ABI" == "arm64-v8a" ]] ; then FULL_ABI="aarch64-linux-android" diff --git a/lib/tgchat/ext/td/example/android/fetch-sdk.sh b/lib/tgchat/ext/td/example/android/fetch-sdk.sh index 61d93170..e3649574 100755 --- a/lib/tgchat/ext/td/example/android/fetch-sdk.sh +++ b/lib/tgchat/ext/td/example/android/fetch-sdk.sh @@ -18,13 +18,13 @@ fi echo "Downloading SDK Manager..." mkdir -p "$ANDROID_SDK_ROOT" || exit 1 cd "$ANDROID_SDK_ROOT" || exit 1 -$WGET "https://dl.google.com/android/repository/commandlinetools-$OS_NAME-8512546_latest.zip" || exit 1 +$WGET "https://dl.google.com/android/repository/commandlinetools-$OS_NAME-11076708_latest.zip" || exit 1 mkdir -p cmdline-tools || exit 1 -unzip -qq "commandlinetools-$OS_NAME-8512546_latest.zip" -d cmdline-tools || exit 1 -rm "commandlinetools-$OS_NAME-8512546_latest.zip" || exit 1 +unzip -qq "commandlinetools-$OS_NAME-11076708_latest.zip" -d cmdline-tools || exit 1 +rm "commandlinetools-$OS_NAME-11076708_latest.zip" || exit 1 mv cmdline-tools/* cmdline-tools/latest/ || exit 1 echo "Installing required SDK tools..." cd cmdline-tools/latest/bin/ || exit 1 yes | $SDKMANAGER --licenses >/dev/null || exit 1 -$SDKMANAGER --install "ndk;$ANDROID_NDK_VERSION" "cmake;3.22.1" "build-tools;33.0.0" "platforms;android-33" > /dev/null || exit 1 +$SDKMANAGER --install "ndk;$ANDROID_NDK_VERSION" "cmake;3.22.1" "build-tools;34.0.0" "platforms;android-34" > /dev/null || exit 1 diff --git a/lib/tgchat/ext/td/example/cpp/CMakeLists.txt b/lib/tgchat/ext/td/example/cpp/CMakeLists.txt index f49f9d75..11452861 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.26 REQUIRED) +find_package(Td 1.8.30 REQUIRED) add_executable(tdjson_example tdjson_example.cpp) target_link_libraries(tdjson_example PRIVATE Td::TdJson) diff --git a/lib/tgchat/ext/td/example/ios/Python-Apple-support.patch b/lib/tgchat/ext/td/example/ios/Python-Apple-support.patch index a8a217a4..4b03ec28 100644 --- a/lib/tgchat/ext/td/example/ios/Python-Apple-support.patch +++ b/lib/tgchat/ext/td/example/ios/Python-Apple-support.patch @@ -1,8 +1,8 @@ diff --git a/Makefile b/Makefile -index a1d13e9..a0841cf 100644 +index a1d13e9..8efcf20 100644 --- a/Makefile +++ b/Makefile -@@ -18,8 +18,11 @@ +@@ -18,8 +18,13 @@ # - OpenSSL - build OpenSSL for all platforms # - OpenSSL-macOS - build OpenSSL for macOS # - OpenSSL-iOS - build OpenSSL for iOS @@ -11,10 +11,12 @@ index a1d13e9..a0841cf 100644 +# - OpenSSL-tvOS-simulator - build OpenSSL for tvOS-simulator # - OpenSSL-watchOS - build OpenSSL for watchOS +# - OpenSSL-watchOS-simulator - build OpenSSL for watchOS-simulator ++# - OpenSSL-visionOS - build OpenSSL for visionOS ++# - OpenSSL-visionOS-simulator - build OpenSSL for visionOS-simulator # - libFFI - build libFFI for all platforms (except macOS) # - libFFI-iOS - build libFFI for iOS # - libFFI-tvOS - build libFFI for tvOS -@@ -50,7 +53,7 @@ XZ_VERSION=5.4.2 +@@ -50,7 +55,7 @@ XZ_VERSION=5.4.2 # Preference is to use OpenSSL 3; however, Cryptography 3.4.8 (and # probably some other packages as well) only works with 1.1.1, so # we need to preserve the ability to build the older OpenSSL (for now...) @@ -23,16 +25,16 @@ index a1d13e9..a0841cf 100644 # OPENSSL_VERSION_NUMBER=1.1.1 # OPENSSL_REVISION=q # OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION) -@@ -59,7 +62,7 @@ LIBFFI_VERSION=3.4.2 +@@ -59,7 +64,7 @@ LIBFFI_VERSION=3.4.2 # Supported OS and dependencies DEPENDENCIES=BZip2 XZ OpenSSL libFFI -OS_LIST=macOS iOS tvOS watchOS -+OS_LIST=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator ++OS_LIST=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator visionOS visionOS-simulator CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar -@@ -69,22 +72,34 @@ VERSION_MIN-macOS=10.15 +@@ -69,22 +74,41 @@ VERSION_MIN-macOS=10.15 CFLAGS-macOS=-mmacosx-version-min=$(VERSION_MIN-macOS) # iOS targets @@ -66,11 +68,29 @@ index a1d13e9..a0841cf 100644 +# watchOS-simulator targets +TARGETS-watchOS-simulator=watchsimulator.i386 watchsimulator.x86_64 watchsimulator.arm64 +CFLAGS-watchOS-simulator=-mwatchos-simulator-version-min=$(VERSION_MIN-watchOS) ++ ++# visionOS targets ++TARGETS-visionOS=xros.arm64 ++PYTHON_CONFIGURE-visionOS=ac_cv_func_sigaltstack=no ++ ++# visionOS-simulator targets ++TARGETS-visionOS-simulator=xrsimulator.x86_64 xrsimulator.arm64 + # The architecture of the machine doing the build HOST_ARCH=$(shell uname -m) HOST_PYTHON=install/macOS/macosx/python-$(PYTHON_VERSION) -@@ -662,7 +677,7 @@ BZIP2_FATLIB-$(sdk)=$$(BZIP2_MERGE-$(sdk))/lib/libbz2.a +@@ -212,6 +236,10 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target))) + + ifeq ($(os),macOS) + TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-darwin ++else ifeq ($(os),visionOS) ++TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-xros ++else ifeq ($(os),visionOS-simulator) ++TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-xros-simulator + else + ifeq ($$(findstring simulator,$$(SDK-$(target))),) + TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target)) +@@ -662,7 +690,7 @@ BZIP2_FATLIB-$(sdk)=$$(BZIP2_MERGE-$(sdk))/lib/libbz2.a XZ_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/$(sdk)/xz-$(XZ_VERSION) XZ_FATLIB-$(sdk)=$$(XZ_MERGE-$(sdk))/lib/liblzma.a @@ -79,7 +99,7 @@ index a1d13e9..a0841cf 100644 OPENSSL_FATINCLUDE-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/include OPENSSL_SSL_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libssl.a OPENSSL_CRYPTO_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libcrypto.a -@@ -716,14 +731,14 @@ $$(OPENSSL_SSL_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENS +@@ -716,14 +744,14 @@ $$(OPENSSL_SSL_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENS mkdir -p $$(OPENSSL_MERGE-$(sdk))/lib lipo -create -output $$@ \ $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_SSL_LIB-$$(target))) \ diff --git a/lib/tgchat/ext/td/example/ios/README.md b/lib/tgchat/ext/td/example/ios/README.md index e8a2f8d1..e1bd1ede 100644 --- a/lib/tgchat/ext/td/example/ios/README.md +++ b/lib/tgchat/ext/td/example/ios/README.md @@ -1,6 +1,6 @@ # Universal XCFramework build example -Below are instructions for building TDLib for iOS, watchOS, tvOS, and also macOS. +Below are instructions for building TDLib for iOS, watchOS, tvOS, visionOS, and also macOS. If you need only a macOS build for the current architecture, take a look at [TDLib build instructions generator](https://tdlib.github.io/td/build.html). @@ -21,7 +21,7 @@ cd native-build cmake .. cmake --build . --target prepare_cross_compiling ``` -* Build OpenSSL for iOS, watchOS, tvOS, and macOS: +* Build OpenSSL for iOS, watchOS, tvOS, visionOS, and macOS: ``` cd /example/ios ./build-openssl.sh @@ -30,7 +30,7 @@ Here we use scripts from [Python Apple support](https://github.com/beeware/Pytho [Python Apple support](https://github.com/beeware/Python-Apple-support) has known problems with spaces in the path to the current directory, so you need to ensure that there are no spaces in the path. Built OpenSSL libraries should be stored in the directory `third_party/openssl/`, because the next script will rely on this location. -* Build TDLib for iOS, watchOS, tvOS, and macOS: +* Build TDLib for iOS, watchOS, tvOS, visionOS, and macOS: ``` cd /example/ios ./build.sh diff --git a/lib/tgchat/ext/td/example/ios/build-openssl.sh b/lib/tgchat/ext/td/example/ios/build-openssl.sh index 7c3c6cd9..91fe6e6d 100755 --- a/lib/tgchat/ext/td/example/ios/build-openssl.sh +++ b/lib/tgchat/ext/td/example/ios/build-openssl.sh @@ -8,7 +8,7 @@ git reset --hard || exit 1 git apply ../Python-Apple-support.patch || exit 1 cd .. -platforms="macOS iOS watchOS tvOS" +platforms="macOS iOS watchOS tvOS visionOS" for platform in $platforms; do diff --git a/lib/tgchat/ext/td/example/ios/build.sh b/lib/tgchat/ext/td/example/ios/build.sh index cd11907b..ca19b285 100755 --- a/lib/tgchat/ext/td/example/ios/build.sh +++ b/lib/tgchat/ext/td/example/ios/build.sh @@ -21,7 +21,7 @@ set_cmake_options () { options="$options -DCMAKE_BUILD_TYPE=Release" } -platforms="macOS iOS watchOS tvOS" +platforms="macOS iOS watchOS tvOS visionOS" #platforms="watchOS" for platform in $platforms; do @@ -41,6 +41,8 @@ do ios_platform="WATCH" elif [[ $platform = "tvOS" ]]; then ios_platform="TV" + elif [[ $platform = "visionOS" ]]; then + ios_platform="VISION" else ios_platform="" fi 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 77ee70e8..f67e3662 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.26 + 1.8.30 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 757b9953..494cc9f8 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/CMakeLists.txt b/lib/tgchat/ext/td/td/generate/CMakeLists.txt index d822357c..d417c078 100644 --- a/lib/tgchat/ext/td/td/generate/CMakeLists.txt +++ b/lib/tgchat/ext/td/td/generate/CMakeLists.txt @@ -14,10 +14,14 @@ set(TL_TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto PARENT_SCOPE) set(TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto/td) -set(TL_TD_AUTO_SOURCE +set(TL_MTPROTO_AUTO_SOURCE ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.cpp ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.h ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.hpp + PARENT_SCOPE +) + +set(TL_TD_AUTO_SOURCE ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.cpp ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.h ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.hpp diff --git a/lib/tgchat/ext/td/td/generate/DotnetTlDocumentationGenerator.php b/lib/tgchat/ext/td/td/generate/DotnetTlDocumentationGenerator.php index fc7b99b8..f6bb561f 100644 --- a/lib/tgchat/ext/td/td/generate/DotnetTlDocumentationGenerator.php +++ b/lib/tgchat/ext/td/td/generate/DotnetTlDocumentationGenerator.php @@ -177,9 +177,12 @@ protected function addClassDocumentation($class_name, $base_class_name, $return_ protected function addFieldDocumentation($class_name, $field_name, $type_name, $field_info, $may_be_null) { $end = ';'; - if (substr($type_name, 0, strlen($field_name)) === $field_name) { + if ($type_name == $field_name.'^' || ($type_name == 'Message^' && $field_name == 'ReplyToMessage')) { $type_name = '::Telegram::Td::Api::'.$type_name; $end = ' {'; + } else if ($class_name == "WebPage" && $field_name == "Stickers" && $type_name == "Array^") { + $type_name = 'Array<::Telegram::Td::Api::Sticker^>^'; + $end = ' {'; } $full_line = $class_name." property $type_name $field_name$end"; $this->addDocumentation($full_line, <<(std::move(message_text), nullptr, true)); * \\endcode * - * \\tparam Type Type of an object to construct. + * \\tparam Type Type of object to construct. * \\param[in] args Arguments to pass to the object constructor. * \\return Wrapped pointer to the created object. */ @@ -287,8 +287,8 @@ protected function addGlobalDocumentation() * } * \\endcode * - * \\tparam ToType Type of a TDLib API object to move to. - * \\tparam FromType Type of a TDLib API object to move from, this is auto-deduced. + * \\tparam ToType Type of TDLib API object to move to. + * \\tparam FromType Type of TDLib API object to move from, this is auto-deduced. * \\param[in] from Wrapped in td::td_api::object_ptr pointer to a TDLib API object. */ EOT 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 aa2ef41d..d8dbc7b6 100644 --- a/lib/tgchat/ext/td/td/generate/scheme/td_api.tl +++ b/lib/tgchat/ext/td/td/generate/scheme/td_api.tl @@ -24,15 +24,23 @@ ok = Ok; //@class AuthenticationCodeType @description Provides information about the method by which an authentication code is delivered to the user -//@description An authentication code is delivered via a private Telegram message, which can be viewed from another active session +//@description A digit-only authentication code is delivered via a private Telegram message, which can be viewed from another active session //@length Length of the code authenticationCodeTypeTelegramMessage length:int32 = AuthenticationCodeType; -//@description An authentication code is delivered via an SMS message to the specified phone number; applications may not receive this type of code +//@description A digit-only authentication code is delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code //@length Length of the code authenticationCodeTypeSms length:int32 = AuthenticationCodeType; -//@description An authentication code is delivered via a phone call to the specified phone number +//@description An authentication code is a word delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code +//@first_letter The first letters of the word if known +authenticationCodeTypeSmsWord first_letter:string = AuthenticationCodeType; + +//@description An authentication code is a phrase from multiple words delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code +//@first_word The first word of the phrase if known +authenticationCodeTypeSmsPhrase first_word:string = AuthenticationCodeType; + +//@description A digit-only authentication code is delivered via a phone call to the specified phone number //@length Length of the code authenticationCodeTypeCall length:int32 = AuthenticationCodeType; @@ -45,17 +53,18 @@ authenticationCodeTypeFlashCall pattern:string = AuthenticationCodeType; //@length Number of digits in the code, excluding the prefix authenticationCodeTypeMissedCall phone_number_prefix:string length:int32 = AuthenticationCodeType; -//@description An authentication code is delivered to https://fragment.com. The user must be logged in there via a wallet owning the phone number's NFT +//@description A digit-only authentication code is delivered to https://fragment.com. The user must be logged in there via a wallet owning the phone number's NFT //@url URL to open to receive the code //@length Length of the code authenticationCodeTypeFragment url:string length:int32 = AuthenticationCodeType; -//@description An authentication code is delivered via Firebase Authentication to the official Android application -//@nonce Nonce to pass to the SafetyNet Attestation API +//@description A digit-only authentication code is delivered via Firebase Authentication to the official Android application +//@use_play_integrity True, if Play Integrity API must be used for device verification. Otherwise, SafetyNet Attestation API must be used +//@nonce Nonce to pass to the Play Integrity API or the SafetyNet Attestation API //@length Length of the code -authenticationCodeTypeFirebaseAndroid nonce:bytes length:int32 = AuthenticationCodeType; +authenticationCodeTypeFirebaseAndroid use_play_integrity:Bool nonce:bytes length:int32 = AuthenticationCodeType; -//@description An authentication code is delivered via Firebase Authentication to the official iOS application +//@description A digit-only authentication code is delivered via Firebase Authentication to the official iOS application //@receipt Receipt of successful application token validation to compare with receipt from push notification //@push_timeout Time after the next authentication method is supposed to be used if verification push notification isn't received, in seconds //@length Length of the code @@ -75,7 +84,7 @@ authenticationCodeInfo phone_number:string type:AuthenticationCodeType next_type emailAddressAuthenticationCodeInfo email_address_pattern:string length:int32 = EmailAddressAuthenticationCodeInfo; -//@class EmailAddressAuthentication @description Contains authentication data for a email address +//@class EmailAddressAuthentication @description Contains authentication data for an email address //@description An authentication code delivered to a user's email address @code The code emailAddressAuthenticationCode code:string = EmailAddressAuthentication; @@ -87,7 +96,7 @@ emailAddressAuthenticationAppleId token:string = EmailAddressAuthentication; emailAddressAuthenticationGoogleId token:string = EmailAddressAuthentication; -//@class EmailAddressResetState @description Describes reset state of a email address +//@class EmailAddressResetState @description Describes reset state of an email address //@description Email address can be reset after the given period. Call resetAuthenticationEmailAddress to reset it and allow the user to authorize with a code sent to the user's phone number //@wait_period Time required to wait before the email address can be reset; 0 if the user is subscribed to Telegram Premium @@ -259,13 +268,13 @@ thumbnailFormatMpeg4 = ThumbnailFormat; //@description The thumbnail is in PNG format. It will be used only for background patterns thumbnailFormatPng = ThumbnailFormat; -//@description The thumbnail is in TGS format. It will be used only for TGS sticker sets +//@description The thumbnail is in TGS format. It will be used only for sticker sets thumbnailFormatTgs = ThumbnailFormat; -//@description The thumbnail is in WEBM format. It will be used only for WEBM sticker sets +//@description The thumbnail is in WEBM format. It will be used only for sticker sets thumbnailFormatWebm = ThumbnailFormat; -//@description The thumbnail is in WEBP format. It will be used only for some stickers +//@description The thumbnail is in WEBP format. It will be used only for some stickers and sticker sets thumbnailFormatWebp = ThumbnailFormat; @@ -311,7 +320,7 @@ stickerFormatTgs = StickerFormat; stickerFormatWebm = StickerFormat; -//@class StickerType @description Describes type of a sticker +//@class StickerType @description Describes type of sticker //@description The sticker is a regular sticker stickerTypeRegular = StickerType; @@ -342,15 +351,15 @@ closedVectorPath commands:vector = ClosedVectorPath; //@description Describes one answer option of a poll -//@text Option text; 1-100 characters +//@text Option text; 1-100 characters. Only custom emoji entities are allowed //@voter_count Number of voters for this option, available only for closed or voted polls //@vote_percentage The percentage of votes for this option; 0-100 //@is_chosen True, if the option was chosen by the user //@is_being_chosen True, if the option is being chosen by a pending setPollAnswer request -pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption; +pollOption text:formattedText voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption; -//@class PollType @description Describes the type of a poll +//@class PollType @description Describes the type of poll //@description A regular poll @allow_multiple_answers True, if multiple answer options can be chosen simultaneously pollTypeRegular allow_multiple_answers:Bool = PollType; @@ -435,16 +444,16 @@ video duration:int32 width:int32 height:int32 file_name:string mime_type:string //@video File containing the video videoNote duration:int32 waveform:bytes length:int32 minithumbnail:minithumbnail thumbnail:thumbnail speech_recognition_result:SpeechRecognitionResult video:file = VideoNote; -//@description Describes a voice note. The voice note must be encoded with the Opus codec, and stored inside an OGG container. Voice notes can have only a single audio channel +//@description Describes a voice note //@duration Duration of the voice note, in seconds; as defined by the sender //@waveform A waveform representation of the voice note in 5-bit format -//@mime_type MIME type of the file; as defined by the sender +//@mime_type MIME type of the file; as defined by the sender. Usually, one of "audio/ogg" for Opus in an OGG container, "audio/mpeg" for an MP3 audio, or "audio/mp4" for an M4A audio //@speech_recognition_result Result of speech recognition in the voice note; may be null //@voice File containing the voice note voiceNote duration:int32 waveform:bytes mime_type:string speech_recognition_result:SpeechRecognitionResult voice:file = VoiceNote; //@description Describes an animated or custom representation of an emoji -//@sticker Sticker for the emoji; may be null if yet unknown for a custom emoji. If the sticker is a custom emoji, it can have arbitrary format different from stickerFormatTgs +//@sticker Sticker for the emoji; may be null if yet unknown for a custom emoji. If the sticker is a custom emoji, then it can have arbitrary format //@sticker_width Expected width of the sticker, which can be used if the sticker is null //@sticker_height Expected height of the sticker, which can be used if the sticker is null //@fitzpatrick_type Emoji modifier fitzpatrick type; 0-6; 0 if none @@ -494,7 +503,7 @@ webApp short_name:string title:string description:string photo:photo animation:a //@description Describes a poll //@id Unique poll identifier -//@question Poll question; 1-300 characters +//@question Poll question; 1-300 characters. Only custom emoji entities are allowed //@options List of poll answer options //@total_voter_count Total number of voters, participating in the poll //@recent_voter_ids Identifiers of recent voters, if the poll is non-anonymous @@ -503,7 +512,7 @@ webApp short_name:string title:string description:string photo:photo animation:a //@open_period Amount of time the poll will be active after creation, in seconds //@close_date Point in time (Unix timestamp) when the poll will automatically be closed //@is_closed True, if the poll is closed -poll id:int64 question:string options:vector total_voter_count:int32 recent_voter_ids:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = Poll; +poll id:int64 question:formattedText options:vector total_voter_count:int32 recent_voter_ids:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = Poll; //@description Describes a chat background @@ -540,7 +549,7 @@ profilePhoto id:int64 small:file big:file minithumbnail:minithumbnail has_animat chatPhotoInfo small:file big:file minithumbnail:minithumbnail has_animation:Bool is_personal:Bool = ChatPhotoInfo; -//@class UserType @description Represents the type of a user. The following types are possible: regular users, deleted users and bots +//@class UserType @description Represents the type of user. The following types are possible: regular users, deleted users and bots //@description A regular user userTypeRegular = UserType; @@ -555,8 +564,9 @@ userTypeDeleted = UserType; //@is_inline True, if the bot supports inline queries //@inline_query_placeholder Placeholder for inline queries (displayed on the application input field) //@need_location True, if the location of the user is expected to be sent with every inline query to this bot +//@can_connect_to_business True, if the bot supports connection to Telegram Business accounts //@can_be_added_to_attachment_menu True, if the bot can be added to attachment or side menu -userTypeBot can_be_edited:Bool can_join_groups:Bool can_read_all_group_messages:Bool is_inline:Bool inline_query_placeholder:string need_location:Bool can_be_added_to_attachment_menu:Bool = UserType; +userTypeBot can_be_edited:Bool can_join_groups:Bool can_read_all_group_messages:Bool is_inline:Bool inline_query_placeholder:string need_location:Bool can_connect_to_business:Bool can_be_added_to_attachment_menu:Bool = UserType; //@description No information on the user besides the user identifier is available, yet this user has not been deleted. This object is extremely rare and must be handled like a deleted user. It is not possible to perform any actions on users of this type userTypeUnknown = UserType; @@ -576,6 +586,13 @@ botMenuButton text:string url:string = BotMenuButton; chatLocation location:location address:string = ChatLocation; +//@description Represents a birthdate of a user @day Day of the month; 1-31 @month Month of the year; 1-12 @year Birth year; 0 if unknown +birthdate day:int32 month:int32 year:int32 = Birthdate; + +//@description Describes a user that had or will have a birthday soon @user_id User identifier @birthdate Birthdate of the user +closeBirthdayUser user_id:int53 birthdate:birthdate = CloseBirthdayUser; + + //@class BusinessAwayMessageSchedule @description Describes conditions for sending of away messages by a Telegram Business account //@description Send away messages always @@ -595,12 +612,13 @@ businessLocation location:location address:string = BusinessLocation; //@description Describes private chats chosen for automatic interaction with a business //@chat_ids Identifiers of selected private chats +//@excluded_chat_ids Identifiers of private chats that are always excluded; for businessConnectedBot only //@select_existing_chats True, if all existing private chats are selected //@select_new_chats True, if all new private chats are selected //@select_contacts True, if all private chats with contacts are selected //@select_non_contacts True, if all private chats with non-contacts are selected //@exclude_selected If true, then all private chats except the selected are chosen. Otherwise, only the selected chats are chosen -businessRecipients chat_ids:vector select_existing_chats:Bool select_new_chats:Bool select_contacts:Bool select_non_contacts:Bool exclude_selected:Bool = BusinessRecipients; +businessRecipients chat_ids:vector excluded_chat_ids:vector select_existing_chats:Bool select_new_chats:Bool select_contacts:Bool select_non_contacts:Bool exclude_selected:Bool = BusinessRecipients; //@description Describes settings for messages that are automatically sent by a Telegram Business account when it is away //@shortcut_id Unique quick reply shortcut identifier for the away messages @@ -621,9 +639,21 @@ businessGreetingMessageSettings shortcut_id:int32 recipients:businessRecipients //@can_reply True, if the bot can send messages to the private chats; false otherwise businessConnectedBot bot_user_id:int53 recipients:businessRecipients can_reply:Bool = BusinessConnectedBot; +//@description Describes settings for a business account start page +//@title Title text of the start page +//@message Message text of the start page +//@sticker Greeting sticker of the start page; may be null if none +businessStartPage title:string message:string sticker:sticker = BusinessStartPage; + +//@description Describes settings for a business account start page to set +//@title Title text of the start page; 0-getOption("business_start_page_title_length_max") characters +//@message Message text of the start page; 0-getOption("business_start_page_message_length_max") characters +//@sticker Greeting sticker of the start page; pass null if none. The sticker must belong to a sticker set and must not be a custom emoji +inputBusinessStartPage title:string message:string sticker:InputFile = InputBusinessStartPage; + //@description Describes an interval of time when the business is open -//@start_minute The first minute of the interval since start of the week; 0-7*24*60 -//@end_minute The first minute after the end of the interval since start of the week; 1-8*24*60 +//@start_minute The minute's sequence number in a week, starting on Monday, marking the start of the time interval during which the business is open; 0-7*24*60 +//@end_minute The minute's sequence number in a week, starting on Monday, marking the end of the time interval during which the business is open; 1-8*24*60 businessOpeningHoursInterval start_minute:int32 end_minute:int32 = BusinessOpeningHoursInterval; //@description Describes opening hours of a business @time_zone_id Unique time zone identifier @opening_hours Intervals of the time when the business is open @@ -632,12 +662,38 @@ businessOpeningHours time_zone_id:string opening_hours:vector = BusinessChatLinks; -//@class ChatPhotoStickerType @description Describes type of a sticker, which was used to create a chat photo +//@description Describes a business chat link to create or edit +//@text Message draft text that will be added to the input field +//@title Link title +inputBusinessChatLink text:formattedText title:string = InputBusinessChatLink; + +//@description Contains information about a business chat link +//@chat_id Identifier of the private chat that created the link +//@text Message draft text that must be added to the input field +businessChatLinkInfo chat_id:int53 text:formattedText = BusinessChatLinkInfo; + + +//@class ChatPhotoStickerType @description Describes type of sticker, which was used to create a chat photo //@description Information about the sticker, which was used to create the chat photo //@sticker_set_id Sticker set identifier @@ -722,12 +778,19 @@ chatPermissions can_send_basic_messages:Bool can_send_audios:Bool can_send_docum //@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them //@can_manage_video_chats True, if the administrator can manage video chats //@can_post_stories True, if the administrator can create new chat stories, or edit and delete posted stories; applicable to supergroups and channels only -//@can_edit_stories True, if the administrator can edit stories posted by other users, pin stories and access story archive; applicable to supergroups and channels only +//@can_edit_stories True, if the administrator can edit stories posted by other users, post stories to the chat page, pin chat stories, and access story archive; applicable to supergroups and channels only //@can_delete_stories True, if the administrator can delete stories posted by other users; applicable to supergroups and channels only //@is_anonymous True, if the administrator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only 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 Contains information about a product that can be paid with invoice +//@title Product title +//@param_description Product description +//@photo Product photo; may be null +productInfo title:string description:formattedText photo:photo = ProductInfo; + + //@description Describes an option for buying Telegram Premium to a user //@currency ISO 4217 currency code for Telegram Premium subscription payment //@amount The amount to pay, in the smallest units of the currency @@ -766,6 +829,62 @@ premiumGiftCodePaymentOptions options:vector = Pre //@use_date Point in time (Unix timestamp) when the code was activated; 0 if none premiumGiftCodeInfo creator_id:MessageSender creation_date:int32 is_from_giveaway:Bool giveaway_message_id:int53 month_count:int32 user_id:int53 use_date:int32 = PremiumGiftCodeInfo; +//@description Describes an option for buying Telegram stars +//@currency ISO 4217 currency code for the payment +//@amount The amount to pay, in the smallest units of the currency +//@star_count Number of stars that will be purchased +//@store_product_id Identifier of the store product associated with the option; may be empty if none +//@is_additional True, if the option must be shown only in the full list of payment options +starPaymentOption currency:string amount:int53 star_count:int53 store_product_id:string is_additional:Bool = StarPaymentOption; + +//@description Contains a list of options for buying Telegram stars @options The list of options +starPaymentOptions options:vector = StarPaymentOptions; + + +//@class StarTransactionDirection @description Describes direction of a transaction with Telegram stars + +//@description The transaction is incoming and increases the number of owned Telegram stars +starTransactionDirectionIncoming = StarTransactionDirection; + +//@description The transaction is outgoing and decreases the number of owned Telegram stars +starTransactionDirectionOutgoing = StarTransactionDirection; + + +//@class StarTransactionSource @description Describes source or recipient of a transaction with Telegram stars + +//@description The transaction is a transaction with Telegram through a bot +starTransactionSourceTelegram = StarTransactionSource; + +//@description The transaction is a transaction with App Store +starTransactionSourceAppStore = StarTransactionSource; + +//@description The transaction is a transaction with Google Play +starTransactionSourceGooglePlay = StarTransactionSource; + +//@description The transaction is a transaction with Fragment +starTransactionSourceFragment = StarTransactionSource; + +//@description The transaction is a transaction with another user @user_id Identifier of the user @product_info Information about the bought product; may be null if none +starTransactionSourceUser user_id:int53 product_info:productInfo = StarTransactionSource; + +//@description The transaction is a transaction with unknown source +starTransactionSourceUnsupported = StarTransactionSource; + + +//@description Represents a transaction changing the amount of owned Telegram stars +//@id Unique identifier of the transaction +//@star_count The amount of added owned Telegram stars; negative for outgoing transactions +//@is_refund True, if the transaction is a refund of a previous transaction +//@date Point in time (Unix timestamp) when the transaction was completed +//@source Source of the transaction, or its recipient for outgoing transactions +starTransaction id:string star_count:int53 is_refund:Bool date:int32 source:StarTransactionSource = StarTransaction; + +//@description Represents a list of Telegram star transactions +//@star_count The amount of owned Telegram stars +//@transactions List of transactions with Telegram stars +//@next_offset The offset for the next request. If empty, then there are no more results +starTransactions star_count:int53 transactions:vector next_offset:string = StarTransactions; + //@class PremiumGiveawayParticipantStatus @description Contains information about status of a user in a Telegram Premium giveaway @@ -838,7 +957,7 @@ emojiStatuses custom_emoji_ids:vector = EmojiStatuses; //@description Describes usernames assigned to a user, a supergroup, or a channel //@active_usernames List of active usernames; the first one must be shown as the primary username. The order of active usernames can be changed with reorderActiveUsernames, reorderBotActiveUsernames or reorderSupergroupActiveUsernames //@disabled_usernames List of currently disabled usernames; the username can be activated with toggleUsernameIsActive, toggleBotUsernameIsActive, or toggleSupergroupUsernameIsActive -//@editable_username The active username, which can be changed with setUsername or setSupergroupUsername +//@editable_username The active username, which can be changed with setUsername or setSupergroupUsername. Information about other active usernames can be received using getCollectibleItemInfo usernames active_usernames:vector disabled_usernames:vector editable_username:string = Usernames; @@ -902,15 +1021,18 @@ botInfo short_description:string description:string photo:photo animation:animat //@has_private_calls True, if the user can't be called due to their privacy settings //@has_private_forwards True, if the user can't be linked in forwarded messages due to their privacy settings //@has_restricted_voice_and_video_note_messages True, if voice and video notes can't be sent or forwarded to the user -//@has_pinned_stories True, if the user has pinned stories +//@has_posted_to_profile_stories True, if the user has posted to profile stories +//@has_sponsored_messages_enabled True, if the user always enabled sponsored messages; known only for the current user //@need_phone_number_privacy_exception True, if the current user needs to explicitly allow to share their phone number with the user when the method addContact is used //@set_chat_background True, if the user set chat background for both chat users and it wasn't reverted yet //@bio A short user bio; may be null for bots +//@birthdate Birthdate of the user; may be null if unknown +//@personal_chat_id Identifier of the personal chat of the user; 0 if none //@premium_gift_options The list of available options for gifting Telegram Premium to the user //@group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user //@business_info Information about business settings for Telegram Business accounts; may be null if none //@bot_info For bots, information about the bot; may be null if the user isn't a bot -userFullInfo personal_photo:chatPhoto photo:chatPhoto public_photo:chatPhoto block_list:BlockList can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool has_pinned_stories:Bool need_phone_number_privacy_exception:Bool set_chat_background:Bool bio:formattedText premium_gift_options:vector group_in_common_count:int32 business_info:businessInfo bot_info:botInfo = UserFullInfo; +userFullInfo personal_photo:chatPhoto photo:chatPhoto public_photo:chatPhoto block_list:BlockList can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool has_posted_to_profile_stories:Bool has_sponsored_messages_enabled:Bool need_phone_number_privacy_exception:Bool set_chat_background:Bool bio:formattedText birthdate:birthdate personal_chat_id:int53 premium_gift_options:vector group_in_common_count:int32 business_info:businessInfo bot_info:botInfo = UserFullInfo; //@description Represents a list of users @total_count Approximate total number of users found @user_ids A list of user identifiers users total_count:int32 user_ids:vector = Users; @@ -1055,7 +1177,7 @@ chatInviteLinkMember user_id:int53 joined_chat_date:int32 via_chat_folder_invite chatInviteLinkMembers total_count:int32 members:vector = ChatInviteLinkMembers; -//@class InviteLinkChatType @description Describes the type of a chat to which points an invite link +//@class InviteLinkChatType @description Describes the type of chat to which points an invite link //@description The link is an invite link for a basic group inviteLinkChatTypeBasicGroup = InviteLinkChatType; @@ -1123,9 +1245,9 @@ basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 memb //@date Point in time (Unix timestamp) when the current user joined, or the point in time when the supergroup or channel was created, in case the user is not a member //@status Status of the current user in the supergroup or channel; custom title will always be empty //@member_count Number of members in the supergroup or channel; 0 if unknown. Currently, it is guaranteed to be known only if the supergroup or channel was received through -//-getChatSimilarChats, getChatsToSendStories, getCreatedPublicChats, getGroupsInCommon, getInactiveSupergroupChats, getSuitableDiscussionChats, getUserPrivacySettingRules, getVideoChatAvailableParticipants, -//-searchChatsNearby, searchPublicChats, or in chatFolderInviteLinkInfo.missing_chat_ids, or for public chats in which where sent messages and posted stories from publicForwards, -//-or for public chats in which where sent messages from getMessagePublicForwards response +//-getChatSimilarChats, getChatsToSendStories, getCreatedPublicChats, getGroupsInCommon, getInactiveSupergroupChats, getRecommendedChats, getSuitableDiscussionChats, +//-getUserPrivacySettingRules, getVideoChatAvailableParticipants, searchChatsNearby, searchPublicChats, or in chatFolderInviteLinkInfo.missing_chat_ids, or in userFullInfo.personal_chat_id, +//-or for chats with messages or stories from publicForwards //@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 @@ -1160,9 +1282,11 @@ supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus membe //@can_set_sticker_set True, if the supergroup sticker set can be changed //@can_set_location True, if the supergroup location can be changed //@can_get_statistics True, if the supergroup or channel statistics are available +//@can_get_revenue_statistics True, if the supergroup or channel revenue statistics are available //@can_toggle_aggressive_anti_spam True, if aggressive anti-spam checks can be enabled or disabled in the supergroup //@is_all_history_available True, if new chat members will have access to old messages. In public, discussion, of forum groups and all channels, old messages are always available, //-so this option affects only private non-forum supergroups without a linked chat. The value of this field is only available to chat administrators +//@can_have_sponsored_messages True, if the chat can have sponsored messages. The value of this field is only available to the owner of the chat //@has_aggressive_anti_spam_enabled True, if aggressive anti-spam checks are enabled in the supergroup. The value of this field is only available to chat administrators //@has_pinned_stories True, if the supergroup or channel has pinned stories //@my_boost_count Number of times the current user boosted the supergroup or channel @@ -1174,7 +1298,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_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool has_aggressive_anti_spam_enabled: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_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_toggle_aggressive_anti_spam:Bool is_all_history_available:Bool can_have_sponsored_messages:Bool has_aggressive_anti_spam_enabled: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 @@ -1289,7 +1413,7 @@ reactionTypeCustomEmoji custom_emoji_id:int64 = ReactionType; //@origin Origin of the forwarded message //@date Point in time (Unix timestamp) when the message was originally sent //@source For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, information about the source message from which the message was forwarded last time; may be null for other forwards or if unknown -//@public_service_announcement_type The type of a public service announcement for the forwarded message +//@public_service_announcement_type The type of public service announcement for the forwarded message messageForwardInfo origin:MessageOrigin date:int32 source:forwardSource public_service_announcement_type:string = MessageForwardInfo; //@description Contains information about a message created with importMessages @@ -1331,6 +1455,24 @@ messageInteractionInfo view_count:int32 forward_count:int32 reply_info:messageRe unreadReaction type:ReactionType sender_id:MessageSender is_big:Bool = UnreadReaction; +//@class MessageEffectType @description Describes type of emoji effect + +//@description An effect from an emoji reaction @select_animation Select animation for the effect in TGS format @effect_animation Effect animation for the effect in TGS format +messageEffectTypeEmojiReaction select_animation:sticker effect_animation:sticker = MessageEffectType; + +//@description An effect from a premium sticker @sticker The premium sticker. The effect can be found at sticker.full_type.premium_animation +messageEffectTypePremiumSticker sticker:sticker = MessageEffectType; + + +//@description Contains information about an effect added to a message +//@id Unique identifier of the effect +//@static_icon Static icon for the effect in WEBP format; may be null if none +//@emoji Emoji corresponding to the effect that can be used if static icon isn't available +//@is_premium True, if Telegram Premium subscription is required to use the effect +//@type Type of the effect +messageEffect id:int64 static_icon:sticker emoji:string is_premium:Bool type:MessageEffectType = MessageEffect; + + //@class MessageSendingState @description Contains information about the sending state of the message //@description The message is being sent now, but has not yet been delivered to the server @sending_id Non-persistent message sending identifier, specified by the application @@ -1390,6 +1532,12 @@ inputMessageReplyToMessage chat_id:int53 message_id:int53 quote:inputTextQuote = inputMessageReplyToStory story_sender_chat_id:int53 story_id:int32 = InputMessageReplyTo; +//@description Describes a fact-check added to the message by an independent checker +//@text Text of the fact-check +//@country_code A two-letter ISO 3166-1 alpha-2 country code of the country for which the fact-check is shown +factCheck text:formattedText country_code:string = FactCheck; + + //@description Describes a message //@id Message identifier; unique for the chat to which the message belongs //@sender_id Identifier of the sender of the message @@ -1398,6 +1546,7 @@ inputMessageReplyToStory story_sender_chat_id:int53 story_id:int32 = InputMessag //@scheduling_state The scheduling state of the message; may be null if the message isn't scheduled //@is_outgoing True, if the message is outgoing //@is_pinned True, if the message is pinned +//@is_from_offline True, if the message was sent because of a scheduled action by the message sender, for example, as away, or greeting service message //@can_be_edited True, if the message can be edited. For live location and poll messages this fields shows whether editMessageLiveLocation or stopPoll can be used with this message by the application //@can_be_forwarded True, if the message can be forwarded //@can_be_replied_in_another_chat True, if the message can be replied in another chat or topic @@ -1421,20 +1570,23 @@ inputMessageReplyToStory story_sender_chat_id:int53 story_id:int32 = InputMessag //@import_info Information about the initial message for messages created with importMessages; may be null if the message isn't imported //@interaction_info Information about interactions with the message; may be null if none //@unread_reactions Information about unread reactions added to the message +//@fact_check Information about fact-check added to the message; may be null if none //@reply_to Information about the message or the story this message is replying to; may be null if none //@message_thread_id If non-zero, the identifier of the message thread the message belongs to; unique within the chat to which the message belongs //@saved_messages_topic_id Identifier of the Saved Messages topic for the message; 0 for messages not from Saved Messages //@self_destruct_type The message's self-destruct type; may be null if none //@self_destruct_in Time left before the message self-destruct timer expires, in seconds; 0 if self-destruction isn't scheduled yet //@auto_delete_in Time left before the message will be automatically deleted by message_auto_delete_time setting of the chat, in seconds; 0 if never -//@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent +//@via_bot_user_id If non-zero, the user identifier of the inline bot through which this message was sent +//@sender_business_bot_user_id If non-zero, the user identifier of the business bot that sent this message //@sender_boost_count Number of times the sender of the message boosted the supergroup at the time the message was sent; 0 if none or unknown. For messages sent by the current user, supergroupFullInfo.my_boost_count must be used instead //@author_signature For channel posts and anonymous group messages, optional author signature -//@media_album_id Unique identifier of an album this message belongs to. Only audios, documents, photos and videos can be grouped together in albums +//@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 //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@content Content of the message //@reply_markup Reply markup for the message; may be null if none -message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_replied_in_another_chat:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_added_reactions:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_read_date:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool can_report_reactions:Bool has_timestamped_media:Bool is_channel_post:Bool is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo import_info:messageImportInfo interaction_info:messageInteractionInfo unread_reactions:vector reply_to:MessageReplyTo message_thread_id:int53 saved_messages_topic_id:int53 self_destruct_type:MessageSelfDestructType self_destruct_in:double auto_delete_in:double via_bot_user_id:int53 sender_boost_count:int32 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message; +message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool is_from_offline:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_replied_in_another_chat:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_added_reactions:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_read_date:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool can_report_reactions:Bool has_timestamped_media:Bool is_channel_post:Bool is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo import_info:messageImportInfo interaction_info:messageInteractionInfo unread_reactions:vector 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; //@description Contains a list of messages @total_count Approximate total number of messages found @messages List of messages; messages may be null messages total_count:int32 messages:vector = Messages; @@ -1458,6 +1610,13 @@ messageCalendarDay total_count:int32 message:message = MessageCalendarDay; messageCalendar total_count:int32 days:vector = MessageCalendar; +//@description Describes a message from a business account as received by a bot @message The message @reply_to_message Message that is replied by the message in the same chat; may be null if none +businessMessage message:message reply_to_message:message = BusinessMessage; + +//@description Contains a list of messages from a business account as received by a bot @messages List of business messages +businessMessages messages:vector = BusinessMessages; + + //@class MessageSource @description Describes source of a message //@description The message is from a chat history @@ -1491,42 +1650,49 @@ messageSourceScreenshot = MessageSource; messageSourceOther = MessageSource; -//@class MessageSponsorType @description Describes type of a message sponsor - -//@description The sponsor is a bot @bot_user_id User identifier of the bot @link An internal link to be opened when the sponsored message is clicked -messageSponsorTypeBot bot_user_id:int53 link:InternalLinkType = MessageSponsorType; - -//@description The sponsor is a web app @web_app_title Web App title @link An internal link to be opened when the sponsored message is clicked -messageSponsorTypeWebApp web_app_title:string link:InternalLinkType = MessageSponsorType; - -//@description The sponsor is a public channel chat @chat_id Sponsor chat identifier @link An internal link to be opened when the sponsored message is clicked; may be null if the sponsor chat needs to be opened instead -messageSponsorTypePublicChannel chat_id:int53 link:InternalLinkType = MessageSponsorType; - -//@description The sponsor is a private channel chat @title Title of the chat @invite_link Invite link for the channel -messageSponsorTypePrivateChannel title:string invite_link:string = MessageSponsorType; - -//@description The sponsor is a website @url URL of the website @name Name of the website -messageSponsorTypeWebsite url:string name:string = MessageSponsorType; - - //@description Information about the sponsor of a message -//@type Type of the sponsor +//@url URL of the sponsor to be opened when the message is clicked //@photo Photo of the sponsor; may be null if must not be shown //@info Additional optional information about the sponsor to be shown along with the message -messageSponsor type:MessageSponsorType photo:chatPhotoInfo info:string = MessageSponsor; +messageSponsor url:string photo:photo info:string = MessageSponsor; //@description Describes a sponsored message //@message_id Message identifier; unique for the chat to which the sponsored message belongs among both ordinary and sponsored messages //@is_recommended True, if the message needs to be labeled as "recommended" instead of "sponsored" +//@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 //@sponsor Information about the sponsor of the message -//@button_text If non-empty, text for the message action button +//@title Title of the sponsored message +//@button_text Text for the message action button +//@accent_color_id Identifier of the accent color for title, button text and message background +//@background_custom_emoji_id Identifier of a custom emoji to be shown on the message background; 0 if none //@additional_info If non-empty, additional information about the sponsored message to be shown along with the message -sponsoredMessage message_id:int53 is_recommended:Bool content:MessageContent sponsor:messageSponsor button_text:string additional_info:string = SponsoredMessage; +sponsoredMessage message_id:int53 is_recommended:Bool can_be_reported:Bool content:MessageContent sponsor:messageSponsor title:string button_text:string accent_color_id:int32 background_custom_emoji_id:int64 additional_info:string = SponsoredMessage; //@description Contains a list of sponsored messages @messages List of sponsored messages @messages_between The minimum number of messages between shown sponsored messages, or 0 if only one sponsored message must be shown after all ordinary messages sponsoredMessages messages:vector messages_between:int32 = SponsoredMessages; +//@description Describes an option to report a sponsored message @id Unique identifier of the option @text Text of the option +reportChatSponsoredMessageOption id:bytes text:string = ReportChatSponsoredMessageOption; + + +//@class ReportChatSponsoredMessageResult @description Describes result of sponsored message report + +//@description The message was reported successfully +reportChatSponsoredMessageResultOk = ReportChatSponsoredMessageResult; + +//@description The sponsored message is too old or not found +reportChatSponsoredMessageResultFailed = ReportChatSponsoredMessageResult; + +//@description The user must choose an option to report the message and repeat request with the chosen option @title Title for the option choice @options List of available options +reportChatSponsoredMessageResultOptionRequired title:string options:vector = ReportChatSponsoredMessageResult; + +//@description Sponsored messages were hidden for the user in all chats +reportChatSponsoredMessageResultAdsHidden = ReportChatSponsoredMessageResult; + +//@description The user asked to hide sponsored messages, but Telegram Premium is required for this +reportChatSponsoredMessageResultPremiumRequired = ReportChatSponsoredMessageResult; + //@description Describes a file added to file download list //@file_id File identifier @@ -1593,6 +1759,26 @@ chatNotificationSettings use_default_mute_for:Bool mute_for:int32 use_default_so scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool use_default_mute_stories:Bool mute_stories:Bool story_sound_id:int64 show_story_sender:Bool disable_pinned_message_notifications:Bool disable_mention_notifications:Bool = ScopeNotificationSettings; +//@class ReactionNotificationSource @description Describes sources of reactions for which notifications will be shown + +//@description Notifications for reactions are disabled +reactionNotificationSourceNone = ReactionNotificationSource; + +//@description Notifications for reactions are shown only for reactions from contacts +reactionNotificationSourceContacts = ReactionNotificationSource; + +//@description Notifications for reactions are shown for all reactions +reactionNotificationSourceAll = ReactionNotificationSource; + + +//@description Contains information about notification settings for reactions +//@message_reaction_source Source of message reactions for which notifications are shown +//@story_reaction_source Source of story reactions for which notifications are shown +//@sound_id Identifier of the notification sound to be played; 0 if sound is disabled +//@show_preview True, if reaction sender and emoji must be displayed in notifications +reactionNotificationSettings message_reaction_source:ReactionNotificationSource story_reaction_source:ReactionNotificationSource sound_id:int64 show_preview:Bool = ReactionNotificationSettings; + + //@description Contains information about a message draft //@reply_to Information about the message to be replied; must be of the type inputMessageReplyToMessage; may be null if none //@date Point in time (Unix timestamp) when the draft was created @@ -1600,7 +1786,7 @@ scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool use_de draftMessage reply_to:InputMessageReplyTo date:int32 input_message_text:InputMessageContent = DraftMessage; -//@class ChatType @description Describes the type of a chat +//@class ChatType @description Describes the type of chat //@description An ordinary chat with a user @user_id User identifier chatTypePrivate user_id:int53 = ChatType; @@ -1622,7 +1808,7 @@ chatFolderIcon name:string = ChatFolderIcon; //@description Represents a folder for user chats //@title The title of the folder; 1-12 characters without line feeds //@icon The chosen icon for the chat folder; may be null. If null, use getChatFolderDefaultIconName to get default icon name for the folder -//@color_id The identifier of the chosen color for the chat folder icon; from -1 to 6. If -1, then color is didabled +//@color_id The identifier of the chosen color for the chat folder icon; from -1 to 6. If -1, then color is disabled. Can't be changed if folder tags are disabled or the current user doesn't have Telegram Premium subscription //@is_shareable True, if at least one link has been created for the folder //@pinned_chat_ids The chat identifiers of pinned chats in the folder. There can be up to getOption("chat_folder_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium //@included_chat_ids The chat identifiers of always included chats in the folder. There can be up to getOption("chat_folder_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium @@ -1641,7 +1827,7 @@ chatFolder title:string icon:chatFolderIcon color_id:int32 is_shareable:Bool pin //@id Unique chat folder identifier //@title The title of the folder; 1-12 characters without line feeds //@icon The chosen or default icon for the chat folder -//@color_id The identifier of the chosen color for the chat folder icon; from -1 to 6. If -1, then color is didabled +//@color_id The identifier of the chosen color for the chat folder icon; from -1 to 6. If -1, then color is disabled //@is_shareable True, if at least one link has been created for the folder //@has_my_invite_links True, if the chat folder has invite links created by the current user chatFolderInfo id:int32 title:string icon:chatFolderIcon color_id:int32 is_shareable:Bool has_my_invite_links:Bool = ChatFolderInfo; @@ -1708,11 +1894,11 @@ 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 -chatAvailableReactionsAll = ChatAvailableReactions; +//@description All reactions are available in the chat @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 -chatAvailableReactionsSome reactions:vector = 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 +chatAvailableReactionsSome reactions:vector max_reaction_count:int32 = ChatAvailableReactions; //@description Represents a tag used in Saved Messages or a Saved Messages topic @@ -1725,6 +1911,14 @@ savedMessagesTag tag:ReactionType label:string count:int32 = SavedMessagesTag; savedMessagesTags tags:vector = SavedMessagesTags; +//@description Contains information about a business bot that manages the chat +//@bot_user_id User identifier of the bot +//@manage_url URL to be opened to manage the bot +//@is_bot_paused True, if the bot is paused. Use toggleBusinessConnectedBotChatIsPaused to change the value of the field +//@can_bot_reply True, if the bot can reply +businessBotManageBar bot_user_id:int53 manage_url:string is_bot_paused:Bool can_bot_reply:Bool = BusinessBotManageBar; + + //@description Describes a video chat //@group_call_id Group call identifier of an active video chat; 0 if none. Full information about the video chat can be received through the method getGroupCall //@has_participants True, if the video chat has participants @@ -1768,17 +1962,32 @@ videoChat group_call_id:int32 has_participants:Bool default_participant_id:Messa //@background Background set for the chat; may be null if none //@theme_name If non-empty, name of a theme, set for the chat //@action_bar Information about actions which must be possible to do through the chat action bar; may be null if none +//@business_bot_manage_bar Information about bar for managing a business bot in the chat; may be null if none //@video_chat Information about video chat of the chat //@pending_join_requests Information about pending join requests; may be null if none //@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat //@draft_message A draft of a message in the chat; may be null if none //@client_data Application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used -chat id:int53 type:ChatType title:string photo:chatPhotoInfo accent_color_id:int32 background_custom_emoji_id:int64 profile_accent_color_id:int32 profile_background_custom_emoji_id:int64 permissions:chatPermissions last_message:message positions:vector chat_lists:vector message_sender_id:MessageSender block_list:BlockList has_protected_content:Bool is_translatable:Bool is_marked_as_unread:Bool view_as_topics:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_auto_delete_time:int32 emoji_status:emojiStatus background:chatBackground theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; +chat id:int53 type:ChatType title:string photo:chatPhotoInfo accent_color_id:int32 background_custom_emoji_id:int64 profile_accent_color_id:int32 profile_background_custom_emoji_id:int64 permissions:chatPermissions last_message:message positions:vector chat_lists:vector message_sender_id:MessageSender block_list:BlockList has_protected_content:Bool is_translatable:Bool is_marked_as_unread:Bool view_as_topics:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_auto_delete_time:int32 emoji_status:emojiStatus background:chatBackground theme_name:string action_bar:ChatActionBar business_bot_manage_bar:businessBotManageBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat; //@description Represents a list of chats @total_count Approximate total number of chats found @chat_ids List of chat identifiers chats total_count:int32 chat_ids:vector = Chats; +//@description Contains information about a user that has failed to be added to a chat +//@user_id User identifier +//@premium_would_allow_invite True, if subscription to Telegram Premium would have allowed to add the user to the chat +//@premium_required_to_send_messages True, if subscription to Telegram Premium is required to send the user chat invite link +failedToAddMember user_id:int53 premium_would_allow_invite:Bool premium_required_to_send_messages:Bool = FailedToAddMember; + +//@description Represents a list of users that has failed to be added to a chat @failed_to_add_members Information about users that weren't added to the chat +failedToAddMembers failed_to_add_members:vector = FailedToAddMembers; + + +//@description Contains information about a newly created basic group chat @chat_id Chat identifier @failed_to_add_members Information about failed to add members +createdBasicGroupChat chat_id:int53 failed_to_add_members:failedToAddMembers = CreatedBasicGroupChat; + + //@description Describes a chat located nearby @chat_id Chat identifier @distance Distance to the chat location, in meters chatNearby chat_id:int53 distance:int32 = ChatNearby; @@ -1786,7 +1995,7 @@ chatNearby chat_id:int53 distance:int32 = ChatNearby; chatsNearby users_nearby:vector supergroups_nearby:vector = ChatsNearby; -//@class PublicChatType @description Describes a type of public chats +//@class PublicChatType @description Describes type of public chat //@description The chat is public, because it has an active username publicChatTypeHasUsername = PublicChatType; @@ -1847,7 +2056,10 @@ keyboardButtonTypeRequestPoll force_regular:Bool force_quiz:Bool = KeyboardButto //@restrict_user_is_premium True, if the shared users must or must not be Telegram Premium users //@user_is_premium True, if the shared users must be Telegram Premium users; otherwise, the shared users must not be Telegram Premium users. Ignored if restrict_user_is_premium is false //@max_quantity The maximum number of users to share -keyboardButtonTypeRequestUsers id:int32 restrict_user_is_bot:Bool user_is_bot:Bool restrict_user_is_premium:Bool user_is_premium:Bool max_quantity:int32 = KeyboardButtonType; +//@request_name Pass true to request name of the users; bots only +//@request_username Pass true to request username of the users; bots only +//@request_photo Pass true to request photo of the users; bots only +keyboardButtonTypeRequestUsers id:int32 restrict_user_is_bot:Bool user_is_bot:Bool restrict_user_is_premium:Bool user_is_premium:Bool max_quantity:int32 request_name:Bool request_username:Bool request_photo:Bool = KeyboardButtonType; //@description A button that requests a chat to be shared by the current user; available only in private chats. Use the method shareChatWithBot to complete the request //@id Unique button identifier @@ -1860,7 +2072,10 @@ keyboardButtonTypeRequestUsers id:int32 restrict_user_is_bot:Bool user_is_bot:Bo //@user_administrator_rights Expected user administrator rights in the chat; may be null if they aren't restricted //@bot_administrator_rights Expected bot administrator rights in the chat; may be null if they aren't restricted //@bot_is_member True, if the bot must be a member of the chat; for basic group and supergroup chats only -keyboardButtonTypeRequestChat id:int32 chat_is_channel:Bool restrict_chat_is_forum:Bool chat_is_forum:Bool restrict_chat_has_username:Bool chat_has_username:Bool chat_is_created:Bool user_administrator_rights:chatAdministratorRights bot_administrator_rights:chatAdministratorRights bot_is_member:Bool = KeyboardButtonType; +//@request_title Pass true to request title of the chat; bots only +//@request_username Pass true to request username of the chat; bots only +//@request_photo Pass true to request photo of the chat; bots only +keyboardButtonTypeRequestChat id:int32 chat_is_channel:Bool restrict_chat_is_forum:Bool chat_is_forum:Bool restrict_chat_has_username:Bool chat_has_username:Bool chat_is_created:Bool user_administrator_rights:chatAdministratorRights bot_administrator_rights:chatAdministratorRights bot_is_member:Bool request_title:Bool request_username:Bool request_photo:Bool = KeyboardButtonType; //@description A button that opens a Web App by calling getWebAppUrl @url An HTTP URL to pass to getWebAppUrl keyboardButtonTypeWebApp url:string = KeyboardButtonType; @@ -1870,7 +2085,7 @@ keyboardButtonTypeWebApp url:string = KeyboardButtonType; keyboardButton text:string type:KeyboardButtonType = KeyboardButton; -//@class InlineKeyboardButtonType @description Describes the type of an inline keyboard button +//@class InlineKeyboardButtonType @description Describes the type of inline keyboard button //@description A button that opens a specified URL @url HTTP or tg:// URL to open inlineKeyboardButtonTypeUrl url:string = InlineKeyboardButtonType; @@ -1962,7 +2177,7 @@ webAppInfo launch_id:int64 url:string = WebAppInfo; messageThreadInfo chat_id:int53 message_thread_id:int53 reply_info:messageReplyInfo unread_message_count:int32 messages:vector draft_message:draftMessage = MessageThreadInfo; -//@class SavedMessagesTopicType @description Describes type of a Saved Messages topic +//@class SavedMessagesTopicType @description Describes type of Saved Messages topic //@description Topic containing messages sent by the current user of forwarded from an unknown chat savedMessagesTopicTypeMyNotes = SavedMessagesTopicType; @@ -2030,6 +2245,22 @@ forumTopics total_count:int32 topics:vector next_offset_date:int32 n linkPreviewOptions is_disabled:Bool url:string force_small_media:Bool force_large_media:Bool show_above_text:Bool = LinkPreviewOptions; +//@description Contains information about a user shared with a bot +//@user_id User identifier +//@first_name First name of the user; for bots only +//@last_name Last name of the user; for bots only +//@username Username of the user; for bots only +//@photo Profile photo of the user; for bots only; may be null +sharedUser user_id:int53 first_name:string last_name:string username:string photo:photo = SharedUser; + +//@description Contains information about a chat shared with a bot +//@chat_id Chat identifier +//@title Title of the chat; for bots only +//@username Username of the chat; for bots only +//@photo Photo of the chat; for bots only; may be null +sharedChat chat_id:int53 title:string username:string photo:photo = SharedChat; + + //@class RichText @description Describes a text object inside an instant-view web page //@description A plain text @text Text @@ -2316,8 +2547,9 @@ webPageInstantView page_blocks:vector view_count:int32 version:int32 //@voice_note Preview of the content as a voice note, if available; may be null //@story_sender_chat_id The identifier of the sender of the previewed story; 0 if none //@story_id The identifier of the previewed story; 0 if none +//@stickers Up to 4 stickers from the sticker set available via the link //@instant_view_version Version of web page instant view (currently, can be 1 or 2); 0 if none -webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string has_large_media:Bool show_large_media:Bool skip_confirmation:Bool show_above_text:Bool animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote story_sender_chat_id:int53 story_id:int32 instant_view_version:int32 = WebPage; +webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string has_large_media:Bool show_large_media:Bool skip_confirmation:Bool show_above_text:Bool animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote story_sender_chat_id:int53 story_id:int32 stickers:vector instant_view_version:int32 = WebPage; //@description Contains information about a country @@ -2335,10 +2567,29 @@ countries countries:vector = Countries; //@country Information about the country to which the phone number belongs; may be null //@country_calling_code The part of the phone number denoting country calling code or its part //@formatted_phone_number The phone number without country calling code formatted accordingly to local rules. Expected digits are returned as '-', but even more digits might be entered by the user -//@is_anonymous True, if the phone number was bought on Fragment and isn't tied to a SIM card +//@is_anonymous True, if the phone number was bought at https://fragment.com and isn't tied to a SIM card. Information about the phone number can be received using getCollectibleItemInfo phoneNumberInfo country:countryInfo country_calling_code:string formatted_phone_number:string is_anonymous:Bool = PhoneNumberInfo; +//@class CollectibleItemType @description Describes a collectible item that can be purchased at https://fragment.com + +//@description A username @username The username +collectibleItemTypeUsername username:string = CollectibleItemType; + +//@description A phone number @phone_number The phone number +collectibleItemTypePhoneNumber phone_number:string = CollectibleItemType; + + +//@description Contains information about a collectible item and its last purchase +//@purchase_date Point in time (Unix timestamp) when the item was purchased +//@currency Currency for the paid amount +//@amount The paid amount, in the smallest units of the currency +//@cryptocurrency Cryptocurrency used to pay for the item +//@cryptocurrency_amount The paid amount, in the smallest units of the cryptocurrency +//@url Individual URL for the item on https://fragment.com +collectibleItemInfo purchase_date:int32 currency:string amount:int53 cryptocurrency:string cryptocurrency_amount:int64 url:string = CollectibleItemInfo; + + //@description Describes an action associated with a bank card number @text Action text @url The URL to be opened bankCardActionOpenUrl text:string url:string = BankCardActionOpenUrl; @@ -2445,10 +2696,10 @@ paymentProviderOther url:string = PaymentProvider; paymentOption title:string url:string = PaymentOption; -//@description Contains information about an invoice payment form -//@id The payment form identifier +//@class PaymentFormType @description Describes type of payment form + +//@description The payment form is for a regular payment //@invoice Full information about the invoice -//@seller_bot_user_id User identifier of the seller bot //@payment_provider_user_id User identifier of the payment provider bot //@payment_provider Information about the payment provider //@additional_payment_options The list of additional payment options @@ -2456,10 +2707,18 @@ paymentOption title:string url:string = PaymentOption; //@saved_credentials The list of saved payment credentials //@can_save_credentials True, if the user can choose to save credentials //@need_password True, if the user will be able to save credentials, if sets up a 2-step verification password -//@product_title Product title -//@product_description Product description -//@product_photo Product photo; may be null -paymentForm id:int64 invoice:invoice seller_bot_user_id:int53 payment_provider_user_id:int53 payment_provider:PaymentProvider additional_payment_options:vector saved_order_info:orderInfo saved_credentials:vector can_save_credentials:Bool need_password:Bool product_title:string product_description:formattedText product_photo:photo = PaymentForm; +paymentFormTypeRegular invoice:invoice payment_provider_user_id:int53 payment_provider:PaymentProvider additional_payment_options:vector saved_order_info:orderInfo saved_credentials:vector can_save_credentials:Bool need_password:Bool = PaymentFormType; + +//@description The payment form is for a payment in Telegram stars @star_count Number of stars that will be paid +paymentFormTypeStars star_count:int53 = PaymentFormType; + + +//@description Contains information about an invoice payment form +//@id The payment form identifier +//@type Type of the payment form +//@seller_bot_user_id User identifier of the seller bot +//@product_info Information about the product +paymentForm id:int64 type:PaymentFormType seller_bot_user_id:int53 product_info:productInfo = PaymentForm; //@description Contains a temporary identifier of validated order information, which is stored for one hour, and the available shipping options @order_info_id Temporary identifier of the order information @shipping_options Available shipping options validatedOrderInfo order_info_id:string shipping_options:vector = ValidatedOrderInfo; @@ -2467,19 +2726,30 @@ validatedOrderInfo order_info_id:string shipping_options:vector //@description Contains the result of a payment request @success True, if the payment request was successful; otherwise, the verification_url will be non-empty @verification_url URL for additional payment credentials verification paymentResult success:Bool verification_url:string = PaymentResult; -//@description Contains information about a successful payment -//@title Product title -//@param_description Product description -//@photo Product photo; may be null -//@date Point in time (Unix timestamp) when the payment was made -//@seller_bot_user_id User identifier of the seller bot + +//@class PaymentReceiptType @description Describes type of successful payment + +//@description The payment was done using a third-party payment provider //@payment_provider_user_id User identifier of the payment provider bot //@invoice Information about the invoice //@order_info Order information; may be null //@shipping_option Chosen shipping option; may be null //@credentials_title Title of the saved credentials chosen by the buyer //@tip_amount The amount of tip chosen by the buyer in the smallest units of the currency -paymentReceipt title:string description:formattedText photo:photo date:int32 seller_bot_user_id:int53 payment_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceipt; +paymentReceiptTypeRegular payment_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceiptType; + +//@description The payment was done using Telegram stars +//@star_count Number of stars that were paid +//@transaction_id Unique identifier of the transaction that can be used to dispute it +paymentReceiptTypeStars star_count:int53 transaction_id:string = PaymentReceiptType; + + +//@description Contains information about a successful payment +//@product_info Information about the product +//@date Point in time (Unix timestamp) when the payment was made +//@seller_bot_user_id User identifier of the seller bot +//@type Type of the payment receipt +paymentReceipt product_info:productInfo date:int32 seller_bot_user_id:int53 type:PaymentReceiptType = PaymentReceipt; //@class InputInvoice @description Describes an invoice to process @@ -2499,7 +2769,7 @@ inputInvoiceTelegram purpose:TelegramPaymentPurpose = InputInvoice; //@description The media is hidden until the invoice is paid //@width Media width; 0 if unknown //@height Media height; 0 if unknown -//@duration Media duration; 0 if unknown +//@duration Media duration, in seconds; 0 if unknown //@minithumbnail Media minithumbnail; may be null //@caption Media caption messageExtendedMediaPreview width:int32 height:int32 duration:int32 minithumbnail:minithumbnail caption:formattedText = MessageExtendedMedia; @@ -2522,7 +2792,7 @@ messageExtendedMediaUnsupported caption:formattedText = MessageExtendedMedia; //@only_new_members True, if only new members of the chats will be eligible for the giveaway //@has_public_winners True, if the list of winners of the giveaway will be available to everyone //@country_codes The list of two-letter ISO 3166-1 alpha-2 codes of countries, users from which will be eligible for the giveaway. If empty, then all users can participate in the giveaway. -//-There can be up to getOption("giveaway_country_count_max") chosen countries. Users with phone number that was bought on Fragment can participate in any giveaway and the country code "FT" must not be specified in the list +//-There can be up to getOption("giveaway_country_count_max") chosen countries. Users with phone number that was bought at https://fragment.com can participate in any giveaway and the country code "FT" must not be specified in the list //@prize_description Additional description of the giveaway prize; 0-128 characters premiumGiveawayParameters boosted_chat_id:int53 additional_chat_ids:vector winners_selection_date:int32 only_new_members:Bool has_public_winners:Bool country_codes:vector prize_description:string = PremiumGiveawayParameters; @@ -2531,7 +2801,7 @@ premiumGiveawayParameters boosted_chat_id:int53 additional_chat_ids:vector = MessageContent; //@description A newly created basic group @title Title of the basic group @member_user_ids User identifiers of members in the basic group @@ -3084,11 +3355,11 @@ messagePremiumGiveawayWinners boosted_chat_id:int53 giveaway_message_id:int53 ad //@description A contact has registered with Telegram messageContactRegistered = MessageContent; -//@description The current user shared users, which were requested by the bot @user_ids Identifier of the shared users @button_id Identifier of the keyboard button with the request -messageUsersShared user_ids:vector button_id:int32 = MessageContent; +//@description The current user shared users, which were requested by the bot @users The shared users @button_id Identifier of the keyboard button with the request +messageUsersShared users:vector button_id:int32 = MessageContent; -//@description The current user shared a chat, which was requested by the bot @chat_id Identifier of the shared chat @button_id Identifier of the keyboard button with the request -messageChatShared chat_id:int53 button_id:int32 = MessageContent; +//@description The current user shared a chat, which was requested by the bot @chat The shared chat @button_id Identifier of the keyboard button with the request +messageChatShared chat:sharedChat button_id:int32 = MessageContent; //@description The user allowed the bot to send messages @reason The reason why the bot was allowed to write messages messageBotWriteAccessAllowed reason:BotWriteAccessAllowReason = MessageContent; @@ -3162,9 +3433,12 @@ textEntityTypePre = TextEntityType; //@description Text that must be formatted as if inside pre, and code HTML tags @language Programming language of the code; as defined by the sender textEntityTypePreCode language:string = TextEntityType; -//@description Text that must be formatted as if inside a blockquote HTML tag +//@description Text that must be formatted as if inside a blockquote HTML tag; not supported in secret chats textEntityTypeBlockQuote = TextEntityType; +//@description Text that must be formatted as if inside a blockquote HTML tag and collapsed by default to 3 lines with the ability to show full text; not supported in secret chats +textEntityTypeExpandableBlockQuote = TextEntityType; + //@description A text description shown instead of a raw URL @url HTTP or tg:// URL to be opened when the link is clicked textEntityTypeTextUrl url:string = TextEntityType; @@ -3209,21 +3483,24 @@ messageSelfDestructTypeImmediately = MessageSelfDestructType; //@protect_content Pass true if the content of the message must be protected from forwarding and saving; for bots only //@update_order_of_installed_sticker_sets Pass true if the user explicitly chosen a sticker or a custom emoji from an installed sticker set; applicable only to sendMessage and sendMessageAlbum //@scheduling_state Message scheduling state; pass null to send message immediately. Messages sent to a secret chat, live location messages and self-destructing messages can't be scheduled +//@effect_id Identifier of the effect to apply to the message; applicable only to sendMessage and sendMessageAlbum in private chats //@sending_id Non-persistent identifier, which will be returned back in messageSendingStatePending object and can be used to match sent messages and corresponding updateNewMessage updates //@only_preview Pass true to get a fake message instead of actually sending them -messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState sending_id:int32 only_preview:Bool = MessageSendOptions; +messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState effect_id:int64 sending_id:int32 only_preview:Bool = MessageSendOptions; //@description Options to be used when a message content is copied without reference to the original sender. Service messages, messages with messageInvoice, messagePremiumGiveaway, or messagePremiumGiveawayWinners content can't be copied //@send_copy True, if content of the message needs to be copied without reference to the original sender. Always true if the message is forwarded to a secret chat or is local //@replace_caption True, if media caption of the message copy needs to be replaced. Ignored if send_copy is false //@new_caption New message caption; pass null to copy message without caption. Ignored if replace_caption is false -messageCopyOptions send_copy:Bool replace_caption:Bool new_caption:formattedText = MessageCopyOptions; +//@new_show_caption_above_media True, if new caption must be shown above the animation; otherwise, new caption must be shown below the animation; not supported in secret chats. Ignored if replace_caption is false +messageCopyOptions send_copy:Bool replace_caption:Bool new_caption:formattedText new_show_caption_above_media:Bool = MessageCopyOptions; //@class InputMessageContent @description The content of a message to send //@description A text message -//@text Formatted text to be sent; 0-getOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually +//@text Formatted text to be sent; 0-getOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, ExpandableBlockQuote, +//-Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually //@link_preview_options Options to be used for generation of a link preview; may be null if none; pass null to use default link preview options //@clear_draft True, if a chat message draft must be deleted inputMessageText text:formattedText link_preview_options:linkPreviewOptions clear_draft:Bool = InputMessageContent; @@ -3236,8 +3513,9 @@ inputMessageText text:formattedText link_preview_options:linkPreviewOptions clea //@width Width of the animation; may be replaced by the server //@height Height of the animation; may be replaced by the server //@caption Animation caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters +//@show_caption_above_media True, if caption must be shown above the animation; otherwise, caption must be shown below the animation; not supported in secret chats //@has_spoiler True, if the animation preview must be covered by a spoiler animation; not supported in secret chats -inputMessageAnimation animation:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 caption:formattedText has_spoiler:Bool = InputMessageContent; +inputMessageAnimation animation:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 caption:formattedText show_caption_above_media:Bool has_spoiler:Bool = InputMessageContent; //@description An audio message //@audio Audio file to be sent @@ -3262,9 +3540,10 @@ inputMessageDocument document:InputFile thumbnail:inputThumbnail disable_content //@width Photo width //@height Photo height //@caption Photo caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters +//@show_caption_above_media True, if caption must be shown above the photo; otherwise, caption must be shown below the photo; not supported in secret chats //@self_destruct_type Photo self-destruct type; pass null if none; private chats only //@has_spoiler True, if the photo preview must be covered by a spoiler animation; not supported in secret chats -inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:formattedText self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; +inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector width:int32 height:int32 caption:formattedText show_caption_above_media:Bool self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; //@description A sticker message //@sticker Sticker to be sent @@ -3283,20 +3562,21 @@ inputMessageSticker sticker:InputFile thumbnail:inputThumbnail width:int32 heigh //@height Video height //@supports_streaming True, if the video is supposed to be streamed //@caption Video caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters +//@show_caption_above_media True, if caption must be shown above the video; otherwise, caption must be shown below the video; not supported in secret chats //@self_destruct_type Video self-destruct type; pass null if none; private chats only //@has_spoiler True, if the video preview must be covered by a spoiler animation; not supported in secret chats -inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 supports_streaming:Bool caption:formattedText self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; +inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector duration:int32 width:int32 height:int32 supports_streaming:Bool caption:formattedText show_caption_above_media:Bool self_destruct_type:MessageSelfDestructType has_spoiler:Bool = InputMessageContent; //@description A video note message //@video_note Video note to be sent //@thumbnail Video thumbnail; may be null if empty; pass null to skip thumbnail uploading -//@duration Duration of the video, in seconds +//@duration Duration of the video, in seconds; 0-60 //@length Video width and height; must be positive and not greater than 640 //@self_destruct_type Video note self-destruct type; may be null if none; pass null if none; private chats only inputMessageVideoNote video_note:InputFile thumbnail:inputThumbnail duration:int32 length:int32 self_destruct_type:MessageSelfDestructType = InputMessageContent; //@description A voice note message -//@voice_note Voice note to be sent +//@voice_note Voice note to be sent. The voice note must be encoded with the Opus codec and stored inside an OGG container with a single audio channel, or be in MP3 or M4A format as regular audio //@duration Duration of the voice note, in seconds //@waveform Waveform representation of the voice note in 5-bit format //@caption Voice note caption; may be null if empty; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters @@ -3305,7 +3585,7 @@ inputMessageVoiceNote voice_note:InputFile duration:int32 waveform:bytes caption //@description A message with a location //@location Location to be sent -//@live_period Period for which the location can be updated, in seconds; must be between 60 and 86400 for a live location and 0 otherwise +//@live_period Period for which the location can be updated, in seconds; must be between 60 and 86400 for a temporary live location, 0x7FFFFFFF for permanent live location, and 0 otherwise //@heading For live locations, a direction in which the location moves, in degrees; 1-360. Pass 0 if unknown //@proximity_alert_radius For live locations, a maximum distance to another chat member for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled. Can't be enabled in channels and Saved Messages inputMessageLocation location:location live_period:int32 heading:int32 proximity_alert_radius:int32 = InputMessageContent; @@ -3331,21 +3611,21 @@ inputMessageGame bot_user_id:int53 game_short_name:string = InputMessageContent; //@photo_width Product photo width //@photo_height Product photo height //@payload The invoice payload -//@provider_token Payment provider token +//@provider_token Payment provider token; may be empty for payments in Telegram Stars //@provider_data JSON-encoded data about the invoice, which will be shared with the payment provider //@start_parameter Unique invoice bot deep link parameter for the generation of this invoice. If empty, it would be possible to pay directly from forwards of the invoice message //@extended_media_content The content of extended media attached to the invoice. The content of the message to be sent. Must be one of the following types: inputMessagePhoto, inputMessageVideo inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string extended_media_content:InputMessageContent = InputMessageContent; //@description A message with a poll. Polls can't be sent to secret chats. Polls can be sent only to a private chat with a bot -//@question Poll question; 1-255 characters (up to 300 characters for bots) -//@options List of poll answer options, 2-10 strings 1-100 characters each +//@question Poll question; 1-255 characters (up to 300 characters for bots). Only custom emoji entities are allowed to be added and only by Premium users +//@options List of poll answer options, 2-10 strings 1-100 characters each. Only custom emoji entities are allowed to be added and only by Premium users //@is_anonymous True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels //@type Type of the poll //@open_period Amount of time the poll will be active after creation, in seconds; for bots only //@close_date Point in time (Unix timestamp) when the poll will automatically be closed; for bots only //@is_closed True, if the poll needs to be sent already closed; for bots only -inputMessagePoll question:string options:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = InputMessageContent; +inputMessagePoll question:formattedText options:vector is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = InputMessageContent; //@description A message with a forwarded story. Stories can't be sent to secret chats. A story can be forwarded only if story.can_be_forwarded //@story_sender_chat_id Identifier of the chat that posted the story @@ -3501,17 +3781,17 @@ emojis emojis:vector = Emojis; //@name Name of the sticker set //@thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed //@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner +//@is_owned True, if the sticker set is owned by the current user //@is_installed True, if the sticker set has been installed by the current user //@is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously //@is_official True, if the sticker set is official -//@sticker_format Format of the stickers in the set //@sticker_type Type of the stickers in the set //@needs_repainting True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only //@is_allowed_as_chat_emoji_status True, if stickers in the sticker set are custom emoji that can be used as chat emoji status; for custom emoji sticker sets only //@is_viewed True for already viewed trending sticker sets //@stickers List of stickers in this set //@emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object -stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType needs_repainting:Bool is_allowed_as_chat_emoji_status:Bool is_viewed:Bool stickers:vector emojis:vector = StickerSet; +stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_owned:Bool is_installed:Bool is_archived:Bool is_official:Bool sticker_type:StickerType needs_repainting:Bool is_allowed_as_chat_emoji_status:Bool is_viewed:Bool stickers:vector emojis:vector = StickerSet; //@description Represents short information about a sticker set //@id Identifier of the sticker set @@ -3519,17 +3799,17 @@ stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outli //@name Name of the sticker set //@thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed //@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner +//@is_owned True, if the sticker set is owned by the current user //@is_installed True, if the sticker set has been installed by the current user //@is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously //@is_official True, if the sticker set is official -//@sticker_format Format of the stickers in the set //@sticker_type Type of the stickers in the set //@needs_repainting True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only //@is_allowed_as_chat_emoji_status True, if stickers in the sticker set are custom emoji that can be used as chat emoji status; for custom emoji sticker sets only //@is_viewed True for already viewed trending sticker sets //@size Total number of stickers in the set //@covers Up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full sticker set needs to be requested -stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType needs_repainting:Bool is_allowed_as_chat_emoji_status:Bool is_viewed:Bool size:int32 covers:vector = StickerSetInfo; +stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector is_owned:Bool is_installed:Bool is_archived:Bool is_official:Bool sticker_type:StickerType needs_repainting:Bool is_allowed_as_chat_emoji_status:Bool is_viewed:Bool size:int32 covers:vector = StickerSetInfo; //@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets stickerSets total_count:int32 sets:vector = StickerSets; @@ -3538,21 +3818,36 @@ stickerSets total_count:int32 sets:vector = StickerSets; trendingStickerSets total_count:int32 sets:vector is_premium:Bool = TrendingStickerSets; -//@description Contains a list of similar emoji to search for in getStickers and searchStickers +//@class EmojiCategorySource @description Describes source of stickers for an emoji category + +//@description The category contains a list of similar emoji to search for in getStickers and searchStickers for stickers, +//-or getInlineQueryResults with the bot getOption("animation_search_bot_username") for animations +//@emojis List of emojis for search for +emojiCategorySourceSearch emojis:vector = EmojiCategorySource; + +//@description The category contains premium stickers that must be found by getPremiumStickers +emojiCategorySourcePremium = EmojiCategorySource; + + +//@description Describes an emoji category //@name Name of the category //@icon Custom emoji sticker, which represents icon of the category -//@emojis List of emojis in the category -emojiCategory name:string icon:sticker emojis:vector = EmojiCategory; +//@source Source of stickers for the emoji category +//@is_greeting True, if the category must be shown first when choosing a sticker for the start page +emojiCategory name:string icon:sticker source:EmojiCategorySource is_greeting:Bool = EmojiCategory; //@description Represents a list of emoji categories @categories List of categories emojiCategories categories:vector = EmojiCategories; -//@class EmojiCategoryType @description Describes type of an emoji category +//@class EmojiCategoryType @description Describes type of emoji category -//@description The category must be used by default +//@description The category must be used by default (e.g., for custom emoji or animation search) emojiCategoryTypeDefault = EmojiCategoryType; +//@description The category must be used by default for regular sticker selection. It may contain greeting emoji category and premium stickers +emojiCategoryTypeRegularStickers = EmojiCategoryType; + //@description The category must be used for emoji status selection emojiCategoryTypeEmojiStatus = EmojiCategoryType; @@ -3569,7 +3864,7 @@ emojiCategoryTypeChatPhoto = EmojiCategoryType; storyAreaPosition x_percentage:double y_percentage:double width_percentage:double height_percentage:double rotation_angle:double = StoryAreaPosition; -//@class StoryAreaType @description Describes type of a clickable rectangle area on a story media +//@class StoryAreaType @description Describes type of clickable rectangle area on a story media //@description An area pointing to a location @location The location storyAreaTypeLocation location:location = StoryAreaType; @@ -3592,7 +3887,7 @@ storyAreaTypeMessage chat_id:int53 message_id:int53 = StoryAreaType; storyArea position:storyAreaPosition type:StoryAreaType = StoryArea; -//@class InputStoryAreaType @description Describes type of a clickable rectangle area on a story media to be added +//@class InputStoryAreaType @description Describes type of clickable rectangle area on a story media to be added //@description An area pointing to a location @location The location inputStoryAreaTypeLocation location:location = InputStoryAreaType; @@ -3706,13 +4001,13 @@ storyInteractionInfo view_count:int32 forward_count:int32 reaction_count:int32 r //@is_being_sent True, if the story is being sent by the current user //@is_being_edited True, if the story is being edited by the current user //@is_edited True, if the story was edited -//@is_pinned True, if the story is saved in the sender's profile and will be available there after expiration +//@is_posted_to_chat_page True, if the story is saved in the sender's profile and will be available there after expiration //@is_visible_only_for_self True, if the story is visible only for the current user //@can_be_deleted True, if the story can be deleted //@can_be_edited True, if the story can be edited //@can_be_forwarded True, if the story can be forwarded as a message. Otherwise, screenshots and saving of the story content must be also forbidden //@can_be_replied True, if the story can be replied in the chat with the story sender -//@can_toggle_is_pinned True, if the story's is_pinned value can be changed +//@can_toggle_is_posted_to_chat_page True, if the story's is_posted_to_chat_page value can be changed //@can_get_statistics True, if the story statistics are available through getStoryStatistics //@can_get_interactions True, if interactions with the story can be received through getStoryInteractions //@has_expired_viewers True, if users viewed the story can't be received, because the story has expired more than getOption("story_viewers_expiration_delay") seconds ago @@ -3723,10 +4018,13 @@ storyInteractionInfo view_count:int32 forward_count:int32 reaction_count:int32 r //@content Content of the story //@areas Clickable areas to be shown on the story content //@caption Caption of the story -story id:int32 sender_chat_id:int53 sender_id:MessageSender date:int32 is_being_sent:Bool is_being_edited:Bool is_edited:Bool is_pinned:Bool is_visible_only_for_self:Bool can_be_deleted:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_replied:Bool can_toggle_is_pinned:Bool can_get_statistics:Bool can_get_interactions:Bool has_expired_viewers:Bool repost_info:storyRepostInfo interaction_info:storyInteractionInfo chosen_reaction_type:ReactionType privacy_settings:StoryPrivacySettings content:StoryContent areas:vector caption:formattedText = Story; +story id:int32 sender_chat_id:int53 sender_id:MessageSender date:int32 is_being_sent:Bool is_being_edited:Bool is_edited:Bool is_posted_to_chat_page:Bool is_visible_only_for_self:Bool can_be_deleted:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_replied:Bool can_toggle_is_posted_to_chat_page:Bool can_get_statistics:Bool can_get_interactions:Bool has_expired_viewers:Bool repost_info:storyRepostInfo interaction_info:storyInteractionInfo chosen_reaction_type:ReactionType privacy_settings:StoryPrivacySettings content:StoryContent areas:vector caption:formattedText = Story; -//@description Represents a list of stories @total_count Approximate total number of stories found @stories The list of stories -stories total_count:int32 stories:vector = Stories; +//@description Represents a list of stories +//@total_count Approximate total number of stories found +//@stories The list of stories +//@pinned_story_ids Identifiers of the pinned stories; returned only in getChatPostedToChatPageStories with from_story_id == 0 +stories total_count:int32 stories:vector pinned_story_ids:vector = Stories; //@description Contains identifier of a story along with identifier of its sender //@sender_chat_id Identifier of the chat that posted the story @@ -3780,13 +4078,16 @@ storyInteractions total_count:int32 total_forward_count:int32 total_reaction_cou //@id Unique message identifier among all quick replies //@sending_state The sending state of the message; may be null if the message isn't being sent and didn't fail to be sent //@can_be_edited True, if the message can be edited -//@reply_to_message_id Information about the identifier of the quick reply message to which the message replies +//@reply_to_message_id The identifier of the quick reply message to which the message replies; 0 if none //@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent -//@media_album_id Unique identifier of an album this message belongs to. Only audios, documents, photos and videos can be grouped together in albums +//@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 //@content Content of the message //@reply_markup Inline keyboard reply markup for the message; may be null if none quickReplyMessage id:int53 sending_state:MessageSendingState can_be_edited:Bool reply_to_message_id:int53 via_bot_user_id:int53 media_album_id:int64 content:MessageContent reply_markup:ReplyMarkup = QuickReplyMessage; +//@description Contains a list of quick reply messages @messages List of quick reply messages; messages may be null +quickReplyMessages messages:vector = QuickReplyMessages; + //@description Describes a shortcut that can be used for a quick reply //@id Unique shortcut identifier //@name The name of the shortcut that can be used to use the shortcut @@ -3825,7 +4126,8 @@ publicForwards total_count:int32 forwards:vector next_offset:stri //@can_set_custom_background True, if custom background can be set in the chat for all users //@can_set_custom_emoji_sticker_set True, if custom emoji sticker set can be set for the chat //@can_recognize_speech True, if speech recognition can be used for video note and voice note messages by all users -chatBoostLevelFeatures level:int32 story_per_day_count:int32 custom_emoji_reaction_count:int32 title_color_count:int32 profile_accent_color_count:int32 can_set_profile_background_custom_emoji:Bool accent_color_count:int32 can_set_background_custom_emoji:Bool can_set_emoji_status:Bool chat_theme_background_count:int32 can_set_custom_background:Bool can_set_custom_emoji_sticker_set:Bool can_recognize_speech:Bool = ChatBoostLevelFeatures; +//@can_disable_sponsored_messages True, if sponsored messages can be disabled in the chat +chatBoostLevelFeatures level:int32 story_per_day_count:int32 custom_emoji_reaction_count:int32 title_color_count:int32 profile_accent_color_count:int32 can_set_profile_background_custom_emoji:Bool accent_color_count:int32 can_set_background_custom_emoji:Bool can_set_emoji_status:Bool chat_theme_background_count:int32 can_set_custom_background:Bool can_set_custom_emoji_sticker_set:Bool can_recognize_speech:Bool can_disable_sponsored_messages:Bool = ChatBoostLevelFeatures; //@description Contains a list of features available on the first chat boost levels //@features The list of features @@ -3836,7 +4138,8 @@ chatBoostLevelFeatures level:int32 story_per_day_count:int32 custom_emoji_reacti //@min_custom_background_boost_level The minimum boost level required to set custom chat background //@min_custom_emoji_sticker_set_boost_level The minimum boost level required to set custom emoji sticker set for the chat; for supergroup chats only //@min_speech_recognition_boost_level The minimum boost level allowing to recognize speech in video note and voice note messages for non-Premium users; for supergroup chats only -chatBoostFeatures features:vector min_profile_background_custom_emoji_boost_level:int32 min_background_custom_emoji_boost_level:int32 min_emoji_status_boost_level:int32 min_chat_theme_background_boost_level:int32 min_custom_background_boost_level:int32 min_custom_emoji_sticker_set_boost_level:int32 min_speech_recognition_boost_level:int32 = ChatBoostFeatures; +//@min_sponsored_message_disable_boost_level The minimum boost level allowing to disable sponsored messages in the chat; for channel chats only +chatBoostFeatures features:vector min_profile_background_custom_emoji_boost_level:int32 min_background_custom_emoji_boost_level:int32 min_emoji_status_boost_level:int32 min_chat_theme_background_boost_level:int32 min_custom_background_boost_level:int32 min_custom_emoji_sticker_set_boost_level:int32 min_speech_recognition_boost_level:int32 min_sponsored_message_disable_boost_level:int32 = ChatBoostFeatures; //@class ChatBoostSource @description Describes source of a chat boost @@ -3904,6 +4207,16 @@ chatBoostSlot slot_id:int32 currently_boosted_chat_id:int53 start_date:int32 exp chatBoostSlots slots:vector = ChatBoostSlots; +//@class ResendCodeReason @description Describes the reason why a code needs to be re-sent + +//@description The user requested to resend the code +resendCodeReasonUserRequest = ResendCodeReason; + +//@description The code is re-sent, because device verification has failed +//@error_message Cause of the verification failure, for example, PLAY_SERVICES_NOT_AVAILABLE, APNS_RECEIVE_TIMEOUT, APNS_INIT_FAILED, etc. +resendCodeReasonVerificationFailed error_message:string = ResendCodeReason; + + //@class CallDiscardReason @description Describes the reason why a call was discarded //@description The call wasn't discarded, or the reason is unknown @@ -3931,7 +4244,7 @@ callDiscardReasonHungUp = CallDiscardReason; callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 library_versions:vector = CallProtocol; -//@class CallServerType @description Describes the type of a call server +//@class CallServerType @description Describes the type of call server //@description A Telegram call reflector @peer_tag A peer tag to be used with the reflector @is_tcp True, if the server uses TCP instead of UDP callServerTypeTelegramReflector peer_tag:bytes is_tcp:Bool = CallServerType; @@ -3975,7 +4288,8 @@ callStateExchangingKeys = CallState; //@encryption_key Call encryption key //@emojis Encryption key emojis fingerprint //@allow_p2p True, if peer-to-peer connection is allowed by users privacy settings -callStateReady protocol:callProtocol servers:vector config:string encryption_key:bytes emojis:vector allow_p2p:Bool = CallState; +//@custom_parameters Custom JSON-encoded call parameters to be passed to tgcalls +callStateReady protocol:callProtocol servers:vector config:string encryption_key:bytes emojis:vector allow_p2p:Bool custom_parameters:string = CallState; //@description The call is hanging up after discardCall has been called callStateHangingUp = CallState; @@ -4074,7 +4388,7 @@ groupCallParticipantVideoInfo source_groups:vector en groupCallParticipant participant_id:MessageSender audio_source_id:int32 screen_sharing_audio_source_id:int32 video_info:groupCallParticipantVideoInfo screen_sharing_video_info:groupCallParticipantVideoInfo bio:string is_current_user:Bool is_speaking:Bool is_hand_raised:Bool can_be_muted_for_all_users:Bool can_be_unmuted_for_all_users:Bool can_be_muted_for_current_user:Bool can_be_unmuted_for_current_user:Bool is_muted_for_all_users:Bool is_muted_for_current_user:Bool can_unmute_self:Bool volume_level:int32 order:string = GroupCallParticipant; -//@class CallProblem @description Describes the exact type of a problem with a call +//@class CallProblem @description Describes the exact type of problem with a call //@description The user heard their own voice callProblemEcho = CallProblem; @@ -4126,10 +4440,11 @@ firebaseAuthenticationSettingsIos device_token:string is_app_sandbox:Bool = Fire //@allow_flash_call Pass true if the authentication code may be sent via a flash call to the specified phone number //@allow_missed_call Pass true if the authentication code may be sent via a missed call to the specified phone number //@is_current_phone_number Pass true if the authenticated phone number is used on the current device +//@has_unknown_phone_number Pass true if there is a SIM card in the current device, but it is not possible to check whether phone number matches //@allow_sms_retriever_api For official applications only. True, if the application can use Android SMS Retriever API (requires Google Play Services >= 10.2) to automatically receive the authentication code from the SMS. See https://developers.google.com/identity/sms-retriever/ for more details //@firebase_authentication_settings For official Android and iOS applications only; pass null otherwise. Settings for Firebase Authentication //@authentication_tokens List of up to 20 authentication tokens, recently received in updateOption("authentication_token") in previously logged out sessions -phoneNumberAuthenticationSettings allow_flash_call:Bool allow_missed_call:Bool is_current_phone_number:Bool allow_sms_retriever_api:Bool firebase_authentication_settings:FirebaseAuthenticationSettings authentication_tokens:vector = PhoneNumberAuthenticationSettings; +phoneNumberAuthenticationSettings allow_flash_call:Bool allow_missed_call:Bool is_current_phone_number:Bool has_unknown_phone_number:Bool allow_sms_retriever_api:Bool firebase_authentication_settings:FirebaseAuthenticationSettings authentication_tokens:vector = PhoneNumberAuthenticationSettings; //@description Represents a reaction applied to a message @@ -4213,6 +4528,16 @@ speechRecognitionResultText text:string = SpeechRecognitionResult; speechRecognitionResultError error:error = SpeechRecognitionResult; +//@description Describes a connection of the bot with a business account +//@id Unique identifier of the connection +//@user_id Identifier of the business user that created the connection +//@user_chat_id Chat identifier of the private chat with the user +//@date Point in time (Unix timestamp) when the connection was established +//@can_reply True, if the bot can send messages to the connected user; false otherwise +//@is_enabled True, if the connection is enabled; false otherwise +businessConnection id:string user_id:int53 user_chat_id:int53 date:int32 can_reply:Bool is_enabled:Bool = BusinessConnection; + + //@description Describes a color to highlight a bot added to attachment menu @light_color Color in the RGB24 format for light themes @dark_color Color in the RGB24 format for dark themes attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotColor; @@ -4484,7 +4809,7 @@ inlineQueryResultVideo id:string video:video title:string description:string = I inlineQueryResultVoiceNote id:string voice_note:voiceNote title:string = InlineQueryResult; -//@class InlineQueryResultsButtonType @description Represents a type of a button in results of inline query +//@class InlineQueryResultsButtonType @description Represents type of button in results of inline query //@description Describes the button that opens a private chat with the bot and sends a start message to the bot with the given parameter @parameter The parameter for the bot start message inlineQueryResultsButtonTypeStartBot parameter:string = InlineQueryResultsButtonType; @@ -4760,7 +5085,7 @@ languagePackInfo id:string base_language_pack_id:string name:string native_name: localizationTargetInfo language_packs:vector = LocalizationTargetInfo; -//@class PremiumLimitType @description Describes type of a limit, increased for Premium users +//@class PremiumLimitType @description Describes type of limit, increased for Premium users //@description The maximum number of joined supergroups and channels premiumLimitTypeSupergroupCount = PremiumLimitType; @@ -4852,7 +5177,7 @@ premiumFeatureAdvancedChatManagement = PremiumFeature; //@description A badge in the user's profile premiumFeatureProfileBadge = PremiumFeature; -//@description An emoji status shown along with the user's name +//@description The ability to show an emoji status along with the user's name premiumFeatureEmojiStatus = PremiumFeature; //@description Profile photo animation on message and chat screens @@ -4889,6 +5214,45 @@ premiumFeatureMessagePrivacy = PremiumFeature; //@description The ability to view last seen and read times of other users even they can't view last seen or read time for the current user premiumFeatureLastSeenTimes = PremiumFeature; +//@description The ability to use Business features +premiumFeatureBusiness = PremiumFeature; + + +//@class BusinessFeature @description Describes a feature available to Business user accounts + +//@description The ability to set location +businessFeatureLocation = BusinessFeature; + +//@description The ability to set opening hours +businessFeatureOpeningHours = BusinessFeature; + +//@description The ability to use quick replies +businessFeatureQuickReplies = BusinessFeature; + +//@description The ability to set up a greeting message +businessFeatureGreetingMessage = BusinessFeature; + +//@description The ability to set up an away message +businessFeatureAwayMessage = BusinessFeature; + +//@description The ability to create links to the business account with predefined message text +businessFeatureAccountLinks = BusinessFeature; + +//@description The ability to customize start page +businessFeatureStartPage = BusinessFeature; + +//@description The ability to connect a bot to the account +businessFeatureBots = BusinessFeature; + +//@description The ability to show an emoji status along with the business name +businessFeatureEmojiStatus = BusinessFeature; + +//@description The ability to display folder names for each chat in the chat list +businessFeatureChatFolderTags = BusinessFeature; + +//@description Allowed to use many additional features for stories +businessFeatureUpgradedStories = BusinessFeature; + //@class PremiumStoryFeature @description Describes a story feature available to Premium users @@ -4923,6 +5287,9 @@ premiumLimit type:PremiumLimitType default_value:int32 premium_value:int32 = Pre //@payment_link An internal link to be opened to pay for Telegram Premium if store payment isn't possible; may be null if direct payment isn't available premiumFeatures features:vector limits:vector payment_link:InternalLinkType = PremiumFeatures; +//@description Contains information about features, available to Business user accounts @features The list of available business features +businessFeatures features:vector = BusinessFeatures; + //@class PremiumSource @description Describes a source from which the Premium features screen is opened @@ -4932,6 +5299,9 @@ premiumSourceLimitExceeded limit_type:PremiumLimitType = PremiumSource; //@description A user tried to use a Premium feature @feature The used feature premiumSourceFeature feature:PremiumFeature = PremiumSource; +//@description A user tried to use a Business feature @feature The used feature; pass null if none specific feature was used +premiumSourceBusinessFeature feature:BusinessFeature = PremiumSource; + //@description A user tried to use a Premium story feature @feature The used feature premiumSourceStoryFeature feature:PremiumStoryFeature = PremiumSource; @@ -4945,11 +5315,15 @@ premiumSourceSettings = PremiumSource; //@description Describes a promotion animation for a Premium feature @feature Premium feature @animation Promotion animation for the feature premiumFeaturePromotionAnimation feature:PremiumFeature animation:animation = PremiumFeaturePromotionAnimation; +//@description Describes a promotion animation for a Business feature @feature Business feature @animation Promotion animation for the feature +businessFeaturePromotionAnimation feature:BusinessFeature animation:animation = BusinessFeaturePromotionAnimation; + //@description Contains state of Telegram Premium subscription and promotion videos for Premium features //@state Text description of the state of the current Premium subscription; may be empty if the current user has no Telegram Premium subscription //@payment_options The list of available options for buying Telegram Premium //@animations The list of available promotion animations for Premium features -premiumState state:formattedText payment_options:vector animations:vector = PremiumState; +//@business_animations The list of available promotion animations for Business features +premiumState state:formattedText payment_options:vector animations:vector business_animations:vector = PremiumState; //@class StorePaymentPurpose @description Describes a purpose of an in-store payment @@ -4973,6 +5347,12 @@ storePaymentPurposePremiumGiftCodes boosted_chat_id:int53 currency:string amount //@amount Paid amount, in the smallest units of the currency storePaymentPurposePremiumGiveaway parameters:premiumGiveawayParameters currency:string amount:int53 = StorePaymentPurpose; +//@description The user buying Telegram stars +//@currency ISO 4217 currency code of the payment currency +//@amount Paid amount, in the smallest units of the currency +//@star_count Number of bought stars +storePaymentPurposeStars currency:string amount:int53 star_count:int53 = StorePaymentPurpose; + //@class TelegramPaymentPurpose @description Describes a purpose of a payment toward Telegram @@ -4992,6 +5372,12 @@ telegramPaymentPurposePremiumGiftCodes boosted_chat_id:int53 currency:string amo //@month_count Number of months the Telegram Premium subscription will be active for the users telegramPaymentPurposePremiumGiveaway parameters:premiumGiveawayParameters currency:string amount:int53 winner_count:int32 month_count:int32 = TelegramPaymentPurpose; +//@description The user buying Telegram stars +//@currency ISO 4217 currency code of the payment currency +//@amount Paid amount, in the smallest units of the currency +//@star_count Number of bought stars +telegramPaymentPurposeStars currency:string amount:int53 star_count:int53 = 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 @@ -5051,11 +5437,11 @@ backgroundFillSolid color:int32 = BackgroundFill; //@rotation_angle Clockwise rotation angle of the gradient, in degrees; 0-359. Must always be divisible by 45 backgroundFillGradient top_color:int32 bottom_color:int32 rotation_angle:int32 = BackgroundFill; -//@description Describes a freeform gradient fill of a background @colors A list of 3 or 4 colors of the freeform gradients in the RGB24 format +//@description Describes a freeform gradient fill of a background @colors A list of 3 or 4 colors of the freeform gradient in the RGB24 format backgroundFillFreeformGradient colors:vector = BackgroundFill; -//@class BackgroundType @description Describes the type of a background +//@class BackgroundType @description Describes the type of background //@description A wallpaper in JPEG format //@is_blurred True, if the wallpaper must be downscaled to fit in 450x450 square and then box-blurred with radius 12 @@ -5166,7 +5552,7 @@ checkChatUsernameResultUsernameInvalid = CheckChatUsernameResult; //@description The username is occupied checkChatUsernameResultUsernameOccupied = CheckChatUsernameResult; -//@description The username can be purchased at fragment.com +//@description The username can be purchased at https://fragment.com. Information about the username can be received using getCollectibleItemInfo checkChatUsernameResultUsernamePurchasable = CheckChatUsernameResult; //@description The user has too many chats with username, one of them must be made private first @@ -5471,6 +5857,9 @@ userPrivacySettingRuleAllowAll = UserPrivacySettingRule; //@description A rule to allow all contacts of the user to do something userPrivacySettingRuleAllowContacts = UserPrivacySettingRule; +//@description A rule to allow all Premium Users to do something; currently, allowed only for userPrivacySettingAllowChatInvites +userPrivacySettingRuleAllowPremiumUsers = UserPrivacySettingRule; + //@description A rule to allow certain specified users to do something @user_ids The user identifiers, total number of users in all rules must not exceed 1000 userPrivacySettingRuleAllowUsers user_ids:vector = UserPrivacySettingRule; @@ -5510,6 +5899,9 @@ userPrivacySettingShowPhoneNumber = UserPrivacySetting; //@description A privacy setting for managing whether the user's bio is visible userPrivacySettingShowBio = UserPrivacySetting; +//@description A privacy setting for managing whether the user's birthdate is visible +userPrivacySettingShowBirthdate = UserPrivacySetting; + //@description A privacy setting for managing whether the user can be invited to chats userPrivacySettingAllowChatInvites = UserPrivacySetting; @@ -5555,7 +5947,7 @@ accountTtl days:int32 = AccountTtl; messageAutoDeleteTime time:int32 = MessageAutoDeleteTime; -//@class SessionType @description Represents the type of a session +//@class SessionType @description Represents the type of session //@description The session is running on an Android device sessionTypeAndroid = SessionType; @@ -5754,6 +6146,11 @@ internalLinkTypeBotStart bot_username:string start_parameter:string autostart:Bo //@administrator_rights Expected administrator rights for the bot; may be null internalLinkTypeBotStartInGroup bot_username:string start_parameter:string administrator_rights:chatAdministratorRights = InternalLinkType; +//@description The link is a link to a business chat. Use getBusinessChatLinkInfo with the provided link name to get information about the link, +//-then open received private chat and replace chat draft with the provided text +//@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 internalLinkTypeChangePhoneNumber = InternalLinkType; @@ -5816,7 +6213,7 @@ internalLinkTypeMessage url:string = InternalLinkType; internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType; //@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application; otherwise, ignore it -//@bot_user_id User identifier of the service's bot +//@bot_user_id User identifier of the service's bot; the corresponding user may be unknown yet //@scope Telegram Passport element types requested by the service //@public_key Service's public key //@nonce Unique request identifier provided by the service @@ -5824,8 +6221,8 @@ internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLin //-If empty, then onActivityResult method must be used to return response on Android, or the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel must be opened otherwise internalLinkTypePassportDataRequest bot_user_id:int53 scope:string public_key:string nonce:string callback_url:string = InternalLinkType; -//@description The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberConfirmationCode with the given hash and phone number to process the link. -//-If succeeded, call checkPhoneNumberConfirmationCode to check entered by the user code, or resendPhoneNumberConfirmationCode to resend it +//@description The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberCode with the given phone number and with phoneNumberCodeTypeConfirmOwnership with the given hash to process the link. +//-If succeeded, call checkPhoneNumberCode to check entered by the user code, or resendPhoneNumberCode to resend it //@hash Hash value from the link //@phone_number Phone number value from the link internalLinkTypePhoneNumberConfirmation hash:string phone_number:string = InternalLinkType; @@ -5852,9 +6249,11 @@ internalLinkTypePrivacyAndSecuritySettings = InternalLinkType; internalLinkTypeProxy server:string port:int32 type:ProxyType = InternalLinkType; //@description The link is a link to a chat by its username. Call searchPublicChat with the given chat username to process the link -//-If the chat is found, open its profile information screen or the chat itself +//-If the chat is found, open its profile information screen or the chat itself. +//-If draft text isn't empty and the chat is a private chat with a regular user, then put the draft text in the input field //@chat_username Username of the chat -internalLinkTypePublicChat chat_username:string = InternalLinkType; +//@draft_text Draft text for message to send in the chat +internalLinkTypePublicChat chat_username:string draft_text:string = InternalLinkType; //@description The link can be used to login the current user on another device, but it must be scanned from QR-code using in-app camera. An alert similar to //-"This code can be used to allow someone to log in to your Telegram account. To confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code" needs to be shown @@ -5898,9 +6297,10 @@ internalLinkTypeUnknownDeepLink link:string = InternalLinkType; internalLinkTypeUnsupportedProxy = InternalLinkType; //@description The link is a link to a user by its phone number. Call searchUserByPhoneNumber with the given phone number to process the link. -//-If the user is found, then call createPrivateChat and open the chat +//-If the user is found, then call createPrivateChat and open the chat. If draft text isn't empty, then put the draft text in the input field //@phone_number Phone number of the user -internalLinkTypeUserPhoneNumber phone_number:string = InternalLinkType; +//@draft_text Draft text for message to send in the chat +internalLinkTypeUserPhoneNumber phone_number:string draft_text:string = InternalLinkType; //@description The link is a link to a user by a temporary token. Call searchUserByToken with the given token to process the link. //-If the user is found, then call createPrivateChat and open the chat @@ -5945,7 +6345,7 @@ chatBoostLink link:string is_public:Bool = ChatBoostLink; chatBoostLinkInfo is_public:Bool chat_id:int53 = ChatBoostLinkInfo; -//@class BlockList @description Describes a type of a block list +//@class BlockList @description Describes type of block list //@description The main block list that disallows writing messages to the current user, receiving their status and photo, viewing of stories, and some other actions blockListMain = BlockList; @@ -5958,7 +6358,7 @@ blockListStories = BlockList; filePart data:bytes = FilePart; -//@class FileType @description Represents the type of a file +//@class FileType @description Represents the type of file //@description The data is not a file fileTypeNone = FileType; @@ -6050,7 +6450,7 @@ storageStatisticsFast files_size:int53 file_count:int32 database_size:int53 lang databaseStatistics statistics:string = DatabaseStatistics; -//@class NetworkType @description Represents the type of a network +//@class NetworkType @description Represents the type of network //@description The network is not available networkTypeNone = NetworkType; @@ -6190,7 +6590,7 @@ foundPosition position:int32 = FoundPosition; foundPositions total_count:int32 positions:vector = FoundPositions; -//@class TMeUrlType @description Describes the type of a URL linking to an internal Telegram entity +//@class TMeUrlType @description Describes the type of URL linking to an internal Telegram entity //@description A URL linking to a user @user_id Identifier of the user tMeUrlTypeUser user_id:int53 = TMeUrlType; @@ -6244,6 +6644,12 @@ suggestedActionSubscribeToAnnualPremium = SuggestedAction; //@description Suggests the user to gift Telegram Premium to friends for Christmas suggestedActionGiftPremiumForChristmas = SuggestedAction; +//@description Suggests the user to set birthdate +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 Contains a counter @count Count count count:int32 = Count; @@ -6272,7 +6678,7 @@ textParseModeMarkdown version:int32 = TextParseMode; textParseModeHTML = TextParseMode; -//@class ProxyType @description Describes the type of a proxy server +//@class ProxyType @description Describes the type of proxy server //@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty proxyTypeSocks5 username:string password:string = ProxyType; @@ -6300,10 +6706,11 @@ proxies proxies:vector = Proxies; //@description A sticker to be added to a sticker set //@sticker File with the sticker; must fit in a 512x512 square. For WEBP stickers the file must be in WEBP or PNG format, which will be converted to WEBP server-side. //-See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements +//@format Format of the sticker //@emojis String with 1-20 emoji corresponding to the sticker //@mask_position Position where the mask is placed; pass null if not specified //@keywords List of up to 20 keywords with total length up to 64 characters, which can be used to find the sticker -inputSticker sticker:InputFile emojis:string mask_position:maskPosition keywords:vector = InputSticker; +inputSticker sticker:InputFile format:StickerFormat emojis:string mask_position:maskPosition keywords:vector = InputSticker; //@description Represents a date range @start_date Point in time (Unix timestamp) at which the date range begins @end_date Point in time (Unix timestamp) at which the date range ends @@ -6326,7 +6733,7 @@ statisticalGraphAsync token:string = StatisticalGraph; statisticalGraphError error_message:string = StatisticalGraph; -//@class ChatStatisticsObjectType @description Describes type of an object, for which statistics are provided +//@class ChatStatisticsObjectType @description Describes type of object, for which statistics are provided //@description Describes a message sent in the chat @message_id Message identifier chatStatisticsObjectTypeMessage message_id:int53 = ChatStatisticsObjectType; @@ -6408,6 +6815,20 @@ chatStatisticsSupergroup period:dateRange member_count:statisticalValue message_ chatStatisticsChannel period:dateRange member_count:statisticalValue mean_message_view_count:statisticalValue mean_message_share_count:statisticalValue mean_message_reaction_count:statisticalValue mean_story_view_count:statisticalValue mean_story_share_count:statisticalValue mean_story_reaction_count:statisticalValue enabled_notifications_percentage:double member_count_graph:StatisticalGraph join_graph:StatisticalGraph mute_graph:StatisticalGraph view_count_by_hour_graph:StatisticalGraph view_count_by_source_graph:StatisticalGraph join_by_source_graph:StatisticalGraph language_graph:StatisticalGraph message_interaction_graph:StatisticalGraph message_reaction_graph:StatisticalGraph story_interaction_graph:StatisticalGraph story_reaction_graph:StatisticalGraph instant_view_interaction_graph:StatisticalGraph recent_interactions:vector = ChatStatistics; +//@description Contains information about revenue earned from sponsored messages in a chat +//@cryptocurrency Cryptocurrency in which revenue is calculated +//@total_amount Total amount of the cryptocurrency earned, in the smallest units of the cryptocurrency +//@balance_amount Amount of the cryptocurrency that isn't withdrawn yet, in the smallest units of the cryptocurrency +//@available_amount Amount of the cryptocurrency available for withdrawal, in the smallest units of the cryptocurrency +chatRevenueAmount cryptocurrency:string total_amount:int64 balance_amount:int64 available_amount:int64 = ChatRevenueAmount; + +//@description A detailed statistics about revenue earned from sponsored messages in a chat +//@revenue_by_hour_graph A graph containing amount of revenue in a given hour +//@revenue_graph A graph containing amount of revenue +//@revenue_amount Amount of earned revenue +//@usd_rate Current conversion rate of the cryptocurrency in which revenue is calculated to USD +chatRevenueStatistics revenue_by_hour_graph:StatisticalGraph revenue_graph:StatisticalGraph revenue_amount:chatRevenueAmount usd_rate:double = ChatRevenueStatistics; + //@description A detailed statistics about a message //@message_interaction_graph A graph containing number of message views and shares //@message_reaction_graph A graph containing number of message reactions @@ -6419,6 +6840,48 @@ messageStatistics message_interaction_graph:StatisticalGraph message_reaction_gr storyStatistics story_interaction_graph:StatisticalGraph story_reaction_graph:StatisticalGraph = StoryStatistics; +//@class ChatRevenueWithdrawalState @description Describes state of a chat revenue withdrawal + +//@description Withdrawal is pending +chatRevenueWithdrawalStatePending = ChatRevenueWithdrawalState; + +//@description Withdrawal was completed +//@date Point in time (Unix timestamp) when the withdrawal was completed +//@url The URL where the withdrawal transaction can be viewed +chatRevenueWithdrawalStateCompleted date:int32 url:string = ChatRevenueWithdrawalState; + +//@description Withdrawal has_failed +chatRevenueWithdrawalStateFailed = ChatRevenueWithdrawalState; + + +//@class ChatRevenueTransactionType @description Describes type of transaction for revenue earned from sponsored messages in a chat + +//@description Describes earnings from sponsored messages in a chat in some time frame +//@start_date Point in time (Unix timestamp) when the earnings started +//@end_date Point in time (Unix timestamp) when the earnings ended +chatRevenueTransactionTypeEarnings start_date:int32 end_date:int32 = ChatRevenueTransactionType; + +//@description Describes a withdrawal of earnings +//@withdrawal_date Point in time (Unix timestamp) when the earnings withdrawal started +//@provider Name of the payment provider +//@state State of the withdrawal +chatRevenueTransactionTypeWithdrawal withdrawal_date:int32 provider:string state:ChatRevenueWithdrawalState = ChatRevenueTransactionType; + +//@description Describes a refund for failed withdrawal of earnings +//@refund_date Point in time (Unix timestamp) when the transaction was refunded +//@provider Name of the payment provider +chatRevenueTransactionTypeRefund refund_date:int32 provider:string = ChatRevenueTransactionType; + +//@description Contains a chat revenue transactions +//@cryptocurrency Cryptocurrency in which revenue is calculated +//@cryptocurrency_amount The withdrawn amount, in the smallest units of the cryptocurrency +//@type Type of the transaction +chatRevenueTransaction cryptocurrency:string cryptocurrency_amount:int64 type:ChatRevenueTransactionType = ChatRevenueTransaction; + +//@description Contains a list of chat revenue transactions @total_count Total number of transactions @transactions List of transactions +chatRevenueTransactions total_count:int32 transactions:vector = ChatRevenueTransactions; + + //@description A point on a Cartesian plane @x The point's first coordinate @y The point's second coordinate point x:double y:double = Point; @@ -6456,6 +6919,19 @@ botCommandScopeChatAdministrators chat_id:int53 = BotCommandScope; botCommandScopeChatMember chat_id:int53 user_id:int53 = BotCommandScope; +//@class PhoneNumberCodeType @description Describes type of the request for which a code is sent to a phone number + +//@description Checks ownership of a new phone number to change the user's authentication phone number; for official Android and iOS applications only +phoneNumberCodeTypeChange = PhoneNumberCodeType; + +//@description Verifies ownership of a phone number to be added to the user's Telegram Passport +phoneNumberCodeTypeVerify = PhoneNumberCodeType; + +//@description Confirms ownership of a phone number to prevent account deletion while handling links of the type internalLinkTypePhoneNumberConfirmation +//@hash Hash value from the link +phoneNumberCodeTypeConfirmOwnership hash:string = PhoneNumberCodeType; + + //@class Update @description Contains notifications about data changes //@description The user authorization state has changed @authorization_state New authorization state @@ -6508,6 +6984,12 @@ updateMessageMentionRead chat_id:int53 message_id:int53 unread_mention_count:int //@unread_reaction_count The new number of messages with unread reactions left in the chat updateMessageUnreadReactions chat_id:int53 message_id:int53 unread_reactions:vector unread_reaction_count:int32 = Update; +//@description A fact-check added to a message was changed +//@chat_id Chat identifier +//@message_id Message identifier +//@fact_check The new fact-check +updateMessageFactCheck chat_id:int53 message_id:int53 fact_check:factCheck = Update; + //@description A message with a live location was viewed. When the update is received, the application is supposed to update the live location //@chat_id Identifier of the chat with the live location message //@message_id Identifier of the message with live location @@ -6559,6 +7041,9 @@ updateChatReadOutbox chat_id:int53 last_read_outbox_message_id:int53 = Update; //@description The chat action bar was changed @chat_id Chat identifier @action_bar The new value of the action bar; may be null updateChatActionBar chat_id:int53 action_bar:ChatActionBar = Update; +//@description The bar for managing business bot was changed in a chat @chat_id Chat identifier @business_bot_manage_bar The new value of the business bot manage bar; may be null +updateChatBusinessBotManageBar chat_id:int53 business_bot_manage_bar:businessBotManageBar = Update; + //@description The chat available reactions were changed @chat_id Chat identifier @available_reactions The new reactions, available in the chat updateChatAvailableReactions chat_id:int53 available_reactions:ChatAvailableReactions = Update; @@ -6666,6 +7151,9 @@ updateForumTopicInfo chat_id:int53 info:forumTopicInfo = Update; //@description Notification settings for some type of chats were updated @scope Types of chats for which notification settings were updated @notification_settings The new notification settings updateScopeNotificationSettings scope:NotificationSettingsScope notification_settings:scopeNotificationSettings = Update; +//@description Notification settings for reactions were updated @notification_settings The new notification settings +updateReactionNotificationSettings notification_settings:reactionNotificationSettings = Update; + //@description A notification was changed @notification_group_id Unique notification group identifier @notification Changed notification updateNotification notification_group_id:int32 notification:notification = Update; @@ -6763,6 +7251,13 @@ updateFileDownload file_id:int32 complete_date:int32 is_paused:Bool counts:downl //@description A file was removed from the file download list. This update is sent only after file download list is loaded for the first time @file_id File identifier @counts New number of being downloaded and recently downloaded files found updateFileRemovedFromDownloads file_id:int32 counts:downloadedFileCounts = Update; +//@description A request can't be completed unless application verification is performed; for official mobile applications only. +//-The method setApplicationVerificationToken must be called once the verification is completed or failed +//@verification_id Unique identifier for the verification process +//@nonce Unique nonce for the classic Play Integrity verification (https://developer.android.com/google/play/integrity/classic) for Android, +//-or a unique string to compare with verify_nonce field from a push notification for iOS +updateApplicationVerificationRequired verification_id:int53 nonce:string = Update; + //@description New call was created or information about a call was updated @call New data about a call updateCall call:call = Update; @@ -6887,6 +7382,11 @@ updateWebAppMessageSent web_app_launch_id:int64 = Update; //@description The list of active emoji reactions has changed @emojis The new list of active emoji reactions updateActiveEmojiReactions emojis:vector = Update; +//@description The list of available message effects has changed +//@reaction_effect_ids The new list of available message effects from emoji reactions +//@sticker_effect_ids The new list of available message effects from Premium stickers +updateAvailableMessageEffects reaction_effect_ids:vector sticker_effect_ids:vector = Update; + //@description The type of default reaction has changed @reaction_type The new type of the default reaction updateDefaultReactionType reaction_type:ReactionType = Update; @@ -6895,8 +7395,16 @@ updateDefaultReactionType reaction_type:ReactionType = Update; //@tags The new tags updateSavedMessagesTags saved_messages_topic_id:int53 tags:savedMessagesTags = 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; + +//@description The revenue earned from sponsored messages in a chat has changed. If chat revenue screen is opened, then getChatRevenueTransactions may be called to fetch new transactions +//@chat_id Identifier of the chat +//@revenue_amount New amount of earned revenue +updateChatRevenueAmount chat_id:int53 revenue_amount:chatRevenueAmount = Update; + //@description The parameters of speech recognition without Telegram Premium subscription has changed -//@max_media_duration The maximum allowed duration of media for speech recognition without Telegram Premium subscription +//@max_media_duration The maximum allowed duration of media for speech recognition without Telegram Premium subscription, in seconds //@weekly_count The total number of allowed speech recognitions per week; 0 if none //@left_count Number of left speech recognition attempts this week //@next_reset_date Point in time (Unix timestamp) when the weekly number of tries will reset; 0 if unknown @@ -6917,12 +7425,32 @@ updateAnimationSearchParameters provider:string emojis:vector = Update; //@description The list of suggested to the user actions has changed @added_actions Added suggested actions @removed_actions Removed suggested actions updateSuggestedActions added_actions:vector removed_actions:vector = Update; -//@description Adding users to a chat has failed because of their privacy settings. An invite link can be shared with the users if appropriate @chat_id Chat identifier @user_ids Identifiers of users, which weren't added because of their privacy settings -updateAddChatMembersPrivacyForbidden chat_id:int53 user_ids:vector = Update; +//@description Download or upload file speed for the user was limited, but it can be restored by subscription to Telegram Premium. The notification can be postponed until a being downloaded or uploaded file is visible to the user +//-Use getOption("premium_download_speedup") or getOption("premium_upload_speedup") to get expected speedup after subscription to Telegram Premium +//@is_upload True, if upload speed was limited; false, if download speed was limited +updateSpeedLimitNotification is_upload:Bool = Update; + +//@description The list of contacts that had birthdays recently or will have birthday soon has changed @close_birthday_users List of contact users with close birthday +updateContactCloseBirthdays close_birthday_users:vector = Update; //@description Autosave settings for some type of chats were updated @scope Type of chats for which autosave settings were updated @settings The new autosave settings; may be null if the settings are reset to default updateAutosaveSettings scope:AutosaveSettingsScope settings:scopeAutosaveSettings = Update; +//@description A business connection has changed; for bots only @connection New data about the connection +updateBusinessConnection connection:businessConnection = Update; + +//@description A new message was added to a business account; for bots only @connection_id Unique identifier of the business connection @message The new message +updateNewBusinessMessage connection_id:string message:businessMessage = Update; + +//@description A message in a business account was edited; for bots only @connection_id Unique identifier of the business connection @message The edited message +updateBusinessMessageEdited connection_id:string message:businessMessage = Update; + +//@description Messages in a business account were deleted; for bots only +//@connection_id Unique identifier of the business connection +//@chat_id Identifier of a chat in the business account in which messages were deleted +//@message_ids Unique message identifiers of the deleted messages +updateBusinessMessagesDeleted connection_id:string chat_id:int53 message_ids:vector = Update; + //@description A new incoming inline query; for bots only //@id Unique query identifier //@sender_user_id Identifier of the user who sent the query @@ -6994,10 +7522,11 @@ updatePollAnswer poll_id:int64 voter_id:MessageSender option_ids:vector = //@actor_user_id Identifier of the user, changing the rights //@date Point in time (Unix timestamp) when the user rights were changed //@invite_link If user has joined the chat using an invite link, the invite link; may be null +//@via_join_request True, if the user has joined the chat after sending a join request and being approved by an administrator //@via_chat_folder_invite_link True, if the user has joined the chat using an invite link for a chat folder //@old_chat_member Previous chat member //@new_chat_member New chat member -updateChatMember chat_id:int53 actor_user_id:int53 date:int32 invite_link:chatInviteLink via_chat_folder_invite_link:Bool old_chat_member:chatMember new_chat_member:chatMember = Update; +updateChatMember chat_id:int53 actor_user_id:int53 date:int32 invite_link:chatInviteLink via_join_request:Bool via_chat_folder_invite_link:Bool old_chat_member:chatMember new_chat_member:chatMember = Update; //@description A user sent a join request to a chat; for bots only //@chat_id Chat identifier @@ -7105,11 +7634,12 @@ setAuthenticationPhoneNumber phone_number:string settings:phoneNumberAuthenticat //@description Sets the email address of the user and sends an authentication code to the email address. Works only when the current authorization state is authorizationStateWaitEmailAddress @email_address The email address of the user setAuthenticationEmailAddress email_address:string = Ok; -//@description Resends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode, the next_code_type of the result is not null and the server-specified timeout has passed, -//-or when the current authorization state is authorizationStateWaitEmailCode -resendAuthenticationCode = Ok; +//@description Resends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode, the next_code_type of the result is not null +//-and the server-specified timeout has passed, or when the current authorization state is authorizationStateWaitEmailCode +//@reason Reason of code resending; pass null if unknown +resendAuthenticationCode reason:ResendCodeReason = Ok; -//@description Checks the authentication of a email address. Works only when the current authorization state is authorizationStateWaitEmailCode @code Email address authentication to check +//@description Checks the authentication of an email address. Works only when the current authorization state is authorizationStateWaitEmailCode @code Email address authentication to check checkAuthenticationEmailCode code:EmailAddressAuthentication = Ok; //@description Checks the authentication code. Works only when the current authorization state is authorizationStateWaitCode @code Authentication code to check @@ -7146,9 +7676,12 @@ checkAuthenticationPasswordRecoveryCode recovery_code:string = Ok; recoverAuthenticationPassword recovery_code:string new_password:string new_hint:string = Ok; //@description Sends Firebase Authentication SMS to the phone number of the user. Works only when the current authorization state is authorizationStateWaitCode and the server returned code of the type authenticationCodeTypeFirebaseAndroid or authenticationCodeTypeFirebaseIos -//@token SafetyNet Attestation API token for the Android application, or secret from push notification for the iOS application +//@token Play Integrity API or SafetyNet Attestation API token for the Android application, or secret from push notification for the iOS application sendAuthenticationFirebaseSms token:string = Ok; +//@description Reports that authentication code wasn't delivered via SMS; for official mobile applications only. Works only when the current authorization state is authorizationStateWaitCode @mobile_network_code Current mobile network code +reportAuthenticationCodeMissing mobile_network_code:string = Ok; + //@description Checks the authentication token of a bot; to log in as a bot. Works only when the current authorization state is authorizationStateWaitPhoneNumber. Can be used instead of setAuthenticationPhoneNumber and checkAuthenticationCode to log in @token The bot token checkAuthenticationBotToken token:string = Ok; @@ -7187,7 +7720,7 @@ getPasswordState = PasswordState; setPassword old_password:string new_password:string new_hint:string set_recovery_email_address:Bool new_recovery_email_address:string = PasswordState; //@description Changes the login email address of the user. The email address can be changed only if the current user already has login email and passwordState.login_email_address_pattern is non-empty. -//-The change will not be applied until the new login email address is confirmed with checkLoginEmailAddressCode. To use Apple ID/Google ID instead of a email address, call checkLoginEmailAddressCode directly +//-The change will not be applied until the new login email address is confirmed with checkLoginEmailAddressCode. To use Apple ID/Google ID instead of an email address, call checkLoginEmailAddressCode directly //@new_login_email_address New login email address setLoginEmailAddress new_login_email_address:string = EmailAddressAuthenticationCodeInfo; @@ -7342,6 +7875,9 @@ searchChatsOnServer query:string limit:int32 = Chats; //@location Current user location searchChatsNearby location:location = ChatsNearby; +//@description Returns a list of channel chats recommended to the current user +getRecommendedChats = Chats; + //@description Returns a list of chats similar to the given chat @chat_id Identifier of the target chat; must be an identifier of a channel chat getChatSimilarChats chat_id:int53 = Chats; @@ -7350,7 +7886,7 @@ getChatSimilarChats chat_id:int53 = Chats; //@return_local Pass true to get the number of chats without sending network requests, or -1 if the number of chats is unknown locally getChatSimilarChatCount chat_id:int53 return_local:Bool = Count; -//@description Informs TDLib that a chat was opened from the list of similar chats. The method is independent from openChat and closeChat methods +//@description Informs TDLib that a chat was opened from the list of similar chats. The method is independent of openChat and closeChat methods //@chat_id Identifier of the original chat, which similar chats were requested //@opened_chat_id Identifier of the opened chat openChatSimilarChat chat_id:int53 opened_chat_id:int53 = Ok; @@ -7394,6 +7930,9 @@ getSuitableDiscussionChats = Chats; //@description Returns a list of recently inactive supergroups and channels. Can be used when user reaches limit on the number of joined supergroups and channels and receives CHANNELS_TOO_MUCH error. Also, the limit can be increased with Telegram Premium getInactiveSupergroupChats = Chats; +//@description Returns a list of channel chats, which can be used as a personal chat +getSuitablePersonalChats = Chats; + //@description Loads more Saved Messages topics. The loaded topics will be sent through updateSavedMessagesTopic. Topics are sorted by their topic.order in descending order. Returns a 404 error if all topics have been loaded //@limit The maximum number of topics to be loaded. For optimal performance, the number of loaded topics is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached @@ -7486,13 +8025,14 @@ searchChatMessages chat_id:int53 query:string sender_id:MessageSender from_messa //@description Searches for messages in all chats except secret chats. Returns the results in reverse chronological order (i.e., in order of decreasing (date, chat_id, message_id)). //-For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit //@chat_list Chat list in which to search messages; pass null to search in all chats regardless of their chat list. Only Main and Archive chat lists are supported +//@only_in_channels Pass true to search only for messages in channels //@query Query to search for //@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results //@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit //@filter Additional filter for messages to search; pass null to search for all messages. Filters searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterUnreadReaction, searchMessagesFilterFailedToSend, and searchMessagesFilterPinned are unsupported in this function //@min_date If not 0, the minimum date of the messages to return //@max_date If not 0, the maximum date of the messages to return -searchMessages chat_list:ChatList query:string offset:string limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = FoundMessages; +searchMessages chat_list:ChatList only_in_channels:Bool query:string offset:string limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = FoundMessages; //@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance, the number of returned messages is chosen by TDLib //@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats @@ -7525,6 +8065,21 @@ searchCallMessages offset:string limit:int32 only_missed:Bool = FoundMessages; //@limit The maximum number of messages to be returned; up to 100 searchOutgoingDocumentMessages query:string limit:int32 = FoundMessages; +//@description Searches for public channel posts with the given hashtag. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit +//@hashtag Hashtag to search for +//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results +//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit +searchPublicHashtagMessages hashtag:string offset:string limit:int32 = FoundMessages; + +//@description Returns recently searched for hashtags by their prefix @prefix Prefix of hashtags to return @limit The maximum number of hashtags to be returned +getSearchedForHashtags prefix:string limit:int32 = Hashtags; + +//@description Removes a hashtag from the list of recently searched for hashtags @hashtag Hashtag to delete +removeSearchedForHashtag hashtag:string = Ok; + +//@description Clears the list of recently searched for hashtags +clearSearchedForHashtags = Ok; + //@description Deletes all call messages @revoke Pass true to delete the messages for all users deleteAllCallMessages revoke:Bool = Ok; @@ -7579,6 +8134,12 @@ getChatSponsoredMessages chat_id:int53 = SponsoredMessages; //@message_id Identifier of the sponsored message clickChatSponsoredMessage chat_id:int53 message_id:int53 = Ok; +//@description Reports a sponsored message to Telegram moderators +//@chat_id Chat identifier of the sponsored message +//@message_id Identifier of the sponsored message +//@option_id Option identifier chosen by the user; leave empty for the initial request +reportChatSponsoredMessage chat_id:int53 message_id:int53 option_id:bytes = ReportChatSponsoredMessageResult; + //@description Removes an active notification from notification list. Needs to be called only if the notification is removed by the current user @notification_group_id Identifier of notification group to which the notification belongs @notification_id Identifier of removed notification removeNotification notification_group_id:int32 notification_id:int32 = Ok; @@ -7633,7 +8194,7 @@ recognizeSpeech chat_id:int53 message_id:int53 = Ok; rateSpeechRecognition chat_id:int53 message_id:int53 is_good:Bool = Ok; -//@description Returns list of message sender identifiers, which can be used to send messages in a chat @chat_id Chat identifier +//@description Returns the list of message sender identifiers, which can be used to send messages in a chat @chat_id Chat identifier getChatAvailableMessageSenders chat_id:int53 = ChatMessageSenders; //@description Selects a message sender to send messages in a chat @chat_id Chat identifier @message_sender_id New message sender for the chat @@ -7648,12 +8209,13 @@ setChatMessageSender chat_id:int53 message_sender_id:MessageSender = Ok; //@input_message_content The content of the message to be sent sendMessage chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; -//@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages +//@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. +//-Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages //@chat_id Target chat //@message_thread_id If not 0, the message thread identifier in which the messages will be sent //@reply_to Information about the message or story to be replied; pass null if none //@options Options to be used to send the messages; pass null to use default options -//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album +//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album. All messages must have the same value of show_caption_above_media sendMessageAlbum chat_id:int53 message_thread_id:int53 reply_to:InputMessageReplyTo options:messageSendOptions input_message_contents:vector = Messages; //@description Invites a bot to a chat (if it is not yet a member) and sends it the /start command; requires can_invite_users member right. Bots can't be invited to a private chat other than the chat with the bot. @@ -7718,38 +8280,46 @@ deleteChatMessagesBySender chat_id:int53 sender_id:MessageSender = Ok; deleteChatMessagesByDate chat_id:int53 min_date:int32 max_date:int32 revoke:Bool = Ok; -//@description Edits the text of a message (or a text of a game message). Returns the edited message after the edit is completed on the server side +//@description Edits the text of a message (or a text of a game message). Returns the edited message after the edit is completed on the server side. +//-Can be used only if message.can_be_edited == true //@chat_id The chat the message belongs to //@message_id Identifier of the message //@reply_markup The new message reply markup; pass null if none; for bots only //@input_message_content New text content of the message. Must be of type inputMessageText editMessageText chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; -//@description Edits the message content of a live location. Messages can be edited for a limited period of time specified in the live location. Returns the edited message after the edit is completed on the server side +//@description Edits the message content of a live location. Messages can be edited for a limited period of time specified in the live location. +//-Returns the edited message after the edit is completed on the server side. Can be used only if message.can_be_edited == true //@chat_id The chat the message belongs to //@message_id Identifier of the message //@reply_markup The new message reply markup; pass null if none; for bots only //@location New location content of the message; pass null to stop sharing the live location +//@live_period New time relative to the message send date, for which the location can be updated, in seconds. If 0x7FFFFFFF specified, then the location can be updated forever. +//-Otherwise, must not exceed the current live_period by more than a day, and the live location expiration date must remain in the next 90 days. Pass 0 to keep the current live_period //@heading The new direction in which the location moves, in degrees; 1-360. Pass 0 if unknown //@proximity_alert_radius The new maximum distance for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled -editMessageLiveLocation chat_id:int53 message_id:int53 reply_markup:ReplyMarkup location:location heading:int32 proximity_alert_radius:int32 = Message; +editMessageLiveLocation chat_id:int53 message_id:int53 reply_markup:ReplyMarkup location:location live_period:int32 heading:int32 proximity_alert_radius:int32 = Message; //@description Edits the content of a message with an animation, an audio, a document, a photo or a video, including message caption. If only the caption needs to be edited, use editMessageCaption instead. -//-The media can't be edited if the message was set to self-destruct or to a self-destructing media. The type of message content in an album can't be changed with exception of replacing a photo with a video or vice versa. Returns the edited message after the edit is completed on the server side +//-The media can't be edited if the message was set to self-destruct or to a self-destructing media. The type of message content in an album can't be changed with exception of replacing a photo with a video or vice versa. +//-Returns the edited message after the edit is completed on the server side. Can be used only if message.can_be_edited == true //@chat_id The chat the message belongs to //@message_id Identifier of the message //@reply_markup The new message reply markup; pass null if none; for bots only //@input_message_content New content of the message. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, inputMessagePhoto or inputMessageVideo editMessageMedia chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message; -//@description Edits the message content caption. Returns the edited message after the edit is completed on the server side +//@description Edits the message content caption. Returns the edited message after the edit is completed on the server side. +//-Can be used only if message.can_be_edited == true //@chat_id The chat the message belongs to //@message_id Identifier of the message //@reply_markup The new message reply markup; pass null if none; for bots only //@caption New message content caption; 0-getOption("message_caption_length_max") characters; pass null to remove caption -editMessageCaption chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:formattedText = Message; +//@show_caption_above_media Pass true to show the caption above the media; otherwise, caption will be shown below the media. Can be true only for animation, photo, and video messages +editMessageCaption chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:formattedText show_caption_above_media:Bool = Message; -//@description Edits the message reply markup; for bots only. Returns the edited message after the edit is completed on the server side +//@description Edits the message reply markup; for bots only. Returns the edited message after the edit is completed on the server side. +//-Can be used only if message.can_be_edited == true //@chat_id The chat the message belongs to //@message_id Identifier of the message //@reply_markup The new message reply markup; pass null if none @@ -7765,9 +8335,11 @@ editInlineMessageText inline_message_id:string reply_markup:ReplyMarkup input_me //@inline_message_id Inline message identifier //@reply_markup The new message reply markup; pass null if none //@location New location content of the message; pass null to stop sharing the live location +//@live_period New time relative to the message send date, for which the location can be updated, in seconds. If 0x7FFFFFFF specified, then the location can be updated forever. +//-Otherwise, must not exceed the current live_period by more than a day, and the live location expiration date must remain in the next 90 days. Pass 0 to keep the current live_period //@heading The new direction in which the location moves, in degrees; 1-360. Pass 0 if unknown //@proximity_alert_radius The new maximum distance for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled -editInlineMessageLiveLocation inline_message_id:string reply_markup:ReplyMarkup location:location heading:int32 proximity_alert_radius:int32 = Ok; +editInlineMessageLiveLocation inline_message_id:string reply_markup:ReplyMarkup location:location live_period:int32 heading:int32 proximity_alert_radius:int32 = Ok; //@description Edits the content of a message with an animation, an audio, a document, a photo or a video in an inline message sent via a bot; for bots only //@inline_message_id Inline message identifier @@ -7779,7 +8351,8 @@ editInlineMessageMedia inline_message_id:string reply_markup:ReplyMarkup input_m //@inline_message_id Inline message identifier //@reply_markup The new message reply markup; pass null if none //@caption New message content caption; pass null to remove caption; 0-getOption("message_caption_length_max") characters -editInlineMessageCaption inline_message_id:string reply_markup:ReplyMarkup caption:formattedText = Ok; +//@show_caption_above_media Pass true to show the caption above the media; otherwise, caption will be shown below the media. Can be true only for animation, photo, and video messages +editInlineMessageCaption inline_message_id:string reply_markup:ReplyMarkup caption:formattedText show_caption_above_media:Bool = Ok; //@description Edits the reply markup of an inline message sent via a bot; for bots only //@inline_message_id Inline message identifier @@ -7792,6 +8365,35 @@ editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = //@scheduling_state The new message scheduling state; pass null to send the message immediately editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok; +//@description Changes the fact-check of a message. Can be only used if getOption("can_edit_fact_check") == true +//@chat_id The channel chat the message belongs to +//@message_id Identifier of the message +//@text New text of the fact-check; 0-getOption("fact_check_length_max") characters; pass null to remove it. Only Bold, Italic, and TextUrl entities with https://t.me/ links are supported +setMessageFactCheck chat_id:int53 message_id:int53 text:formattedText = Ok; + + +//@description Sends a message on behalf of a business account; for bots only. Returns the message after it was sent +//@business_connection_id Unique identifier of business connection on behalf of which to send the request +//@chat_id Target chat +//@reply_to Information about the message to be replied; pass null if none +//@disable_notification Pass true to disable notification for the message +//@protect_content Pass true if the content of the message must be protected from forwarding and saving +//@effect_id Identifier of the effect to apply to the message +//@reply_markup Markup for replying to the message; pass null if none +//@input_message_content The content of the message to be sent +sendBusinessMessage business_connection_id:string chat_id:int53 reply_to:InputMessageReplyTo disable_notification:Bool protect_content:Bool effect_id:int64 reply_markup:ReplyMarkup input_message_content:InputMessageContent = BusinessMessage; + +//@description Sends 2-10 messages grouped together into an album on behalf of a business account; for bots only. Currently, only audio, document, photo and video messages can be grouped into an album. +//-Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages +//@business_connection_id Unique identifier of business connection on behalf of which to send the request +//@chat_id Target chat +//@reply_to Information about the message to be replied; pass null if none +//@disable_notification Pass true to disable notification for the message +//@protect_content Pass true if the content of the message must be protected from forwarding and saving +//@effect_id Identifier of the effect to apply to the message +//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album. All messages must have the same value of show_caption_above_media +sendBusinessMessageAlbum business_connection_id:string chat_id:int53 reply_to:InputMessageReplyTo disable_notification:Bool protect_content:Bool effect_id:int64 input_message_contents:vector = BusinessMessages; + //@description Checks validness of a name for a quick reply shortcut. Can be called synchronously @name The name of the shortcut; 1-32 characters checkQuickReplyShortcutName name:string = Ok; @@ -7817,8 +8419,44 @@ loadQuickReplyShortcutMessages shortcut_id:int32 = Ok; //@message_ids Unique identifiers of the messages deleteQuickReplyShortcutMessages shortcut_id:int32 message_ids:vector = Ok; +//@description Adds a message to a quick reply shortcut. If shortcut doesn't exist and there are less than getOption("quick_reply_shortcut_count_max") shortcuts, then a new shortcut is created. +//-The shortcut must not contain more than getOption("quick_reply_shortcut_message_count_max") messages after adding the new message. Returns the added message +//@shortcut_name Name of the target shortcut +//@reply_to_message_id Identifier of a quick reply message in the same shortcut to be replied; pass 0 if none +//@input_message_content The content of the message to be added; inputMessagePoll, inputMessageForwarded and inputMessageLocation with live_period aren't supported +addQuickReplyShortcutMessage shortcut_name:string reply_to_message_id:int53 input_message_content:InputMessageContent = QuickReplyMessage; + +//@description Adds a message to a quick reply shortcut via inline bot. If shortcut doesn't exist and there are less than getOption("quick_reply_shortcut_count_max") shortcuts, then a new shortcut is created. +//-The shortcut must not contain more than getOption("quick_reply_shortcut_message_count_max") messages after adding the new message. Returns the added message +//@shortcut_name Name of the target shortcut +//@reply_to_message_id Identifier of a quick reply message in the same shortcut to be replied; pass 0 if none +//@query_id Identifier of the inline query +//@result_id Identifier of the inline query result +//@hide_via_bot Pass true to hide the bot, via which the message is sent. Can be used only for bots getOption("animation_search_bot_username"), getOption("photo_search_bot_username"), and getOption("venue_search_bot_username") +addQuickReplyShortcutInlineQueryResultMessage shortcut_name:string reply_to_message_id:int53 query_id:int64 result_id:string hide_via_bot:Bool = QuickReplyMessage; + +//@description Adds 2-10 messages grouped together into an album to a quick reply shortcut. Currently, only audio, document, photo and video messages can be grouped into an album. +//-Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages +//@shortcut_name Name of the target shortcut +//@reply_to_message_id Identifier of a quick reply message in the same shortcut to be replied; pass 0 if none +//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album. All messages must have the same value of show_caption_above_media +addQuickReplyShortcutMessageAlbum shortcut_name:string reply_to_message_id:int53 input_message_contents:vector = QuickReplyMessages; + +//@description Readds quick reply messages which failed to add. Can be called only for messages for which messageSendingStateFailed.can_retry is true and after specified in messageSendingStateFailed.retry_after time passed. +//-If a message is readded, the corresponding failed to send message is deleted. Returns the sent messages in the same order as the message identifiers passed in message_ids. If a message can't be readded, null will be returned instead of the message +//@shortcut_name Name of the target shortcut +//@message_ids Identifiers of the quick reply messages to readd. Message identifiers must be in a strictly increasing order +readdQuickReplyShortcutMessages shortcut_name:string message_ids:vector = QuickReplyMessages; + +//@description Asynchronously edits the text, media or caption of a quick reply message. Use quickReplyMessage.can_be_edited to check whether a message can be edited. +//-Text message can be edited only to a text message. The type of message content in an album can't be changed with exception of replacing a photo with a video or vice versa +//@shortcut_id Unique identifier of the quick reply shortcut with the message +//@message_id Identifier of the message +//@input_message_content New content of the message. Must be one of the following types: inputMessageText, inputMessageAnimation, inputMessageAudio, inputMessageDocument, inputMessagePhoto or inputMessageVideo +editQuickReplyMessage shortcut_id:int32 message_id:int53 input_message_content:InputMessageContent = Ok; + -//@description Returns list of custom emojis, which can be used as forum topic icon by all users +//@description Returns the list of custom emojis, which can be used as forum topic icon by all users getForumTopicDefaultIcons = Stickers; //@description Creates a topic in a forum supergroup chat; requires can_manage_topics administrator or can_create_topics member right in the supergroup @@ -7936,6 +8574,9 @@ getSavedMessagesTags saved_messages_topic_id:int53 = SavedMessagesTags; //@description Changes label of a Saved Messages tag; for Telegram Premium users only @tag The tag which label will be changed @label New label for the tag; 0-12 characters setSavedMessagesTagLabel tag:ReactionType label:string = Ok; +//@description Returns information about a message effect. Returns a 404 error if the effect is not found @effect_id Unique identifier of the effect +getMessageEffect effect_id:int64 = MessageEffect; + //@description Searches for a given quote in a text. Returns found quote start position in UTF-16 code units. Returns a 404 error if the quote is not found. Can be called synchronously //@text Text in which to search for the quote @@ -7946,7 +8587,10 @@ searchQuote text:formattedText quote:formattedText quote_position:int32 = FoundP //@description Returns all entities (mentions, hashtags, cashtags, bot commands, bank card numbers, URLs, and email addresses) found in the text. Can be called synchronously @text The text in which to look for entities getTextEntities text:string = TextEntities; -//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, Code, Pre, PreCode, TextUrl and MentionName entities from a marked-up text. Can be called synchronously @text The text to parse @parse_mode Text parse mode +//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, ExpandableBlockQuote, Code, Pre, PreCode, TextUrl +//-and MentionName entities from a marked-up text. Can be called synchronously +//@text The text to parse +//@parse_mode Text parse mode parseTextEntities text:string parse_mode:TextParseMode = FormattedText; //@description Parses Markdown entities in a human-friendly format, ignoring markup errors. Can be called synchronously @@ -8009,6 +8653,13 @@ stopPoll chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Ok; //@description Hides a suggested action @action Suggested action to hide hideSuggestedAction action:SuggestedAction = Ok; +//@description Hides the list of contacts that have close birthdays for 24 hours +hideContactCloseBirthdays = Ok; + + +//@description Returns information about a business connection by its identifier; for bots only @connection_id Identifier of the business connection to return +getBusinessConnection connection_id:string = BusinessConnection; + //@description Returns information about a button of type inlineKeyboardButtonTypeLoginUrl. The method needs to be called when the user presses the button //@chat_id Chat identifier of the message with the button @@ -8162,8 +8813,12 @@ getInlineGameHighScores inline_message_id:string user_id:int53 = GameHighScores; deleteChatReplyMarkup chat_id:int53 message_id:int53 = Ok; -//@description Sends a notification about user activity in a chat @chat_id Chat identifier @message_thread_id If not 0, the message thread identifier in which the action was performed @action The action description; pass null to cancel the currently active action -sendChatAction chat_id:int53 message_thread_id:int53 action:ChatAction = Ok; +//@description Sends a notification about user activity in a chat +//@chat_id Chat identifier +//@message_thread_id If not 0, the message thread identifier in which the action was performed +//@business_connection_id Unique identifier of business connection on behalf of which to send the request; for bots only +//@action The action description; pass null to cancel the currently active action +sendChatAction chat_id:int53 message_thread_id:int53 business_connection_id:string action:ChatAction = Ok; //@description Informs TDLib that the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) @chat_id Chat identifier @@ -8192,7 +8847,7 @@ clickAnimatedEmojiMessage chat_id:int53 message_id:int53 = Sticker; //@description Returns an HTTPS or a tg: link with the given type. Can be called before authorization @type Expected type of the link @is_http Pass true to create an HTTPS link (only available for some link types); pass false to create a tg: link getInternalLink type:InternalLinkType is_http:Bool = HttpUrl; -//@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link +//@description Returns information about the type of internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link getInternalLinkType link:string = InternalLinkType; //@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if web page preview is disabled in secret chats @link The link @@ -8229,11 +8884,11 @@ createSupergroupChat supergroup_id:int53 force:Bool = Chat; //@description Returns an existing chat corresponding to a known secret chat @secret_chat_id Secret chat identifier createSecretChat secret_chat_id:int32 = Chat; -//@description Creates a new basic group and sends a corresponding messageBasicGroupChatCreate. Returns the newly created chat +//@description Creates a new basic group and sends a corresponding messageBasicGroupChatCreate. Returns information about the newly created chat //@user_ids Identifiers of users to be added to the basic group; may be empty to create a basic group without other members //@title Title of the new basic group; 1-128 characters //@message_auto_delete_time Message auto-delete time value, in seconds; must be from 0 up to 365 * 86400 and be divisible by 86400. If 0, then messages aren't deleted automatically -createNewBasicGroupChat user_ids:vector title:string message_auto_delete_time:int32 = Chat; +createNewBasicGroupChat user_ids:vector title:string message_auto_delete_time:int32 = CreatedBasicGroupChat; //@description Creates a new supergroup or channel and sends a corresponding messageSupergroupChatCreate. Returns the newly created chat //@title Title of the new chat; 1-128 characters @@ -8466,16 +9121,17 @@ joinChat chat_id:int53 = Ok; //@description Removes the current user from chat members. Private and secret chats can't be left using this method @chat_id Chat identifier leaveChat chat_id:int53 = Ok; -//@description Adds a new member to a chat; requires can_invite_users member right. Members can't be added to private or secret chats +//@description Adds a new member to a chat; requires can_invite_users member right. Members can't be added to private or secret chats. Returns information about members that weren't added //@chat_id Chat identifier //@user_id Identifier of the user //@forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 100. Ignored for supergroups and channels, or if the added user is a bot -addChatMember chat_id:int53 user_id:int53 forward_limit:int32 = Ok; +addChatMember chat_id:int53 user_id:int53 forward_limit:int32 = FailedToAddMembers; -//@description Adds multiple new members to a chat; requires can_invite_users member right. Currently, this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members +//@description Adds multiple new members to a chat; requires can_invite_users member right. Currently, this method is only available for supergroups and channels. +//-This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members. Returns information about members that weren't added //@chat_id Chat identifier //@user_ids Identifiers of the users to be added to the chat. The maximum number of added users is 20 for supergroups and 100 for channels -addChatMembers chat_id:int53 user_ids:vector = Ok; +addChatMembers chat_id:int53 user_ids:vector = FailedToAddMembers; //@description Changes the status of a chat member; requires can_invite_users member right to add a chat member, can_promote_members administrator right to change administrator rights of the member, //-and can_restrict_members administrator right to change restrictions of a user. This function is currently not suitable for transferring chat ownership; use transferChatOwnership instead. @@ -8522,7 +9178,7 @@ clearAllDraftMessages exclude_secret_chats:Bool = Ok; //@description Returns saved notification sound by its identifier. Returns a 404 error if there is no saved notification sound with the specified identifier @notification_sound_id Identifier of the notification sound getSavedNotificationSound notification_sound_id:int64 = NotificationSounds; -//@description Returns list of saved notification sounds. If a sound isn't in the list, then default sound needs to be used +//@description Returns the list of saved notification sounds. If a sound isn't in the list, then default sound needs to be used getSavedNotificationSounds = NotificationSounds; //@description Adds a new notification sound to the list of saved notification sounds. The new notification sound is added to the top of the list. If it is already in the list, its position isn't changed @sound Notification sound file to add @@ -8532,7 +9188,7 @@ addSavedNotificationSound sound:InputFile = NotificationSound; removeSavedNotificationSound notification_sound_id:int64 = Ok; -//@description Returns list of chats with non-default notification settings for new messages +//@description Returns the list of chats with non-default notification settings for new messages //@scope If specified, only chats from the scope will be returned; pass null to return chats from all scopes //@compare_sound Pass true to include in the response chats with only non-default sound getChatNotificationSettingsExceptions scope:NotificationSettingsScope compare_sound:Bool = Chats; @@ -8543,7 +9199,10 @@ getScopeNotificationSettings scope:NotificationSettingsScope = ScopeNotification //@description Changes notification settings for chats of a given type @scope Types of chats for which to change the notification settings @notification_settings The new notification settings for the given scope setScopeNotificationSettings scope:NotificationSettingsScope notification_settings:scopeNotificationSettings = Ok; -//@description Resets all notification settings to their default values. By default, all chats are unmuted and message previews are shown +//@description Changes notification settings for reactions @notification_settings The new notification settings for reactions +setReactionNotificationSettings notification_settings:reactionNotificationSettings = Ok; + +//@description Resets all chat and scope notification settings to their default values. By default, all chats are unmuted and message previews are shown resetAllNotificationSettings = Ok; @@ -8576,13 +9235,13 @@ canSendStory chat_id:int53 = CanSendStoryResult; //@chat_id Identifier of the chat that will post the story //@content Content of the story //@areas Clickable rectangle areas to be shown on the story media; pass null if none -//@caption Story caption; pass null to use an empty caption; 0-getOption("story_caption_length_max") characters +//@caption Story caption; pass null to use an empty caption; 0-getOption("story_caption_length_max") characters; can have entities only if getOption("can_use_text_entities_in_story_caption") //@privacy_settings The privacy settings for the story; ignored for stories sent to supergroup and channel chats //@active_period Period after which the story is moved to archive, in seconds; must be one of 6 * 3600, 12 * 3600, 86400, or 2 * 86400 for Telegram Premium users, and 86400 otherwise //@from_story_full_id Full identifier of the original story, which content was used to create the story -//@is_pinned Pass true to keep the story accessible after expiration +//@is_posted_to_chat_page Pass true to keep the story accessible after expiration //@protect_content Pass true if the content of the story must be protected from forwarding and screenshotting -sendStory chat_id:int53 content:InputStoryContent areas:inputStoryAreas caption:formattedText privacy_settings:StoryPrivacySettings active_period:int32 from_story_full_id:storyFullId is_pinned:Bool protect_content:Bool = Story; +sendStory chat_id:int53 content:InputStoryContent areas:inputStoryAreas caption:formattedText privacy_settings:StoryPrivacySettings active_period:int32 from_story_full_id:storyFullId is_posted_to_chat_page:Bool protect_content:Bool = Story; //@description Changes content and caption of a story. Can be called only if story.can_be_edited == true //@story_sender_chat_id Identifier of the chat that posted the story @@ -8597,18 +9256,18 @@ editStory story_sender_chat_id:int53 story_id:int32 content:InputStoryContent ar //@privacy_settings The new privacy settigs for the story setStoryPrivacySettings story_id:int32 privacy_settings:StoryPrivacySettings = Ok; -//@description Toggles whether a story is accessible after expiration. Can be called only if story.can_toggle_is_pinned == true +//@description Toggles whether a story is accessible after expiration. Can be called only if story.can_toggle_is_posted_to_chat_page == true //@story_sender_chat_id Identifier of the chat that posted the story //@story_id Identifier of the story -//@is_pinned Pass true to make the story accessible after expiration; pass false to make it private -toggleStoryIsPinned story_sender_chat_id:int53 story_id:int32 is_pinned:Bool = Ok; +//@is_posted_to_chat_page Pass true to make the story accessible after expiration; pass false to make it private +toggleStoryIsPostedToChatPage story_sender_chat_id:int53 story_id:int32 is_posted_to_chat_page:Bool = Ok; //@description Deletes a previously sent story. Can be called only if story.can_be_deleted == true //@story_sender_chat_id Identifier of the chat that posted the story //@story_id Identifier of the story to delete deleteStory story_sender_chat_id:int53 story_id:int32 = Ok; -//@description Returns list of chats with non-default notification settings for stories +//@description Returns the list of chats with non-default notification settings for stories getStoryNotificationSettingsExceptions = Chats; //@description Loads more active stories from a story list. The loaded stories will be sent through updates. Active stories are sorted by @@ -8622,13 +9281,13 @@ setChatActiveStoriesList chat_id:int53 story_list:StoryList = Ok; //@description Returns the list of active stories posted by the given chat @chat_id Chat identifier getChatActiveStories chat_id:int53 = ChatActiveStories; -//@description Returns the list of pinned stories posted by the given chat. The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). -//-For optimal performance, the number of returned stories is chosen by TDLib +//@description Returns the list of stories that posted by the given chat to its chat page. If from_story_id == 0, then pinned stories are returned first. +//-Then, stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). For optimal performance, the number of returned stories is chosen by TDLib //@chat_id Chat identifier -//@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from the last story +//@from_story_id Identifier of the story starting from which stories must be returned; use 0 to get results from pinned and the newest story //@limit The maximum number of stories to be returned //-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit -getChatPinnedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; +getChatPostedToChatPageStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; //@description Returns the list of all stories posted by the given chat; requires can_edit_stories right in the chat. //-The stories are returned in a reverse chronological order (i.e., in order of decreasing story_id). For optimal performance, the number of returned stories is chosen by TDLib @@ -8638,6 +9297,11 @@ getChatPinnedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; //-For optimal performance, the number of returned stories is chosen by TDLib and can be smaller than the specified limit getChatArchivedStories chat_id:int53 from_story_id:int32 limit:int32 = Stories; +//@description Changes the list of pinned stories on a chat page; requires can_edit_stories right in the chat +//@chat_id Identifier of the chat that posted the stories +//@story_ids New list of pinned stories. All stories must be posted to the chat page first. There can be up to getOption("pinned_story_count_max") pinned stories on a chat page +setChatPinnedStories chat_id:int53 story_ids:vector = Ok; + //@description Informs TDLib that a story is opened and is being viewed by the user //@story_sender_chat_id The identifier of the sender of the opened story //@story_id The identifier of the story @@ -8697,12 +9361,12 @@ activateStoryStealthMode = Ok; getStoryPublicForwards story_sender_chat_id:int53 story_id:int32 offset:string limit:int32 = PublicForwards; -//@description Returns list of features available on the specific chat boost level; this is an offline request +//@description Returns the list of features available on the specific chat boost level; this is an offline request //@is_channel Pass true to get the list of features for channels; pass false to get the list of features for supergroups //@level Chat boost level getChatBoostLevelFeatures is_channel:Bool level:int32 = ChatBoostLevelFeatures; -//@description Returns list of features available on the first 10 chat boost levels; this is an offline request +//@description Returns the list of features available for different chat boost levels; this is an offline request //@is_channel Pass true to get the list of features for channels; pass false to get the list of features for supergroups getChatBoostFeatures is_channel:Bool = ChatBoostFeatures; @@ -8723,14 +9387,14 @@ getChatBoostLink chat_id:int53 = ChatBoostLink; //@description Returns information about a link to boost a chat. Can be called for any internal link of the type internalLinkTypeChatBoost @url The link to boost a chat getChatBoostLinkInfo url:string = ChatBoostLinkInfo; -//@description Returns list of boosts applied to a chat; requires administrator rights in the chat +//@description Returns the list of boosts applied to a chat; requires administrator rights in the chat //@chat_id Identifier of the chat //@only_gift_codes Pass true to receive only boosts received from gift codes and giveaways created by the chat //@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results //@limit The maximum number of boosts to be returned; up to 100. For optimal performance, the number of returned boosts can be smaller than the specified limit getChatBoosts chat_id:int53 only_gift_codes:Bool offset:string limit:int32 = FoundChatBoosts; -//@description Returns list of boosts applied to a chat by a given user; requires administrator rights in the chat; for bots only +//@description Returns the list of boosts applied to a chat by a given user; requires administrator rights in the chat; for bots only //@chat_id Identifier of the chat //@user_id Identifier of the user getUserChatBoosts chat_id:int53 user_id:int53 = FoundChatBoosts; @@ -8786,8 +9450,9 @@ cancelDownloadFile file_id:int32 only_if_pending:Bool = Ok; //@description Returns suggested name for saving a file in a given directory @file_id Identifier of the file @directory Directory in which the file is supposed to be saved getSuggestedFileName file_id:int32 directory:string = Text; -//@description Preliminary uploads a file to the cloud before sending it in a message, which can be useful for uploading of being recorded voice and video notes. Updates updateFile will be used -//-to notify about upload progress and successful completion of the upload. The file will not have a persistent remote identifier until it is sent in a message +//@description Preliminary uploads a file to the cloud before sending it in a message, which can be useful for uploading of being recorded voice and video notes. +//-In all other cases there is no need to preliminary upload a file. Updates updateFile will be used to notify about upload progress. +//-The upload will not be completed until the file is sent in a message //@file File to upload //@file_type File type; pass null if unknown //@priority Priority of the upload (1-32). The higher the priority, the earlier the file will be uploaded. If the priorities of two files are equal, then the first one for which preliminaryUploadFile was called will be uploaded first @@ -8823,7 +9488,7 @@ readFilePart file_id:int32 offset:int53 count:int53 = FilePart; deleteFile file_id:int32 = Ok; //@description Adds a file from a message to the list of file downloads. Download progress and completion of the download will be notified through updateFile updates. -//-If message database is used, the list of file downloads is persistent across application restarts. The downloading is independent from download using downloadFile, i.e. it continues if downloadFile is canceled or is used to download a part of the file +//-If message database is used, the list of file downloads is persistent across application restarts. The downloading is independent of download using downloadFile, i.e. it continues if downloadFile is canceled or is used to download a part of the file //@file_id Identifier of the file to download //@chat_id Chat identifier of the message with the file //@message_id Message identifier @@ -8856,6 +9521,13 @@ removeAllFilesFromDownloads only_active:Bool only_completed:Bool delete_from_cac searchFileDownloads query:string only_active:Bool only_completed:Bool offset:string limit:int32 = FoundFileDownloads; +//@description Application verification has been completed. Can be called before authorization +//@verification_id Unique identifier for the verification process as received from updateApplicationVerificationRequired +//@token Play Integrity API token for the Android application, or secret from push notification for the iOS application; +//-pass an empty string to abort verification and receive error VERIFICATION_FAILED for the request +setApplicationVerificationToken verification_id:int53 token:string = Ok; + + //@description Returns information about a file with messages exported from another application @message_file_head Beginning of the message file; up to 100 first lines getMessageFileType message_file_head:string = MessageFileType; @@ -8895,7 +9567,7 @@ editChatInviteLink chat_id:int53 invite_link:string name:string expiration_date: //@invite_link Invite link to get getChatInviteLink chat_id:int53 invite_link:string = ChatInviteLink; -//@description Returns list of chat administrators with number of their invite links. Requires owner privileges in the chat @chat_id Chat identifier +//@description Returns the list of chat administrators with number of their invite links. Requires owner privileges in the chat @chat_id Chat identifier getChatInviteLinkCounts chat_id:int53 = ChatInviteLinkCounts; //@description Returns invite links for a chat created by specified administrator. Requires administrator privileges and can_invite_users right in the chat to get own links and owner privileges to get other links @@ -8983,7 +9655,7 @@ sendCallDebugInformation call_id:int32 debug_information:string = Ok; sendCallLog call_id:int32 log_file:InputFile = Ok; -//@description Returns list of participant identifiers, on whose behalf a video chat in the chat can be joined @chat_id Chat identifier +//@description Returns the list of participant identifiers, on whose behalf a video chat in the chat can be joined @chat_id Chat identifier getVideoChatAvailableParticipants chat_id:int53 = MessageSenders; //@description Changes default participant identifier, on whose behalf a video chat in the chat will be joined @chat_id Chat identifier @default_participant_id Default group call participant identifier to join the video chats @@ -9182,8 +9854,10 @@ setUserPersonalProfilePhoto user_id:int53 photo:InputChatPhoto = Ok; suggestUserProfilePhoto user_id:int53 photo:InputChatPhoto = Ok; -//@description Searches a user by their phone number. Returns a 404 error if the user can't be found @phone_number Phone number to search for -searchUserByPhoneNumber phone_number:string = User; +//@description Searches a user by their phone number. Returns a 404 error if the user can't be found +//@phone_number Phone number to search for +//@only_local Pass true to get only locally available information without sending network requests +searchUserByPhoneNumber phone_number:string only_local:Bool = User; //@description Shares the phone number of the current user with a mutual contact. Supposed to be called when the user clicks on chatActionBarSharePhoneNumber @user_id Identifier of the user with whom to share the phone number. The user must be a mutual contact sharePhoneNumber user_id:int53 = Ok; @@ -9213,13 +9887,19 @@ getAllStickerEmojis sticker_type:StickerType query:string chat_id:int53 return_o //@limit The maximum number of stickers to be returned; 0-100 searchStickers sticker_type:StickerType emojis:string limit:int32 = Stickers; +//@description Returns greeting stickers from regular sticker sets that can be used for the start page of other users +getGreetingStickers = Stickers; + //@description Returns premium stickers from regular sticker sets @limit The maximum number of stickers to be returned; 0-100 getPremiumStickers limit:int32 = Stickers; //@description Returns a list of installed sticker sets @sticker_type Type of the sticker sets to return getInstalledStickerSets sticker_type:StickerType = StickerSets; -//@description Returns a list of archived sticker sets @sticker_type Type of the sticker sets to return @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit The maximum number of sticker sets to return; up to 100 +//@description Returns a list of archived sticker sets +//@sticker_type Type of the sticker sets to return +//@offset_sticker_set_id Identifier of the sticker set from which to return the result; use 0 to get results from the beginning +//@limit The maximum number of sticker sets to return; up to 100 getArchivedStickerSets sticker_type:StickerType offset_sticker_set_id:int64 limit:int32 = StickerSets; //@description Returns a list of trending sticker sets. For optimal performance, the number of returned sticker sets is chosen by TDLib @@ -9258,7 +9938,7 @@ reorderInstalledStickerSets sticker_type:StickerType sticker_set_ids:vector = Stickers; //@description Returns default list of custom emoji stickers for placing on a chat photo @@ -9318,7 +10000,8 @@ getDefaultBackgroundCustomEmojiStickers = Stickers; //@description Returns saved animations getSavedAnimations = Animations; -//@description Manually adds a new animation to the list of saved animations. The new animation is added to the beginning of the list. If the animation was already in the list, it is removed first. Only non-secret video animations with MIME type "video/mp4" can be added to the list +//@description Manually adds a new animation to the list of saved animations. The new animation is added to the beginning of the list. If the animation was already in the list, it is removed first. +//-Only non-secret video animations with MIME type "video/mp4" can be added to the list //@animation The animation file to be added. Only animations known to the server (i.e., successfully sent via a message) can be added to the list addSavedAnimation animation:InputFile = Ok; @@ -9382,16 +10065,27 @@ toggleUsernameIsActive username:string is_active:Bool = Ok; //@description Changes order of active usernames of the current user @usernames The new order of active usernames. All currently active usernames must be specified reorderActiveUsernames usernames:vector = Ok; +//@description Changes the birthdate of the current user @birthdate The new value of the current user's birthdate; pass null to remove the birthdate +setBirthdate birthdate:birthdate = Ok; + +//@description Changes the personal chat of the current user @chat_id Identifier of the new personal chat; pass 0 to remove the chat. Use getSuitablePersonalChats to get suitable chats +setPersonalChat chat_id:int53 = Ok; + //@description Changes the emoji status of the current user; for Telegram Premium users only @emoji_status New emoji status; pass null to switch to the default badge setEmojiStatus emoji_status:emojiStatus = Ok; //@description Changes the location of the current user. Needs to be called if getOption("is_location_visible") is true and location changes for more than 1 kilometer. Must not be called if the user has a business location @location The new location of the user setLocation location:location = Ok; +//@description Toggles whether the current user has sponsored messages enabled. The setting has no effect for users without Telegram Premium for which sponsored messages are always enabled +//@has_sponsored_messages_enabled Pass true to enable sponsored messages for the current user; false to disable them +toggleHasSponsoredMessagesEnabled has_sponsored_messages_enabled:Bool = Ok; + //@description Changes the business location of the current user. Requires Telegram Business subscription @location The new location of the business; pass null to remove the location setBusinessLocation location:businessLocation = Ok; -//@description Changes the business opening hours of the current user. Requires Telegram Business subscription @opening_hours The new opening hours of the business; pass null to remove the opening hours +//@description Changes the business opening hours of the current user. Requires Telegram Business subscription +//@opening_hours The new opening hours of the business; pass null to remove the opening hours; up to 28 time intervals can be specified setBusinessOpeningHours opening_hours:businessOpeningHours = Ok; //@description Changes the business greeting message settings of the current user. Requires Telegram Business subscription @greeting_message_settings The new settings for the greeting message of the business; pass null to disable the greeting message @@ -9400,16 +10094,29 @@ setBusinessGreetingMessageSettings greeting_message_settings:businessGreetingMes //@description Changes the business away message settings of the current user. Requires Telegram Business subscription @away_message_settings The new settings for the away message of the business; pass null to disable the away message setBusinessAwayMessageSettings away_message_settings:businessAwayMessageSettings = Ok; -//@description Changes the phone number of the user and sends an authentication code to the user's new phone number; for official Android and iOS applications only. On success, returns information about the sent code -//@phone_number The new phone number of the user in international format +//@description Changes the business start page of the current user. Requires Telegram Business subscription @start_page The new start page of the business; pass null to remove custom start page +setBusinessStartPage start_page:inputBusinessStartPage = Ok; + + +//@description Sends a code to the specified phone number. Aborts previous phone number verification if there was one. On success, returns information about the sent code +//@phone_number The phone number, in international format //@settings Settings for the authentication of the user's phone number; pass null to use default settings -changePhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo; +//@type Type of the request for which the code is sent +sendPhoneNumberCode phone_number:string settings:phoneNumberAuthenticationSettings type:PhoneNumberCodeType = AuthenticationCodeInfo; -//@description Resends the authentication code sent to confirm a new phone number for the current user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed -resendChangePhoneNumberCode = AuthenticationCodeInfo; +//@description Sends Firebase Authentication SMS to the specified phone number. Works only when received a code of the type authenticationCodeTypeFirebaseAndroid or authenticationCodeTypeFirebaseIos +//@token Play Integrity API or SafetyNet Attestation API token for the Android application, or secret from push notification for the iOS application +sendPhoneNumberFirebaseSms token:string = Ok; -//@description Checks the authentication code sent to confirm a new phone number of the user @code Authentication code to check -checkChangePhoneNumberCode code:string = Ok; +//@description Reports that authentication code wasn't delivered via SMS to the specified phone number; for official mobile applications only @mobile_network_code Current mobile network code +reportPhoneNumberCodeMissing mobile_network_code:string = Ok; + +//@description Resends the authentication code sent to a phone number. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed +//@reason Reason of code resending; pass null if unknown +resendPhoneNumberCode reason:ResendCodeReason = AuthenticationCodeInfo; + +//@description Check the authentication code and completes the request for which the code was sent if appropriate @code Authentication code to check +checkPhoneNumberCode code:string = Ok; //@description Returns the business bot that is connected to the current user account. Returns a 404 error if there is no connected bot @@ -9421,6 +10128,31 @@ setBusinessConnectedBot bot:businessConnectedBot = Ok; //@description Deletes the business bot that is connected to the current user account @bot_user_id Unique user identifier for the bot deleteBusinessConnectedBot bot_user_id:int53 = Ok; +//@description Pauses or resumes the connected business bot in a specific chat @chat_id Chat identifier @is_paused Pass true to pause the connected bot in the chat; pass false to resume the bot +toggleBusinessConnectedBotChatIsPaused chat_id:int53 is_paused:Bool = Ok; + +//@description Removes the connected business bot from a specific chat by adding the chat to businessRecipients.excluded_chat_ids @chat_id Chat identifier +removeBusinessConnectedBotFromChat chat_id:int53 = Ok; + + +//@description Returns business chat links created for the current account +getBusinessChatLinks = BusinessChatLinks; + +//@description Creates a business chat link for the current account. Requires Telegram Business subscription. There can be up to getOption("business_chat_link_count_max") links created. Returns the created link +//@link_info Information about the link to create +createBusinessChatLink link_info:inputBusinessChatLink = BusinessChatLink; + +//@description Edits a business chat link of the current account. Requires Telegram Business subscription. Returns the edited link +//@link The link to edit +//@link_info New description of the link +editBusinessChatLink link:string link_info:inputBusinessChatLink = BusinessChatLink; + +//@description Deletes a business chat link of the current account @link The link to delete +deleteBusinessChatLink link:string = Ok; + +//@description Returns information about a business chat link @link_name Name of the link +getBusinessChatLinkInfo link_name:string = BusinessChatLinkInfo; + //@description Returns an HTTPS link, which can be used to get information about the current user getUserLink = UserLink; @@ -9440,7 +10172,7 @@ setCommands scope:BotCommandScope language_code:string commands:vector getPreferredCountryLanguage country_code:string = Text; -//@description Sends a code to verify a phone number to be added to a user's Telegram Passport -//@phone_number The phone number of the user, in international format -//@settings Settings for the authentication of the user's phone number; pass null to use default settings -sendPhoneNumberVerificationCode phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo; - -//@description Resends the code to verify a phone number to be added to a user's Telegram Passport -resendPhoneNumberVerificationCode = AuthenticationCodeInfo; - -//@description Checks the phone number verification code for Telegram Passport @code Verification code to check -checkPhoneNumberVerificationCode code:string = Ok; - - //@description Sends a code to verify an email address to be added to a user's Telegram Passport @email_address Email address sendEmailAddressVerificationCode email_address:string = EmailAddressAuthenticationCodeInfo; @@ -9981,19 +10730,6 @@ getPassportAuthorizationFormAvailableElements authorization_form_id:int32 passwo sendPassportAuthorizationForm authorization_form_id:int32 types:vector = Ok; -//@description Sends phone number confirmation code to handle links of the type internalLinkTypePhoneNumberConfirmation -//@hash Hash value from the link -//@phone_number Phone number value from the link -//@settings Settings for the authentication of the user's phone number; pass null to use default settings -sendPhoneNumberConfirmationCode hash:string phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo; - -//@description Resends phone number confirmation code -resendPhoneNumberConfirmationCode = AuthenticationCodeInfo; - -//@description Checks phone number confirmation code @code Confirmation code to check -checkPhoneNumberConfirmationCode code:string = Ok; - - //@description Informs the server about the number of pending bot updates if they haven't been processed for a long time; for bots only @pending_update_count The number of pending updates @error_message The last error message setBotUpdatesStatus pending_update_count:int32 error_message:string = Ok; @@ -10014,61 +10750,73 @@ checkStickerSetName name:string = CheckStickerSetNameResult; //@description Creates a new sticker set. Returns the newly created sticker set //@user_id Sticker set owner; ignored for regular users //@title Sticker set title; 1-64 characters -//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_"* (** is case insensitive) for bots; 1-64 characters -//@sticker_format Format of the stickers in the set +//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_"* (** is case insensitive) for bots; 0-64 characters. +//-If empty, then the name returned by getSuggestedStickerSetName will be used automatically //@sticker_type Type of the stickers in the set //@needs_repainting Pass true if stickers in the sticker set must be repainted; for custom emoji sticker sets only -//@stickers List of stickers to be added to the set; must be non-empty. All stickers must have the same format. For TGS stickers, uploadStickerFile must be used before the sticker is shown +//@stickers List of stickers to be added to the set; 1-200 stickers for custom emoji sticker sets, and 1-120 stickers otherwise. For TGS stickers, uploadStickerFile must be used before the sticker is shown //@source Source of the sticker set; may be empty if unknown -createNewStickerSet user_id:int53 title:string name:string sticker_format:StickerFormat sticker_type:StickerType needs_repainting:Bool stickers:vector source:string = StickerSet; - +createNewStickerSet user_id:int53 title:string name:string sticker_type:StickerType needs_repainting:Bool stickers:vector source:string = StickerSet; -//@description Adds a new sticker to a set; for bots only -//@user_id Sticker set owner -//@name Sticker set name +//@description Adds a new sticker to a set +//@user_id Sticker set owner; ignored for regular users +//@name Sticker set name. The sticker set must be owned by the current user, and contain less than 200 stickers for custom emoji sticker sets and less than 120 otherwise //@sticker Sticker to add to the set addStickerToSet user_id:int53 name:string sticker:inputSticker = Ok; -//@description Sets a sticker set thumbnail; for bots only -//@user_id Sticker set owner -//@name Sticker set name -//@thumbnail Thumbnail to set in PNG, TGS, or WEBM format; pass null to remove the sticker set thumbnail. Thumbnail format must match the format of stickers in the set -setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile = Ok; +//@description Replaces existing sticker in a set. The function is equivalent to removeStickerFromSet, then addStickerToSet, then setStickerPositionInSet +//@user_id Sticker set owner; ignored for regular users +//@name Sticker set name. The sticker set must be owned by the current user +//@old_sticker Sticker to remove from the set +//@new_sticker Sticker to add to the set +replaceStickerInSet user_id:int53 name:string old_sticker:InputFile new_sticker:inputSticker = Ok; + +//@description Sets a sticker set thumbnail +//@user_id Sticker set owner; ignored for regular users +//@name Sticker set name. The sticker set must be owned by the current user +//@thumbnail Thumbnail to set; pass null to remove the sticker set thumbnail +//@format Format of the thumbnail; pass null if thumbnail is removed +setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile format:StickerFormat = Ok; -//@description Sets a custom emoji sticker set thumbnail; for bots only -//@name Sticker set name +//@description Sets a custom emoji sticker set thumbnail +//@name Sticker set name. The sticker set must be owned by the current user //@custom_emoji_id Identifier of the custom emoji from the sticker set, which will be set as sticker set thumbnail; pass 0 to remove the sticker set thumbnail setCustomEmojiStickerSetThumbnail name:string custom_emoji_id:int64 = Ok; -//@description Sets a sticker set title; for bots only @name Sticker set name @title New sticker set title +//@description Sets a sticker set title @name Sticker set name. The sticker set must be owned by the current user @title New sticker set title setStickerSetTitle name:string title:string = Ok; -//@description Deleted a sticker set; for bots only @name Sticker set name +//@description Completely deletes a sticker set @name Sticker set name. The sticker set must be owned by the current user deleteStickerSet name:string = Ok; -//@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot +//@description Changes the position of a sticker in the set to which it belongs. The sticker set must be owned by the current user //@sticker Sticker //@position New position of the sticker in the set, 0-based setStickerPositionInSet sticker:InputFile position:int32 = Ok; -//@description Removes a sticker from the set to which it belongs; for bots only. The sticker set must have been created by the bot @sticker Sticker +//@description Removes a sticker from the set to which it belongs. The sticker set must be owned by the current user @sticker Sticker to remove from the set removeStickerFromSet sticker:InputFile = Ok; -//@description Changes the list of emoji corresponding to a sticker; for bots only. The sticker must belong to a regular or custom emoji sticker set created by the bot +//@description Changes the list of emoji corresponding to a sticker. The sticker must belong to a regular or custom emoji sticker set that is owned by the current user //@sticker Sticker //@emojis New string with 1-20 emoji corresponding to the sticker setStickerEmojis sticker:InputFile emojis:string = Ok; -//@description Changes the list of keywords of a sticker; for bots only. The sticker must belong to a regular or custom emoji sticker set created by the bot +//@description Changes the list of keywords of a sticker. The sticker must belong to a regular or custom emoji sticker set that is owned by the current user //@sticker Sticker //@keywords List of up to 20 keywords with total length up to 64 characters, which can be used to find the sticker setStickerKeywords sticker:InputFile keywords:vector = Ok; -//@description Changes the mask position of a mask sticker; for bots only. The sticker must belong to a mask sticker set created by the bot +//@description Changes the mask position of a mask sticker. The sticker must belong to a mask sticker set that is owned by the current user //@sticker Sticker //@mask_position Position where the mask is placed; pass null to remove mask position setStickerMaskPosition sticker:InputFile mask_position:maskPosition = Ok; +//@description Returns sticker sets owned by the current user +//@offset_sticker_set_id Identifier of the sticker set from which to return owned sticker sets; use 0 to get results from the beginning +//@limit The maximum number of sticker sets to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned objects is chosen by TDLib and can be smaller than the specified limit +getOwnedStickerSets offset_sticker_set_id:int64 limit:int32 = StickerSets; + //@description Returns information about a file with a map thumbnail in PNG format. Only map thumbnail files with size less than 1MB can be downloaded //@location Location of the map center @@ -10118,8 +10866,16 @@ launchPrepaidPremiumGiveaway giveaway_id:int64 parameters:premiumGiveawayParamet //@message_id Identifier of the giveaway or a giveaway winners message in the chat getPremiumGiveawayInfo chat_id:int53 message_id:int53 = PremiumGiveawayInfo; -//@description Checks whether Telegram Premium purchase is possible. Must be called before in-store Premium purchase @purpose Transaction purpose -canPurchasePremium purpose:StorePaymentPurpose = Ok; +//@description Returns available options for Telegram stars purchase +getStarPaymentOptions = StarPaymentOptions; + +//@description Returns the list of Telegram star transactions for the current user +//@offset Offset of the first transaction to return as received from the previous request; use empty string to get the first chunk of results +//@direction Direction of the transactions to receive; pass null to get all transactions +getStarTransactions offset:string direction:StarTransactionDirection = StarTransactions; + +//@description Checks whether an in-store purchase is possible. Must be called before any in-store purchase @purpose Transaction purpose +canPurchaseFromStore purpose:StorePaymentPurpose = Ok; //@description Informs server about a purchase through App Store. For official applications only @receipt App Store receipt @purpose Transaction purpose assignAppStoreTransaction receipt:bytes purpose:StorePaymentPurpose = Ok; @@ -10132,6 +10888,10 @@ assignAppStoreTransaction receipt:bytes purpose:StorePaymentPurpose = Ok; assignGooglePlayTransaction package_name:string store_product_id:string purchase_token:string purpose:StorePaymentPurpose = Ok; +//@description Returns information about features, available to Business users @source Source of the request; pass null if the method is called from settings or some non-standard source +getBusinessFeatures source:BusinessFeature = BusinessFeatures; + + //@description Accepts Telegram terms of services @terms_of_service_id Terms of service identifier acceptTermsOfService terms_of_service_id:string = Ok; @@ -10170,6 +10930,11 @@ getPhoneNumberInfo phone_number_prefix:string = PhoneNumberInfo; getPhoneNumberInfoSync language_code:string phone_number_prefix:string = PhoneNumberInfo; +//@description Returns information about a given collectible item that was purchased at https://fragment.com +//@type Type of the collectible item. The item must be used by a user and must be visible to the current user +getCollectibleItemInfo type:CollectibleItemType = CollectibleItemInfo; + + //@description Returns information about a tg:// deep link. Use "tg://need_update_for_some_feature" or "tg:some_unsupported_feature" for testing. Returns a 404 error for unknown links. Can be called before authorization @link The link getDeepLinkInfo link:string = DeepLinkInfo; @@ -10208,7 +10973,7 @@ disableProxy = Ok; //@description Removes a proxy server. Can be called before authorization @proxy_id Proxy identifier removeProxy proxy_id:int32 = Ok; -//@description Returns list of proxies that are currently set up. Can be called before authorization +//@description Returns the list of proxies that are currently set up. Can be called before authorization getProxies = Proxies; //@description Returns an HTTPS link, which can be used to add a proxy. Available only for SOCKS5 and MTProto proxies. Can be called before authorization @proxy_id Proxy identifier @@ -10232,7 +10997,7 @@ setLogVerbosityLevel new_verbosity_level:int32 = Ok; //@description Returns current verbosity level of the internal logging of TDLib. Can be called synchronously getLogVerbosityLevel = LogVerbosityLevel; -//@description Returns list of available TDLib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. Can be called synchronously +//@description Returns the list of available TDLib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. Can be called synchronously getLogTags = LogTags; //@description Sets the verbosity level for a specified TDLib internal log tag. Can be called synchronously diff --git a/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl b/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl index b85bf4b9..decf43b4 100644 --- a/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl +++ b/lib/tgchat/ext/td/td/generate/scheme/telegram_api.tl @@ -24,10 +24,13 @@ inputStickerSetThumbLegacy#dbaeae9 stickerset:InputStickerSet volume_id:long loc ---functions--- -test.useError = Error; test.useConfigSimple = help.ConfigSimple; test.parseInputAppEvent = InputAppEvent; +invokeWithBusinessConnectionPrefix#dd289f8e connection_id:string = Error; +invokeWithGooglePlayIntegrityPrefix#1df92984 nonce:string token:string = Error; +invokeWithApnsSecretPrefix#0dae54f8 nonce:string secret:string = Error; + ---types--- inputPeerEmpty#7f3b18ea = InputPeer; @@ -59,7 +62,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro inputMediaPhotoExternal#e5bbfe1a flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; +inputMediaInvoice#405fef0d flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:flags.3?string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -103,7 +106,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#215c4438 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User; +user#215c4438 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -121,8 +124,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?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#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#44c054a7 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 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 stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; +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 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; @@ -135,7 +138,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#a66c7efc flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int = Message; +message#94345242 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -197,6 +200,7 @@ messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags messageActionGiveawayLaunch#332ba9ed = MessageAction; messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction; messageActionBoostApply#cc02aa6d boosts:int = MessageAction; +messageActionRequestedPeerSentMe#93b31848 button_id:int peers:Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -232,7 +236,7 @@ inputPeerNotifySettings#cacb6ae2 flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#99622c0c flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool stories_ios_sound:flags.8?NotificationSound stories_android_sound:flags.9?NotificationSound stories_other_sound:flags.10?NotificationSound = PeerNotifySettings; -peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings; +peerSettings#acd66c5e flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true business_bot_paused:flags.11?true business_bot_can_reply:flags.12?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int business_bot_id:flags.13?long business_bot_manage_url:flags.13?string = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -248,7 +252,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#22ff3e85 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage = UserFull; +userFull#cc997720 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -409,7 +413,6 @@ updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long to updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; updateUser#20529438 user_id:long = Update; updateAutoSaveSettings#ec05b097 = Update; -updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; updateStory#75b3b798 peer:Peer story:StoryItem = Update; updateReadStories#f74e932b peer:Peer max_id:int = Update; updateStoryID#1bf335b9 id:int random_id:long = Update; @@ -429,6 +432,13 @@ updateNewQuickReply#f53da717 quick_reply:QuickReply = Update; updateDeleteQuickReply#53e6f1ec shortcut_id:int = Update; updateQuickReplyMessage#3e050d0f message:Message = Update; updateDeleteQuickReplyMessages#566fe7cd shortcut_id:int messages:Vector = Update; +updateBotBusinessConnect#8ae5c97a connection:BotBusinessConnection qts:int = Update; +updateBotNewBusinessMessage#9ddb347c flags:# connection_id:string message:Message reply_to_message:flags.0?Message qts:int = Update; +updateBotEditBusinessMessage#7df587c flags:# connection_id:string message:Message reply_to_message:flags.0?Message qts:int = Update; +updateBotDeleteBusinessMessage#a02a982e connection_id:string peer:Peer messages:Vector qts:int = Update; +updateNewStoryReaction#1824e40b story_id:int peer:Peer reaction:Reaction = Update; +updateBroadcastRevenueTransactions#dfd961f5 peer:Peer balances:BroadcastRevenueBalances = Update; +updateStarsBalance#fb85198 balance:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -534,6 +544,7 @@ inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey; inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey; inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey; inputPrivacyKeyAbout#3823cc40 = InputPrivacyKey; +inputPrivacyKeyBirthday#d65a11cc = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; @@ -545,6 +556,7 @@ privacyKeyPhoneNumber#d19ae46d = PrivacyKey; privacyKeyAddedByPhone#42ffd42b = PrivacyKey; privacyKeyVoiceMessages#697f414 = PrivacyKey; privacyKeyAbout#a486b761 = PrivacyKey; +privacyKeyBirthday#2000a518 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -555,6 +567,7 @@ inputPrivacyValueDisallowUsers#90110467 users:Vector = InputPrivacyRu inputPrivacyValueAllowChatParticipants#840649cf chats:Vector = InputPrivacyRule; inputPrivacyValueDisallowChatParticipants#e94f0f86 chats:Vector = InputPrivacyRule; inputPrivacyValueAllowCloseFriends#2f453e49 = InputPrivacyRule; +inputPrivacyValueAllowPremium#77cdc9f1 = InputPrivacyRule; privacyValueAllowContacts#fffe1bac = PrivacyRule; privacyValueAllowAll#65427b82 = PrivacyRule; @@ -565,6 +578,7 @@ privacyValueDisallowUsers#e4621141 users:Vector = PrivacyRule; privacyValueAllowChatParticipants#6b134e8e chats:Vector = PrivacyRule; privacyValueDisallowChatParticipants#41c87565 chats:Vector = PrivacyRule; privacyValueAllowCloseFriends#f7e8d89b = PrivacyRule; +privacyValueAllowPremium#ece9814b = PrivacyRule; account.privacyRules#50a04e45 rules:Vector chats:Vector users:Vector = account.PrivacyRules; @@ -627,7 +641,7 @@ inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; inputStickerSetEmojiChannelDefaultStatuses#49748553 = InputStickerSet; -stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true text_color:flags.9?true channel_emoji_status:flags.10?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; +stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true emojis:flags.7?true text_color:flags.9?true channel_emoji_status:flags.10?true creator:flags.11?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; @@ -652,6 +666,7 @@ keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonWebView#13767230 text:string url:string = KeyboardButton; keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton; keyboardButtonRequestPeer#53d7bfd8 text:string button_id:int peer_type:RequestPeerType max_quantity:int = KeyboardButton; +inputKeyboardButtonRequestPeer#c9662d05 flags:# name_requested:flags.0?true username_requested:flags.1?true photo_requested:flags.2?true text:string button_id:int peer_type:RequestPeerType max_quantity:int = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -680,7 +695,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; -messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; +messageEntityBlockquote#f1ccaaac flags:# collapsed:flags.0?true offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -768,7 +783,9 @@ auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeTyp auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; -auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; +auth.sentCodeTypeFirebaseSms#13c90f17 flags:# nonce:flags.0?bytes play_integrity_nonce:flags.2?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; +auth.sentCodeTypeSmsWord#a416ac81 flags:# beginning:flags.0?string = auth.SentCodeType; +auth.sentCodeTypeSmsPhrase#b37794af flags:# beginning:flags.0?string = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -909,6 +926,7 @@ inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# small:flags.2?true document upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; +payments.paymentFormStars#7bf6b15c flags:# form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -916,6 +934,7 @@ payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult; payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult; payments.paymentReceipt#70c4fe03 flags:# date:int bot_id:long provider_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; +payments.paymentReceiptStars#dabbf83a flags:# date:int bot_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice currency:string total_amount:long transaction_id:string users:Vector = payments.PaymentReceipt; payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo; @@ -936,7 +955,7 @@ phoneCallEmpty#5366c915 id:long = PhoneCall; phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; +phoneCall#30535af5 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; @@ -1160,9 +1179,9 @@ help.supportName#8c05f1c9 name:string = help.SupportName; help.userInfoEmpty#f3ae2eed = help.UserInfo; help.userInfo#1eb3758 message:string entities:Vector author:string date:int = help.UserInfo; -pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; +pollAnswer#ff16e2ca text:TextWithEntities option:bytes = PollAnswer; -poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector close_period:flags.4?int close_date:flags.5?int = Poll; +poll#58747131 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:TextWithEntities answers:Vector close_period:flags.4?int close_date:flags.5?int = Poll; pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; @@ -1183,7 +1202,7 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true allow_firebase:flags.7?true logout_tokens:flags.6?Vector token:flags.8?string app_sandbox:flags.8?Bool = CodeSettings; +codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true allow_firebase:flags.7?true unknown_number:flags.9?true logout_tokens:flags.6?Vector token:flags.8?string app_sandbox:flags.8?Bool = CodeSettings; wallPaperSettings#372efcd0 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int emoticon:flags.7?string = WallPaperSettings; @@ -1248,6 +1267,7 @@ themeSettings#fa58b6d4 flags:# message_colors_animated:flags.2?true base_theme:B webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; webPageAttributeStory#2e94c3e7 flags:# peer:Peer id:int story:flags.0?StoryItem = WebPageAttribute; +webPageAttributeStickerSet#50cc03d3 flags:# emojis:flags.0?true text_color:flags.1?true stickers:Vector = WebPageAttribute; messages.votesList#4899484e flags:# count:int votes:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.VotesList; @@ -1373,7 +1393,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#ed5383f7 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string webpage:flags.9?SponsoredWebPage app:flags.10?BotApp message:string entities:flags.1?Vector button_text:flags.11?string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; +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; messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; @@ -1454,6 +1474,7 @@ attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; inputInvoiceSlug#c326caef slug:string = InputInvoice; inputInvoicePremiumGiftCode#98986c0d purpose:InputStorePaymentPurpose option:PremiumGiftCodeOption = InputInvoice; +inputInvoiceStars#1da33ad8 option:StarsTopupOption = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1465,6 +1486,7 @@ inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgra inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiftCode#a3805f3f flags:# users:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentStars#4f0ee8df flags:# stars:long currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1527,6 +1549,8 @@ emojiListNotModified#481eadfa = EmojiList; emojiList#7a1e11d1 hash:long document_id:Vector = EmojiList; emojiGroup#7a9abda9 title:string icon_emoji_id:long emoticons:Vector = EmojiGroup; +emojiGroupGreeting#80d26cc7 title:string icon_emoji_id:long emoticons:Vector = EmojiGroup; +emojiGroupPremium#93bcf34 title:string icon_emoji_id:long = EmojiGroup; messages.emojiGroupsNotModified#6fb4ad87 = messages.EmojiGroups; messages.emojiGroups#881fb94b hash:int groups:Vector = messages.EmojiGroups; @@ -1577,8 +1601,6 @@ messagePeerVote#b6cc2d5c peer:Peer option:bytes date:int = MessagePeerVote; messagePeerVoteInputOption#74cda504 peer:Peer date:int = MessagePeerVote; messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector date:int = MessagePeerVote; -sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage; - storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int forwards_count:flags.2?int reactions:flags.3?Vector reactions_count:flags.4?int recent_viewers:flags.0?Vector = StoryViews; storyItemDeleted#51e6ee4f id:int = StoryItem; @@ -1588,7 +1610,7 @@ storyItem#79b26a24 flags:# pinned:flags.5?true public:flags.7?true close_friends stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; stories.allStories#6efc5e81 flags:# has_more:flags.0?true count:int state:string peer_stories:Vector chats:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; -stories.stories#5dd8c3c8 count:int stories:Vector chats:Vector users:Vector = stories.Stories; +stories.stories#63c3dd0a flags:# count:int stories:Vector pinned_to_top:flags.0?Vector chats:Vector users:Vector = stories.Stories; storyView#b0bdeac5 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true user_id:long date:int reaction:flags.2?Reaction = StoryView; storyViewPublicForward#9083670b flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true message:Message = StoryView; @@ -1721,12 +1743,94 @@ inputQuickReplyShortcutId#1190cf1 shortcut_id:int = InputQuickReplyShortcut; messages.quickReplies#c68d6695 quick_replies:Vector messages:Vector chats:Vector users:Vector = messages.QuickReplies; messages.quickRepliesNotModified#5f91eb5b = messages.QuickReplies; -connectedBot#e7e999e7 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessRecipients = ConnectedBot; +connectedBot#bd068601 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessBotRecipients = ConnectedBot; account.connectedBots#17d7f87b connected_bots:Vector users:Vector = account.ConnectedBots; messages.dialogFilters#2ad93719 flags:# tags_enabled:flags.0?true filters:Vector = messages.DialogFilters; +birthday#6c8e1e06 flags:# day:int month:int year:flags.0?int = Birthday; + +botBusinessConnection#896433b4 flags:# can_reply:flags.0?true disabled:flags.1?true connection_id:string user_id:long dc_id:int date:int = BotBusinessConnection; + +inputBusinessIntro#9c469cd flags:# title:string description:string sticker:flags.0?InputDocument = InputBusinessIntro; + +businessIntro#5a0a066d flags:# title:string description:string sticker:flags.0?Document = BusinessIntro; + +messages.myStickers#faff629d count:int sets:Vector = messages.MyStickers; + +inputCollectibleUsername#e39460a9 username:string = InputCollectible; +inputCollectiblePhone#a2e214a4 phone:string = InputCollectible; + +fragment.collectibleInfo#6ebdff91 purchase_date:int currency:string amount:long crypto_currency:string crypto_amount:long url:string = fragment.CollectibleInfo; + +inputBusinessBotRecipients#c4e5921e flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector exclude_users:flags.6?Vector = InputBusinessBotRecipients; + +businessBotRecipients#b88cf373 flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector exclude_users:flags.6?Vector = BusinessBotRecipients; + +contactBirthday#1d998733 contact_id:long birthday:Birthday = ContactBirthday; + +contacts.contactBirthdays#114ff30d contacts:Vector users:Vector = contacts.ContactBirthdays; + +missingInvitee#628c9224 flags:# premium_would_allow_invite:flags.0?true premium_required_for_pm:flags.1?true user_id:long = MissingInvitee; + +messages.invitedUsers#7f5defa6 updates:Updates missing_invitees:Vector = messages.InvitedUsers; + +inputBusinessChatLink#11679fa7 flags:# message:string entities:flags.0?Vector title:flags.1?string = InputBusinessChatLink; + +businessChatLink#b4ae666f flags:# link:string message:string entities:flags.0?Vector title:flags.1?string views:int = BusinessChatLink; + +account.businessChatLinks#ec43a2d1 links:Vector chats:Vector users:Vector = account.BusinessChatLinks; + +account.resolvedBusinessChatLinks#9a23af21 flags:# peer:Peer message:string entities:flags.0?Vector chats:Vector users:Vector = account.ResolvedBusinessChatLinks; + +requestedPeerUser#d62ff46a flags:# user_id:long first_name:flags.0?string last_name:flags.0?string username:flags.1?string photo:flags.2?Photo = RequestedPeer; +requestedPeerChat#7307544f flags:# chat_id:long title:flags.0?string photo:flags.2?Photo = RequestedPeer; +requestedPeerChannel#8ba403e4 flags:# channel_id:long title:flags.0?string username:flags.1?string photo:flags.2?Photo = RequestedPeer; + +sponsoredMessageReportOption#430d3150 text:string option:bytes = SponsoredMessageReportOption; + +channels.sponsoredMessageReportResultChooseOption#846f9e42 title:string options:Vector = channels.SponsoredMessageReportResult; +channels.sponsoredMessageReportResultAdsHidden#3e3bcf2f = channels.SponsoredMessageReportResult; +channels.sponsoredMessageReportResultReported#ad798849 = channels.SponsoredMessageReportResult; + +stats.broadcastRevenueStats#5407e297 top_hours_graph:StatsGraph revenue_graph:StatsGraph balances:BroadcastRevenueBalances usd_rate:double = stats.BroadcastRevenueStats; + +stats.broadcastRevenueWithdrawalUrl#ec659737 url:string = stats.BroadcastRevenueWithdrawalUrl; + +broadcastRevenueTransactionProceeds#557e2cc4 amount:long from_date:int to_date:int = BroadcastRevenueTransaction; +broadcastRevenueTransactionWithdrawal#5a590978 flags:# pending:flags.0?true failed:flags.2?true amount:long date:int provider:string transaction_date:flags.1?int transaction_url:flags.1?string = BroadcastRevenueTransaction; +broadcastRevenueTransactionRefund#42d30d2e amount:long date:int provider:string = BroadcastRevenueTransaction; + +stats.broadcastRevenueTransactions#87158466 count:int transactions:Vector = stats.BroadcastRevenueTransactions; + +reactionNotificationsFromContacts#bac3a61a = ReactionNotificationsFrom; +reactionNotificationsFromAll#4b9e22a0 = ReactionNotificationsFrom; + +reactionsNotifySettings#56e34970 flags:# messages_notify_from:flags.0?ReactionNotificationsFrom stories_notify_from:flags.1?ReactionNotificationsFrom sound:NotificationSound show_previews:Bool = ReactionsNotifySettings; + +broadcastRevenueBalances#8438f1c6 current_balance:long available_balance:long overall_revenue:long = BroadcastRevenueBalances; + +availableEffect#93c3e27e flags:# premium_required:flags.2?true id:long emoticon:string static_icon_id:flags.0?long effect_sticker_id:long effect_animation_id:flags.1?long = AvailableEffect; + +messages.availableEffectsNotModified#d1ed9a5b = messages.AvailableEffects; +messages.availableEffects#bddb616e hash:int effects:Vector documents:Vector = messages.AvailableEffects; + +factCheck#b89bfccf flags:# need_check:flags.0?true country:flags.1?string text:flags.1?TextWithEntities hash:long = FactCheck; + +starsTransactionPeerUnsupported#95f2bfe4 = StarsTransactionPeer; +starsTransactionPeerAppStore#b457b375 = StarsTransactionPeer; +starsTransactionPeerPlayMarket#7b560a0b = StarsTransactionPeer; +starsTransactionPeerPremiumBot#250dbaf8 = StarsTransactionPeer; +starsTransactionPeerFragment#e92fd902 = StarsTransactionPeer; +starsTransactionPeer#d80da15d peer:Peer = StarsTransactionPeer; + +starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; + +starsTransaction#cc7079b2 flags:# refund:flags.3?true id:string stars:long date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument = StarsTransaction; + +payments.starsStatus#8cf4ee60 flags:# balance:long history:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1736,6 +1840,9 @@ invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; +invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X; +invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X; +invokeWithApnsSecret#0dae54f8 {X:Type} nonce:string secret:string query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#aac7b717 flags:# no_joined_notifications:flags.0?true phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; @@ -1749,7 +1856,7 @@ auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_au auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#37096c70 flags:# code:string new_settings:flags.0?account.PasswordInputSettings = auth.Authorization; -auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; +auth.resendCode#cae47523 flags:# phone_number:string phone_code_hash:string reason:flags.0?string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector = auth.LoginToken; @@ -1757,8 +1864,9 @@ auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; -auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; +auth.requestFirebaseSms#8e39261e flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string play_integrity_token:flags.2?string ios_push_secret:flags.1?string = Bool; auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode; +auth.reportMissingCode#cb9deff6 phone_number:string phone_code_hash:string mnc:string = Bool; account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; @@ -1856,8 +1964,22 @@ account.updateBusinessWorkHours#4b00e066 flags:# business_work_hours:flags.0?Bus account.updateBusinessLocation#9e6b131a flags:# geo_point:flags.1?InputGeoPoint address:flags.0?string = Bool; account.updateBusinessGreetingMessage#66cdafc4 flags:# message:flags.0?InputBusinessGreetingMessage = Bool; account.updateBusinessAwayMessage#a26a7fa5 flags:# message:flags.0?InputBusinessAwayMessage = Bool; -account.updateConnectedBot#9c2d527d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessRecipients = Updates; +account.updateConnectedBot#43d8521d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessBotRecipients = Updates; account.getConnectedBots#4ea4c80f = account.ConnectedBots; +account.getBotBusinessConnection#76a86270 connection_id:string = Updates; +account.updateBusinessIntro#a614d034 flags:# intro:flags.0?InputBusinessIntro = Bool; +account.toggleConnectedBotPaused#646e1097 peer:InputPeer paused:Bool = Bool; +account.disablePeerConnectedBot#5e437ed9 peer:InputPeer = Bool; +account.updateBirthday#cc6e0c11 flags:# birthday:flags.0?Birthday = Bool; +account.createBusinessChatLink#8851e68e link:InputBusinessChatLink = BusinessChatLink; +account.editBusinessChatLink#8c3410af slug:string link:InputBusinessChatLink = BusinessChatLink; +account.deleteBusinessChatLink#60073674 slug:string = Bool; +account.getBusinessChatLinks#6f70dde1 = account.BusinessChatLinks; +account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessChatLinks; +account.updatePersonalChannel#d94305e0 channel:InputChannel = Bool; +account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool; +account.getReactionsNotifySettings#6dd654c = ReactionsNotifySettings; +account.setReactionsNotifySettings#316ce548 settings:ReactionsNotifySettings = ReactionsNotifySettings; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1889,6 +2011,7 @@ contacts.exportContactToken#f8654027 = ExportedContactToken; contacts.importContactToken#13005788 token:string = User; contacts.editCloseFriends#ba6705f0 id:Vector = Bool; contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector limit:int = Bool; +contacts.getBirthdays#daeda864 = contacts.ContactBirthdays; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; @@ -1899,8 +2022,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#dff8042c flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; -messages.sendMedia#7bd66041 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; +messages.sendMessage#983f9745 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; +messages.sendMedia#7852834e flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; messages.forwardMessages#d5039208 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; @@ -1909,9 +2032,9 @@ messages.getChats#49e9528f id:Vector = messages.Chats; messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull; messages.editChatTitle#73783ffd chat_id:long title:string = Updates; messages.editChatPhoto#35ddd674 chat_id:long photo:InputChatPhoto = Updates; -messages.addChatUser#f24753e3 chat_id:long user_id:InputUser fwd_limit:int = Updates; +messages.addChatUser#cbc6d107 chat_id:long user_id:InputUser fwd_limit:int = messages.InvitedUsers; messages.deleteChatUser#a2185cab flags:# revoke_history:flags.0?true chat_id:long user_id:InputUser = Updates; -messages.createChat#34a818 flags:# users:Vector title:string ttl_period:flags.0?int = Updates; +messages.createChat#92ceddd4 flags:# users:Vector title:string ttl_period:flags.0?int = messages.InvitedUsers; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; @@ -1937,7 +2060,7 @@ messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_par messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool = messages.MessageViews; messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#a2875319 chat_id:long = Updates; -messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#4bc6589a flags:# broadcasts_only:flags.1?true folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Bool; messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document; messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; @@ -1972,14 +2095,14 @@ messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs; messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector = Bool; messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool; -messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia; +messages.uploadMedia#14967978 flags:# business_connection_id:flags.0?string peer:InputPeer media:InputMedia = MessageMedia; messages.sendScreenshotNotification#a1405817 peer:InputPeer reply_to:InputReplyTo random_id:long = Updates; messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers; messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#c964709 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; +messages.sendMultiMedia#37b74355 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -2041,7 +2164,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#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates; +messages.setChatAvailableReactions#5a150bd4 flags:# peer:InputPeer available_reactions:ChatReactions reactions_limit:flags.0?int = 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; @@ -2094,9 +2217,15 @@ messages.checkQuickReplyShortcut#f1d0fbd3 shortcut:string = Bool; messages.editQuickReplyShortcut#5c003cef shortcut_id:int shortcut:string = Bool; messages.deleteQuickReplyShortcut#3cc04740 shortcut_id:int = Bool; messages.getQuickReplyMessages#94a495c3 flags:# shortcut_id:int id:flags.0?Vector hash:long = messages.Messages; -messages.sendQuickReplyMessages#33153ad4 peer:InputPeer shortcut_id:int = Updates; +messages.sendQuickReplyMessages#6c750de1 peer:InputPeer shortcut_id:int id:Vector random_id:Vector = Updates; messages.deleteQuickReplyMessages#e105e910 shortcut_id:int id:Vector = Updates; messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = Bool; +messages.getMyStickers#d0b5e1fc offset_id:long limit:int = messages.MyStickers; +messages.getEmojiStickerGroups#1dd840f5 hash:int = messages.EmojiGroups; +messages.getAvailableEffects#dea20a39 hash:int = messages.AvailableEffects; +messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities = Updates; +messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates; +messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector = Vector; 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; @@ -2159,11 +2288,11 @@ channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool; channels.updateUsername#3514b3de channel:InputChannel username:string = Bool; channels.joinChannel#24b524c5 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates; -channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = 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.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; +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; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; @@ -2201,10 +2330,13 @@ channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = U channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool; channels.updateColor#d8aa3671 flags:# for_profile:flags.1?true channel:InputChannel color:flags.2?int background_emoji_id:flags.0?long = Updates; channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates; -channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats; +channels.getChannelRecommendations#25a71742 flags:# channel:flags.0?InputChannel = messages.Chats; channels.updateEmojiStatus#f0d3e6a8 channel:InputChannel emoji_status:EmojiStatus = Updates; channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int = Updates; channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; +channels.reportSponsoredMessage#af8ff6b9 channel:InputChannel random_id:bytes option:bytes = channels.SponsoredMessageReportResult; +channels.restrictSponsoredMessages#9ae91519 channel:InputChannel restricted:Bool = Updates; +channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2239,8 +2371,13 @@ payments.checkGiftCode#8e51b4c1 slug:string = payments.CheckedGiftCode; payments.applyGiftCode#f6e26854 slug:string = Updates; payments.getGiveawayInfo#f4239425 peer:InputPeer msg_id:int = payments.GiveawayInfo; payments.launchPrepaidGiveaway#5ff58f20 peer:InputPeer giveaway_id:long purpose:InputStorePaymentPurpose = Updates; +payments.getStarsTopupOptions#c00ec7d3 = Vector; +payments.getStarsStatus#104fcfa7 peer:InputPeer = payments.StarsStatus; +payments.getStarsTransactions#673ac2f9 flags:# inbound:flags.0?true outbound:flags.1?true peer:InputPeer offset:string = payments.StarsStatus; +payments.sendStarsForm#2bb731d flags:# form_id:long invoice:InputInvoice = payments.PaymentResult; +payments.refundStarsCharge#25ae8f4a user_id:InputUser charge_id:string = Updates; -stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; +stickers.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; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; @@ -2250,6 +2387,7 @@ stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName; stickers.changeSticker#f5537ebc flags:# sticker:InputDocument emoji:flags.0?string mask_coords:flags.1?MaskCoords keywords:flags.2?string = messages.StickerSet; stickers.renameStickerSet#124b1c00 stickerset:InputStickerSet title:string = messages.StickerSet; stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; +stickers.replaceSticker#4696459a sticker:InputDocument new_sticker:InputStickerSetItem = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -2298,6 +2436,9 @@ stats.getMessagePublicForwards#5f150144 channel:InputChannel msg_id:int offset:s stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; stats.getStoryStats#374fef40 flags:# dark:flags.0?true peer:InputPeer id:int = stats.StoryStats; stats.getStoryPublicForwards#a6437ef6 peer:InputPeer id:int offset:string limit:int = stats.PublicForwards; +stats.getBroadcastRevenueStats#75dfb671 flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastRevenueStats; +stats.getBroadcastRevenueWithdrawalUrl#2a65ef73 channel:InputChannel password:InputCheckPasswordSRP = stats.BroadcastRevenueWithdrawalUrl; +stats.getBroadcastRevenueTransactions#69280f channel:InputChannel offset:int limit:int = stats.BroadcastRevenueTransactions; chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; @@ -2335,6 +2476,7 @@ stories.getPeerMaxIDs#535983c3 id:Vector = Vector; stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; stories.getStoryReactionsList#b9b2881f flags:# forwards_first:flags.2?true peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; +stories.togglePinnedToTop#b297e9b peer:InputPeer id:Vector = Bool; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; @@ -2349,3 +2491,5 @@ smsjobs.updateSettings#93fa0bf flags:# allow_international:flags.0?true = Bool; smsjobs.getStatus#10a698e8 = smsjobs.Status; smsjobs.getSmsJob#778d902f job_id:string = SmsJob; smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; + +fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; diff --git a/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h b/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h index dfb34637..754ffb83 100644 --- a/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h +++ b/lib/tgchat/ext/td/td/generate/tl_writer_dotnet.h @@ -244,8 +244,12 @@ class TlWriterDotNet final : public TL_writer { if (field_name == class_name) { fixed_field_name += "Value"; } - if (type_name.substr(0, field_name.size()) == field_name) { - auto fixed_type_name = "::Telegram::Td::Api::" + type_name; + auto is_web_page_stickers = + (class_name == "WebPage" && field_name == "Stickers" && type_name == "Array^"); + if (type_name == field_name + "^" || (type_name == "Message^" && field_name == "ReplyToMessage") || + is_web_page_stickers) { + auto fixed_type_name = + is_web_page_stickers ? "Array<::Telegram::Td::Api::Sticker^>^" : "::Telegram::Td::Api::" + type_name; std::stringstream ss; ss << "private:\n"; ss << " " << fixed_type_name << " " << fixed_field_name << "PrivateField;\n"; diff --git a/lib/tgchat/ext/td/td/generate/tl_writer_td.cpp b/lib/tgchat/ext/td/td/generate/tl_writer_td.cpp index 2713d586..dd5455e5 100644 --- a/lib/tgchat/ext/td/td/generate/tl_writer_td.cpp +++ b/lib/tgchat/ext/td/td/generate/tl_writer_td.cpp @@ -64,7 +64,7 @@ bool TD_TL_writer::is_full_constructor_generated(const tl::tl_combinator *t, boo t->name == "langPackString" || t->name == "langPackStringPluralized" || t->name == "langPackStringDeleted" || t->name == "peerUser" || t->name == "peerChat" || t->name == "updateServiceNotification" || t->name == "updateNewMessage" || t->name == "updateChannelTooLong" || t->name == "messages.stickerSet" || - t->name == "updates.differenceSlice"; + t->name == "updates.differenceSlice" || t->name == "contacts.contactBirthdays"; } int TD_TL_writer::get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const { diff --git a/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp b/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp index 0941c4c7..6635a72f 100644 --- a/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp +++ b/lib/tgchat/ext/td/td/mtproto/SessionConnection.cpp @@ -201,7 +201,7 @@ Status SessionConnection::parse_message(TlParser &parser, MsgInfo *info, Slice * << "] is not divisible by 4"); } - *packet = parser.fetch_string_raw(bytes); + *packet = parser.template fetch_string_raw(bytes); if (parser.get_error() != nullptr) { return Status::Error(PSLICE() << "Failed to parse mtproto_api::message: " << parser.get_error()); } diff --git a/lib/tgchat/ext/td/td/telegram/AccountManager.cpp b/lib/tgchat/ext/td/td/telegram/AccountManager.cpp index 7e8c7c5e..d2ed528b 100644 --- a/lib/tgchat/ext/td/td/telegram/AccountManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AccountManager.cpp @@ -7,7 +7,6 @@ #include "td/telegram/AccountManager.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DeviceTokenManager.h" #include "td/telegram/Global.h" #include "td/telegram/LinkManager.h" @@ -18,6 +17,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" @@ -461,7 +461,7 @@ class GetWebAuthorizationsQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebAuthorizationsQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetWebAuthorizationsQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetWebAuthorizationsQuery"); auto results = td_api::make_object(); results->websites_.reserve(ptr->authorizations_.size()); @@ -475,7 +475,7 @@ class GetWebAuthorizationsQuery final : public Td::ResultHandler { results->websites_.push_back(td_api::make_object( authorization->hash_, authorization->domain_, - td_->contacts_manager_->get_user_id_object(bot_user_id, "GetWebAuthorizationsQuery"), authorization->browser_, + td_->user_manager_->get_user_id_object(bot_user_id, "GetWebAuthorizationsQuery"), authorization->browser_, authorization->platform_, authorization->date_created_, authorization->date_active_, authorization->ip_, authorization->region_)); } @@ -591,9 +591,9 @@ class ImportContactTokenQuery final : public Td::ResultHandler { auto user = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for ImportContactTokenQuery: " << to_string(user); - auto user_id = ContactsManager::get_user_id(user); - td_->contacts_manager_->on_get_user(std::move(user), "ImportContactTokenQuery"); - promise_.set_value(td_->contacts_manager_->get_user_object(user_id)); + auto user_id = UserManager::get_user_id(user); + td_->user_manager_->on_get_user(std::move(user), "ImportContactTokenQuery"); + promise_.set_value(td_->user_manager_->get_user_object(user_id)); } void on_error(Status status) final { @@ -1117,7 +1117,7 @@ void AccountManager::disconnect_all_websites(Promise &&promise) { } void AccountManager::get_user_link(Promise> &&promise) { - td_->contacts_manager_->get_me( + td_->user_manager_->get_me( PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); @@ -1129,10 +1129,10 @@ void AccountManager::get_user_link(Promise> void AccountManager::get_user_link_impl(Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - auto username = td_->contacts_manager_->get_user_first_username(td_->contacts_manager_->get_my_id()); + auto username = td_->user_manager_->get_user_first_username(td_->user_manager_->get_my_id()); if (!username.empty()) { return promise.set_value( - td_api::make_object(LinkManager::get_public_dialog_link(username, true), 0)); + td_api::make_object(LinkManager::get_public_dialog_link(username, Slice(), true), 0)); } td_->create_handler(std::move(promise))->send(); } diff --git a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp index 617b99bd..dae801d3 100644 --- a/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AttachMenuManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" @@ -24,6 +24,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/WebApp.h" #include "td/utils/algorithm.h" @@ -222,7 +223,7 @@ class ProlongWebViewQuery final : public Td::ResultHandler { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (input_peer == nullptr || r_input_user.is_error()) { return; } @@ -275,7 +276,7 @@ class InvokeWebViewCustomMethodQuery final : public Td::ResultHandler { } void send(UserId bot_user_id, const string &method, const string ¶meters) { - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } @@ -770,8 +771,8 @@ void AttachMenuManager::schedule_ping_web_view() { void AttachMenuManager::get_web_app(UserId bot_user_id, const string &web_app_short_name, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(bot_user_id)); auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), bot_user_id, web_app_short_name, promise = std::move(promise)]( Result> result) mutable { @@ -823,12 +824,11 @@ void AttachMenuManager::request_app_web_view(DialogId dialog_id, UserId bot_user string &&start_parameter, const td_api::object_ptr &theme, string &&platform, bool allow_write_access, Promise &&promise) { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || - dialog_id.get_type() == DialogType::SecretChat) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { dialog_id = DialogId(bot_user_id); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(bot_user_id)); td_->create_handler(std::move(promise)) ->send(dialog_id, std::move(input_user), web_app_short_name, start_parameter, theme, platform, @@ -839,38 +839,19 @@ void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, td_api::object_ptr &&reply_to, string &&url, td_api::object_ptr &&theme, string &&platform, Promise> &&promise) { - TRY_STATUS_PROMISE(promise, td_->contacts_manager_->get_bot_data(bot_user_id)); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); - - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "request_web_view")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - switch (dialog_id.get_type()) { - case DialogType::User: - case DialogType::Chat: - case DialogType::Channel: - // ok - break; - case DialogType::SecretChat: - return promise.set_error(Status::Error(400, "Web Apps can't be opened in secret chats")); - case DialogType::None: - default: - UNREACHABLE(); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Have no write access to the chat")); - } + TRY_STATUS_PROMISE(promise, td_->user_manager_->get_bot_data(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(bot_user_id)); + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, "request_web_view")); if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server() || dialog_id.get_type() != DialogType::Channel || - !td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id())) { + !td_->chat_manager_->is_megagroup_channel(dialog_id.get_channel_id())) { top_thread_message_id = MessageId(); } - auto input_reply_to = - td_->messages_manager_->get_message_input_reply_to(dialog_id, top_thread_message_id, std::move(reply_to), false); + auto input_reply_to = td_->messages_manager_->create_message_input_reply_to(dialog_id, top_thread_message_id, + std::move(reply_to), false); bool silent = td_->messages_manager_->get_dialog_silent_send_message(dialog_id); DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id); @@ -917,7 +898,7 @@ void AttachMenuManager::invoke_web_view_custom_method( Result AttachMenuManager::get_attach_menu_bot( tl_object_ptr &&bot) { UserId user_id(bot->bot_id_); - if (!td_->contacts_manager_->have_user(user_id)) { + if (!td_->user_manager_->have_user(user_id)) { return Status::Error(PSLICE() << "Have no information about " << user_id); } @@ -1102,7 +1083,7 @@ void AttachMenuManager::on_reload_attach_menu_bots( CHECK(constructor_id == telegram_api::attachMenuBots::ID); auto attach_menu_bots = move_tl_object_as(attach_menu_bots_ptr); - td_->contacts_manager_->on_get_users(std::move(attach_menu_bots->users_), "on_reload_attach_menu_bots"); + td_->user_manager_->on_get_users(std::move(attach_menu_bots->users_), "on_reload_attach_menu_bots"); auto new_hash = attach_menu_bots->hash_; vector new_attach_menu_bots; @@ -1147,9 +1128,9 @@ void AttachMenuManager::remove_bot_from_attach_menu(UserId user_id) { void AttachMenuManager::get_attach_menu_bot(UserId user_id, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(user_id)); if (!bot_data.can_be_added_to_attach_menu) { return promise.set_error(Status::Error(400, "The bot can't be added to attachment menu")); } @@ -1168,7 +1149,7 @@ void AttachMenuManager::reload_attach_menu_bot(UserId user_id, Promise &&p return promise.set_error(Status::Error(400, "Can't reload attachment menu bot")); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); auto wrapped_promise = PromiseCreator::lambda( [promise = std::move(promise)](Result> result) mutable { @@ -1193,7 +1174,7 @@ void AttachMenuManager::on_get_attach_menu_bot( TRY_STATUS_PROMISE(promise, G()->close_status()); TRY_RESULT_PROMISE(promise, bot, std::move(result)); - td_->contacts_manager_->on_get_users(std::move(bot->users_), "on_get_attach_menu_bot"); + td_->user_manager_->on_get_users(std::move(bot->users_), "on_get_attach_menu_bot"); auto r_attach_menu_bot = get_attach_menu_bot(std::move(bot->bot_)); if (r_attach_menu_bot.is_error()) { @@ -1263,10 +1244,10 @@ void AttachMenuManager::toggle_bot_is_added_to_attach_menu(UserId user_id, bool Promise &&promise) { CHECK(is_active()); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); if (is_added) { - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(user_id)); if (!bot_data.can_be_added_to_attach_menu) { return promise.set_error(Status::Error(400, "The bot can't be added to attachment menu")); } @@ -1304,8 +1285,8 @@ td_api::object_ptr AttachMenuManager::get_attachment_ }; return td_api::make_object( - td_->contacts_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"), - bot.supports_self_dialog_, bot.supports_user_dialogs_, bot.supports_bot_dialogs_, bot.supports_group_dialogs_, + td_->user_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"), bot.supports_self_dialog_, + bot.supports_user_dialogs_, bot.supports_bot_dialogs_, bot.supports_group_dialogs_, bot.supports_broadcast_dialogs_, bot.request_write_access_, bot.is_added_, bot.show_in_attach_menu_, bot.show_in_side_menu_, bot.side_menu_disclaimer_needed_, bot.name_, get_attach_menu_bot_color_object(bot.name_color_), get_file(bot.default_icon_file_id_), diff --git a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp index 51456cad..16f91729 100644 --- a/lib/tgchat/ext/td/td/telegram/AuthManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AuthManager.cpp @@ -9,7 +9,6 @@ #include "td/telegram/AttachMenuManager.h" #include "td/telegram/AuthManager.hpp" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogFilterManager.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" @@ -32,6 +31,7 @@ #include "td/telegram/ThemeManager.h" #include "td/telegram/TopDialogManager.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/utils/base64.h" @@ -287,7 +287,7 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par if (is_bot_str == "true") { is_bot_ = true; } - auto my_id = ContactsManager::load_my_id(); + auto my_id = UserManager::load_my_id(); if (my_id.is_valid()) { // just in case LOG(INFO) << "Logged in as " << my_id; @@ -295,8 +295,8 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par update_state(State::Ok); } else { LOG(ERROR) << "Restore unknown my_id"; - ContactsManager::send_get_me_query( - td_, PromiseCreator::lambda([this](Result result) { update_state(State::Ok); })); + UserManager::send_get_me_query(td_, + PromiseCreator::lambda([this](Result result) { update_state(State::Ok); })); } G()->net_query_dispatcher().check_authorization_is_ok(); } else if (auth_str == "logout") { @@ -539,6 +539,15 @@ void AuthManager::set_firebase_token(uint64 query_id, string token) { G()->net_query_creator().create_unauth(send_code_helper_.request_firebase_sms(token))); } +void AuthManager::report_missing_code(uint64 query_id, string mobile_network_code) { + if (state_ != State::WaitCode) { + return on_query_error(query_id, Status::Error(400, "Call to reportAuthenticationCodeMissing unexpected")); + } + G()->net_query_dispatcher().dispatch_with_callback( + G()->net_query_creator().create_unauth(send_code_helper_.report_missing_code(mobile_network_code)), + actor_shared(this)); +} + void AuthManager::set_email_address(uint64 query_id, string email_address) { if (state_ != State::WaitEmailAddress) { if (state_ == State::WaitEmailCode && net_query_id_ == 0) { @@ -559,7 +568,7 @@ void AuthManager::set_email_address(uint64 query_id, string email_address) { G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_))); } -void AuthManager::resend_authentication_code(uint64 query_id) { +void AuthManager::resend_authentication_code(uint64 query_id, td_api::object_ptr &&reason) { if (state_ != State::WaitCode) { if (state_ == State::WaitEmailCode) { on_new_query(query_id); @@ -571,7 +580,7 @@ void AuthManager::resend_authentication_code(uint64 query_id) { return on_query_error(query_id, Status::Error(400, "Call to resendAuthenticationCode unexpected")); } - auto r_resend_code = send_code_helper_.resend_code(); + auto r_resend_code = send_code_helper_.resend_code(std::move(reason)); if (r_resend_code.is_error()) { return on_query_error(query_id, r_resend_code.move_as_error()); } @@ -1143,7 +1152,7 @@ void AuthManager::on_account_banned() const { return; } LOG(ERROR) << "Your account was banned for suspicious activity. If you think that this is a mistake, please try to " - "log in from an official mobile app and send a email to recover the account by following instructions " + "log in from an official mobile app and send an email to recover the account by following instructions " "provided by the app"; } @@ -1233,9 +1242,9 @@ void AuthManager::on_get_authorization(tl_object_ptrself_ = true; } } - td_->contacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization"); + td_->user_manager_->on_get_user(std::move(auth->user_), "on_get_authorization"); update_state(State::Ok); - if (!td_->contacts_manager_->get_my_id().is_valid()) { + if (!td_->user_manager_->get_my_id().is_valid()) { LOG(ERROR) << "Server didsn't send proper authorization"; on_current_query_error(Status::Error(500, "Server didn't send proper authorization")); log_out(0); diff --git a/lib/tgchat/ext/td/td/telegram/AuthManager.h b/lib/tgchat/ext/td/td/telegram/AuthManager.h index 5707e88e..4c2ff21e 100644 --- a/lib/tgchat/ext/td/td/telegram/AuthManager.h +++ b/lib/tgchat/ext/td/td/telegram/AuthManager.h @@ -39,8 +39,9 @@ class AuthManager final : public NetActor { void set_phone_number(uint64 query_id, string phone_number, td_api::object_ptr settings); void set_firebase_token(uint64 query_id, string token); + void report_missing_code(uint64 query_id, string mobile_network_code); void set_email_address(uint64 query_id, string email_address); - void resend_authentication_code(uint64 query_id); + void resend_authentication_code(uint64 query_id, td_api::object_ptr &&reason); void check_email_code(uint64 query_id, EmailVerification &&code); void reset_email_address(uint64 query_id); void check_code(uint64 query_id, string code); diff --git a/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp b/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp index e9ed7c9b..432d94cb 100644 --- a/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/AutosaveManager.cpp @@ -7,7 +7,7 @@ #include "td/telegram/AutosaveManager.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -15,6 +15,7 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/db/SqliteKeyValueAsync.h" @@ -76,12 +77,7 @@ class SaveAutoSaveSettingsQuery final : public Td::ResultHandler { } else { flags |= telegram_api::account_saveAutoSaveSettings::PEER_MASK; input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - if (dialog_id.get_type() == DialogType::SecretChat) { - return on_error(Status::Error(400, "Can't set autosave settings for secret chats")); - } - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); } send_query(G()->net_query_creator().create( telegram_api::account_saveAutoSaveSettings(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, @@ -384,8 +380,8 @@ void AutosaveManager::on_get_autosave_settings( } auto settings = r_settings.move_as_ok(); - td_->contacts_manager_->on_get_users(std::move(settings->users_), "on_get_autosave_settings"); - td_->contacts_manager_->on_get_chats(std::move(settings->chats_), "on_get_autosave_settings"); + td_->user_manager_->on_get_users(std::move(settings->users_), "on_get_autosave_settings"); + td_->chat_manager_->on_get_chats(std::move(settings->chats_), "on_get_autosave_settings"); DialogAutosaveSettings new_user_settings(settings->users_settings_.get()); DialogAutosaveSettings new_chat_settings(settings->chats_settings_.get()); @@ -482,9 +478,8 @@ void AutosaveManager::set_autosave_settings(td_api::object_ptr(scope.get())->chat_id_); - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_autosave_settings")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "set_autosave_settings")); old_settings = &settings_.exceptions_[dialog_id]; break; default: diff --git a/lib/tgchat/ext/td/td/telegram/BackgroundInfo.h b/lib/tgchat/ext/td/td/telegram/BackgroundInfo.h index 34b9a2d8..b84f2e32 100644 --- a/lib/tgchat/ext/td/td/telegram/BackgroundInfo.h +++ b/lib/tgchat/ext/td/td/telegram/BackgroundInfo.h @@ -39,7 +39,8 @@ class BackgroundInfo { } bool operator==(const BackgroundInfo &other) const { - return background_id_ == other.background_id_ && background_type_ == other.background_type_; + return background_type_ == other.background_type_ && + (background_id_ == other.background_id_ || (background_id_.is_local() && other.background_id_.is_local())); } bool operator!=(const BackgroundInfo &other) const { @@ -54,7 +55,7 @@ class BackgroundInfo { }; inline StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundInfo &background_info) { - return string_builder << background_info.background_id_ << " with type " << background_info.background_type_; + return string_builder << background_info.background_id_ << " with " << background_info.background_type_; } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BackgroundManager.cpp b/lib/tgchat/ext/td/td/telegram/BackgroundManager.cpp index 9057d70e..e30d5e8a 100644 --- a/lib/tgchat/ext/td/td/telegram/BackgroundManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BackgroundManager.cpp @@ -9,7 +9,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundType.hpp" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" @@ -25,6 +25,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/db/SqliteKeyValueAsync.h" @@ -243,9 +244,7 @@ class UploadBackgroundQuery final : public Td::ResultHandler { // TODO td_->background_manager_->on_upload_background_file_parts_missing(file_id_, std::move(bad_parts)); // return; } else { - if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); } td_->file_manager_->cancel_upload(file_id_); promise_.set_error(std::move(status)); @@ -432,6 +431,7 @@ void BackgroundManager::start_up() { background.id.get() > max_local_background_id_.get()) { set_max_local_background_id(background.id); } + add_local_background_to_cache(background); } } @@ -450,13 +450,14 @@ void BackgroundManager::start_up() { if (background.id.get() > max_local_background_id_.get()) { set_max_local_background_id(background.id); } + add_local_background_to_cache(background); add_background(background, true); local_background_ids_[for_dark_theme].push_back(background.id); } } } - // then add selected backgrounds fixing their ID + // then add selected backgrounds fixing their identifiers for (int i = 0; i < 2; i++) { bool for_dark_theme = i != 0; if (has_selected_background[i]) { @@ -465,7 +466,7 @@ void BackgroundManager::start_up() { bool need_resave = false; if (!background.has_new_local_id && !background.type.has_file()) { background.has_new_local_id = true; - background.id = get_next_local_background_id(); + set_local_background_id(background); need_resave = true; } @@ -518,6 +519,7 @@ void BackgroundManager::parse_background(BackgroundId &background_id, LogEventPa set_max_local_background_id(background.id); } background_id = background.id; + add_local_background_to_cache(background); add_background(background, false); } @@ -626,6 +628,7 @@ void BackgroundManager::on_load_background_from_database(string name, string val LOG(ERROR) << "Expected background " << name << ", but received " << background.name; name_to_background_id_.emplace(std::move(name), background.id); } + add_local_background_to_cache(background); add_background(background, false); } } @@ -669,14 +672,34 @@ BackgroundId BackgroundManager::get_next_local_background_id() { return max_local_background_id_; } +void BackgroundManager::set_local_background_id(Background &background) { + CHECK(!background.name.empty() || background.type != BackgroundType()); + CHECK(background.has_new_local_id); + auto &background_id = local_backgrounds_[background]; + if (!background_id.is_valid()) { + background_id = get_next_local_background_id(); + } + background.id = background_id; +} + +void BackgroundManager::add_local_background_to_cache(const Background &background) { + if (!background.has_new_local_id || !background.id.is_local()) { + return; + } + auto &background_id = local_backgrounds_[background]; + if (!background_id.is_valid()) { + background_id = background.id; + } +} + BackgroundId BackgroundManager::add_local_background(const BackgroundType &type) { Background background; - background.id = get_next_local_background_id(); background.is_creator = true; background.is_default = false; background.is_dark = type.is_dark(); background.type = type; background.name = type.get_link(); + set_local_background_id(background); add_background(background, true); return background.id; @@ -742,12 +765,7 @@ void BackgroundManager::delete_background(bool for_dark_theme, Promise &&p } Result BackgroundManager::get_background_dialog(DialogId dialog_id) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_background_dialog")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, true, AccessRights::Write, "get_background_dialog")); switch (dialog_id.get_type()) { case DialogType::User: @@ -756,14 +774,13 @@ Result BackgroundManager::get_background_dialog(DialogId dialog_id) { return Status::Error(400, "Can't change background in the chat"); case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id) - .can_change_info_and_settings_as_administrator()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_change_info_and_settings_as_administrator()) { return Status::Error(400, "Not enough rights in the chat"); } return dialog_id; } case DialogType::SecretChat: { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return Status::Error(400, "Can't access the user"); } @@ -1286,9 +1303,6 @@ std::pair BackgroundManager::on_get_background( LOG(ERROR) << "Receive " << to_string(wallpaper); return {}; } - if (!background_id.is_valid()) { - background_id = get_next_local_background_id(); - } Background background; background.id = background_id; @@ -1297,9 +1311,12 @@ std::pair BackgroundManager::on_get_background( background.is_dark = wallpaper->dark_; background.type = BackgroundType(true, false, std::move(wallpaper->settings_)); background.name = background.type.get_link(); + if (!background.id.is_valid()) { + set_local_background_id(background); + } add_background(background, replace_type); - return {background_id, background.type}; + return {background.id, background.type}; } auto wallpaper = move_tl_object_as(wallpaper_ptr); diff --git a/lib/tgchat/ext/td/td/telegram/BackgroundManager.h b/lib/tgchat/ext/td/td/telegram/BackgroundManager.h index bc8b2c3a..db811a8a 100644 --- a/lib/tgchat/ext/td/td/telegram/BackgroundManager.h +++ b/lib/tgchat/ext/td/td/telegram/BackgroundManager.h @@ -21,6 +21,7 @@ #include "td/utils/common.h" #include "td/utils/FlatHashMap.h" #include "td/utils/FlatHashSet.h" +#include "td/utils/HashTableUtils.h" #include "td/utils/Promise.h" #include "td/utils/Status.h" @@ -97,6 +98,19 @@ class BackgroundManager final : public Actor { void parse(ParserT &parser); }; + struct LocalBackgroundHash { + uint32 operator()(const Background &background) const { + return Hash()(background.name); + } + }; + + struct LocalBackgroundEquals { + bool operator()(const Background &lhs, const Background &rhs) const { + return lhs.name == rhs.name && lhs.type == rhs.type && lhs.is_creator == rhs.is_creator && + lhs.is_default == rhs.is_default && lhs.is_dark == rhs.is_dark; + } + }; + class BackgroundLogEvent; class BackgroundsLogEvent; @@ -128,6 +142,10 @@ class BackgroundManager final : public Actor { BackgroundId get_next_local_background_id(); + void set_local_background_id(Background &background); + + void add_local_background_to_cache(const Background &background); + BackgroundId add_local_background(const BackgroundType &type); void add_background(const Background &background, bool replace_type); @@ -211,6 +229,8 @@ class BackgroundManager final : public Actor { }; FlatHashMap being_uploaded_files_; + FlatHashMap local_backgrounds_; + BackgroundId max_local_background_id_; vector local_background_ids_[2]; diff --git a/lib/tgchat/ext/td/td/telegram/BackgroundType.cpp b/lib/tgchat/ext/td/td/telegram/BackgroundType.cpp index 4546d747..4fecd853 100644 --- a/lib/tgchat/ext/td/td/telegram/BackgroundType.cpp +++ b/lib/tgchat/ext/td/td/telegram/BackgroundType.cpp @@ -89,6 +89,9 @@ BackgroundFill::BackgroundFill(const telegram_api::wallPaperSettings *settings) } else { bottom_color_ = top_color_; } + if (get_type() != Type::Gradient) { + rotation_angle_ = 0; + } } Result BackgroundFill::get_background_fill(const td_api::BackgroundFill *fill) { @@ -238,6 +241,11 @@ bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs) { lhs.fourth_color_ == rhs.fourth_color_; } +StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundFill &fill) { + return string_builder << "BackgroundFill[" << fill.top_color_ << '~' << fill.bottom_color_ << '~' << fill.third_color_ + << '~' << fill.fourth_color_ << ':' << fill.rotation_angle_ << ']'; +} + string BackgroundType::get_mime_type() const { CHECK(has_file()); return type_ == Type::Pattern ? "image/png" : "image/jpeg"; @@ -340,6 +348,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &t UNREACHABLE(); break; } + // string_builder << ' ' << type.is_blurred_ << ' ' << type.is_moving_ << ' ' << type.intensity_ << ' ' << type.fill_ + // << ' ' << type.theme_name_ << ' '; return string_builder << '[' << type.get_link() << ']'; } diff --git a/lib/tgchat/ext/td/td/telegram/BackgroundType.h b/lib/tgchat/ext/td/td/telegram/BackgroundType.h index 0b121ca5..2e6ab0a0 100644 --- a/lib/tgchat/ext/td/td/telegram/BackgroundType.h +++ b/lib/tgchat/ext/td/td/telegram/BackgroundType.h @@ -28,6 +28,9 @@ class BackgroundFill { } BackgroundFill(int32 top_color, int32 bottom_color, int32 rotation_angle) : top_color_(top_color), bottom_color_(bottom_color), rotation_angle_(rotation_angle) { + if (get_type() != Type::Gradient) { + rotation_angle_ = 0; + } } BackgroundFill(int32 first_color, int32 second_color, int32 third_color, int32 fourth_color) : top_color_(first_color), bottom_color_(second_color), third_color_(third_color), fourth_color_(fourth_color) { @@ -54,6 +57,8 @@ class BackgroundFill { friend bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs); + friend StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundFill &fill); + friend class BackgroundType; static Result get_background_fill(Slice name); @@ -63,6 +68,8 @@ class BackgroundFill { bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs); +StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundFill &fill); + class BackgroundType { enum class Type : int32 { Wallpaper, Pattern, Fill, ChatTheme }; Type type_ = Type::Fill; diff --git a/lib/tgchat/ext/td/td/telegram/Birthdate.cpp b/lib/tgchat/ext/td/td/telegram/Birthdate.cpp new file mode 100644 index 00000000..18c5dd12 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/Birthdate.cpp @@ -0,0 +1,69 @@ +// +// 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/Birthdate.h" + +#include "td/utils/HttpDate.h" + +namespace td { + +void Birthdate::init(int32 day, int32 month, int32 year) { + if (year < 1800 || year > 3000) { + year = 0; + } + if (month <= 0 || month > 12 || day <= 0 || day > HttpDate::days_in_month(year, month)) { + return; + } + birthdate_ = day | (month << 5) | (year << 9); +} + +Birthdate::Birthdate(telegram_api::object_ptr birthday) { + if (birthday == nullptr) { + return; + } + init(birthday->day_, birthday->month_, birthday->year_); +} + +Birthdate::Birthdate(td_api::object_ptr birthdate) { + if (birthdate == nullptr) { + return; + } + init(birthdate->day_, birthdate->month_, birthdate->year_); +} + +td_api::object_ptr Birthdate::get_birthdate_object() const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(get_day(), get_month(), get_year()); +} + +telegram_api::object_ptr Birthdate::get_input_birthday() const { + int32 flags = 0; + auto year = get_year(); + if (year != 0) { + flags |= telegram_api::birthday::YEAR_MASK; + } + return telegram_api::make_object(flags, get_day(), get_month(), year); +} + +bool operator==(const Birthdate &lhs, const Birthdate &rhs) { + return lhs.birthdate_ == rhs.birthdate_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const Birthdate &birthdate) { + if (birthdate.is_empty()) { + return string_builder << "unknown birthdate"; + } + string_builder << "birthdate " << birthdate.get_day() << '.' << birthdate.get_month(); + auto year = birthdate.get_year(); + if (year != 0) { + string_builder << '.' << year; + } + return string_builder; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Birthdate.h b/lib/tgchat/ext/td/td/telegram/Birthdate.h new file mode 100644 index 00000000..da48b1ac --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/Birthdate.h @@ -0,0 +1,69 @@ +// +// 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 Birthdate { + public: + Birthdate() = default; + + explicit Birthdate(telegram_api::object_ptr birthday); + + explicit Birthdate(td_api::object_ptr birthdate); + + td_api::object_ptr get_birthdate_object() const; + + telegram_api::object_ptr get_input_birthday() const; + + bool is_empty() const { + return birthdate_ == 0; + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + + private: + int32 birthdate_ = 0; + + int32 get_day() const { + return birthdate_ & 31; + } + + int32 get_month() const { + return (birthdate_ >> 5) & 15; + } + + int32 get_year() const { + return birthdate_ >> 9; + } + + void init(int32 day, int32 month, int32 year); + + friend bool operator==(const Birthdate &lhs, const Birthdate &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const Birthdate &birthdate); +}; + +bool operator==(const Birthdate &lhs, const Birthdate &rhs); + +inline bool operator!=(const Birthdate &lhs, const Birthdate &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const Birthdate &birthdate); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Birthdate.hpp b/lib/tgchat/ext/td/td/telegram/Birthdate.hpp new file mode 100644 index 00000000..b797a7ed --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/Birthdate.hpp @@ -0,0 +1,25 @@ +// +// 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/Birthdate.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void Birthdate::store(StorerT &storer) const { + td::store(birthdate_, storer); +} + +template +void Birthdate::parse(ParserT &parser) { + td::parse(birthdate_, parser); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BoostManager.cpp b/lib/tgchat/ext/td/td/telegram/BoostManager.cpp index 1b03a75c..bc767cab 100644 --- a/lib/tgchat/ext/td/td/telegram/BoostManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BoostManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/LinkManager.h" @@ -19,6 +19,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -26,6 +27,8 @@ #include "td/utils/misc.h" #include "td/utils/SliceBuilder.h" +#include + namespace td { static td_api::object_ptr get_chat_boost_object( @@ -41,7 +44,7 @@ static td_api::object_ptr get_chat_boost_object( giveaway_message_id = MessageId::min(); } return td_api::make_object( - td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourceGiveaway"), boost->used_gift_slug_, + td->user_manager_->get_user_id_object(user_id, "chatBoostSourceGiveaway"), boost->used_gift_slug_, giveaway_message_id.get(), boost->unclaimed_); } if (boost->gift_) { @@ -50,7 +53,7 @@ static td_api::object_ptr get_chat_boost_object( return nullptr; } return td_api::make_object( - td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourceGiftCode"), boost->used_gift_slug_); + td->user_manager_->get_user_id_object(user_id, "chatBoostSourceGiftCode"), boost->used_gift_slug_); } UserId user_id(boost->user_id_); @@ -58,7 +61,7 @@ static td_api::object_ptr get_chat_boost_object( return nullptr; } return td_api::make_object( - td->contacts_manager_->get_user_id_object(user_id, "chatBoostSourcePremium")); + td->user_manager_->get_user_id_object(user_id, "chatBoostSourcePremium")); }(); if (source == nullptr) { LOG(ERROR) << "Receive " << to_string(boost); @@ -70,8 +73,8 @@ static td_api::object_ptr get_chat_boost_object( static td_api::object_ptr get_chat_boost_slots_object( Td *td, telegram_api::object_ptr &&my_boosts) { - td->contacts_manager_->on_get_users(std::move(my_boosts->users_), "GetMyBoostsQuery"); - td->contacts_manager_->on_get_chats(std::move(my_boosts->chats_), "GetMyBoostsQuery"); + td->user_manager_->on_get_users(std::move(my_boosts->users_), "GetMyBoostsQuery"); + td->chat_manager_->on_get_chats(std::move(my_boosts->chats_), "GetMyBoostsQuery"); vector> slots; for (auto &my_boost : my_boosts->my_boosts_) { auto expiration_date = my_boost->expires_; @@ -178,7 +181,7 @@ class GetBoostsStatusQuery final : public Td::ResultHandler { premium_member_count = max(0, static_cast(result->premium_audience_->part_)); auto participant_count = max(static_cast(result->premium_audience_->total_), premium_member_count); if (dialog_id_.get_type() == DialogType::Channel) { - td_->contacts_manager_->on_update_channel_participant_count(dialog_id_.get_channel_id(), participant_count); + td_->chat_manager_->on_update_channel_participant_count(dialog_id_.get_channel_id(), participant_count); } if (participant_count > 0) { premium_member_percentage = 100.0 * premium_member_count / participant_count; @@ -268,7 +271,7 @@ class GetBoostsListQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for GetBoostsListQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetBoostsListQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetBoostsListQuery"); auto total_count = result->count_; vector> boosts; @@ -302,7 +305,7 @@ class GetUserBoostsQuery final : public Td::ResultHandler { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); CHECK(input_peer != nullptr); - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); CHECK(r_input_user.is_ok()); send_query(G()->net_query_creator().create( telegram_api::premium_getUserBoosts(std::move(input_peer), r_input_user.move_as_ok()))); @@ -316,7 +319,7 @@ class GetUserBoostsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for GetUserBoostsQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetUserBoostsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetUserBoostsQuery"); auto total_count = result->count_; vector> boosts; @@ -360,27 +363,38 @@ td_api::object_ptr BoostManager::get_chat_boost_ auto can_set_custom_background = have_enough_boost_level("custom_wallpaper"); auto can_set_custom_emoji_sticker_set = have_enough_boost_level("emoji_stickers"); auto can_recognize_speech = have_enough_boost_level("transcribe"); + auto can_restrict_sponsored_messages = have_enough_boost_level("restrict_sponsored"); return td_api::make_object( level, actual_level, for_megagroup ? 0 : actual_level, theme_counts.title_color_count_, theme_counts.profile_accent_color_count_, can_set_profile_background_custom_emoji, theme_counts.accent_color_count_, can_set_background_custom_emoji, can_set_emoji_status, - theme_counts.chat_theme_count_, can_set_custom_background, can_set_custom_emoji_sticker_set, - can_recognize_speech); + theme_counts.chat_theme_count_, can_set_custom_background, can_set_custom_emoji_sticker_set, can_recognize_speech, + can_restrict_sponsored_messages); } td_api::object_ptr BoostManager::get_chat_boost_features_object(bool for_megagroup) const { - vector> features; - for (int32 level = 1; level <= 10; level++) { - features.push_back(get_chat_boost_level_features_object(for_megagroup, level)); - } + vector big_levels; auto get_min_boost_level = [&](Slice name) { - return narrow_cast(td_->option_manager_->get_option_integer( + auto min_level = narrow_cast(td_->option_manager_->get_option_integer( PSLICE() << (for_megagroup ? "group" : "channel") << '_' << name << "_level_min", 1000000000)); + if (min_level > 10 && min_level < 1000000) { + big_levels.push_back(min_level); + } + return min_level; }; - return td_api::make_object( - std::move(features), get_min_boost_level("profile_bg_icon"), get_min_boost_level("bg_icon"), + auto result = td_api::make_object( + Auto(), get_min_boost_level("profile_bg_icon"), get_min_boost_level("bg_icon"), get_min_boost_level("emoji_status"), get_min_boost_level("wallpaper"), get_min_boost_level("custom_wallpaper"), - get_min_boost_level("emoji_stickers"), get_min_boost_level("transcribe")); + get_min_boost_level("emoji_stickers"), get_min_boost_level("transcribe"), + get_min_boost_level("restrict_sponsored")); + for (int32 level = 1; level <= 10; level++) { + result->features_.push_back(get_chat_boost_level_features_object(for_megagroup, level)); + } + td::unique(big_levels); + for (auto level : big_levels) { + result->features_.push_back(get_chat_boost_level_features_object(for_megagroup, level)); + } + return result; } void BoostManager::get_boost_slots(Promise> &&promise) { @@ -389,24 +403,15 @@ void BoostManager::get_boost_slots(Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_dialog_boost_status")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "get_dialog_boost_status")); td_->create_handler(std::move(promise))->send(dialog_id); } void BoostManager::boost_dialog(DialogId dialog_id, vector slot_ids, Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "boost_dialog")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "boost_dialog")); if (slot_ids.empty()) { return get_boost_slots(std::move(promise)); } @@ -415,12 +420,7 @@ void BoostManager::boost_dialog(DialogId dialog_id, vector slot_ids, } Result> BoostManager::get_dialog_boost_link(DialogId dialog_id) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_dialog_boost_link")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "get_dialog_boost_link")); if (dialog_id.get_type() != DialogType::Channel) { return Status::Error(400, "Can't boost the chat"); } @@ -428,7 +428,7 @@ Result> BoostManager::get_dialog_boost_link(DialogId dia SliceBuilder sb; sb << LinkManager::get_t_me_url() << "boost"; - auto username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); + auto username = td_->chat_manager_->get_channel_first_username(dialog_id.get_channel_id()); bool is_public = !username.empty(); if (is_public) { sb << '/' << username; @@ -464,12 +464,8 @@ td_api::object_ptr BoostManager::get_chat_boost_link_ void BoostManager::get_dialog_boosts(DialogId dialog_id, bool only_gift_codes, const string &offset, int32 limit, Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_dialog_boosts")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "get_dialog_boosts")); if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } @@ -479,12 +475,8 @@ void BoostManager::get_dialog_boosts(DialogId dialog_id, bool only_gift_codes, c void BoostManager::get_user_dialog_boosts(DialogId dialog_id, UserId user_id, Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_user_dialog_boosts")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "get_user_dialog_boosts")); if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "User not found")); } diff --git a/lib/tgchat/ext/td/td/telegram/BotCommand.cpp b/lib/tgchat/ext/td/td/telegram/BotCommand.cpp index da814257..3fe6bf06 100644 --- a/lib/tgchat/ext/td/td/telegram/BotCommand.cpp +++ b/lib/tgchat/ext/td/td/telegram/BotCommand.cpp @@ -7,11 +7,11 @@ #include "td/telegram/BotCommand.h" #include "td/telegram/BotCommandScope.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -100,7 +100,7 @@ class GetBotCommandsQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - BotCommands commands(td_->contacts_manager_->get_my_id(), result_ptr.move_as_ok()); + BotCommands commands(td_->user_manager_->get_my_id(), result_ptr.move_as_ok()); promise_.set_value(commands.get_bot_commands_object(td_)); } @@ -137,7 +137,7 @@ BotCommands::BotCommands(UserId bot_user_id, vector BotCommands::get_bot_commands_object(Td *td) const { auto commands = transform(commands_, [](const auto &command) { return command.get_bot_command_object(); }); return td_api::make_object( - td->contacts_manager_->get_user_id_object(bot_user_id_, "get_bot_commands_object"), std::move(commands)); + td->user_manager_->get_user_id_object(bot_user_id_, "get_bot_commands_object"), std::move(commands)); } bool BotCommands::update_all_bot_commands(vector &all_bot_commands, BotCommands &&bot_commands) { diff --git a/lib/tgchat/ext/td/td/telegram/BotCommandScope.cpp b/lib/tgchat/ext/td/td/telegram/BotCommandScope.cpp index 1c9fc790..6b712835 100644 --- a/lib/tgchat/ext/td/td/telegram/BotCommandScope.cpp +++ b/lib/tgchat/ext/td/td/telegram/BotCommandScope.cpp @@ -8,9 +8,10 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" namespace td { @@ -54,7 +55,7 @@ Result BotCommandScope::get_bot_command_scope(Td *td, type = Type::DialogParticipant; dialog_id = DialogId(scope->chat_id_); user_id = UserId(scope->user_id_); - TRY_STATUS(td->contacts_manager_->get_input_user(user_id)); + TRY_STATUS(td->user_manager_->get_input_user(user_id)); break; } default: @@ -62,12 +63,8 @@ Result BotCommandScope::get_bot_command_scope(Td *td, return BotCommandScope(Type::Default); } - if (!td->dialog_manager_->have_dialog_force(dialog_id, "get_bot_command_scope")) { - return Status::Error(400, "Chat not found"); - } - if (!td->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_STATUS(td->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "get_bot_command_scope")); + switch (dialog_id.get_type()) { case DialogType::User: if (type != Type::Dialog) { @@ -78,13 +75,13 @@ Result BotCommandScope::get_bot_command_scope(Td *td, // ok break; case DialogType::Channel: - if (td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + if (td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { return Status::Error(400, "Can't change commands in channel chats"); } break; case DialogType::SecretChat: default: - return Status::Error(400, "Can't access the chat"); + UNREACHABLE(); } return BotCommandScope(type, dialog_id, user_id); @@ -94,7 +91,7 @@ telegram_api::object_ptr BotCommandScope::get_inp const Td *td) const { auto input_peer = dialog_id_.is_valid() ? td->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read) : nullptr; - auto r_input_user = td->contacts_manager_->get_input_user(user_id_); + auto r_input_user = td->user_manager_->get_input_user(user_id_); auto input_user = r_input_user.is_ok() ? r_input_user.move_as_ok() : nullptr; switch (type_) { case Type::Default: diff --git a/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp b/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp index 54393bd1..b9553a0b 100644 --- a/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BotInfoManager.cpp @@ -7,13 +7,13 @@ #include "td/telegram/BotInfoManager.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -44,7 +44,7 @@ class SetBotGroupDefaultAdminRightsQuery final : public Td::ResultHandler { bool result = result_ptr.move_as_ok(); LOG_IF(WARNING, !result) << "Failed to set group default administrator rights"; - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_value(Unit()); } @@ -52,7 +52,7 @@ class SetBotGroupDefaultAdminRightsQuery final : public Td::ResultHandler { if (status.message() == "RIGHTS_NOT_MODIFIED") { return promise_.set_value(Unit()); } - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_error(std::move(status)); } }; @@ -77,7 +77,7 @@ class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler { bool result = result_ptr.move_as_ok(); LOG_IF(WARNING, !result) << "Failed to set channel default administrator rights"; - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_value(Unit()); } @@ -85,7 +85,7 @@ class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler { if (status.message() == "RIGHTS_NOT_MODIFIED") { return promise_.set_value(Unit()); } - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); promise_.set_error(std::move(status)); } }; @@ -98,7 +98,7 @@ class CanBotSendMessageQuery final : public Td::ResultHandler { } void send(UserId bot_user_id) { - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } @@ -132,7 +132,7 @@ class AllowBotSendMessageQuery final : public Td::ResultHandler { } void send(UserId bot_user_id) { - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } @@ -158,15 +158,15 @@ class AllowBotSendMessageQuery final : public Td::ResultHandler { static Result> get_bot_input_user(const Td *td, UserId bot_user_id) { if (td->auth_manager_->is_bot()) { - if (bot_user_id != UserId() && bot_user_id != td->contacts_manager_->get_my_id()) { + if (bot_user_id != UserId() && bot_user_id != td->user_manager_->get_my_id()) { return Status::Error(400, "Invalid bot user identifier specified"); } } else { - TRY_RESULT(bot_data, td->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT(bot_data, td->user_manager_->get_bot_data(bot_user_id)); if (!bot_data.can_be_edited) { return Status::Error(400, "The bot can't be edited"); } - return td->contacts_manager_->get_input_user(bot_user_id); + return td->user_manager_->get_input_user(bot_user_id); } return nullptr; } @@ -179,7 +179,7 @@ class SetBotInfoQuery final : public Td::ResultHandler { void invalidate_bot_info() { if (set_info_) { - td_->contacts_manager_->invalidate_user_full(bot_user_id_); + td_->user_manager_->invalidate_user_full(bot_user_id_); } } @@ -207,7 +207,7 @@ class SetBotInfoQuery final : public Td::ResultHandler { flags |= telegram_api::bots_setBotInfo::BOT_MASK; bot_user_id_ = bot_user_id; } else { - bot_user_id_ = td_->contacts_manager_->get_my_id(); + bot_user_id_ = td_->user_manager_->get_my_id(); } set_name_ = set_name; set_info_ = set_about || set_description; @@ -228,11 +228,11 @@ class SetBotInfoQuery final : public Td::ResultHandler { if (set_info_) { invalidate_bot_info(); if (!td_->auth_manager_->is_bot()) { - return td_->contacts_manager_->reload_user_full(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); + return td_->user_manager_->reload_user_full(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); } } if (set_name_) { - return td_->contacts_manager_->reload_user(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); + return td_->user_manager_->reload_user(bot_user_id_, std::move(promise_), "SetBotInfoQuery"); } // invalidation is enough for bots if name wasn't changed promise_.set_value(Unit()); @@ -374,13 +374,13 @@ void BotInfoManager::timeout_expired() { void BotInfoManager::set_default_group_administrator_rights(AdministratorRights administrator_rights, Promise &&promise) { - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); td_->create_handler(std::move(promise))->send(administrator_rights); } void BotInfoManager::set_default_channel_administrator_rights(AdministratorRights administrator_rights, Promise &&promise) { - td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id()); + td_->user_manager_->invalidate_user_full(td_->user_manager_->get_my_id()); td_->create_handler(std::move(promise))->send(administrator_rights); } diff --git a/lib/tgchat/ext/td/td/telegram/BotMenuButton.cpp b/lib/tgchat/ext/td/td/telegram/BotMenuButton.cpp index 857fd538..0f330deb 100644 --- a/lib/tgchat/ext/td/td/telegram/BotMenuButton.cpp +++ b/lib/tgchat/ext/td/td/telegram/BotMenuButton.cpp @@ -7,12 +7,12 @@ #include "td/telegram/BotMenuButton.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/LinkManager.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" @@ -29,7 +29,7 @@ class SetBotMenuButtonQuery final : public Td::ResultHandler { } void send(UserId user_id, telegram_api::object_ptr input_bot_menu_button) { - auto input_user = user_id.is_valid() ? td_->contacts_manager_->get_input_user(user_id).move_as_ok() + auto input_user = user_id.is_valid() ? td_->user_manager_->get_input_user(user_id).move_as_ok() : make_tl_object(); send_query(G()->net_query_creator().create( telegram_api::bots_setBotMenuButton(std::move(input_user), std::move(input_bot_menu_button)))); @@ -61,7 +61,7 @@ class GetBotMenuButtonQuery final : public Td::ResultHandler { } void send(UserId user_id) { - auto input_user = user_id.is_valid() ? td_->contacts_manager_->get_input_user(user_id).move_as_ok() + auto input_user = user_id.is_valid() ? td_->user_manager_->get_input_user(user_id).move_as_ok() : make_tl_object(); send_query(G()->net_query_creator().create(telegram_api::bots_getBotMenuButton(std::move(input_user)))); } diff --git a/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.cpp b/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.cpp index 63ffe920..14e6a4e9 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.cpp @@ -6,6 +6,8 @@ // #include "td/telegram/BusinessAwayMessage.h" +#include "td/telegram/Dependencies.h" + namespace td { BusinessAwayMessage::BusinessAwayMessage(telegram_api::object_ptr away_message) { @@ -23,7 +25,7 @@ BusinessAwayMessage::BusinessAwayMessage(td_api::object_ptrshortcut_id_); - recipients_ = BusinessRecipients(std::move(away_message->recipients_)); + recipients_ = BusinessRecipients(std::move(away_message->recipients_), false); schedule_ = BusinessAwayMessageSchedule(std::move(away_message->schedule_)); offline_only_ = away_message->offline_only_; } @@ -49,6 +51,10 @@ telegram_api::object_ptr BusinessAwayMes recipients_.get_input_business_recipients(td)); } +void BusinessAwayMessage::add_dependencies(Dependencies &dependencies) const { + recipients_.add_dependencies(dependencies); +} + bool operator==(const BusinessAwayMessage &lhs, const BusinessAwayMessage &rhs) { return lhs.shortcut_id_ == rhs.shortcut_id_ && lhs.recipients_ == rhs.recipients_ && lhs.schedule_ == rhs.schedule_ && lhs.offline_only_ == rhs.offline_only_; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.h b/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.h index 8f9fde6f..d8efb2be 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessAwayMessage.h @@ -17,6 +17,7 @@ namespace td { +class Dependencies; class Td; class BusinessAwayMessage { @@ -39,6 +40,8 @@ class BusinessAwayMessage { return shortcut_id_.is_server(); } + void add_dependencies(Dependencies &dependencies) const; + template void store(StorerT &storer) const; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.cpp b/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.cpp new file mode 100644 index 00000000..6152f94a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.cpp @@ -0,0 +1,94 @@ +// +// 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/BusinessBotManageBar.h" + +#include "td/telegram/Dependencies.h" +#include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" + +#include "td/utils/logging.h" + +namespace td { + +unique_ptr BusinessBotManageBar::create(bool is_business_bot_paused, bool can_business_bot_reply, + UserId business_bot_user_id, + string business_bot_manage_url) { + auto action_bar = make_unique(); + action_bar->is_business_bot_paused_ = is_business_bot_paused; + action_bar->can_business_bot_reply_ = can_business_bot_reply; + action_bar->business_bot_user_id_ = business_bot_user_id; + action_bar->business_bot_manage_url_ = std::move(business_bot_manage_url); + if (action_bar->is_empty()) { + return nullptr; + } + return action_bar; +} + +bool BusinessBotManageBar::is_empty() const { + return !business_bot_user_id_.is_valid(); +} + +void BusinessBotManageBar::fix(DialogId dialog_id) { + bool is_valid = business_bot_user_id_.is_valid() + ? dialog_id.get_type() == DialogType::User && !business_bot_manage_url_.empty() + : business_bot_manage_url_.empty() && !is_business_bot_paused_ && !can_business_bot_reply_; + if (!is_valid) { + LOG(ERROR) << "Receive business bot " << business_bot_user_id_ << " in " << dialog_id << " with manage URL " + << business_bot_manage_url_; + *this = {}; + } +} + +td_api::object_ptr BusinessBotManageBar::get_business_bot_manage_bar_object( + Td *td) const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object( + td->user_manager_->get_user_id_object(business_bot_user_id_, "businessBotManageBar"), business_bot_manage_url_, + is_business_bot_paused_, can_business_bot_reply_); +} + +bool BusinessBotManageBar::on_user_deleted() { + if (is_empty()) { + return false; + } + + *this = {}; + return true; +} + +bool BusinessBotManageBar::set_business_bot_is_paused(bool is_paused) { + if (!business_bot_user_id_.is_valid() || is_business_bot_paused_ == is_paused) { + return false; + } + is_business_bot_paused_ = is_paused; + return true; +} + +void BusinessBotManageBar::add_dependencies(Dependencies &dependencies) const { + dependencies.add(business_bot_user_id_); +} + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs) { + if (lhs == nullptr) { + return rhs == nullptr; + } + if (rhs == nullptr) { + return false; + } + return lhs->business_bot_user_id_ == rhs->business_bot_user_id_ && + lhs->business_bot_manage_url_ == rhs->business_bot_manage_url_ && + lhs->is_business_bot_paused_ == rhs->is_business_bot_paused_ && + lhs->can_business_bot_reply_ == rhs->can_business_bot_reply_; +} + +bool operator!=(const unique_ptr &lhs, const unique_ptr &rhs) { + return !(lhs == rhs); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.h b/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.h new file mode 100644 index 00000000..56351dd2 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessBotManageBar.h @@ -0,0 +1,86 @@ +// +// 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/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class Dependencies; +class Td; + +class BusinessBotManageBar { + UserId business_bot_user_id_; + string business_bot_manage_url_; + bool is_business_bot_paused_ = false; + bool can_business_bot_reply_ = false; + + friend bool operator==(const unique_ptr &lhs, const unique_ptr &rhs); + + public: + static unique_ptr create(bool is_business_bot_paused, bool can_business_bot_reply, + UserId business_bot_user_id, string business_bot_manage_url); + + bool is_empty() const; + + void fix(DialogId dialog_id); + + td_api::object_ptr get_business_bot_manage_bar_object(Td *td) const; + + bool on_user_deleted(); + + bool set_business_bot_is_paused(bool is_paused); + + void add_dependencies(Dependencies &dependencies) const; + + template + void store(StorerT &storer) const { + bool has_business_bot_user_id = business_bot_user_id_.is_valid(); + bool has_business_bot_manage_url = !business_bot_manage_url_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_business_bot_paused_); + STORE_FLAG(can_business_bot_reply_); + STORE_FLAG(has_business_bot_user_id); + STORE_FLAG(has_business_bot_manage_url); + END_STORE_FLAGS(); + if (has_business_bot_user_id) { + td::store(business_bot_user_id_, storer); + } + if (has_business_bot_manage_url) { + td::store(business_bot_manage_url_, storer); + } + } + + template + void parse(ParserT &parser) { + bool has_business_bot_user_id; + bool has_business_bot_manage_url; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_business_bot_paused_); + PARSE_FLAG(can_business_bot_reply_); + PARSE_FLAG(has_business_bot_user_id); + PARSE_FLAG(has_business_bot_manage_url); + END_PARSE_FLAGS(); + if (has_business_bot_user_id) { + td::parse(business_bot_user_id_, parser); + } + if (has_business_bot_manage_url) { + td::parse(business_bot_manage_url_, parser); + } + } +}; + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs); + +bool operator!=(const unique_ptr &lhs, const unique_ptr &rhs); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessChatLink.cpp b/lib/tgchat/ext/td/td/telegram/BusinessChatLink.cpp new file mode 100644 index 00000000..5e5e7e07 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessChatLink.cpp @@ -0,0 +1,53 @@ +// +// 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/BusinessChatLink.h" + +#include "td/utils/algorithm.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" + +namespace td { + +BusinessChatLink::BusinessChatLink(const UserManager *user_manager, + telegram_api::object_ptr &&link) + : link_(std::move(link->link_)) + , text_(get_message_text(user_manager, std::move(link->message_), std::move(link->entities_), true, true, 0, false, + "BusinessChatLink")) + , title_(std::move(link->title_)) + , view_count_(link->views_) { +} + +td_api::object_ptr BusinessChatLink::get_business_chat_link_object() const { + return td_api::make_object(link_, get_formatted_text_object(text_, true, -1), title_, + view_count_); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLink &link) { + return string_builder << '[' << link.link_ << ' ' << link.title_ << ' ' << link.view_count_ << ']'; +} + +BusinessChatLinks::BusinessChatLinks(const UserManager *user_manager, + vector> &&links) { + for (auto &link : links) { + business_chat_links_.emplace_back(user_manager, std::move(link)); + if (!business_chat_links_.back().is_valid()) { + LOG(ERROR) << "Receive invalid " << business_chat_links_.back() << " business link"; + business_chat_links_.pop_back(); + } + } +} + +td_api::object_ptr BusinessChatLinks::get_business_chat_links_object() const { + return td_api::make_object(transform( + business_chat_links_, [](const BusinessChatLink &link) { return link.get_business_chat_link_object(); })); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLinks &links) { + return string_builder << links.business_chat_links_; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessChatLink.h b/lib/tgchat/ext/td/td/telegram/BusinessChatLink.h new file mode 100644 index 00000000..cb1159e3 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessChatLink.h @@ -0,0 +1,54 @@ +// +// 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/MessageEntity.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 UserManager; + +class BusinessChatLink { + string link_; + FormattedText text_; + string title_; + int32 view_count_ = 0; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLink &link); + + public: + BusinessChatLink(const UserManager *user_manager, telegram_api::object_ptr &&link); + + bool is_valid() const { + return !link_.empty(); + } + + td_api::object_ptr get_business_chat_link_object() const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLink &link); + +class BusinessChatLinks { + vector business_chat_links_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLinks &links); + + public: + explicit BusinessChatLinks(const UserManager *user_manager, + vector> &&links); + + td_api::object_ptr get_business_chat_links_object() const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessChatLinks &links); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectedBot.cpp b/lib/tgchat/ext/td/td/telegram/BusinessConnectedBot.cpp index 308705fb..eabdf3c3 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessConnectedBot.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectedBot.cpp @@ -6,8 +6,8 @@ // #include "td/telegram/BusinessConnectedBot.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" namespace td { @@ -23,14 +23,14 @@ BusinessConnectedBot::BusinessConnectedBot(td_api::object_ptrbot_user_id_); - recipients_ = BusinessRecipients(std::move(connected_bot->recipients_)); + recipients_ = BusinessRecipients(std::move(connected_bot->recipients_), true); can_reply_ = connected_bot->can_reply_; } td_api::object_ptr BusinessConnectedBot::get_business_connected_bot_object(Td *td) const { CHECK(is_valid()); return td_api::make_object( - td->contacts_manager_->get_user_id_object(user_id_, "businessConnectedBot"), + td->user_manager_->get_user_id_object(user_id_, "businessConnectedBot"), recipients_.get_business_recipients_object(td), can_reply_); } diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectionId.h b/lib/tgchat/ext/td/td/telegram/BusinessConnectionId.h new file mode 100644 index 00000000..9dff460a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectionId.h @@ -0,0 +1,79 @@ +// +// 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/utils/common.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class BusinessConnectionId { + string business_connection_id_; + + public: + BusinessConnectionId() = default; + + explicit BusinessConnectionId(string &&business_connection_id) + : business_connection_id_(std::move(business_connection_id)) { + } + + explicit BusinessConnectionId(const string &business_connection_id) + : business_connection_id_(business_connection_id) { + } + + bool is_empty() const { + return business_connection_id_.empty(); + } + + bool is_valid() const { + return !business_connection_id_.empty(); + } + + const string &get() const { + return business_connection_id_; + } + + bool operator==(const BusinessConnectionId &other) const { + return business_connection_id_ == other.business_connection_id_; + } + + bool operator!=(const BusinessConnectionId &other) const { + return business_connection_id_ != other.business_connection_id_; + } + + telegram_api::object_ptr get_invoke_prefix() const { + if (is_empty()) { + return nullptr; + } + return telegram_api::make_object(business_connection_id_); + } + + template + void store(StorerT &storer) const { + storer.store_string(business_connection_id_); + } + + template + void parse(ParserT &parser) { + business_connection_id_ = parser.template fetch_string(); + } +}; + +struct BusinessConnectionIdHash { + uint32 operator()(BusinessConnectionId business_connection_id) const { + return Hash()(business_connection_id.get()); + } +}; + +inline StringBuilder &operator<<(StringBuilder &string_builder, BusinessConnectionId business_connection_id) { + return string_builder << "business connection " << business_connection_id.get(); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp new file mode 100644 index 00000000..67faa680 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.cpp @@ -0,0 +1,1088 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/BusinessConnectionManager.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" +#include "td/telegram/Global.h" +#include "td/telegram/MessageContent.h" +#include "td/telegram/MessageContentType.h" +#include "td/telegram/MessageCopyOptions.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessageQuote.h" +#include "td/telegram/MessageSelfDestructType.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/ReplyMarkup.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/Td.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" + +#include "td/utils/buffer.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/Random.h" + +namespace td { + +class GetBotBusinessConnectionQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetBotBusinessConnectionQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const BusinessConnectionId &connection_id) { + send_query(G()->net_query_creator().create(telegram_api::account_getBotBusinessConnection(connection_id.get()))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetBotBusinessConnectionQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +struct BusinessConnectionManager::BusinessConnection { + BusinessConnectionId connection_id_; + UserId user_id_; + DcId dc_id_; + int32 connection_date_ = 0; + bool can_reply_ = false; + bool is_disabled_ = false; + + explicit BusinessConnection(const telegram_api::object_ptr &connection) + : connection_id_(connection->connection_id_) + , user_id_(connection->user_id_) + , dc_id_(DcId::create(connection->dc_id_)) + , connection_date_(connection->date_) + , can_reply_(connection->can_reply_) + , is_disabled_(connection->disabled_) { + } + + BusinessConnection(const BusinessConnection &) = delete; + BusinessConnection &operator=(const BusinessConnection &) = delete; + BusinessConnection(BusinessConnection &&) = delete; + BusinessConnection &operator=(BusinessConnection &&) = delete; + ~BusinessConnection() = default; + + bool is_valid() const { + return connection_id_.is_valid() && user_id_.is_valid() && !dc_id_.is_empty() && connection_date_ > 0; + } + + td_api::object_ptr get_business_connection_object(Td *td) const { + DialogId user_dialog_id(user_id_); + td->dialog_manager_->force_create_dialog(user_dialog_id, "get_business_connection_object"); + return td_api::make_object( + connection_id_.get(), td->user_manager_->get_user_id_object(user_id_, "businessConnection"), + td->dialog_manager_->get_chat_id_object(user_dialog_id, "businessConnection"), connection_date_, can_reply_, + !is_disabled_); + } +}; + +struct BusinessConnectionManager::PendingMessage { + BusinessConnectionId business_connection_id_; + DialogId dialog_id_; + MessageInputReplyTo input_reply_to_; + string send_emoji_; + MessageSelfDestructType ttl_; + unique_ptr content_; + unique_ptr reply_markup_; + int64 random_id_ = 0; + int64 effect_id_ = 0; + bool noforwards_ = false; + bool disable_notification_ = false; + bool invert_media_ = false; + bool disable_web_page_preview_ = false; +}; + +class BusinessConnectionManager::SendBusinessMessageQuery final : public Td::ResultHandler { + Promise> promise_; + unique_ptr message_; + + public: + explicit SendBusinessMessageQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(unique_ptr message) { + message_ = std::move(message); + + int32 flags = 0; + if (message_->disable_web_page_preview_) { + flags |= telegram_api::messages_sendMessage::NO_WEBPAGE_MASK; + } + if (message_->disable_notification_) { + flags |= telegram_api::messages_sendMessage::SILENT_MASK; + } + if (message_->noforwards_) { + flags |= telegram_api::messages_sendMessage::NOFORWARDS_MASK; + } + if (message_->effect_id_) { + flags |= telegram_api::messages_sendMessage::EFFECT_MASK; + } + if (message_->invert_media_) { + flags |= telegram_api::messages_sendMessage::INVERT_MEDIA_MASK; + } + + auto input_peer = td_->dialog_manager_->get_input_peer(message_->dialog_id_, AccessRights::Know); + CHECK(input_peer != nullptr); + + auto reply_to = message_->input_reply_to_.get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; + } + + const FormattedText *message_text = get_message_content_text(message_->content_.get()); + CHECK(message_text != nullptr); + auto entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendBusinessMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMessage::ENTITIES_MASK; + } + + if (message_->reply_markup_ != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_MARKUP_MASK; + } + + send_query(G()->net_query_creator().create_with_prefix( + message_->business_connection_id_.get_invoke_prefix(), + telegram_api::messages_sendMessage( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), message_text->text, + message_->random_id_, get_input_reply_markup(td_->user_manager_.get(), message_->reply_markup_), + std::move(entities), 0, nullptr, nullptr, message_->effect_id_), + td_->business_connection_manager_->get_business_connection_dc_id(message_->business_connection_id_), + {{message_->dialog_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendBusinessMessageQuery: " << to_string(ptr); + td_->business_connection_manager_->process_sent_business_message(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendBusinessMessageQuery: " << status; + promise_.set_error(std::move(status)); + } +}; + +class BusinessConnectionManager::SendBusinessMediaQuery final : public Td::ResultHandler { + Promise> promise_; + unique_ptr message_; + + public: + explicit SendBusinessMediaQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(unique_ptr message, telegram_api::object_ptr &&input_media) { + CHECK(input_media != nullptr); + message_ = std::move(message); + + int32 flags = 0; + if (message_->disable_notification_) { + flags |= telegram_api::messages_sendMedia::SILENT_MASK; + } + if (message_->noforwards_) { + flags |= telegram_api::messages_sendMedia::NOFORWARDS_MASK; + } + if (message_->effect_id_) { + flags |= telegram_api::messages_sendMedia::EFFECT_MASK; + } + if (message_->invert_media_) { + flags |= telegram_api::messages_sendMedia::INVERT_MEDIA_MASK; + } + + auto input_peer = td_->dialog_manager_->get_input_peer(message_->dialog_id_, AccessRights::Know); + CHECK(input_peer != nullptr); + + auto reply_to = message_->input_reply_to_.get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMedia::REPLY_TO_MASK; + } + + const FormattedText *message_text = get_message_content_text(message_->content_.get()); + auto entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendBusinessMediaQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; + } + + if (message_->reply_markup_ != nullptr) { + flags |= telegram_api::messages_sendMedia::REPLY_MARKUP_MASK; + } + + send_query(G()->net_query_creator().create_with_prefix( + message_->business_connection_id_.get_invoke_prefix(), + telegram_api::messages_sendMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), + std::move(reply_to), std::move(input_media), + message_text == nullptr ? string() : message_text->text, message_->random_id_, + get_input_reply_markup(td_->user_manager_.get(), message_->reply_markup_), + std::move(entities), 0, nullptr, nullptr, message_->effect_id_), + td_->business_connection_manager_->get_business_connection_dc_id(message_->business_connection_id_), + {{message_->dialog_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendBusinessMediaQuery: " << to_string(ptr); + td_->business_connection_manager_->process_sent_business_message(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendBusinessMediaQuery: " << status; + promise_.set_error(std::move(status)); + } +}; + +class BusinessConnectionManager::SendBusinessMultiMediaQuery final : public Td::ResultHandler { + Promise> promise_; + vector> messages_; + + public: + explicit SendBusinessMultiMediaQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(vector> &&messages, + vector> &&input_single_media) { + CHECK(!messages.empty()); + messages_ = std::move(messages); + + int32 flags = 0; + if (messages_[0]->disable_notification_) { + flags |= telegram_api::messages_sendMultiMedia::SILENT_MASK; + } + if (messages_[0]->noforwards_) { + flags |= telegram_api::messages_sendMultiMedia::NOFORWARDS_MASK; + } + if (messages_[0]->effect_id_) { + flags |= telegram_api::messages_sendMultiMedia::EFFECT_MASK; + } + if (messages_[0]->invert_media_) { + flags |= telegram_api::messages_sendMultiMedia::INVERT_MEDIA_MASK; + } + + auto input_peer = td_->dialog_manager_->get_input_peer(messages_[0]->dialog_id_, AccessRights::Know); + CHECK(input_peer != nullptr); + + auto reply_to = messages_[0]->input_reply_to_.get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMultiMedia::REPLY_TO_MASK; + } + + send_query(G()->net_query_creator().create_with_prefix( + messages_[0]->business_connection_id_.get_invoke_prefix(), + telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_peer), std::move(reply_to), std::move(input_single_media), + 0, nullptr, nullptr, messages_[0]->effect_id_), + td_->business_connection_manager_->get_business_connection_dc_id(messages_[0]->business_connection_id_), + {{messages_[0]->dialog_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendBusinessMultiMediaQuery: " << to_string(ptr); + td_->business_connection_manager_->process_sent_business_message_album(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for SendBusinessMultiMediaQuery: " << status; + promise_.set_error(std::move(status)); + } +}; + +class BusinessConnectionManager::UploadBusinessMediaQuery final : public Td::ResultHandler { + Promise promise_; + unique_ptr message_; + bool was_uploaded_ = false; + bool was_thumbnail_uploaded_ = false; + + void delete_thumbnail() { + if (!was_thumbnail_uploaded_) { + return; + } + + auto file_id = get_message_file_id(message_); + CHECK(file_id.is_valid()); + auto thumbnail_file_id = td_->business_connection_manager_->get_message_thumbnail_file_id(message_, file_id); + CHECK(thumbnail_file_id.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id); + } + + public: + explicit UploadBusinessMediaQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(unique_ptr message, telegram_api::object_ptr &&input_media) { + CHECK(input_media != nullptr); + message_ = std::move(message); + was_uploaded_ = FileManager::extract_was_uploaded(input_media); + was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); + + if (was_uploaded_ && false) { + return on_error(Status::Error(400, "FILE_PART_1_MISSING")); + } + + int32 flags = telegram_api::messages_uploadMedia::BUSINESS_CONNECTION_ID_MASK; + auto input_peer = td_->dialog_manager_->get_input_peer(message_->dialog_id_, AccessRights::Know); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::messages_uploadMedia( + flags, message_->business_connection_id_.get(), std::move(input_peer), std::move(input_media)))); + } + + 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()); + } + + delete_thumbnail(); + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for UploadBusinessMediaQuery: " << to_string(ptr); + td_->business_connection_manager_->complete_upload_media(std::move(message_), std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for UploadBusinessMediaQuery: " << status; + + if (was_uploaded_) { + delete_thumbnail(); + + auto file_id = get_message_file_id(message_); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->business_connection_manager_->upload_media(std::move(message_), std::move(promise_), std::move(bad_parts)); + return; + } else { + td_->file_manager_->delete_partial_remote_location_if_needed(file_id, status); + } + } + promise_.set_error(std::move(status)); + } +}; + +class BusinessConnectionManager::UploadMediaCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->business_connection_manager(), &BusinessConnectionManager::on_upload_media, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->business_connection_manager(), &BusinessConnectionManager::on_upload_media_error, file_id, + std::move(error)); + } +}; + +class BusinessConnectionManager::UploadThumbnailCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->business_connection_manager(), &BusinessConnectionManager::on_upload_thumbnail, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->business_connection_manager(), &BusinessConnectionManager::on_upload_thumbnail, file_id, + nullptr); + } +}; + +BusinessConnectionManager::BusinessConnectionManager(Td *td, ActorShared<> parent) + : td_(td), parent_(std::move(parent)) { + upload_media_callback_ = std::make_shared(); + upload_thumbnail_callback_ = std::make_shared(); +} + +BusinessConnectionManager::~BusinessConnectionManager() { + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), business_connections_); +} + +void BusinessConnectionManager::tear_down() { + parent_.reset(); +} + +Status BusinessConnectionManager::check_business_connection(const BusinessConnectionId &connection_id, + DialogId dialog_id) const { + auto connection = business_connections_.get_pointer(connection_id); + if (connection == nullptr) { + return Status::Error(400, "Business connection not found"); + } + if (dialog_id.get_type() != DialogType::User) { + return Status::Error(400, "Chat must be a private chat"); + } + if (dialog_id == DialogId(connection->user_id_)) { + return Status::Error(400, "Messages must not be sent to self"); + } + // no need to check connection->can_reply_ and connection->is_disabled_ + return Status::OK(); +} + +DcId BusinessConnectionManager::get_business_connection_dc_id(const BusinessConnectionId &connection_id) const { + if (connection_id.is_empty()) { + return DcId::main(); + } + auto connection = business_connections_.get_pointer(connection_id); + CHECK(connection != nullptr); + return connection->dc_id_; +} + +void BusinessConnectionManager::on_update_bot_business_connect( + telegram_api::object_ptr &&connection) { + CHECK(connection != nullptr); + auto business_connection = make_unique(connection); + if (!business_connection->is_valid()) { + LOG(ERROR) << "Receive invalid " << to_string(connection); + return; + } + if (!td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Receive " << to_string(connection); + return; + } + + auto &stored_connection = business_connections_[business_connection->connection_id_]; + stored_connection = std::move(business_connection); + send_closure(G()->td(), &Td::send_update, get_update_business_connection(stored_connection.get())); +} + +void BusinessConnectionManager::on_update_bot_new_business_message( + const BusinessConnectionId &connection_id, telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message) { + if (!td_->auth_manager_->is_bot() || !connection_id.is_valid()) { + LOG(ERROR) << "Receive " << to_string(message); + return; + } + auto message_object = + td_->messages_manager_->get_business_message_object(std::move(message), std::move(reply_to_message)); + if (message_object == nullptr) { + return; + } + send_closure(G()->td(), &Td::send_update, + td_api::make_object(connection_id.get(), std::move(message_object))); +} + +void BusinessConnectionManager::on_update_bot_edit_business_message( + const BusinessConnectionId &connection_id, telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message) { + if (!td_->auth_manager_->is_bot() || !connection_id.is_valid()) { + LOG(ERROR) << "Receive " << to_string(message); + return; + } + auto message_object = + td_->messages_manager_->get_business_message_object(std::move(message), std::move(reply_to_message)); + if (message_object == nullptr) { + return; + } + send_closure( + G()->td(), &Td::send_update, + td_api::make_object(connection_id.get(), std::move(message_object))); +} + +void BusinessConnectionManager::on_update_bot_delete_business_messages(const BusinessConnectionId &connection_id, + DialogId dialog_id, vector &&messages) { + if (!td_->auth_manager_->is_bot() || !connection_id.is_valid() || dialog_id.get_type() != DialogType::User) { + LOG(ERROR) << "Receive deletion of messages " << messages << " in " << dialog_id; + return; + } + vector message_ids; + for (auto message : messages) { + message_ids.push_back(MessageId(ServerMessageId(message)).get()); + } + td_->dialog_manager_->force_create_dialog(dialog_id, "on_update_bot_delete_business_messages", true); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + connection_id.get(), td_->dialog_manager_->get_chat_id_object(dialog_id, "updateBusinessMessageDeleted"), + std::move(message_ids))); +} + +void BusinessConnectionManager::get_business_connection( + const BusinessConnectionId &connection_id, Promise> &&promise) { + auto connection = business_connections_.get_pointer(connection_id); + if (connection != nullptr) { + return promise.set_value(connection->get_business_connection_object(td_)); + } + + if (connection_id.is_empty()) { + return promise.set_error(Status::Error(400, "Connection iedntifier must be non-empty")); + } + + auto &queries = get_business_connection_queries_[connection_id]; + queries.push_back(std::move(promise)); + if (queries.size() == 1u) { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), connection_id](Result> r_updates) { + send_closure(actor_id, &BusinessConnectionManager::on_get_business_connection, connection_id, + std::move(r_updates)); + }); + td_->create_handler(std::move(query_promise))->send(connection_id); + } +} + +void BusinessConnectionManager::on_get_business_connection( + const BusinessConnectionId &connection_id, Result> r_updates) { + G()->ignore_result_if_closing(r_updates); + auto queries_it = get_business_connection_queries_.find(connection_id); + CHECK(queries_it != get_business_connection_queries_.end()); + CHECK(!queries_it->second.empty()); + auto promises = std::move(queries_it->second); + get_business_connection_queries_.erase(queries_it); + if (r_updates.is_error()) { + return fail_promises(promises, r_updates.move_as_error()); + } + auto connection = business_connections_.get_pointer(connection_id); + if (connection != nullptr) { + for (auto &promise : promises) { + promise.set_value(connection->get_business_connection_object(td_)); + } + return; + } + + auto updates_ptr = r_updates.move_as_ok(); + if (updates_ptr->get_id() != telegram_api::updates::ID) { + LOG(ERROR) << "Receive " << to_string(updates_ptr); + return fail_promises(promises, Status::Error(500, "Receive invalid business connection info")); + } + auto updates = telegram_api::move_object_as(updates_ptr); + if (updates->updates_.size() != 1 || updates->updates_[0]->get_id() != telegram_api::updateBotBusinessConnect::ID) { + if (updates->updates_.empty()) { + return fail_promises(promises, Status::Error(400, "Business connection not found")); + } + LOG(ERROR) << "Receive " << to_string(updates); + return fail_promises(promises, Status::Error(500, "Receive invalid business connection info")); + } + auto update = telegram_api::move_object_as(updates->updates_[0]); + + td_->user_manager_->on_get_users(std::move(updates->users_), "on_get_business_connection"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "on_get_business_connection"); + + auto business_connection = make_unique(update->connection_); + if (!business_connection->is_valid() || connection_id != business_connection->connection_id_) { + LOG(ERROR) << "Receive for " << connection_id << ": " << to_string(update->connection_); + return fail_promises(promises, Status::Error(500, "Receive invalid business connection info")); + } + + auto &stored_connection = business_connections_[connection_id]; + CHECK(stored_connection == nullptr); + stored_connection = std::move(business_connection); + for (auto &promise : promises) { + promise.set_value(stored_connection->get_business_connection_object(td_)); + } +} + +MessageInputReplyTo BusinessConnectionManager::create_business_message_input_reply_to( + td_api::object_ptr &&reply_to) { + if (reply_to == nullptr) { + return {}; + } + switch (reply_to->get_id()) { + case td_api::inputMessageReplyToStory::ID: + return {}; + case td_api::inputMessageReplyToMessage::ID: { + auto reply_to_message = td_api::move_object_as(reply_to); + auto message_id = MessageId(reply_to_message->message_id_); + if (!message_id.is_valid() || !message_id.is_server()) { + return {}; + } + if (reply_to_message->chat_id_ != 0) { + return {}; + } + return MessageInputReplyTo{message_id, DialogId(), MessageQuote(td_, std::move(reply_to_message->quote_))}; + } + default: + UNREACHABLE(); + return {}; + } +} + +Result BusinessConnectionManager::process_input_message_content( + td_api::object_ptr &&input_message_content) { + if (input_message_content == nullptr) { + return Status::Error(400, "Can't send message without content"); + } + if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { + return Status::Error(400, "Can't forward messages as business"); + } + return get_input_message_content(DialogId(), std::move(input_message_content), td_, true); +} + +unique_ptr BusinessConnectionManager::create_business_message_to_send( + BusinessConnectionId business_connection_id, DialogId dialog_id, MessageInputReplyTo &&input_reply_to, + bool disable_notification, bool protect_content, int64 effect_id, unique_ptr &&reply_markup, + InputMessageContent &&input_content) const { + auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), input_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + auto message = make_unique(); + message->business_connection_id_ = business_connection_id; + message->dialog_id_ = dialog_id; + message->input_reply_to_ = std::move(input_reply_to); + message->noforwards_ = protect_content; + message->effect_id_ = effect_id; + message->content_ = std::move(content); + message->reply_markup_ = std::move(reply_markup); + message->disable_notification_ = disable_notification; + message->invert_media_ = input_content.invert_media; + message->disable_web_page_preview_ = input_content.disable_web_page_preview; + message->ttl_ = input_content.ttl; + message->send_emoji_ = std::move(input_content.emoji); + message->random_id_ = Random::secure_int64(); + return message; +} + +void BusinessConnectionManager::send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, + bool disable_notification, bool protect_content, int64 effect_id, + td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + TRY_RESULT_PROMISE(promise, input_content, process_input_message_content(std::move(input_message_content))); + auto input_reply_to = create_business_message_input_reply_to(std::move(reply_to)); + TRY_RESULT_PROMISE(promise, message_reply_markup, + get_reply_markup(std::move(reply_markup), DialogType::User, true, false)); + + auto message = create_business_message_to_send(std::move(business_connection_id), dialog_id, + std::move(input_reply_to), disable_notification, protect_content, + effect_id, std::move(message_reply_markup), std::move(input_content)); + + do_send_message(std::move(message), std::move(promise)); +} + +void BusinessConnectionManager::do_send_message(unique_ptr &&message, + Promise> &&promise) { + LOG(INFO) << "Send business message to " << message->dialog_id_; + + const auto *content = message->content_.get(); + CHECK(content != nullptr); + auto content_type = content->get_type(); + if (content_type == MessageContentType::Text) { + auto input_media = get_message_content_input_media_web_page(td_, content); + if (input_media == nullptr) { + td_->create_handler(std::move(promise))->send(std::move(message)); + } else { + td_->create_handler(std::move(promise))->send(std::move(message), std::move(input_media)); + } + return; + } + + auto input_media = get_input_media(content, td_, message->ttl_, message->send_emoji_, td_->auth_manager_->is_bot()); + if (input_media != nullptr) { + td_->create_handler(std::move(promise))->send(std::move(message), std::move(input_media)); + return; + } + if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll || + content_type == MessageContentType::Story) { + return promise.set_error(Status::Error(400, "Message has no file")); + } + upload_media(std::move(message), 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()); + } + auto message_input_media = result.move_as_ok(); + send_closure(actor_id, &BusinessConnectionManager::complete_send_media, + std::move(message_input_media.message_), std::move(message_input_media.input_media_), + std::move(promise)); + })); +} + +void BusinessConnectionManager::process_sent_business_message( + telegram_api::object_ptr &&updates_ptr, + Promise> &&promise) { + if (updates_ptr->get_id() != telegram_api::updates::ID) { + LOG(ERROR) << "Receive " << to_string(updates_ptr); + return promise.set_error(Status::Error(500, "Receive invalid business connection messages")); + } + auto updates = telegram_api::move_object_as(updates_ptr); + if (updates->updates_.size() != 1 || + updates->updates_[0]->get_id() != telegram_api::updateBotNewBusinessMessage::ID) { + LOG(ERROR) << "Receive " << to_string(updates); + return promise.set_error(Status::Error(500, "Receive invalid business connection messages")); + } + auto update = telegram_api::move_object_as(updates->updates_[0]); + + td_->user_manager_->on_get_users(std::move(updates->users_), "SendBusinessMediaQuery"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "SendBusinessMediaQuery"); + + promise.set_value(td_->messages_manager_->get_business_message_object(std::move(update->message_), + std::move(update->reply_to_message_))); +} + +FileId BusinessConnectionManager::get_message_file_id(const unique_ptr &message) { + CHECK(message != nullptr); + return get_message_content_any_file_id( + message->content_.get()); // any_file_id, because it could be a photo sent by ID +} + +FileId BusinessConnectionManager::get_message_thumbnail_file_id(const unique_ptr &message, + FileId file_id) const { + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (get_main_file_type(file_view.get_type()) == FileType::Photo) { + return FileId(); + } + CHECK(message != nullptr); + return get_message_content_thumbnail_file_id(message->content_.get(), td_); +} + +void BusinessConnectionManager::upload_media(unique_ptr &&message, Promise &&promise, + vector bad_parts) { + auto file_id = get_message_file_id(message); + CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (file_view.is_encrypted()) { + return promise.set_error(Status::Error(400, "Can't use encrypted file")); + } + if (file_view.has_remote_location() && file_view.main_remote_location().is_web()) { + return promise.set_error(Status::Error(400, "Can't use a web file")); + } + + BeingUploadedMedia media; + media.message_ = std::move(message); + media.promise_ = std::move(promise); + + if (!file_view.has_remote_location() && file_view.has_url()) { + return do_upload_media(std::move(media), nullptr); + } + + LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; + CHECK(file_id.is_valid()); + bool is_inserted = being_uploaded_files_.emplace(file_id, std::move(media)).second; + CHECK(is_inserted); + // need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_ + // and to send is_uploading_active == true in the updates + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, 0); +} + +void BusinessConnectionManager::complete_send_media(unique_ptr &&message, + telegram_api::object_ptr &&input_media, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(message != nullptr); + CHECK(input_media != nullptr); + td_->create_handler(std::move(promise))->send(std::move(message), std::move(input_media)); +} + +void BusinessConnectionManager::on_upload_media(FileId file_id, + telegram_api::object_ptr input_file) { + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_files_.find(file_id); + CHECK(it != being_uploaded_files_.end()); + auto being_uploaded_media = std::move(it->second); + being_uploaded_files_.erase(it); + + being_uploaded_media.input_file_ = std::move(input_file); + auto thumbnail_file_id = get_message_thumbnail_file_id(being_uploaded_media.message_, file_id); + if (being_uploaded_media.input_file_ != nullptr && thumbnail_file_id.is_valid()) { + // TODO: download thumbnail if needed (like in secret chats) + LOG(INFO) << "Ask to upload thumbnail " << thumbnail_file_id; + bool is_inserted = being_uploaded_thumbnails_.emplace(thumbnail_file_id, std::move(being_uploaded_media)).second; + CHECK(is_inserted); + td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 1, 0); + } else { + do_upload_media(std::move(being_uploaded_media), nullptr); + } +} + +void BusinessConnectionManager::on_upload_media_error(FileId file_id, Status status) { + CHECK(status.is_error()); + + auto it = being_uploaded_files_.find(file_id); + CHECK(it != being_uploaded_files_.end()); + auto being_uploaded_media = std::move(it->second); + being_uploaded_files_.erase(it); + + being_uploaded_media.promise_.set_error(std::move(status)); +} + +void BusinessConnectionManager::on_upload_thumbnail( + FileId thumbnail_file_id, telegram_api::object_ptr thumbnail_input_file) { + LOG(INFO) << "Thumbnail " << thumbnail_file_id << " has been uploaded as " << to_string(thumbnail_input_file); + + auto it = being_uploaded_thumbnails_.find(thumbnail_file_id); + CHECK(it != being_uploaded_thumbnails_.end()); + auto being_uploaded_media = std::move(it->second); + being_uploaded_thumbnails_.erase(it); + + if (thumbnail_input_file == nullptr) { + delete_message_content_thumbnail(being_uploaded_media.message_->content_.get(), td_); + } + + do_upload_media(std::move(being_uploaded_media), std::move(thumbnail_input_file)); +} + +void BusinessConnectionManager::do_upload_media(BeingUploadedMedia &&being_uploaded_media, + telegram_api::object_ptr input_thumbnail) { + auto file_id = get_message_file_id(being_uploaded_media.message_); + auto thumbnail_file_id = get_message_thumbnail_file_id(being_uploaded_media.message_, file_id); + auto input_file = std::move(being_uploaded_media.input_file_); + bool have_input_file = input_file != nullptr; + bool have_input_thumbnail = input_thumbnail != nullptr; + LOG(INFO) << "Do upload media file " << file_id << " with thumbnail " << thumbnail_file_id + << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail; + + const auto *message = being_uploaded_media.message_.get(); + auto input_media = get_input_media(message->content_.get(), td_, std::move(input_file), std::move(input_thumbnail), + file_id, thumbnail_file_id, message->ttl_, message->send_emoji_, true); + CHECK(input_media != nullptr); + auto input_media_id = input_media->get_id(); + if (input_media_id == telegram_api::inputMediaDocument::ID || input_media_id == telegram_api::inputMediaPhoto::ID) { + // can use input media directly + UploadMediaResult result; + result.message_ = std::move(being_uploaded_media.message_); + result.input_media_ = std::move(input_media); + return being_uploaded_media.promise_.set_value(std::move(result)); + } + + switch (input_media->get_id()) { + case telegram_api::inputMediaUploadedDocument::ID: + if (message->content_->get_type() != MessageContentType::Animation) { + static_cast(input_media.get())->flags_ |= + telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; + } + // fallthrough + case telegram_api::inputMediaUploadedPhoto::ID: + case telegram_api::inputMediaDocumentExternal::ID: + case telegram_api::inputMediaPhotoExternal::ID: + td_->create_handler(std::move(being_uploaded_media.promise_)) + ->send(std::move(being_uploaded_media.message_), std::move(input_media)); + break; + default: + LOG(ERROR) << "Have wrong input media " << to_string(input_media); + being_uploaded_media.promise_.set_error(Status::Error(400, "Invalid input media")); + } +} + +void BusinessConnectionManager::complete_upload_media(unique_ptr &&message, + telegram_api::object_ptr &&media, + Promise &&promise) { + auto *content = message->content_.get(); + auto *caption = get_message_content_caption(content); + auto has_spoiler = get_message_content_has_spoiler(content); + auto new_content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), + td_->dialog_manager_->get_my_dialog_id(), G()->unix_time(), false, UserId(), + nullptr, nullptr, "complete_upload_media"); + set_message_content_has_spoiler(new_content.get(), has_spoiler); + + bool is_content_changed = false; + bool need_update = false; + + unique_ptr &old_content = message->content_; + MessageContentType old_content_type = old_content->get_type(); + MessageContentType new_content_type = new_content->get_type(); + + auto old_file_id = get_message_file_id(message); + if (old_content_type != new_content_type) { + need_update = true; + + td_->file_manager_->try_merge_documents(old_file_id, get_message_content_any_file_id(new_content.get())); + } else { + merge_message_contents(td_, old_content.get(), new_content.get(), false, DialogId(), true, is_content_changed, + need_update); + compare_message_contents(td_, old_content.get(), new_content.get(), is_content_changed, need_update); + } + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, old_file_id); + + if (is_content_changed || need_update) { + old_content = std::move(new_content); + update_message_content_file_id_remote(old_content.get(), old_file_id); + } else { + update_message_content_file_id_remote(old_content.get(), get_message_content_any_file_id(new_content.get())); + } + + auto input_media = get_input_media(message->content_.get(), td_, message->ttl_, message->send_emoji_, true); + if (input_media == nullptr) { + return promise.set_error(Status::Error(400, "Failed to upload file")); + } + UploadMediaResult result; + result.message_ = std::move(message); + result.input_media_ = std::move(input_media); + promise.set_value(std::move(result)); +} + +void BusinessConnectionManager::send_message_album( + BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, bool disable_notification, bool protect_content, + int64 effect_id, vector> &&input_message_contents, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, check_business_connection(business_connection_id, dialog_id)); + + vector message_contents; + for (auto &input_message_content : input_message_contents) { + TRY_RESULT_PROMISE(promise, message_content, process_input_message_content(std::move(input_message_content))); + message_contents.push_back(std::move(message_content)); + } + TRY_STATUS_PROMISE(promise, check_message_group_message_contents(message_contents)); + + auto input_reply_to = create_business_message_input_reply_to(std::move(reply_to)); + + 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.promise_ = std::move(promise); + + for (size_t media_pos = 0; media_pos < message_contents.size(); media_pos++) { + auto &message_content = message_contents[media_pos]; + auto message = + create_business_message_to_send(business_connection_id, dialog_id, input_reply_to.clone(), disable_notification, + protect_content, effect_id, nullptr, std::move(message_content)); + auto input_media = get_input_media(message->content_.get(), td_, message->ttl_, message->send_emoji_, + td_->auth_manager_->is_bot()); + if (input_media != nullptr) { + auto file_id = get_message_file_id(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(message); + result.input_media_ = std::move(input_media); + on_upload_message_album_media(request_id, media_pos, std::move(result)); + continue; + } + } + upload_media(std::move(message), PromiseCreator::lambda([actor_id = actor_id(this), request_id, + media_pos](Result &&result) mutable { + send_closure(actor_id, &BusinessConnectionManager::on_upload_message_album_media, request_id, + media_pos, std::move(result)); + })); + } +} + +void BusinessConnectionManager::on_upload_message_album_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 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 promise = std::move(request.promise_); + media_group_send_requests_.erase(it); + + for (auto &r_upload_result : upload_results) { + if (r_upload_result.is_error()) { + return promise.set_error(r_upload_result.move_as_error()); + } + } + vector> messages; + vector> input_single_media; + for (auto &r_upload_result : upload_results) { + auto upload_result = r_upload_result.move_as_ok(); + auto message = std::move(upload_result.message_); + int32 flags = 0; + const FormattedText *caption = get_message_content_text(message->content_.get()); + auto entities = get_input_message_entities(td_->user_manager_.get(), caption, "on_upload_message_album_media"); + if (!entities.empty()) { + flags |= telegram_api::inputSingleMedia::ENTITIES_MASK; + } + input_single_media.push_back(telegram_api::make_object( + flags, std::move(upload_result.input_media_), message->random_id_, + caption == nullptr ? string() : caption->text, std::move(entities))); + messages.push_back(std::move(message)); + } + + td_->create_handler(std::move(promise)) + ->send(std::move(messages), std::move(input_single_media)); +} + +void BusinessConnectionManager::process_sent_business_message_album( + telegram_api::object_ptr &&updates_ptr, + Promise> &&promise) { + if (updates_ptr->get_id() != telegram_api::updates::ID) { + LOG(ERROR) << "Receive " << to_string(updates_ptr); + return promise.set_error(Status::Error(500, "Receive invalid business connection messages")); + } + auto updates = telegram_api::move_object_as(updates_ptr); + for (auto &update : updates->updates_) { + if (update->get_id() != telegram_api::updateBotNewBusinessMessage::ID) { + LOG(ERROR) << "Receive " << to_string(updates); + return promise.set_error(Status::Error(500, "Receive invalid business connection messages")); + } + } + td_->user_manager_->on_get_users(std::move(updates->users_), "process_sent_business_message_album"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "process_sent_business_message_album"); + + auto messages = td_api::make_object(); + for (auto &update_ptr : updates->updates_) { + auto update = telegram_api::move_object_as(update_ptr); + messages->messages_.push_back(td_->messages_manager_->get_business_message_object( + std::move(update->message_), std::move(update->reply_to_message_))); + } + promise.set_value(std::move(messages)); +} + +td_api::object_ptr BusinessConnectionManager::get_update_business_connection( + const BusinessConnection *connection) const { + return td_api::make_object(connection->get_business_connection_object(td_)); +} + +void BusinessConnectionManager::get_current_state(vector> &updates) const { + business_connections_.foreach([&](const BusinessConnectionId &business_connection_id, + const unique_ptr &business_connection) { + updates.push_back(get_update_business_connection(business_connection.get())); + }); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h new file mode 100644 index 00000000..daaafa25 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessConnectionManager.h @@ -0,0 +1,180 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/BusinessConnectionId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/MessageInputReplyTo.h" +#include "td/telegram/net/DcId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" +#include "td/utils/WaitFreeHashMap.h" + +#include + +namespace td { + +struct InputMessageContent; +struct ReplyMarkup; +class Td; + +class BusinessConnectionManager final : public Actor { + public: + BusinessConnectionManager(Td *td, ActorShared<> parent); + BusinessConnectionManager(const BusinessConnectionManager &) = delete; + BusinessConnectionManager &operator=(const BusinessConnectionManager &) = delete; + BusinessConnectionManager(BusinessConnectionManager &&) = delete; + BusinessConnectionManager &operator=(BusinessConnectionManager &&) = delete; + ~BusinessConnectionManager() final; + + Status check_business_connection(const BusinessConnectionId &connection_id, DialogId dialog_id) const; + + DcId get_business_connection_dc_id(const BusinessConnectionId &connection_id) const; + + void on_update_bot_business_connect(telegram_api::object_ptr &&connection); + + void on_update_bot_new_business_message(const BusinessConnectionId &connection_id, + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message); + + void on_update_bot_edit_business_message(const BusinessConnectionId &connection_id, + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message); + + void on_update_bot_delete_business_messages(const BusinessConnectionId &connection_id, DialogId dialog_id, + vector &&messages); + + void get_business_connection(const BusinessConnectionId &connection_id, + Promise> &&promise); + + void send_message(BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, bool disable_notification, + bool protect_content, int64 effect_id, td_api::object_ptr &&reply_markup, + td_api::object_ptr &&input_message_content, + Promise> &&promise); + + void send_message_album(BusinessConnectionId business_connection_id, DialogId dialog_id, + td_api::object_ptr &&reply_to, bool disable_notification, + bool protect_content, int64 effect_id, + vector> &&input_message_contents, + Promise> &&promise); + + void get_current_state(vector> &updates) const; + + private: + struct BusinessConnection; + struct PendingMessage; + class SendBusinessMessageQuery; + class SendBusinessMediaQuery; + class SendBusinessMultiMediaQuery; + class UploadBusinessMediaQuery; + class UploadMediaCallback; + class UploadThumbnailCallback; + + struct UploadMediaResult { + unique_ptr message_; + telegram_api::object_ptr input_media_; + }; + + struct BeingUploadedMedia { + unique_ptr message_; + telegram_api::object_ptr input_file_; + Promise promise_; + }; + + struct MediaGroupSendRequest { + size_t finished_count_ = 0; + vector> upload_results_; + Promise> promise_; + }; + + void tear_down() final; + + void on_get_business_connection(const BusinessConnectionId &connection_id, + Result> r_updates); + + MessageInputReplyTo create_business_message_input_reply_to( + td_api::object_ptr &&reply_to); + + Result process_input_message_content( + td_api::object_ptr &&input_message_content); + + unique_ptr create_business_message_to_send(BusinessConnectionId business_connection_id, + DialogId dialog_id, MessageInputReplyTo &&input_reply_to, + bool disable_notification, bool protect_content, + int64 effect_id, unique_ptr &&reply_markup, + InputMessageContent &&input_content) const; + + void do_send_message(unique_ptr &&message, + Promise> &&promise); + + void process_sent_business_message(telegram_api::object_ptr &&updates_ptr, + Promise> &&promise); + + static FileId get_message_file_id(const unique_ptr &message); + + FileId get_message_thumbnail_file_id(const unique_ptr &message, FileId file_id) const; + + void upload_media(unique_ptr &&message, Promise &&promise, + vector bad_parts = {}); + + void complete_send_media(unique_ptr &&message, + telegram_api::object_ptr &&input_media, + Promise> &&promise); + + void on_upload_media(FileId file_id, telegram_api::object_ptr input_file); + + void on_upload_media_error(FileId file_id, Status status); + + void on_upload_thumbnail(FileId thumbnail_file_id, + telegram_api::object_ptr thumbnail_input_file); + + void do_upload_media(BeingUploadedMedia &&being_uploaded_media, + telegram_api::object_ptr input_thumbnail); + + void complete_upload_media(unique_ptr &&message, + telegram_api::object_ptr &&media, + Promise &&promise); + + int64 generate_new_media_album_id(); + + void on_upload_message_album_media(int64 request_id, size_t media_pos, Result &&result); + + void process_sent_business_message_album(telegram_api::object_ptr &&updates_ptr, + Promise> &&promise); + + td_api::object_ptr get_update_business_connection( + const BusinessConnection *connection) const; + + WaitFreeHashMap, BusinessConnectionIdHash> business_connections_; + + FlatHashMap>>, + BusinessConnectionIdHash> + get_business_connection_queries_; + + int64 current_media_group_send_request_id_ = 0; + FlatHashMap media_group_send_requests_; + + std::shared_ptr upload_media_callback_; + std::shared_ptr upload_thumbnail_callback_; + + FlatHashMap being_uploaded_files_; + FlatHashMap being_uploaded_thumbnails_; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.cpp b/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.cpp index 59393d96..e34f3afe 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.cpp @@ -6,6 +6,8 @@ // #include "td/telegram/BusinessGreetingMessage.h" +#include "td/telegram/Dependencies.h" + #include "td/utils/misc.h" namespace td { @@ -30,7 +32,7 @@ BusinessGreetingMessage::BusinessGreetingMessage( return; } shortcut_id_ = QuickReplyShortcutId(greeting_message->shortcut_id_); - recipients_ = BusinessRecipients(std::move(greeting_message->recipients_)); + recipients_ = BusinessRecipients(std::move(greeting_message->recipients_), false); inactivity_days_ = inactivity_days; } @@ -49,6 +51,10 @@ BusinessGreetingMessage::get_input_business_greeting_message(Td *td) const { shortcut_id_.get(), recipients_.get_input_business_recipients(td), inactivity_days_); } +void BusinessGreetingMessage::add_dependencies(Dependencies &dependencies) const { + recipients_.add_dependencies(dependencies); +} + bool operator==(const BusinessGreetingMessage &lhs, const BusinessGreetingMessage &rhs) { return lhs.shortcut_id_ == rhs.shortcut_id_ && lhs.recipients_ == rhs.recipients_ && lhs.inactivity_days_ == rhs.inactivity_days_; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.h b/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.h index f1919f57..adffaf2b 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessGreetingMessage.h @@ -16,6 +16,7 @@ namespace td { +class Dependencies; class Td; class BusinessGreetingMessage { @@ -40,6 +41,8 @@ class BusinessGreetingMessage { return shortcut_id_.is_server(); } + void add_dependencies(Dependencies &dependencies) const; + template void store(StorerT &storer) const; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessInfo.cpp b/lib/tgchat/ext/td/td/telegram/BusinessInfo.cpp index de9723fd..be0801e8 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessInfo.cpp @@ -6,16 +6,22 @@ // #include "td/telegram/BusinessInfo.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/Global.h" + namespace td { td_api::object_ptr BusinessInfo::get_business_info_object(Td *td) const { if (is_empty()) { return nullptr; } - return td_api::make_object(location_.get_business_location_object(), - work_hours_.get_business_opening_hours_object(), - greeting_message_.get_business_greeting_message_settings_object(td), - away_message_.get_business_away_message_settings_object(td)); + auto unix_time = G()->unix_time(); + return td_api::make_object( + location_.get_business_location_object(), work_hours_.get_business_opening_hours_object(), + work_hours_.get_local_business_opening_hours_object(td), work_hours_.get_next_open_close_in(td, unix_time, false), + work_hours_.get_next_open_close_in(td, unix_time, true), + greeting_message_.get_business_greeting_message_settings_object(td), + away_message_.get_business_away_message_settings_object(td), intro_.get_business_start_page_object(td)); } bool BusinessInfo::is_empty_location(const DialogLocation &location) { @@ -24,7 +30,7 @@ bool BusinessInfo::is_empty_location(const DialogLocation &location) { bool BusinessInfo::is_empty() const { return is_empty_location(location_) && work_hours_.is_empty() && away_message_.is_empty() && - greeting_message_.is_empty(); + greeting_message_.is_empty() && intro_.is_empty(); } bool BusinessInfo::set_location(unique_ptr &business_info, DialogLocation &&location) { @@ -84,4 +90,27 @@ bool BusinessInfo::set_greeting_message(unique_ptr &business_info, return false; } +bool BusinessInfo::set_intro(unique_ptr &business_info, BusinessIntro &&intro) { + if (business_info == nullptr) { + if (intro.is_empty()) { + return false; + } + business_info = make_unique(); + } + if (business_info->intro_ != intro) { + business_info->intro_ = std::move(intro); + return true; + } + return false; +} + +void BusinessInfo::add_dependencies(Dependencies &dependencies) const { + away_message_.add_dependencies(dependencies); + greeting_message_.add_dependencies(dependencies); +} + +vector BusinessInfo::get_file_ids(const Td *td) const { + return intro_.get_file_ids(td); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessInfo.h b/lib/tgchat/ext/td/td/telegram/BusinessInfo.h index 4750be32..b4611c26 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessInfo.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessInfo.h @@ -8,14 +8,17 @@ #include "td/telegram/BusinessAwayMessage.h" #include "td/telegram/BusinessGreetingMessage.h" +#include "td/telegram/BusinessIntro.h" #include "td/telegram/BusinessWorkHours.h" #include "td/telegram/DialogLocation.h" +#include "td/telegram/files/FileId.h" #include "td/telegram/td_api.h" #include "td/utils/common.h" namespace td { +class Dependencies; class Td; class BusinessInfo { @@ -32,6 +35,12 @@ class BusinessInfo { static bool set_greeting_message(unique_ptr &business_info, BusinessGreetingMessage &&greeting_message); + static bool set_intro(unique_ptr &business_info, BusinessIntro &&intro); + + void add_dependencies(Dependencies &dependencies) const; + + vector get_file_ids(const Td *td) const; + template void store(StorerT &storer) const; @@ -45,6 +54,7 @@ class BusinessInfo { BusinessWorkHours work_hours_; BusinessAwayMessage away_message_; BusinessGreetingMessage greeting_message_; + BusinessIntro intro_; }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessInfo.hpp b/lib/tgchat/ext/td/td/telegram/BusinessInfo.hpp index 390ba313..c59764d6 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessInfo.hpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessInfo.hpp @@ -10,6 +10,7 @@ #include "td/telegram/BusinessAwayMessage.hpp" #include "td/telegram/BusinessGreetingMessage.hpp" +#include "td/telegram/BusinessIntro.hpp" #include "td/telegram/BusinessWorkHours.hpp" #include "td/utils/common.h" @@ -23,11 +24,13 @@ void BusinessInfo::store(StorerT &storer) const { bool has_work_hours = !work_hours_.is_empty(); bool has_away_message = away_message_.is_valid(); bool has_greeting_message = greeting_message_.is_valid(); + bool has_intro = !intro_.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_location); STORE_FLAG(has_work_hours); STORE_FLAG(has_away_message); STORE_FLAG(has_greeting_message); + STORE_FLAG(has_intro); END_STORE_FLAGS(); if (has_location) { td::store(location_, storer); @@ -41,6 +44,9 @@ void BusinessInfo::store(StorerT &storer) const { if (has_greeting_message) { td::store(greeting_message_, storer); } + if (has_intro) { + td::store(intro_, storer); + } } template @@ -49,11 +55,13 @@ void BusinessInfo::parse(ParserT &parser) { bool has_work_hours; bool has_away_message; bool has_greeting_message; + bool has_intro; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_location); PARSE_FLAG(has_work_hours); PARSE_FLAG(has_away_message); PARSE_FLAG(has_greeting_message); + PARSE_FLAG(has_intro); END_PARSE_FLAGS(); if (has_location) { td::parse(location_, parser); @@ -67,6 +75,9 @@ void BusinessInfo::parse(ParserT &parser) { if (has_greeting_message) { td::parse(greeting_message_, parser); } + if (has_intro) { + td::parse(intro_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessIntro.cpp b/lib/tgchat/ext/td/td/telegram/BusinessIntro.cpp new file mode 100644 index 00000000..a276e2d8 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessIntro.cpp @@ -0,0 +1,94 @@ +// +// 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/BusinessIntro.h" + +#include "td/telegram/DialogId.h" +#include "td/telegram/Document.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" +#include "td/telegram/misc.h" +#include "td/telegram/StickerFormat.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/StickerType.h" +#include "td/telegram/Td.h" + +namespace td { + +BusinessIntro::BusinessIntro(Td *td, telegram_api::object_ptr intro) { + if (intro == nullptr) { + return; + } + if (!clean_input_string(intro->title_)) { + intro->title_.clear(); + } + if (!clean_input_string(intro->description_)) { + intro->description_.clear(); + } + title_ = std::move(intro->title_); + description_ = std::move(intro->description_); + sticker_file_id_ = + td->stickers_manager_->on_get_sticker_document(std::move(intro->sticker_), StickerFormat::Unknown).second; +} + +BusinessIntro::BusinessIntro(Td *td, td_api::object_ptr intro) { + if (intro == nullptr) { + return; + } + title_ = std::move(intro->title_); + description_ = std::move(intro->message_); + auto r_file_id = td->file_manager_->get_input_file_id(FileType::Sticker, intro->sticker_, DialogId(), true, false); + auto file_id = r_file_id.is_ok() ? r_file_id.move_as_ok() : FileId(); + if (file_id.is_valid()) { + auto file_view = td->file_manager_->get_file_view(file_id); + if (!file_view.has_remote_location() || !file_view.main_remote_location().is_document() || + file_view.main_remote_location().is_web() || + td->stickers_manager_->get_sticker_type(file_id) == StickerType::CustomEmoji) { + file_id = FileId(); + } + } + sticker_file_id_ = file_id; +} + +td_api::object_ptr BusinessIntro::get_business_start_page_object(Td *td) const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(title_, description_, + td->stickers_manager_->get_sticker_object(sticker_file_id_)); +} + +telegram_api::object_ptr BusinessIntro::get_input_business_intro(Td *td) const { + int32 flags = 0; + telegram_api::object_ptr input_document; + if (sticker_file_id_.is_valid()) { + auto file_view = td->file_manager_->get_file_view(sticker_file_id_); + input_document = file_view.main_remote_location().as_input_document(); + flags |= telegram_api::inputBusinessIntro::STICKER_MASK; + } + + return telegram_api::make_object(flags, title_, description_, + std::move(input_document)); +} + +vector BusinessIntro::get_file_ids(const Td *td) const { + if (!sticker_file_id_.is_valid()) { + return {}; + } + return Document(Document::Type::Sticker, sticker_file_id_).get_file_ids(td); +} + +bool operator==(const BusinessIntro &lhs, const BusinessIntro &rhs) { + return lhs.title_ == rhs.title_ && lhs.description_ == rhs.description_ && + lhs.sticker_file_id_ == rhs.sticker_file_id_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessIntro &intro) { + return string_builder << "business intro " << intro.title_ << '|' << intro.description_ << '|' + << intro.sticker_file_id_; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessIntro.h b/lib/tgchat/ext/td/td/telegram/BusinessIntro.h new file mode 100644 index 00000000..17e3dfa0 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessIntro.h @@ -0,0 +1,62 @@ +// +// 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/files/FileId.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 BusinessIntro { + public: + BusinessIntro() = default; + + BusinessIntro(Td *td, telegram_api::object_ptr intro); + + BusinessIntro(Td *td, td_api::object_ptr intro); + + td_api::object_ptr get_business_start_page_object(Td *td) const; + + telegram_api::object_ptr get_input_business_intro(Td *td) const; + + bool is_empty() const { + return title_.empty() && description_.empty() && !sticker_file_id_.is_valid(); + } + + vector get_file_ids(const Td *td) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + + private: + string title_; + string description_; + FileId sticker_file_id_; + + friend bool operator==(const BusinessIntro &lhs, const BusinessIntro &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const BusinessIntro &intro); +}; + +bool operator==(const BusinessIntro &lhs, const BusinessIntro &rhs); + +inline bool operator!=(const BusinessIntro &lhs, const BusinessIntro &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const BusinessIntro &intro); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessIntro.hpp b/lib/tgchat/ext/td/td/telegram/BusinessIntro.hpp new file mode 100644 index 00000000..78367c1d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/BusinessIntro.hpp @@ -0,0 +1,63 @@ +// +// 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/BusinessIntro.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/StickersManager.hpp" +#include "td/telegram/Td.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void BusinessIntro::store(StorerT &storer) const { + bool has_title = !title_.empty(); + bool has_description = !description_.empty(); + bool has_sticker_file_id = sticker_file_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_title); + STORE_FLAG(has_description); + STORE_FLAG(has_sticker_file_id); + END_STORE_FLAGS(); + if (has_title) { + td::store(title_, storer); + } + if (has_description) { + td::store(description_, storer); + } + if (has_sticker_file_id) { + StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); + stickers_manager->store_sticker(sticker_file_id_, false, storer, "BusinessIntro"); + } +} + +template +void BusinessIntro::parse(ParserT &parser) { + bool has_title; + bool has_description; + bool has_sticker_file_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_title); + PARSE_FLAG(has_description); + PARSE_FLAG(has_sticker_file_id); + END_PARSE_FLAGS(); + if (has_title) { + td::parse(title_, parser); + } + if (has_description) { + td::parse(description_, parser); + } + if (has_sticker_file_id) { + StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); + sticker_file_id_ = stickers_manager->parse_sticker(false, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp b/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp index 1b7804d7..9afa34c8 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessManager.cpp @@ -6,17 +6,25 @@ // #include "td/telegram/BusinessManager.h" +#include "td/telegram/AccessRights.h" #include "td/telegram/BusinessAwayMessage.h" +#include "td/telegram/BusinessChatLink.h" #include "td/telegram/BusinessConnectedBot.h" #include "td/telegram/BusinessGreetingMessage.h" +#include "td/telegram/BusinessIntro.h" #include "td/telegram/BusinessRecipients.h" #include "td/telegram/BusinessWorkHours.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogLocation.h" +#include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" +#include "td/telegram/InputBusinessChatLink.h" +#include "td/telegram/MessageEntity.h" +#include "td/telegram/MessagesManager.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" @@ -45,7 +53,7 @@ class GetConnectedBotsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetConnectedBotsQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetConnectedBotsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetConnectedBotsQuery"); if (result->connected_bots_.size() > 1u) { return on_error(Status::Error(500, "Receive invalid response")); } @@ -78,7 +86,7 @@ class UpdateConnectedBotQuery final : public Td::ResultHandler { } send_query(G()->net_query_creator().create( telegram_api::account_updateConnectedBot(flags, false /*ignored*/, false /*ignored*/, std::move(input_user), - bot.get_recipients().get_input_business_recipients(td_)), + bot.get_recipients().get_input_business_bot_recipients(td_)), {{"me"}})); } @@ -86,7 +94,7 @@ class UpdateConnectedBotQuery final : public Td::ResultHandler { int32 flags = telegram_api::account_updateConnectedBot::DELETED_MASK; send_query(G()->net_query_creator().create( telegram_api::account_updateConnectedBot(flags, false /*ignored*/, false /*ignored*/, std::move(input_user), - BusinessRecipients().get_input_business_recipients(td_)), + BusinessRecipients().get_input_business_bot_recipients(td_)), {{"me"}})); } @@ -98,6 +106,7 @@ class UpdateConnectedBotQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for UpdateConnectedBotQuery: " << to_string(ptr); + td_->messages_manager_->hide_all_business_bot_manager_bars(); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } @@ -106,6 +115,242 @@ class UpdateConnectedBotQuery final : public Td::ResultHandler { } }; +class ToggleConnectedBotPausedQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit ToggleConnectedBotPausedQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, bool is_paused) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::account_toggleConnectedBotPaused(std::move(input_peer), is_paused), {{"me"}, {dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (!result_ptr.ok()) { + LOG(INFO) << "Failed to toggle business bot is paused"; + } + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "ToggleConnectedBotPausedQuery"); + promise_.set_error(std::move(status)); + } +}; + +class DisablePeerConnectedBotQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit DisablePeerConnectedBotQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create(telegram_api::account_disablePeerConnectedBot(std::move(input_peer)), + {{"me"}, {dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (!result_ptr.ok()) { + LOG(INFO) << "Failed to remove business bot"; + } else { + td_->messages_manager_->on_update_dialog_business_bot_removed(dialog_id_); + } + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "DisablePeerConnectedBotQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetBusinessChatLinksQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetBusinessChatLinksQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::account_getBusinessChatLinks(), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetBusinessChatLinksQuery: " << to_string(ptr); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetBusinessChatLinksQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetBusinessChatLinksQuery"); + promise_.set_value( + BusinessChatLinks(td_->user_manager_.get(), std::move(ptr->links_)).get_business_chat_links_object()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class CreateBusinessChatLinkQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CreateBusinessChatLinkQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(InputBusinessChatLink &&link) { + send_query(G()->net_query_creator().create( + telegram_api::account_createBusinessChatLink(link.get_input_business_chat_link(td_->user_manager_.get())), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for CreateBusinessChatLinkQuery: " << to_string(ptr); + promise_.set_value(BusinessChatLink(td_->user_manager_.get(), std::move(ptr)).get_business_chat_link_object()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class EditBusinessChatLinkQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit EditBusinessChatLinkQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &link, InputBusinessChatLink &&input_link) { + send_query( + G()->net_query_creator().create(telegram_api::account_editBusinessChatLink( + link, input_link.get_input_business_chat_link(td_->user_manager_.get())), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for EditBusinessChatLinkQuery: " << to_string(ptr); + promise_.set_value(BusinessChatLink(td_->user_manager_.get(), std::move(ptr)).get_business_chat_link_object()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class DeleteBusinessChatLinkQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit DeleteBusinessChatLinkQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &link) { + send_query(G()->net_query_creator().create(telegram_api::account_deleteBusinessChatLink(link), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ResolveBusinessChatLinkQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit ResolveBusinessChatLinkQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &link) { + send_query(G()->net_query_creator().create(telegram_api::account_resolveBusinessChatLink(link), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ResolveBusinessChatLinkQuery: " << to_string(ptr); + td_->user_manager_->on_get_users(std::move(ptr->users_), "ResolveBusinessChatLinkQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "ResolveBusinessChatLinkQuery"); + + auto text = get_message_text(td_->user_manager_.get(), std::move(ptr->message_), std::move(ptr->entities_), true, + true, 0, false, "ResolveBusinessChatLinkQuery"); + if (text.text[0] == '@') { + text.text = ' ' + text.text; + for (auto &entity : text.entities) { + entity.offset++; + } + } + DialogId dialog_id(ptr->peer_); + if (dialog_id.get_type() != DialogType::User) { + LOG(ERROR) << "Receive " << dialog_id; + return on_error(Status::Error(500, "Receive invalid business chat")); + } + remove_unallowed_entities(td_, text, dialog_id); + td_->dialog_manager_->force_create_dialog(dialog_id, "ResolveBusinessChatLinkQuery"); + + promise_.set_value(td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "businessChatLinkInfo"), + get_formatted_text_object(text, true, -1))); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class UpdateBusinessLocationQuery final : public Td::ResultHandler { Promise promise_; DialogLocation location_; @@ -134,7 +379,7 @@ class UpdateBusinessLocationQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->contacts_manager_->on_update_user_location(td_->contacts_manager_->get_my_id(), std::move(location_)); + td_->user_manager_->on_update_user_location(td_->user_manager_->get_my_id(), std::move(location_)); promise_.set_value(Unit()); } @@ -168,7 +413,7 @@ class UpdateBusinessWorkHoursQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->contacts_manager_->on_update_user_work_hours(td_->contacts_manager_->get_my_id(), std::move(work_hours_)); + td_->user_manager_->on_update_user_work_hours(td_->user_manager_->get_my_id(), std::move(work_hours_)); promise_.set_value(Unit()); } @@ -203,8 +448,7 @@ class UpdateBusinessGreetingMessageQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->contacts_manager_->on_update_user_greeting_message(td_->contacts_manager_->get_my_id(), - std::move(greeting_message_)); + td_->user_manager_->on_update_user_greeting_message(td_->user_manager_->get_my_id(), std::move(greeting_message_)); promise_.set_value(Unit()); } @@ -239,7 +483,42 @@ class UpdateBusinessAwayMessageQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->contacts_manager_->on_update_user_away_message(td_->contacts_manager_->get_my_id(), std::move(away_message_)); + td_->user_manager_->on_update_user_away_message(td_->user_manager_->get_my_id(), std::move(away_message_)); + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateBusinessIntroQuery final : public Td::ResultHandler { + Promise promise_; + BusinessIntro intro_; + + public: + explicit UpdateBusinessIntroQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(BusinessIntro &&intro) { + intro_ = std::move(intro); + int32 flags = 0; + if (!intro_.is_empty()) { + flags |= telegram_api::account_updateBusinessIntro::INTRO_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::account_updateBusinessIntro(flags, intro_.get_input_business_intro(td_)), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_update_user_intro(td_->user_manager_->get_my_id(), std::move(intro_)); promise_.set_value(Unit()); } @@ -266,15 +545,63 @@ void BusinessManager::set_business_connected_bot(td_api::object_ptrcontacts_manager_->get_input_user(connected_bot.get_user_id())); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(connected_bot.get_user_id())); td_->create_handler(std::move(promise))->send(connected_bot, std::move(input_user)); } void BusinessManager::delete_business_connected_bot(UserId bot_user_id, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); td_->create_handler(std::move(promise))->send(std::move(input_user)); } +void BusinessManager::toggle_business_connected_bot_dialog_is_paused(DialogId dialog_id, bool is_paused, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "toggle_business_connected_bot_dialog_is_paused")); + if (dialog_id.get_type() != DialogType::User) { + return promise.set_error(Status::Error(400, "The chat has no connected bot")); + } + td_->messages_manager_->on_update_dialog_business_bot_is_paused(dialog_id, is_paused); + td_->create_handler(std::move(promise))->send(dialog_id, is_paused); +} + +void BusinessManager::remove_business_connected_bot_from_dialog(DialogId dialog_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "remove_business_connected_bot_from_dialog")); + if (dialog_id.get_type() != DialogType::User) { + return promise.set_error(Status::Error(400, "The chat has no connected bot")); + } + td_->messages_manager_->on_update_dialog_business_bot_removed(dialog_id); + td_->create_handler(std::move(promise))->send(dialog_id); +} + +void BusinessManager::get_business_chat_links(Promise> &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +void BusinessManager::create_business_chat_link(td_api::object_ptr &&link_info, + Promise> &&promise) { + td_->create_handler(std::move(promise)) + ->send(InputBusinessChatLink(td_, std::move(link_info))); +} + +void BusinessManager::edit_business_chat_link(const string &link, + td_api::object_ptr &&link_info, + Promise> &&promise) { + td_->create_handler(std::move(promise)) + ->send(link, InputBusinessChatLink(td_, std::move(link_info))); +} + +void BusinessManager::delete_business_chat_link(const string &link, Promise &&promise) { + td_->create_handler(std::move(promise))->send(link); +} + +void BusinessManager::get_business_chat_link_info(const string &link, + Promise> &&promise) { + td_->create_handler(std::move(promise))->send(link); +} + void BusinessManager::set_business_location(DialogLocation &&location, Promise &&promise) { td_->create_handler(std::move(promise))->send(std::move(location)); } @@ -292,4 +619,8 @@ void BusinessManager::set_business_away_message(BusinessAwayMessage &&away_messa td_->create_handler(std::move(promise))->send(std::move(away_message)); } +void BusinessManager::set_business_intro(BusinessIntro &&intro, Promise &&promise) { + td_->create_handler(std::move(promise))->send(std::move(intro)); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessManager.h b/lib/tgchat/ext/td/td/telegram/BusinessManager.h index b47731b7..d915b1f9 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessManager.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessManager.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/DialogId.h" #include "td/telegram/td_api.h" #include "td/telegram/UserId.h" @@ -18,6 +19,7 @@ namespace td { class BusinessAwayMessage; class BusinessGreetingMessage; +class BusinessIntro; class BusinessWorkHours; class DialogLocation; class Td; @@ -32,6 +34,23 @@ class BusinessManager final : public Actor { void delete_business_connected_bot(UserId bot_user_id, Promise &&promise); + void toggle_business_connected_bot_dialog_is_paused(DialogId dialog_id, bool is_paused, Promise &&promise); + + void remove_business_connected_bot_from_dialog(DialogId dialog_id, Promise &&promise); + + void get_business_chat_links(Promise> &&promise); + + void create_business_chat_link(td_api::object_ptr &&link_info, + Promise> &&promise); + + void edit_business_chat_link(const string &link, td_api::object_ptr &&link_info, + Promise> &&promise); + + void delete_business_chat_link(const string &link, Promise &&promise); + + void get_business_chat_link_info(const string &link, + Promise> &&promise); + void set_business_location(DialogLocation &&location, Promise &&promise); void set_business_work_hours(BusinessWorkHours &&work_hours, Promise &&promise); @@ -40,6 +59,8 @@ class BusinessManager final : public Actor { void set_business_away_message(BusinessAwayMessage &&away_message, Promise &&promise); + void set_business_intro(BusinessIntro &&intro, Promise &&promise); + private: void tear_down() final; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.cpp b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.cpp index 275f68fa..dabc8509 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.cpp @@ -6,10 +6,11 @@ // #include "td/telegram/BusinessRecipients.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" @@ -25,7 +26,19 @@ BusinessRecipients::BusinessRecipients(telegram_api::object_ptr recipients) { +BusinessRecipients::BusinessRecipients(telegram_api::object_ptr recipients) + : user_ids_(UserId::get_user_ids(recipients->users_)) + , excluded_user_ids_(UserId::get_user_ids(recipients->exclude_users_)) + , existing_chats_(recipients->existing_chats_) + , new_chats_(recipients->new_chats_) + , contacts_(recipients->contacts_) + , non_contacts_(recipients->non_contacts_) + , exclude_selected_(recipients->exclude_selected_) { + td::remove_if(user_ids_, [](UserId user_id) { return !user_id.is_valid(); }); + td::remove_if(excluded_user_ids_, [](UserId user_id) { return !user_id.is_valid(); }); +} + +BusinessRecipients::BusinessRecipients(td_api::object_ptr recipients, bool allow_excluded) { if (recipients == nullptr) { return; } @@ -35,6 +48,18 @@ BusinessRecipients::BusinessRecipients(td_api::object_ptrexcluded_chat_ids_) { + DialogId dialog_id(chat_id); + if (dialog_id.get_type() == DialogType::User) { + excluded_user_ids_.push_back(dialog_id.get_user_id()); + } + } + if (recipients->exclude_selected_) { + append(user_ids_, std::move(excluded_user_ids_)); + reset_to_empty(excluded_user_ids_); + } + } existing_chats_ = recipients->select_existing_chats_; new_chats_ = recipients->select_new_chats_; contacts_ = recipients->select_contacts_; @@ -50,8 +75,16 @@ td_api::object_ptr BusinessRecipients::get_business_ CHECK(td->dialog_manager_->have_dialog_force(dialog_id, "get_business_recipients_object")); chat_ids.push_back(td->dialog_manager_->get_chat_id_object(dialog_id, "businessRecipients")); } - return td_api::make_object(std::move(chat_ids), existing_chats_, new_chats_, contacts_, - non_contacts_, exclude_selected_); + vector excluded_chat_ids; + for (auto user_id : excluded_user_ids_) { + DialogId dialog_id(user_id); + td->dialog_manager_->force_create_dialog(dialog_id, "get_business_recipients_object", true); + CHECK(td->dialog_manager_->have_dialog_force(dialog_id, "get_business_recipients_object")); + excluded_chat_ids.push_back(td->dialog_manager_->get_chat_id_object(dialog_id, "businessRecipients")); + } + return td_api::make_object(std::move(chat_ids), std::move(excluded_chat_ids), + existing_chats_, new_chats_, contacts_, non_contacts_, + exclude_selected_); } telegram_api::object_ptr BusinessRecipients::get_input_business_recipients( @@ -74,7 +107,7 @@ telegram_api::object_ptr BusinessRecipien } vector> input_users; for (auto user_id : user_ids_) { - auto r_input_user = td->contacts_manager_->get_input_user(user_id); + auto r_input_user = td->user_manager_->get_input_user(user_id); if (r_input_user.is_ok()) { input_users.push_back(r_input_user.move_as_ok()); } @@ -87,9 +120,62 @@ telegram_api::object_ptr BusinessRecipien false /*ignored*/, std::move(input_users)); } +telegram_api::object_ptr +BusinessRecipients::get_input_business_bot_recipients(Td *td) const { + int32 flags = 0; + if (existing_chats_) { + flags |= telegram_api::inputBusinessBotRecipients::EXISTING_CHATS_MASK; + } + if (new_chats_) { + flags |= telegram_api::inputBusinessBotRecipients::NEW_CHATS_MASK; + } + if (contacts_) { + flags |= telegram_api::inputBusinessBotRecipients::CONTACTS_MASK; + } + if (non_contacts_) { + flags |= telegram_api::inputBusinessBotRecipients::NON_CONTACTS_MASK; + } + if (exclude_selected_) { + flags |= telegram_api::inputBusinessBotRecipients::EXCLUDE_SELECTED_MASK; + } + vector> input_users; + for (auto user_id : user_ids_) { + auto r_input_user = td->user_manager_->get_input_user(user_id); + if (r_input_user.is_ok()) { + input_users.push_back(r_input_user.move_as_ok()); + } + } + if (!input_users.empty()) { + flags |= telegram_api::inputBusinessBotRecipients::USERS_MASK; + } + vector> excluded_input_users; + for (auto user_id : excluded_user_ids_) { + auto r_input_user = td->user_manager_->get_input_user(user_id); + if (r_input_user.is_ok()) { + excluded_input_users.push_back(r_input_user.move_as_ok()); + } + } + if (!excluded_input_users.empty()) { + flags |= telegram_api::inputBusinessBotRecipients::EXCLUDE_USERS_MASK; + } + return telegram_api::make_object( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + std::move(input_users), std::move(excluded_input_users)); +} + +void BusinessRecipients::add_dependencies(Dependencies &dependencies) const { + for (auto user_id : user_ids_) { + dependencies.add(user_id); + } + for (auto user_id : excluded_user_ids_) { + dependencies.add(user_id); + } +} + bool operator==(const BusinessRecipients &lhs, const BusinessRecipients &rhs) { - return lhs.user_ids_ == rhs.user_ids_ && lhs.existing_chats_ == rhs.existing_chats_ && - lhs.new_chats_ == rhs.new_chats_ && lhs.contacts_ == rhs.contacts_ && lhs.non_contacts_ == rhs.non_contacts_ && + return lhs.user_ids_ == rhs.user_ids_ && lhs.excluded_user_ids_ == rhs.excluded_user_ids_ && + lhs.existing_chats_ == rhs.existing_chats_ && lhs.new_chats_ == rhs.new_chats_ && + lhs.contacts_ == rhs.contacts_ && lhs.non_contacts_ == rhs.non_contacts_ && lhs.exclude_selected_ == rhs.exclude_selected_; } diff --git a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.h b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.h index 2141bb8b..36feb55f 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.h @@ -15,6 +15,7 @@ namespace td { +class Dependencies; class Td; class BusinessRecipients { @@ -23,12 +24,18 @@ class BusinessRecipients { explicit BusinessRecipients(telegram_api::object_ptr recipients); - explicit BusinessRecipients(td_api::object_ptr recipients); + explicit BusinessRecipients(telegram_api::object_ptr recipients); + + BusinessRecipients(td_api::object_ptr recipients, bool allow_excluded); td_api::object_ptr get_business_recipients_object(Td *td) const; telegram_api::object_ptr get_input_business_recipients(Td *td) const; + telegram_api::object_ptr get_input_business_bot_recipients(Td *td) const; + + void add_dependencies(Dependencies &dependencies) const; + template void store(StorerT &storer) const; @@ -37,6 +44,7 @@ class BusinessRecipients { private: vector user_ids_; + vector excluded_user_ids_; bool existing_chats_ = false; bool new_chats_ = false; bool contacts_ = false; diff --git a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.hpp b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.hpp index f073a6fe..e37ee444 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessRecipients.hpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessRecipients.hpp @@ -16,6 +16,7 @@ namespace td { template void BusinessRecipients::store(StorerT &storer) const { bool has_user_ids = !user_ids_.empty(); + bool has_excluded_user_ids = !excluded_user_ids_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(existing_chats_); STORE_FLAG(new_chats_); @@ -23,15 +24,20 @@ void BusinessRecipients::store(StorerT &storer) const { STORE_FLAG(non_contacts_); STORE_FLAG(exclude_selected_); STORE_FLAG(has_user_ids); + STORE_FLAG(has_excluded_user_ids); END_STORE_FLAGS(); if (has_user_ids) { td::store(user_ids_, storer); } + if (has_excluded_user_ids) { + td::store(excluded_user_ids_, storer); + } } template void BusinessRecipients::parse(ParserT &parser) { bool has_user_ids; + bool has_excluded_user_ids; BEGIN_PARSE_FLAGS(); PARSE_FLAG(existing_chats_); PARSE_FLAG(new_chats_); @@ -39,10 +45,14 @@ void BusinessRecipients::parse(ParserT &parser) { PARSE_FLAG(non_contacts_); PARSE_FLAG(exclude_selected_); PARSE_FLAG(has_user_ids); + PARSE_FLAG(has_excluded_user_ids); END_PARSE_FLAGS(); if (has_user_ids) { td::parse(user_ids_, parser); } + if (has_excluded_user_ids) { + td::parse(excluded_user_ids_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.cpp b/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.cpp index f4e6a34b..40d4d7ec 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.cpp +++ b/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.cpp @@ -6,9 +6,15 @@ // #include "td/telegram/BusinessWorkHours.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TimeZoneManager.h" + #include "td/utils/algorithm.h" #include "td/utils/format.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" #include @@ -68,6 +74,46 @@ td_api::object_ptr BusinessWorkHours::get_business return td_api::make_object(time_zone_id_, std::move(intervals)); } +td_api::object_ptr BusinessWorkHours::get_local_business_opening_hours_object( + Td *td) const { + if (is_empty() || td->auth_manager_->is_bot()) { + return nullptr; + } + + auto offset = (td->time_zone_manager_->get_time_zone_offset(time_zone_id_) - + narrow_cast(td->option_manager_->get_option_integer("utc_time_offset"))) / + 60; + if (offset == 0) { + return get_business_opening_hours_object(); + } + + BusinessWorkHours local_work_hours; + for (auto &interval : work_hours_) { + auto start_minute = interval.start_minute_ - offset; + auto end_minute = interval.end_minute_ - offset; + if (start_minute < 0) { + if (end_minute <= 24 * 60) { + start_minute += 7 * 24 * 60; + end_minute += 7 * 24 * 60; + } else { + local_work_hours.work_hours_.emplace_back(start_minute + 7 * 24 * 60, 7 * 24 * 60); + start_minute = 0; + } + } else if (end_minute > 8 * 24 * 60) { + if (start_minute >= 7 * 24 * 60) { + start_minute -= 7 * 24 * 60; + end_minute -= 7 * 24 * 60; + } else { + local_work_hours.work_hours_.emplace_back(0, end_minute - 7 * 24 * 60); + end_minute = 7 * 24 * 60; + } + } + local_work_hours.work_hours_.emplace_back(start_minute, end_minute); + } + local_work_hours.sanitize_work_hours(); + return local_work_hours.get_business_opening_hours_object(); +} + telegram_api::object_ptr BusinessWorkHours::get_input_business_work_hours() const { if (is_empty()) { return nullptr; @@ -78,6 +124,31 @@ telegram_api::object_ptr BusinessWorkHours::get })); } +int32 BusinessWorkHours::get_next_open_close_in(Td *td, int32 unix_time, bool is_close) const { + if (is_empty()) { + return 0; + } + auto get_week_time = [](int32 time) { + const auto week_length = 7 * 86400; + return ((time % week_length) + week_length) % week_length; + }; + // the Unix time 0 was on a Thursday, the first Monday was at 4 * 86400 + auto current_week_time = get_week_time(unix_time - 4 * 86400); + auto offset = td->time_zone_manager_->get_time_zone_offset(time_zone_id_); + int32 result = 1000000000; + for (auto &interval : work_hours_) { + auto change_week_time = get_week_time((is_close ? interval.end_minute_ : interval.start_minute_) * 60 - offset); + auto wait_time = change_week_time - current_week_time; + if (wait_time < 0) { + wait_time += 7 * 86400; + } + if (wait_time < result) { + result = wait_time; + } + } + return result; +} + void BusinessWorkHours::sanitize_work_hours() { // remove invalid work hour intervals td::remove_if(work_hours_, [](const WorkHoursInterval &interval) { @@ -139,7 +210,7 @@ void BusinessWorkHours::combine_work_hour_intervals() { auto max_minute = work_hours_[0].start_minute_ + 7 * 24 * 60; if (work_hours_.back().end_minute_ > max_minute || work_hours_.back().start_minute_ >= 7 * 24 * 60) { auto size = work_hours_.size(); - for (size_t i = 1; i < size; i++) { + for (size_t i = 0; i < size; i++) { if (work_hours_[i].start_minute_ >= 7 * 24 * 60) { work_hours_[i].start_minute_ -= 7 * 24 * 60; work_hours_[i].end_minute_ -= 7 * 24 * 60; @@ -148,6 +219,7 @@ void BusinessWorkHours::combine_work_hour_intervals() { work_hours_[i].end_minute_ = max_minute; } } + LOG(INFO) << "Need to normalize " << work_hours_; combine_work_hour_intervals(); } } diff --git a/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.h b/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.h index b386334d..20ddecb0 100644 --- a/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.h +++ b/lib/tgchat/ext/td/td/telegram/BusinessWorkHours.h @@ -14,6 +14,8 @@ namespace td { +class Td; + class BusinessWorkHours { public: BusinessWorkHours() = default; @@ -26,8 +28,12 @@ class BusinessWorkHours { td_api::object_ptr get_business_opening_hours_object() const; + td_api::object_ptr get_local_business_opening_hours_object(Td *td) const; + telegram_api::object_ptr get_input_business_work_hours() const; + int32 get_next_open_close_in(Td *td, int32 unix_time, bool is_close) const; + template void store(StorerT &storer) const; diff --git a/lib/tgchat/ext/td/td/telegram/CallActor.cpp b/lib/tgchat/ext/td/td/telegram/CallActor.cpp index 5c31437f..e0a247aa 100644 --- a/lib/tgchat/ext/td/td/telegram/CallActor.cpp +++ b/lib/tgchat/ext/td/td/telegram/CallActor.cpp @@ -6,7 +6,6 @@ // #include "td/telegram/CallActor.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DhCache.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileManager.h" @@ -19,6 +18,7 @@ #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/as.h" @@ -124,7 +124,8 @@ tl_object_ptr CallState::get_call_state_object() const { case Type::Ready: { auto call_connections = transform(connections, [](auto &c) { return c.get_call_server_object(); }); return make_tl_object(protocol.get_call_protocol_object(), std::move(call_connections), - config, key, vector(emojis_fingerprint), allow_p2p); + config, key, vector(emojis_fingerprint), allow_p2p, + custom_parameters); } case Type::HangingUp: return make_tl_object(); @@ -504,7 +505,7 @@ void CallActor::update_call(tl_object_ptr call) { void CallActor::update_call_inner(tl_object_ptr call) { LOG(INFO) << "Update call with " << to_string(call); - send_closure(G()->contacts_manager(), &ContactsManager::on_get_users, std::move(call->users_), "UpdatePhoneCall"); + send_closure(G()->user_manager(), &UserManager::on_get_users, std::move(call->users_), "UpdatePhoneCall"); update_call(std::move(call->phone_call_)); } @@ -634,6 +635,9 @@ Status CallActor::do_update_call(const telegram_api::phoneCall &call) { } call_state_.protocol = CallProtocol(*call.protocol_); call_state_.allow_p2p = call.p2p_allowed_; + if (call.custom_parameters_ != nullptr) { + call_state_.custom_parameters = std::move(call.custom_parameters_->data_); + } call_state_.type = CallState::Type::Ready; call_state_need_flush_ = true; @@ -920,12 +924,16 @@ void CallActor::flush_call_state() { } call_state_need_flush_ = false; - // TODO can't call const function - // send_closure(G()->contacts_manager(), &ContactsManager::get_user_id_object, user_id_, "flush_call_state"); - send_closure(G()->td(), &Td::send_update, - make_tl_object(make_tl_object( - local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_user_id_.get(), is_outgoing_, - is_video_, call_state_.get_call_state_object()))); + auto peer_id = is_outgoing_ ? user_id_ : call_admin_user_id_; + auto update = td_api::make_object(td_api::make_object( + local_call_id_.get(), 0, is_outgoing_, is_video_, call_state_.get_call_state_object())); + send_closure(G()->user_manager(), &UserManager::get_user_id_object_async, peer_id, + [td_actor = G()->td(), update = std::move(update)](Result r_user_id) mutable { + if (r_user_id.is_ok()) { + update->call_->user_id_ = r_user_id.ok(); + send_closure(td_actor, &Td::send_update, std::move(update)); + } + }); } } diff --git a/lib/tgchat/ext/td/td/telegram/CallActor.h b/lib/tgchat/ext/td/td/telegram/CallActor.h index 083c551b..c97f2104 100644 --- a/lib/tgchat/ext/td/td/telegram/CallActor.h +++ b/lib/tgchat/ext/td/td/telegram/CallActor.h @@ -85,6 +85,7 @@ struct CallState { string key; string config; vector emojis_fingerprint; + string custom_parameters; bool allow_p2p{false}; Status error; diff --git a/lib/tgchat/ext/td/td/telegram/CallbackQueriesManager.cpp b/lib/tgchat/ext/td/td/telegram/CallbackQueriesManager.cpp index d8537665..661583f7 100644 --- a/lib/tgchat/ext/td/td/telegram/CallbackQueriesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/CallbackQueriesManager.cpp @@ -8,7 +8,6 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/InlineQueriesManager.h" @@ -17,6 +16,7 @@ #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/actor/actor.h" @@ -174,7 +174,7 @@ void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id, LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id << " in " << dialog_id; return; } - LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; + LOG_IF(ERROR, !td_->user_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive new callback query"; return; @@ -191,12 +191,11 @@ void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id, } td_->dialog_manager_->force_create_dialog(dialog_id, "on_new_callback_query", true); - send_closure( - G()->td(), &Td::send_update, - td_api::make_object( - callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewCallbackQuery"), - td_->dialog_manager_->get_chat_id_object(dialog_id, "updateNewCallbackQuery"), message_id.get(), - chat_instance, std::move(payload))); + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + callback_query_id, td_->user_manager_->get_user_id_object(sender_user_id, "updateNewCallbackQuery"), + td_->dialog_manager_->get_chat_id_object(dialog_id, "updateNewCallbackQuery"), message_id.get(), + chat_instance, std::move(payload))); } void CallbackQueriesManager::on_new_inline_query( @@ -207,7 +206,7 @@ void CallbackQueriesManager::on_new_inline_query( LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id; return; } - LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; + LOG_IF(ERROR, !td_->user_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive new callback query"; return; @@ -221,7 +220,7 @@ void CallbackQueriesManager::on_new_inline_query( send_closure( G()->td(), &Td::send_update, make_tl_object( - callback_query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"), + callback_query_id, td_->user_manager_->get_user_id_object(sender_user_id, "updateNewInlineCallbackQuery"), InlineQueriesManager::get_inline_message_id(std::move(inline_message_id)), chat_instance, std::move(payload))); } @@ -238,10 +237,8 @@ void CallbackQueriesManager::send_callback_query(MessageFullId message_full_id, } auto dialog_id = message_full_id.get_dialog_id(); - td_->dialog_manager_->have_dialog_force(dialog_id, "send_callback_query"); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "send_callback_query")); if (!td_->messages_manager_->have_message_force(message_full_id, "send_callback_query")) { return promise.set_error(Status::Error(400, "Message not found")); @@ -252,9 +249,6 @@ void CallbackQueriesManager::send_callback_query(MessageFullId message_full_id, if (!message_full_id.get_message_id().is_server()) { return promise.set_error(Status::Error(400, "Bad message identifier")); } - if (dialog_id.get_type() == DialogType::SecretChat) { - return promise.set_error(Status::Error(400, "Secret chat messages can't have callback buttons")); - } if (payload->get_id() == td_api::callbackQueryPayloadDataWithPassword::ID) { auto password = static_cast(payload.get())->password_; @@ -279,10 +273,9 @@ void CallbackQueriesManager::send_get_callback_answer_query( TRY_STATUS_PROMISE(promise, G()->close_status()); auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - if (!td_->messages_manager_->have_message_force(message_full_id, "send_callback_query")) { + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Read)); + if (!td_->messages_manager_->have_message_force(message_full_id, "send_get_callback_answer_query")) { return promise.set_error(Status::Error(400, "Message not found")); } diff --git a/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.cpp b/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.cpp new file mode 100644 index 00000000..c8d0048e --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.cpp @@ -0,0 +1,541 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/ChannelRecommendationManager.h" + +#include "td/telegram/AccessRights.h" +#include "td/telegram/Application.h" +#include "td/telegram/ChatManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" + +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class GetChannelRecommendationsQuery final : public Td::ResultHandler { + Promise>>> promise_; + ChannelId channel_id_; + + public: + explicit GetChannelRecommendationsQuery( + Promise>>> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(!channel_id.is_valid() || input_channel != nullptr); + int32 flags = 0; + if (input_channel != nullptr) { + flags |= telegram_api::channels_getChannelRecommendations::CHANNEL_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::channels_getChannelRecommendations(flags, std::move(input_channel)))); + } + + 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 chats_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + auto total_count = static_cast(chats->chats_.size()); + return promise_.set_value({total_count, std::move(chats->chats_)}); + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + return promise_.set_value({chats->count_, std::move(chats->chats_)}); + } + default: + UNREACHABLE(); + return promise_.set_error(Status::Error("Unreachable")); + } + } + + void on_error(Status status) final { + if (channel_id_.is_valid()) { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +template +void ChannelRecommendationManager::RecommendedDialogs::store(StorerT &storer) const { + bool has_dialog_ids = !dialog_ids_.empty(); + bool has_total_count = static_cast(total_count_) != dialog_ids_.size(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_dialog_ids); + STORE_FLAG(has_total_count); + END_STORE_FLAGS(); + if (has_dialog_ids) { + td::store(dialog_ids_, storer); + } + store_time(next_reload_time_, storer); + if (has_total_count) { + td::store(total_count_, storer); + } +} + +template +void ChannelRecommendationManager::RecommendedDialogs::parse(ParserT &parser) { + bool has_dialog_ids; + bool has_total_count; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_dialog_ids); + PARSE_FLAG(has_total_count); + END_PARSE_FLAGS(); + if (has_dialog_ids) { + td::parse(dialog_ids_, parser); + } + parse_time(next_reload_time_, parser); + if (has_total_count) { + td::parse(total_count_, parser); + } else { + total_count_ = static_cast(dialog_ids_.size()); + } +} + +ChannelRecommendationManager::ChannelRecommendationManager(Td *td, ActorShared<> parent) + : td_(td), parent_(std::move(parent)) { + if (G()->use_sqlite_pmc() && !G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto()); + } +} + +void ChannelRecommendationManager::tear_down() { + parent_.reset(); +} + +bool ChannelRecommendationManager::is_suitable_recommended_channel(DialogId dialog_id) const { + if (dialog_id.get_type() != DialogType::Channel) { + return false; + } + return is_suitable_recommended_channel(dialog_id.get_channel_id()); +} + +bool ChannelRecommendationManager::is_suitable_recommended_channel(ChannelId channel_id) const { + auto status = td_->chat_manager_->get_channel_status(channel_id); + return !status.is_member() && td_->chat_manager_->have_input_peer_channel(channel_id, AccessRights::Read); +} + +bool ChannelRecommendationManager::are_suitable_recommended_dialogs( + const RecommendedDialogs &recommended_dialogs) const { + for (auto recommended_dialog_id : recommended_dialogs.dialog_ids_) { + if (!is_suitable_recommended_channel(recommended_dialog_id)) { + return false; + } + } + auto is_premium = td_->option_manager_->get_option_boolean("is_premium"); + auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast(recommended_dialogs.total_count_); + if (!have_all && is_premium) { + return false; + } + return true; +} + +void ChannelRecommendationManager::get_recommended_channels(Promise> &&promise) { + bool use_database = true; + if (are_recommended_channels_inited_) { + if (are_suitable_recommended_dialogs(recommended_channels_)) { + auto next_reload_time = recommended_channels_.next_reload_time_; + promise.set_value(td_->dialog_manager_->get_chats_object( + recommended_channels_.total_count_, recommended_channels_.dialog_ids_, "get_recommended_channels")); + if (next_reload_time > Time::now()) { + return; + } + promise = {}; + } else { + LOG(INFO) << "Drop cache for recommended chats"; + are_recommended_channels_inited_ = false; + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + } + } + use_database = false; + } + load_recommended_channels(use_database, std::move(promise)); +} + +string ChannelRecommendationManager::get_recommended_channels_database_key() { + return "recommended_channels"; +} + +void ChannelRecommendationManager::load_recommended_channels(bool use_database, + Promise> &&promise) { + get_recommended_channels_queries_.push_back(std::move(promise)); + if (get_recommended_channels_queries_.size() == 1) { + if (G()->use_message_database() && use_database) { + G()->td_db()->get_sqlite_pmc()->get( + get_recommended_channels_database_key(), PromiseCreator::lambda([actor_id = actor_id(this)](string value) { + send_closure(actor_id, &ChannelRecommendationManager::on_load_recommended_channels_from_database, + std::move(value)); + })); + } else { + reload_recommended_channels(); + } + } +} + +void ChannelRecommendationManager::fail_load_recommended_channels_queries(Status &&error) { + CHECK(!get_recommended_channels_queries_.empty()); + fail_promises(get_recommended_channels_queries_, std::move(error)); +} + +void ChannelRecommendationManager::finish_load_recommended_channels_queries(int32 total_count, + vector dialog_ids) { + are_recommended_channels_inited_ = true; + auto promises = std::move(get_recommended_channels_queries_); + CHECK(!promises.empty()); + for (auto &promise : promises) { + if (promise) { + promise.set_value( + td_->dialog_manager_->get_chats_object(total_count, dialog_ids, "finish_load_recommended_channels_queries")); + } + } +} + +void ChannelRecommendationManager::on_load_recommended_channels_from_database(string value) { + if (G()->close_flag()) { + return fail_load_recommended_channels_queries(G()->close_status()); + } + + if (value.empty()) { + return reload_recommended_channels(); + } + if (log_event_parse(recommended_channels_, value).is_error()) { + recommended_channels_ = {}; + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + return reload_recommended_channels(); + } + Dependencies dependencies; + for (auto dialog_id : recommended_channels_.dialog_ids_) { + dependencies.add_dialog_and_dependencies(dialog_id); + } + if (!dependencies.resolve_force(td_, "on_load_recommended_channels_from_database") || + !are_suitable_recommended_dialogs(recommended_channels_)) { + recommended_channels_ = {}; + G()->td_db()->get_sqlite_pmc()->erase(get_recommended_channels_database_key(), Auto()); + return reload_recommended_channels(); + } + + auto next_reload_time = recommended_channels_.next_reload_time_; + finish_load_recommended_channels_queries(recommended_channels_.total_count_, recommended_channels_.dialog_ids_); + + if (next_reload_time <= Time::now()) { + load_recommended_channels(false, Auto()); + } +} + +void ChannelRecommendationManager::reload_recommended_channels() { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this)](Result>>> &&result) { + send_closure(actor_id, &ChannelRecommendationManager::on_get_recommended_channels, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(ChannelId()); +} + +void ChannelRecommendationManager::on_get_recommended_channels( + Result>>> &&r_chats) { + G()->ignore_result_if_closing(r_chats); + + if (r_chats.is_error()) { + return fail_load_recommended_channels_queries(r_chats.move_as_error()); + } + + auto chats = r_chats.move_as_ok(); + auto total_count = chats.first; + auto channel_ids = td_->chat_manager_->get_channel_ids(std::move(chats.second), "on_get_recommended_channels"); + vector dialog_ids; + if (total_count < static_cast(channel_ids.size())) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " recommended chats"; + total_count = static_cast(channel_ids.size()); + } + for (auto recommended_channel_id : channel_ids) { + auto recommended_dialog_id = DialogId(recommended_channel_id); + td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_recommended_channels"); + if (is_suitable_recommended_channel(recommended_channel_id)) { + dialog_ids.push_back(recommended_dialog_id); + } else { + total_count--; + } + } + recommended_channels_.total_count_ = total_count; + recommended_channels_.dialog_ids_ = dialog_ids; + recommended_channels_.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; + + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->set(get_recommended_channels_database_key(), + log_event_store(recommended_channels_).as_slice().str(), Promise()); + } + + finish_load_recommended_channels_queries(total_count, std::move(dialog_ids)); +} + +void ChannelRecommendationManager::get_channel_recommendations( + DialogId dialog_id, bool return_local, Promise> &&chats_promise, + Promise> &&count_promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) { + if (chats_promise) { + chats_promise.set_error(Status::Error(400, "Chat not found")); + } + if (count_promise) { + count_promise.set_error(Status::Error(400, "Chat not found")); + } + return; + } + if (dialog_id.get_type() != DialogType::Channel) { + if (chats_promise) { + chats_promise.set_value(td_api::make_object()); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(0)); + } + return; + } + auto channel_id = dialog_id.get_channel_id(); + if (!td_->chat_manager_->is_broadcast_channel(channel_id) || + td_->chat_manager_->get_input_channel(channel_id) == nullptr) { + if (chats_promise) { + chats_promise.set_value(td_api::make_object()); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(0)); + } + return; + } + bool use_database = true; + auto it = channel_recommended_dialogs_.find(channel_id); + if (it != channel_recommended_dialogs_.end()) { + if (are_suitable_recommended_dialogs(it->second)) { + auto next_reload_time = it->second.next_reload_time_; + if (chats_promise) { + chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_, + "get_channel_recommendations")); + } + if (count_promise) { + count_promise.set_value(td_api::make_object(it->second.total_count_)); + } + if (next_reload_time > Time::now()) { + return; + } + chats_promise = {}; + count_promise = {}; + } else { + LOG(INFO) << "Drop cache for similar chats of " << dialog_id; + channel_recommended_dialogs_.erase(it); + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + } + } + use_database = false; + } + load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise), + std::move(count_promise)); +} + +string ChannelRecommendationManager::get_channel_recommendations_database_key(ChannelId channel_id) { + return PSTRING() << "channel_recommendations" << channel_id.get(); +} + +void ChannelRecommendationManager::load_channel_recommendations( + ChannelId channel_id, bool use_database, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise) { + if (count_promise) { + get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise)); + } + auto &queries = get_channel_recommendations_queries_[channel_id]; + queries.push_back(std::move(chats_promise)); + if (queries.size() == 1) { + if (G()->use_message_database() && use_database) { + G()->td_db()->get_sqlite_pmc()->get( + get_channel_recommendations_database_key(channel_id), + PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) { + send_closure(actor_id, &ChannelRecommendationManager::on_load_channel_recommendations_from_database, + channel_id, std::move(value)); + })); + } else { + reload_channel_recommendations(channel_id); + } + } +} + +void ChannelRecommendationManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) { + for (int return_local = 0; return_local < 2; return_local++) { + auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); + if (it != get_channel_recommendation_count_queries_[return_local].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[return_local].erase(it); + fail_promises(promises, error.clone()); + } + } + auto it = get_channel_recommendations_queries_.find(channel_id); + CHECK(it != get_channel_recommendations_queries_.end()); + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendations_queries_.erase(it); + fail_promises(promises, std::move(error)); +} + +void ChannelRecommendationManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, + vector dialog_ids) { + for (int return_local = 0; return_local < 2; return_local++) { + auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); + if (it != get_channel_recommendation_count_queries_[return_local].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[return_local].erase(it); + for (auto &promise : promises) { + promise.set_value(td_api::make_object(total_count)); + } + } + } + auto it = get_channel_recommendations_queries_.find(channel_id); + CHECK(it != get_channel_recommendations_queries_.end()); + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendations_queries_.erase(it); + for (auto &promise : promises) { + if (promise) { + promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids, + "finish_load_channel_recommendations_queries")); + } + } +} + +void ChannelRecommendationManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) { + if (G()->close_flag()) { + return fail_load_channel_recommendations_queries(channel_id, G()->close_status()); + } + + if (value.empty()) { + return reload_channel_recommendations(channel_id); + } + auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; + if (log_event_parse(recommended_dialogs, value).is_error()) { + channel_recommended_dialogs_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + return reload_channel_recommendations(channel_id); + } + Dependencies dependencies; + for (auto dialog_id : recommended_dialogs.dialog_ids_) { + dependencies.add_dialog_and_dependencies(dialog_id); + } + if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") || + !are_suitable_recommended_dialogs(recommended_dialogs)) { + channel_recommended_dialogs_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); + return reload_channel_recommendations(channel_id); + } + + auto next_reload_time = recommended_dialogs.next_reload_time_; + finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_, + recommended_dialogs.dialog_ids_); + + if (next_reload_time <= Time::now()) { + load_channel_recommendations(channel_id, false, false, Auto(), Auto()); + } +} + +void ChannelRecommendationManager::reload_channel_recommendations(ChannelId channel_id) { + auto it = get_channel_recommendation_count_queries_[1].find(channel_id); + if (it != get_channel_recommendation_count_queries_[1].end()) { + auto promises = std::move(it->second); + CHECK(!promises.empty()); + get_channel_recommendation_count_queries_[1].erase(it); + for (auto &promise : promises) { + promise.set_value(td_api::make_object(-1)); + } + } + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), channel_id]( + Result>>> &&result) { + send_closure(actor_id, &ChannelRecommendationManager::on_get_channel_recommendations, channel_id, + std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(channel_id); +} + +void ChannelRecommendationManager::on_get_channel_recommendations( + ChannelId channel_id, Result>>> &&r_chats) { + G()->ignore_result_if_closing(r_chats); + + if (r_chats.is_error()) { + return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error()); + } + + auto chats = r_chats.move_as_ok(); + auto total_count = chats.first; + auto channel_ids = td_->chat_manager_->get_channel_ids(std::move(chats.second), "on_get_channel_recommendations"); + vector dialog_ids; + if (total_count < static_cast(channel_ids.size())) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for " + << channel_id; + total_count = static_cast(channel_ids.size()); + } + for (auto recommended_channel_id : channel_ids) { + auto recommended_dialog_id = DialogId(recommended_channel_id); + td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations"); + if (is_suitable_recommended_channel(recommended_channel_id)) { + dialog_ids.push_back(recommended_dialog_id); + } else { + total_count--; + } + } + auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; + recommended_dialogs.total_count_ = total_count; + recommended_dialogs.dialog_ids_ = dialog_ids; + recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; + + if (G()->use_message_database()) { + G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id), + log_event_store(recommended_dialogs).as_slice().str(), Promise()); + } + + finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids)); +} + +void ChannelRecommendationManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, + Promise &&promise) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") || + !td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Invalid chat specified")); + } + vector> data; + data.push_back(telegram_api::make_object( + "ref_channel_id", make_tl_object(to_string(dialog_id.get_channel_id().get())))); + data.push_back(telegram_api::make_object( + "open_channel_id", make_tl_object(to_string(opened_dialog_id.get_channel_id().get())))); + save_app_log(td_, "channels.open_recommended_channel", DialogId(), + telegram_api::make_object(std::move(data)), std::move(promise)); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.h b/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.h new file mode 100644 index 00000000..e21a1487 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ChannelRecommendationManager.h @@ -0,0 +1,108 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/ChannelId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +#include + +namespace td { + +class Td; + +class ChannelRecommendationManager final : public Actor { + public: + ChannelRecommendationManager(Td *td, ActorShared<> parent); + + void get_recommended_channels(Promise> &&promise); + + void get_channel_recommendations(DialogId dialog_id, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise); + + void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise &&promise); + + private: + static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit + + struct RecommendedDialogs { + int32 total_count_ = 0; + vector dialog_ids_; + double next_reload_time_ = 0.0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + void tear_down() final; + + bool is_suitable_recommended_channel(DialogId dialog_id) const; + + bool is_suitable_recommended_channel(ChannelId channel_id) const; + + bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const; + + static string get_recommended_channels_database_key(); + + void load_recommended_channels(bool use_database, Promise> &&promise); + + void fail_load_recommended_channels_queries(Status &&error); + + void finish_load_recommended_channels_queries(int32 total_count, vector dialog_ids); + + void on_load_recommended_channels_from_database(string value); + + void reload_recommended_channels(); + + void on_get_recommended_channels(Result>>> &&r_chats); + + static string get_channel_recommendations_database_key(ChannelId channel_id); + + void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, + Promise> &&chats_promise, + Promise> &&count_promise); + + void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error); + + void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, + vector dialog_ids); + + void on_load_channel_recommendations_from_database(ChannelId channel_id, string value); + + void reload_channel_recommendations(ChannelId channel_id); + + void on_get_channel_recommendations( + ChannelId channel_id, Result>>> &&r_chats); + + FlatHashMap channel_recommended_dialogs_; + FlatHashMap>>, ChannelIdHash> + get_channel_recommendations_queries_; + FlatHashMap>>, ChannelIdHash> + get_channel_recommendation_count_queries_[2]; + + RecommendedDialogs recommended_channels_; + vector>> get_recommended_channels_queries_; + bool are_recommended_channels_inited_ = false; + + Td *td_; + ActorShared<> parent_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ChatManager.cpp b/lib/tgchat/ext/td/td/telegram/ChatManager.cpp new file mode 100644 index 00000000..c61a656d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ChatManager.cpp @@ -0,0 +1,8925 @@ +// +// 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/ChatManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogAdministrator.h" +#include "td/telegram/DialogInviteLink.h" +#include "td/telegram/DialogInviteLinkManager.h" +#include "td/telegram/DialogLocation.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/DialogParticipantManager.h" +#include "td/telegram/FileReferenceManager.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/FolderId.h" +#include "td/telegram/Global.h" +#include "td/telegram/GroupCallManager.h" +#include "td/telegram/InputGroupCallId.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/MessageSender.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/MessageTtl.h" +#include "td/telegram/MinChannel.h" +#include "td/telegram/misc.h" +#include "td/telegram/MissingInvitee.h" +#include "td/telegram/net/NetQuery.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/PeerColor.h" +#include "td/telegram/Photo.h" +#include "td/telegram/Photo.hpp" +#include "td/telegram/PhotoSize.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/StickersManager.h" +#include "td/telegram/StoryManager.h" +#include "td/telegram/SuggestedAction.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" +#include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" + +#include "td/db/binlog/BinlogEvent.h" +#include "td/db/binlog/BinlogHelper.h" +#include "td/db/SqliteKeyValue.h" +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/actor/MultiPromise.h" +#include "td/actor/SleepActor.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" +#include "td/utils/utf8.h" + +#include +#include + +namespace td { + +class CreateChatQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CreateChatQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(vector> &&input_users, const string &title, MessageTtl message_ttl) { + int32 flags = telegram_api::messages_createChat::TTL_PERIOD_MASK; + send_query(G()->net_query_creator().create( + telegram_api::messages_createChat(flags, std::move(input_users), title, message_ttl.get_input_ttl_period()))); + } + + 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 CreateChatQuery: " << to_string(ptr); + td_->messages_manager_->on_create_new_dialog( + std::move(ptr->updates_), MissingInvitees(std::move(ptr->missing_invitees_)), std::move(promise_), Auto()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class CreateChannelQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit CreateChannelQuery(Promise> &&promise) : promise_(std::move(promise)) { + } + + void send(const string &title, bool is_forum, bool is_megagroup, const string &about, const DialogLocation &location, + bool for_import, MessageTtl message_ttl) { + int32 flags = telegram_api::channels_createChannel::TTL_PERIOD_MASK; + if (is_forum) { + flags |= telegram_api::channels_createChannel::FORUM_MASK; + } else if (is_megagroup) { + flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK; + } else { + flags |= telegram_api::channels_createChannel::BROADCAST_MASK; + } + if (!location.empty()) { + flags |= telegram_api::channels_createChannel::GEO_POINT_MASK; + } + if (for_import) { + flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK; + } + + send_query(G()->net_query_creator().create(telegram_api::channels_createChannel( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, about, + location.get_input_geo_point(), location.get_address(), message_ttl.get_input_ttl_period()))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->messages_manager_->on_create_new_dialog(result_ptr.move_as_ok(), MissingInvitees(), Auto(), + std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateChannelUsernameQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + string username_; + + public: + explicit UpdateChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, const string &username) { + channel_id_ = channel_id; + username_ = username; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_updateUsername(std::move(input_channel), username), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for UpdateChannelUsernameQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Supergroup username is not updated")); + } + + td_->chat_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelUsernameQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleChannelUsernameQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + string username_; + bool is_active_; + + public: + explicit ToggleChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, string &&username, bool is_active) { + channel_id_ = channel_id; + username_ = std::move(username); + is_active_ = is_active; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleUsername(std::move(input_channel), username_, is_active_), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ToggleChannelUsernameQuery: " << result; + td_->chat_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, + std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, + std::move(promise_)); + return; + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelUsernameQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class DeactivateAllChannelUsernamesQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit DeactivateAllChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create(telegram_api::channels_deactivateAllUsernames(std::move(input_channel)), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for DeactivateAllChannelUsernamesQuery: " << result; + td_->chat_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); + return; + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeactivateAllChannelUsernamesQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ReorderChannelUsernamesQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + vector usernames_; + + public: + explicit ReorderChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, vector &&usernames) { + channel_id_ = channel_id; + usernames_ = usernames; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_reorderUsernames(std::move(input_channel), std::move(usernames)), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ReorderChannelUsernamesQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Supergroup usernames weren't updated")); + } + + td_->chat_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), + std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), + std::move(promise_)); + return; + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReorderChannelUsernamesQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class UpdateChannelColorQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit UpdateChannelColorQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool for_profile, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + int32 flags = 0; + if (for_profile) { + flags |= telegram_api::channels_updateColor::FOR_PROFILE_MASK; + } + if (accent_color_id.is_valid()) { + flags |= telegram_api::channels_updateColor::COLOR_MASK; + } + if (background_custom_emoji_id.is_valid()) { + flags |= telegram_api::channels_updateColor::BACKGROUND_EMOJI_ID_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::channels_updateColor(flags, false /*ignored*/, std::move(input_channel), accent_color_id.get(), + background_custom_emoji_id.get()), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for UpdateChannelColorQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelColorQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class UpdateChannelEmojiStatusQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit UpdateChannelEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, const EmojiStatus &emoji_status) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_updateEmojiStatus(std::move(input_channel), emoji_status.get_input_emoji_status()), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for UpdateChannelEmojiStatusQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelEmojiStatusQuery"); + get_recent_emoji_statuses(td_, Auto()); + } + promise_.set_error(std::move(status)); + } +}; + +class SetChannelStickerSetQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + StickerSetId sticker_set_id_; + + public: + explicit SetChannelStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, StickerSetId sticker_set_id, + telegram_api::object_ptr &&input_sticker_set) { + channel_id_ = channel_id; + sticker_set_id_ = sticker_set_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_setStickers(std::move(input_channel), std::move(input_sticker_set)), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for SetChannelStickerSetQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Supergroup sticker set not updated")); + } + + td_->chat_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelStickerSetQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class SetChannelEmojiStickerSetQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + StickerSetId sticker_set_id_; + + public: + explicit SetChannelEmojiStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, StickerSetId sticker_set_id, + telegram_api::object_ptr &&input_sticker_set) { + channel_id_ = channel_id; + sticker_set_id_ = sticker_set_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_setEmojiStickers(std::move(input_channel), std::move(input_sticker_set)), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for SetChannelEmojiStickerSetQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Supergroup custom emoji sticker set not updated")); + } + + td_->chat_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelEmojiStickerSetQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class SetChannelBoostsToUnblockRestrictionsQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + int32 unrestrict_boost_count_; + + public: + explicit SetChannelBoostsToUnblockRestrictionsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, int32 unrestrict_boost_count) { + channel_id_ = channel_id; + unrestrict_boost_count_ = unrestrict_boost_count; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_setBoostsToUnblockRestrictions(std::move(input_channel), unrestrict_boost_count), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for SetChannelBoostsToUnblockRestrictionsQuery: " << to_string(ptr); + td_->chat_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "SetChannelBoostsToUnblockRestrictionsQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleChannelSignaturesQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleChannelSignaturesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool sign_messages) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleChannelSignaturesQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelSignaturesQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleChannelJoinToSendQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleChannelJoinToSendQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool join_to_send) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleJoinToSend(std::move(input_channel), join_to_send), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleChannelJoinToSendQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinToSendQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleChannelJoinRequestQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleChannelJoinRequestQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool join_request) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleJoinRequest(std::move(input_channel), join_request), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleChannelJoinRequestQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinRequestQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class TogglePrehistoryHiddenQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + bool is_all_history_available_; + + public: + explicit TogglePrehistoryHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool is_all_history_available) { + channel_id_ = channel_id; + is_all_history_available_ = is_all_history_available; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_togglePreHistoryHidden(std::move(input_channel), !is_all_history_available), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for TogglePrehistoryHiddenQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates( + std::move(ptr), + PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, + is_all_history_available = is_all_history_available_](Unit result) mutable { + send_closure(actor_id, &ChatManager::on_update_channel_is_all_history_available, channel_id, + is_all_history_available, std::move(promise)); + })); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "TogglePrehistoryHiddenQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class RestrictSponsoredMessagesQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + bool can_have_sponsored_messages_; + + public: + explicit RestrictSponsoredMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool can_have_sponsored_messages) { + channel_id_ = channel_id; + can_have_sponsored_messages_ = can_have_sponsored_messages; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_restrictSponsoredMessages(std::move(input_channel), !can_have_sponsored_messages), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for RestrictSponsoredMessagesQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates( + std::move(ptr), + PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, + can_have_sponsored_messages = can_have_sponsored_messages_](Unit result) mutable { + send_closure(actor_id, &ChatManager::on_update_channel_can_have_sponsored_messages, channel_id, + can_have_sponsored_messages, std::move(promise)); + })); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "RestrictSponsoredMessagesQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleParticipantsHiddenQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + bool has_hidden_participants_; + + public: + explicit ToggleParticipantsHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool has_hidden_participants) { + channel_id_ = channel_id; + has_hidden_participants_ = has_hidden_participants; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleParticipantsHidden(std::move(input_channel), has_hidden_participants), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleParticipantsHiddenQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates( + std::move(ptr), + PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, + has_hidden_participants = has_hidden_participants_](Unit result) mutable { + send_closure(actor_id, &ChatManager::on_update_channel_has_hidden_participants, channel_id, + has_hidden_participants, std::move(promise)); + })); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleParticipantsHiddenQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleAntiSpamQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + bool has_aggressive_anti_spam_enabled_; + + public: + explicit ToggleAntiSpamQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool has_aggressive_anti_spam_enabled) { + channel_id_ = channel_id; + has_aggressive_anti_spam_enabled_ = has_aggressive_anti_spam_enabled; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleAntiSpam(std::move(input_channel), has_aggressive_anti_spam_enabled), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleAntiSpamQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates( + std::move(ptr), + PromiseCreator::lambda( + [actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, + has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled_](Unit result) mutable { + send_closure(actor_id, &ChatManager::on_update_channel_has_aggressive_anti_spam_enabled, channel_id, + has_aggressive_anti_spam_enabled, std::move(promise)); + })); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleAntiSpamQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleForumQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ToggleForumQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool is_forum) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create(telegram_api::channels_toggleForum(std::move(input_channel), is_forum), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleForumQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleForumQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ConvertToGigagroupQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ConvertToGigagroupQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create(telegram_api::channels_convertToGigagroup(std::move(input_channel)), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ConvertToGigagroupQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + promise_.set_value(Unit()); + return; + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ConvertToGigagroupQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class EditChatAboutQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + string about_; + + void on_success() { + switch (dialog_id_.get_type()) { + case DialogType::Chat: + return td_->chat_manager_->on_update_chat_description(dialog_id_.get_chat_id(), std::move(about_)); + case DialogType::Channel: + return td_->chat_manager_->on_update_channel_description(dialog_id_.get_channel_id(), std::move(about_)); + case DialogType::User: + case DialogType::SecretChat: + case DialogType::None: + UNREACHABLE(); + } + } + + public: + explicit EditChatAboutQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, const string &about) { + dialog_id_ = dialog_id; + about_ = about; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); + if (input_peer == nullptr) { + return on_error(Status::Error(400, "Can't access the chat")); + } + send_query(G()->net_query_creator().create(telegram_api::messages_editChatAbout(std::move(input_peer), about), + {{dialog_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for EditChatAboutQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Chat description is not updated")); + } + + on_success(); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_ABOUT_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { + on_success(); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "EditChatAboutQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class SetDiscussionGroupQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId broadcast_channel_id_; + ChannelId group_channel_id_; + + public: + explicit SetDiscussionGroupQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId broadcast_channel_id, + telegram_api::object_ptr broadcast_input_channel, ChannelId group_channel_id, + telegram_api::object_ptr group_input_channel) { + broadcast_channel_id_ = broadcast_channel_id; + group_channel_id_ = group_channel_id; + send_query(G()->net_query_creator().create( + telegram_api::channels_setDiscussionGroup(std::move(broadcast_input_channel), std::move(group_input_channel)), + {{broadcast_channel_id}, {group_channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.move_as_ok(); + LOG_IF(INFO, !result) << "Set discussion group has failed"; + + td_->chat_manager_->on_update_channel_linked_channel_id(broadcast_channel_id_, group_channel_id_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "LINK_NOT_MODIFIED") { + return promise_.set_value(Unit()); + } + promise_.set_error(std::move(status)); + } +}; + +class EditLocationQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + DialogLocation location_; + + public: + explicit EditLocationQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, const DialogLocation &location) { + channel_id_ = channel_id; + location_ = location; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::channels_editLocation(std::move(input_channel), location_.get_input_geo_point(), + location_.get_address()), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.move_as_ok(); + LOG_IF(INFO, !result) << "Edit chat location has failed"; + + td_->chat_manager_->on_update_channel_location(channel_id_, location_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditLocationQuery"); + promise_.set_error(std::move(status)); + } +}; + +class ToggleSlowModeQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + int32 slow_mode_delay_ = 0; + + public: + explicit ToggleSlowModeQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, int32 slow_mode_delay) { + channel_id_ = channel_id; + slow_mode_delay_ = slow_mode_delay; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::channels_toggleSlowMode(std::move(input_channel), slow_mode_delay), {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for ToggleSlowModeQuery: " << to_string(ptr); + + td_->updates_manager_->on_get_updates( + std::move(ptr), + PromiseCreator::lambda([actor_id = G()->chat_manager(), promise = std::move(promise_), channel_id = channel_id_, + slow_mode_delay = slow_mode_delay_](Unit result) mutable { + send_closure(actor_id, &ChatManager::on_update_channel_slow_mode_delay, channel_id, slow_mode_delay, + std::move(promise)); + })); + } + + void on_error(Status status) final { + if (status.message() == "CHAT_NOT_MODIFIED") { + td_->chat_manager_->on_update_channel_slow_mode_delay(channel_id_, slow_mode_delay_, Promise()); + if (!td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + } else { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ToggleSlowModeQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ReportChannelSpamQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + DialogId sender_dialog_id_; + + public: + explicit ReportChannelSpamQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, DialogId sender_dialog_id, const vector &message_ids) { + channel_id_ = channel_id; + sender_dialog_id_ = sender_dialog_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + + auto input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::channels_reportSpam( + std::move(input_channel), std::move(input_peer), MessageId::get_server_message_ids(message_ids)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.move_as_ok(); + LOG_IF(INFO, !result) << "Report spam has failed in " << channel_id_; + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (sender_dialog_id_.get_type() != DialogType::Channel) { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReportChannelSpamQuery"); + } + promise_.set_error(std::move(status)); + } +}; + +class ReportChannelAntiSpamFalsePositiveQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit ReportChannelAntiSpamFalsePositiveQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, MessageId message_id) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::channels_reportAntiSpamFalsePositive( + std::move(input_channel), message_id.get_server_message_id().get()))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.move_as_ok(); + LOG_IF(INFO, !result) << "Report anti-spam false positive has failed in " << channel_id_; + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReportChannelAntiSpamFalsePositiveQuery"); + promise_.set_error(std::move(status)); + } +}; + +class DeleteChatQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit DeleteChatQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChatId chat_id) { + send_query(G()->net_query_creator().create(telegram_api::messages_deleteChat(chat_id.get()), {{chat_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()); + } + + LOG(INFO) << "Receive result for DeleteChatQuery: " << result_ptr.ok(); + td_->updates_manager_->get_difference("DeleteChatQuery"); + td_->updates_manager_->on_get_updates(make_tl_object(), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class DeleteChannelQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit DeleteChannelQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + send_query(G()->net_query_creator().create(telegram_api::channels_deleteChannel(std::move(input_channel)), + {{channel_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for DeleteChannelQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetCreatedPublicChannelsQuery final : public Td::ResultHandler { + Promise promise_; + PublicDialogType type_; + + public: + explicit GetCreatedPublicChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(PublicDialogType type, bool check_limit) { + type_ = type; + int32 flags = 0; + if (type_ == PublicDialogType::IsLocationBased) { + flags |= telegram_api::channels_getAdminedPublicChannels::BY_LOCATION_MASK; + } + if (type_ == PublicDialogType::ForPersonalDialog) { + CHECK(!check_limit); + flags |= telegram_api::channels_getAdminedPublicChannels::FOR_PERSONAL_MASK; + } + if (check_limit) { + flags |= telegram_api::channels_getAdminedPublicChannels::CHECK_LIMIT_MASK; + } + send_query(G()->net_query_creator().create(telegram_api::channels_getAdminedPublicChannels( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/))); + } + + 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 chats_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetCreatedPublicChannelsQuery: " << to_string(chats_ptr); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + td_->chat_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); + break; + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + LOG(ERROR) << "Receive chatsSlice in result of GetCreatedPublicChannelsQuery"; + td_->chat_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); + break; + } + default: + UNREACHABLE(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetGroupsForDiscussionQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetGroupsForDiscussionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::channels_getGroupsForDiscussion())); + } + + 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 chats_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetGroupsForDiscussionQuery: " << to_string(chats_ptr); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + td_->chat_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); + break; + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + LOG(ERROR) << "Receive chatsSlice in result of GetGroupsForDiscussionQuery"; + td_->chat_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); + break; + } + default: + UNREACHABLE(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetInactiveChannelsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetInactiveChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::channels_getInactiveChannels())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetInactiveChannelsQuery: " << to_string(result); + // don't need to use result->dates_, because chat.last_message.date is more reliable + td_->user_manager_->on_get_users(std::move(result->users_), "GetInactiveChannelsQuery"); + td_->chat_manager_->on_get_inactive_channels(std::move(result->chats_), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetChatsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetChatsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector &&chat_ids) { + send_query(G()->net_query_creator().create(telegram_api::messages_getChats(std::move(chat_ids)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto chats_ptr = result_ptr.move_as_ok(); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery"); + break; + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + LOG(ERROR) << "Receive chatsSlice in result of GetChatsQuery"; + td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery slice"); + break; + } + default: + UNREACHABLE(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetFullChatQuery final : public Td::ResultHandler { + Promise promise_; + ChatId chat_id_; + + public: + explicit GetFullChatQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChatId chat_id) { + send_query(G()->net_query_creator().create(telegram_api::messages_getFullChat(chat_id.get()))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullChatQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChatQuery"); + td_->chat_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_chat_full_failed(chat_id_); + promise_.set_error(std::move(status)); + } +}; + +class GetChannelsQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit GetChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(tl_object_ptr &&input_channel) { + CHECK(input_channel != nullptr); + if (input_channel->get_id() == telegram_api::inputChannel::ID) { + channel_id_ = ChannelId(static_cast(input_channel.get())->channel_id_); + } else if (input_channel->get_id() == telegram_api::inputChannelFromMessage::ID) { + channel_id_ = + ChannelId(static_cast(input_channel.get())->channel_id_); + } + + vector> input_channels; + input_channels.push_back(std::move(input_channel)); + send_query(G()->net_query_creator().create(telegram_api::channels_getChannels(std::move(input_channels)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + // LOG(INFO) << "Receive result for GetChannelsQuery: " << to_string(result_ptr.ok()); + auto chats_ptr = result_ptr.move_as_ok(); + switch (chats_ptr->get_id()) { + case telegram_api::messages_chats::ID: { + auto chats = move_tl_object_as(chats_ptr); + td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery"); + break; + } + case telegram_api::messages_chatsSlice::ID: { + auto chats = move_tl_object_as(chats_ptr); + LOG(ERROR) << "Receive chatsSlice in result of GetChannelsQuery"; + td_->chat_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery slice"); + break; + } + default: + UNREACHABLE(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelsQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetFullChannelQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit GetFullChannelQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, tl_object_ptr &&input_channel) { + channel_id_ = channel_id; + send_query(G()->net_query_creator().create(telegram_api::channels_getFullChannel(std::move(input_channel)))); + } + + 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(); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullChannelQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChannelQuery"); + td_->chat_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetFullChannelQuery"); + td_->chat_manager_->on_get_channel_full_failed(channel_id_); + promise_.set_error(std::move(status)); + } +}; + +ChatManager::ChatManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + channel_emoji_status_timeout_.set_callback(on_channel_emoji_status_timeout_callback); + channel_emoji_status_timeout_.set_callback_data(static_cast(this)); + + channel_unban_timeout_.set_callback(on_channel_unban_timeout_callback); + channel_unban_timeout_.set_callback_data(static_cast(this)); + + slow_mode_delay_timeout_.set_callback(on_slow_mode_delay_timeout_callback); + slow_mode_delay_timeout_.set_callback_data(static_cast(this)); + + get_chat_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + td_->create_handler(std::move(promise))->send(std::move(query_ids)); + }); + get_channel_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(query_ids.size() == 1); + auto input_channel = get_input_channel(ChannelId(query_ids[0])); + if (input_channel == nullptr) { + return promise.set_error(Status::Error(400, "Channel not found")); + } + td_->create_handler(std::move(promise))->send(std::move(input_channel)); + }); +} + +ChatManager::~ChatManager() { + Scheduler::instance()->destroy_on_scheduler( + G()->get_gc_scheduler_id(), chats_, chats_full_, unknown_chats_, chat_full_file_source_ids_, min_channels_, + channels_, channels_full_, unknown_channels_, invalidated_channels_full_, channel_full_file_source_ids_); + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), loaded_from_database_chats_, + unavailable_chat_fulls_, loaded_from_database_channels_, + unavailable_channel_fulls_, linked_channel_ids_, restricted_channel_ids_); +} + +void ChatManager::tear_down() { + parent_.reset(); + + LOG(DEBUG) << "Have " << chats_.calc_size() << " basic groups and " << channels_.calc_size() + << " supergroups to free"; + LOG(DEBUG) << "Have " << chats_full_.calc_size() << " full basic groups and " << channels_full_.calc_size() + << " full supergroups to free"; +} + +void ChatManager::on_channel_emoji_status_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { + if (G()->close_flag()) { + return; + } + + auto chat_manager = static_cast(chat_manager_ptr); + send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_channel_emoji_status_timeout, + ChannelId(channel_id_long)); +} + +void ChatManager::on_channel_emoji_status_timeout(ChannelId channel_id) { + if (G()->close_flag()) { + return; + } + + auto c = get_channel(channel_id); + CHECK(c != nullptr); + CHECK(c->is_update_supergroup_sent); + + update_channel(c, channel_id); +} + +void ChatManager::on_channel_unban_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { + if (G()->close_flag()) { + return; + } + + auto chat_manager = static_cast(chat_manager_ptr); + send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_channel_unban_timeout, + ChannelId(channel_id_long)); +} + +void ChatManager::on_channel_unban_timeout(ChannelId channel_id) { + if (G()->close_flag()) { + return; + } + + auto c = get_channel(channel_id); + CHECK(c != nullptr); + + auto old_status = c->status; + c->status.update_restrictions(); + if (c->status == old_status) { + LOG_IF(ERROR, c->status.is_restricted() || c->status.is_banned()) + << "Status of " << channel_id << " wasn't updated: " << c->status; + } else { + c->is_changed = true; + } + + LOG(INFO) << "Update " << channel_id << " status"; + c->is_status_changed = true; + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_unban_timeout"); + update_channel(c, channel_id); // always call, because in case of failure we need to reactivate timeout +} + +void ChatManager::on_slow_mode_delay_timeout_callback(void *chat_manager_ptr, int64 channel_id_long) { + if (G()->close_flag()) { + return; + } + + auto chat_manager = static_cast(chat_manager_ptr); + send_closure_later(chat_manager->actor_id(chat_manager), &ChatManager::on_slow_mode_delay_timeout, + ChannelId(channel_id_long)); +} + +void ChatManager::on_slow_mode_delay_timeout(ChannelId channel_id) { + if (G()->close_flag()) { + return; + } + + on_update_channel_slow_mode_next_send_date(channel_id, 0); +} + +template +void ChatManager::Chat::store(StorerT &storer) const { + using td::store; + bool has_photo = photo.small_file_id.is_valid(); + bool use_new_rights = true; + bool has_default_permissions_version = default_permissions_version != -1; + bool has_pinned_message_version = pinned_message_version != -1; + bool has_cache_version = cache_version != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(is_active); + STORE_FLAG(has_photo); + STORE_FLAG(use_new_rights); + STORE_FLAG(has_default_permissions_version); + STORE_FLAG(has_pinned_message_version); + STORE_FLAG(has_cache_version); + STORE_FLAG(noforwards); + END_STORE_FLAGS(); + + store(title, storer); + if (has_photo) { + store(photo, storer); + } + store(participant_count, storer); + store(date, storer); + store(migrated_to_channel_id, storer); + store(version, storer); + store(status, storer); + store(default_permissions, storer); + if (has_default_permissions_version) { + store(default_permissions_version, storer); + } + if (has_pinned_message_version) { + store(pinned_message_version, storer); + } + if (has_cache_version) { + store(cache_version, storer); + } +} + +template +void ChatManager::Chat::parse(ParserT &parser) { + using td::parse; + bool has_photo; + bool left; + bool kicked; + bool is_creator; + bool is_administrator; + bool everyone_is_administrator; + bool can_edit; + bool use_new_rights; + bool has_default_permissions_version; + bool has_pinned_message_version; + bool has_cache_version; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(left); + PARSE_FLAG(kicked); + PARSE_FLAG(is_creator); + PARSE_FLAG(is_administrator); + PARSE_FLAG(everyone_is_administrator); + PARSE_FLAG(can_edit); + PARSE_FLAG(is_active); + PARSE_FLAG(has_photo); + PARSE_FLAG(use_new_rights); + PARSE_FLAG(has_default_permissions_version); + PARSE_FLAG(has_pinned_message_version); + PARSE_FLAG(has_cache_version); + PARSE_FLAG(noforwards); + END_PARSE_FLAGS(); + + parse(title, parser); + if (has_photo) { + parse(photo, parser); + } + parse(participant_count, parser); + parse(date, parser); + parse(migrated_to_channel_id, parser); + parse(version, parser); + if (use_new_rights) { + parse(status, parser); + parse(default_permissions, parser); + } else { + if (can_edit != (is_creator || is_administrator || everyone_is_administrator)) { + LOG(ERROR) << "Have wrong can_edit flag"; + } + + if (kicked || !is_active) { + status = DialogParticipantStatus::Banned(0); + } else if (left) { + status = DialogParticipantStatus::Left(); + } else if (is_creator) { + status = DialogParticipantStatus::Creator(true, false, string()); + } else if (is_administrator && !everyone_is_administrator) { + status = DialogParticipantStatus::GroupAdministrator(false); + } else { + status = DialogParticipantStatus::Member(); + } + default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, + everyone_is_administrator, everyone_is_administrator, + everyone_is_administrator, false, ChannelType::Unknown); + } + if (has_default_permissions_version) { + parse(default_permissions_version, parser); + } + if (has_pinned_message_version) { + parse(pinned_message_version, parser); + } + if (has_cache_version) { + parse(cache_version, parser); + } + + if (!check_utf8(title)) { + LOG(ERROR) << "Have invalid title \"" << title << '"'; + title.clear(); + cache_version = 0; + } + + if (status.is_administrator() && !status.is_creator()) { + status = DialogParticipantStatus::GroupAdministrator(false); + } +} + +template +void ChatManager::ChatFull::store(StorerT &storer) const { + using td::store; + bool has_description = !description.empty(); + bool has_legacy_invite_link = false; + bool has_photo = !photo.is_empty(); + bool has_invite_link = invite_link.is_valid(); + bool has_bot_commands = !bot_commands.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_description); + STORE_FLAG(has_legacy_invite_link); + STORE_FLAG(can_set_username); + STORE_FLAG(has_photo); + STORE_FLAG(has_invite_link); + STORE_FLAG(has_bot_commands); + END_STORE_FLAGS(); + store(version, storer); + store(creator_user_id, storer); + store(participants, storer); + if (has_description) { + store(description, storer); + } + if (has_photo) { + store(photo, storer); + } + if (has_invite_link) { + store(invite_link, storer); + } + if (has_bot_commands) { + store(bot_commands, storer); + } +} + +template +void ChatManager::ChatFull::parse(ParserT &parser) { + using td::parse; + bool has_description; + bool legacy_has_invite_link; + bool has_photo; + bool has_invite_link; + bool has_bot_commands; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_description); + PARSE_FLAG(legacy_has_invite_link); + PARSE_FLAG(can_set_username); + PARSE_FLAG(has_photo); + PARSE_FLAG(has_invite_link); + PARSE_FLAG(has_bot_commands); + END_PARSE_FLAGS(); + parse(version, parser); + parse(creator_user_id, parser); + parse(participants, parser); + if (has_description) { + parse(description, parser); + } + if (legacy_has_invite_link) { + string legacy_invite_link; + parse(legacy_invite_link, parser); + } + if (has_photo) { + parse(photo, parser); + } + if (has_invite_link) { + parse(invite_link, parser); + } + if (has_bot_commands) { + parse(bot_commands, parser); + } +} + +template +void ChatManager::Channel::store(StorerT &storer) const { + using td::store; + bool has_photo = photo.small_file_id.is_valid(); + bool legacy_has_username = false; + bool use_new_rights = true; + bool has_participant_count = participant_count != 0; + bool have_default_permissions = true; + bool has_cache_version = cache_version != 0; + bool has_restriction_reasons = !restriction_reasons.empty(); + bool legacy_has_active_group_call = false; + bool has_usernames = !usernames.is_empty(); + bool has_flags2 = true; + bool has_max_active_story_id = max_active_story_id.is_valid(); + bool has_max_read_story_id = max_read_story_id.is_valid(); + bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); + bool has_accent_color_id = accent_color_id.is_valid(); + bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); + bool has_profile_accent_color_id = profile_accent_color_id.is_valid(); + bool has_profile_background_custom_emoji_id = profile_background_custom_emoji_id.is_valid(); + bool has_boost_level = boost_level != 0; + bool has_emoji_status = !emoji_status.is_empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(sign_messages); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(false); + STORE_FLAG(is_megagroup); + STORE_FLAG(is_verified); + STORE_FLAG(has_photo); + STORE_FLAG(legacy_has_username); + STORE_FLAG(false); + STORE_FLAG(use_new_rights); + STORE_FLAG(has_participant_count); + STORE_FLAG(have_default_permissions); + STORE_FLAG(is_scam); + STORE_FLAG(has_cache_version); + STORE_FLAG(has_linked_channel); + STORE_FLAG(has_location); + STORE_FLAG(is_slow_mode_enabled); + STORE_FLAG(has_restriction_reasons); + STORE_FLAG(legacy_has_active_group_call); + STORE_FLAG(is_fake); + STORE_FLAG(is_gigagroup); + STORE_FLAG(noforwards); + STORE_FLAG(can_be_deleted); + STORE_FLAG(join_to_send); + STORE_FLAG(join_request); + STORE_FLAG(has_usernames); + STORE_FLAG(has_flags2); + END_STORE_FLAGS(); + if (has_flags2) { + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_forum); + STORE_FLAG(has_max_active_story_id); + STORE_FLAG(has_max_read_story_id); + STORE_FLAG(has_max_active_story_id_next_reload_time); + STORE_FLAG(stories_hidden); + STORE_FLAG(has_accent_color_id); + STORE_FLAG(has_background_custom_emoji_id); + STORE_FLAG(has_profile_accent_color_id); + STORE_FLAG(has_profile_background_custom_emoji_id); + STORE_FLAG(has_boost_level); + STORE_FLAG(has_emoji_status); + END_STORE_FLAGS(); + } + + store(status, storer); + store(access_hash, storer); + store(title, storer); + if (has_photo) { + store(photo, storer); + } + store(date, storer); + if (has_restriction_reasons) { + store(restriction_reasons, storer); + } + if (has_participant_count) { + store(participant_count, storer); + } + if (is_megagroup) { + store(default_permissions, storer); + } + if (has_cache_version) { + store(cache_version, storer); + } + if (has_usernames) { + store(usernames, storer); + } + if (has_max_active_story_id) { + store(max_active_story_id, storer); + } + if (has_max_read_story_id) { + store(max_read_story_id, storer); + } + if (has_max_active_story_id_next_reload_time) { + store_time(max_active_story_id_next_reload_time, storer); + } + if (has_accent_color_id) { + store(accent_color_id, storer); + } + if (has_background_custom_emoji_id) { + store(background_custom_emoji_id, storer); + } + if (has_profile_accent_color_id) { + store(profile_accent_color_id, storer); + } + if (has_profile_background_custom_emoji_id) { + store(profile_background_custom_emoji_id, storer); + } + if (has_boost_level) { + store(boost_level, storer); + } + if (has_emoji_status) { + store(emoji_status, storer); + } +} + +template +void ChatManager::Channel::parse(ParserT &parser) { + using td::parse; + bool has_photo; + bool legacy_has_username; + bool legacy_is_restricted; + bool left; + bool kicked; + bool is_creator; + bool can_edit; + bool can_moderate; + bool anyone_can_invite; + bool use_new_rights; + bool has_participant_count; + bool have_default_permissions; + bool has_cache_version; + bool has_restriction_reasons; + bool legacy_has_active_group_call; + bool has_usernames; + bool has_flags2; + bool has_max_active_story_id = false; + bool has_max_read_story_id = false; + bool has_max_active_story_id_next_reload_time = false; + bool has_accent_color_id = false; + bool has_background_custom_emoji_id = false; + bool has_profile_accent_color_id = false; + bool has_profile_background_custom_emoji_id = false; + bool has_boost_level = false; + bool has_emoji_status = false; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(left); + PARSE_FLAG(kicked); + PARSE_FLAG(anyone_can_invite); + PARSE_FLAG(sign_messages); + PARSE_FLAG(is_creator); + PARSE_FLAG(can_edit); + PARSE_FLAG(can_moderate); + PARSE_FLAG(is_megagroup); + PARSE_FLAG(is_verified); + PARSE_FLAG(has_photo); + PARSE_FLAG(legacy_has_username); + PARSE_FLAG(legacy_is_restricted); + PARSE_FLAG(use_new_rights); + PARSE_FLAG(has_participant_count); + PARSE_FLAG(have_default_permissions); + PARSE_FLAG(is_scam); + PARSE_FLAG(has_cache_version); + PARSE_FLAG(has_linked_channel); + PARSE_FLAG(has_location); + PARSE_FLAG(is_slow_mode_enabled); + PARSE_FLAG(has_restriction_reasons); + PARSE_FLAG(legacy_has_active_group_call); + PARSE_FLAG(is_fake); + PARSE_FLAG(is_gigagroup); + PARSE_FLAG(noforwards); + PARSE_FLAG(can_be_deleted); + PARSE_FLAG(join_to_send); + PARSE_FLAG(join_request); + PARSE_FLAG(has_usernames); + PARSE_FLAG(has_flags2); + END_PARSE_FLAGS(); + if (has_flags2) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_forum); + PARSE_FLAG(has_max_active_story_id); + PARSE_FLAG(has_max_read_story_id); + PARSE_FLAG(has_max_active_story_id_next_reload_time); + PARSE_FLAG(stories_hidden); + PARSE_FLAG(has_accent_color_id); + PARSE_FLAG(has_background_custom_emoji_id); + PARSE_FLAG(has_profile_accent_color_id); + PARSE_FLAG(has_profile_background_custom_emoji_id); + PARSE_FLAG(has_boost_level); + PARSE_FLAG(has_emoji_status); + END_PARSE_FLAGS(); + } + + if (use_new_rights) { + parse(status, parser); + } else { + if (kicked) { + status = DialogParticipantStatus::Banned(0); + } else if (left) { + status = DialogParticipantStatus::Left(); + } else if (is_creator) { + status = DialogParticipantStatus::Creator(true, false, string()); + } else if (can_edit || can_moderate) { + status = DialogParticipantStatus::ChannelAdministrator(false, is_megagroup); + } else { + status = DialogParticipantStatus::Member(); + } + } + parse(access_hash, parser); + parse(title, parser); + if (has_photo) { + parse(photo, parser); + } + if (legacy_has_username) { + if (has_usernames) { + parser.set_error("Have invalid channel flags"); + return; + } + string username; + parse(username, parser); + usernames = Usernames(std::move(username), vector>()); + } + parse(date, parser); + if (legacy_is_restricted) { + string restriction_reason; + parse(restriction_reason, parser); + restriction_reasons = get_restriction_reasons(restriction_reason); + } else if (has_restriction_reasons) { + parse(restriction_reasons, parser); + } + if (has_participant_count) { + parse(participant_count, parser); + } + if (is_megagroup) { + if (have_default_permissions) { + parse(default_permissions, parser); + } else { + default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, + true, false, anyone_can_invite, false, false, ChannelType::Megagroup); + } + } + if (has_cache_version) { + parse(cache_version, parser); + } + if (has_usernames) { + CHECK(!legacy_has_username); + parse(usernames, parser); + } + if (has_max_active_story_id) { + parse(max_active_story_id, parser); + } + if (has_max_read_story_id) { + parse(max_read_story_id, parser); + } + if (has_max_active_story_id_next_reload_time) { + parse_time(max_active_story_id_next_reload_time, parser); + } + if (has_accent_color_id) { + parse(accent_color_id, parser); + } + if (has_background_custom_emoji_id) { + parse(background_custom_emoji_id, parser); + } + if (has_profile_accent_color_id) { + parse(profile_accent_color_id, parser); + } + if (has_profile_background_custom_emoji_id) { + parse(profile_background_custom_emoji_id, parser); + } + if (has_boost_level) { + parse(boost_level, parser); + } + if (has_emoji_status) { + parse(emoji_status, parser); + } + + if (!check_utf8(title)) { + LOG(ERROR) << "Have invalid title \"" << title << '"'; + title.clear(); + cache_version = 0; + } + if (legacy_has_active_group_call) { + cache_version = 0; + } + if (!is_megagroup && status.is_restricted()) { + if (status.is_member()) { + status = DialogParticipantStatus::Member(); + } else { + status = DialogParticipantStatus::Left(); + } + } +} + +template +void ChatManager::ChannelFull::store(StorerT &storer) const { + using td::store; + bool has_description = !description.empty(); + bool has_administrator_count = administrator_count != 0; + bool has_restricted_count = restricted_count != 0; + bool has_banned_count = banned_count != 0; + bool legacy_has_invite_link = false; + bool has_sticker_set = sticker_set_id.is_valid(); + bool has_linked_channel_id = linked_channel_id.is_valid(); + bool has_migrated_from_max_message_id = migrated_from_max_message_id.is_valid(); + bool has_migrated_from_chat_id = migrated_from_chat_id.is_valid(); + bool has_location = !location.empty(); + bool has_bot_user_ids = !bot_user_ids.empty(); + bool is_slow_mode_enabled = slow_mode_delay != 0; + bool is_slow_mode_delay_active = slow_mode_next_send_date != 0; + bool has_stats_dc_id = stats_dc_id.is_exact(); + bool has_photo = !photo.is_empty(); + bool legacy_has_active_group_call_id = false; + bool has_invite_link = invite_link.is_valid(); + bool has_bot_commands = !bot_commands.empty(); + bool has_flags2 = true; + bool has_emoji_sticker_set = emoji_sticker_set_id.is_valid(); + bool has_boost_count = boost_count != 0; + bool has_unrestrict_boost_count = unrestrict_boost_count != 0; + bool has_can_have_sponsored_messages = true; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_description); + STORE_FLAG(has_administrator_count); + STORE_FLAG(has_restricted_count); + STORE_FLAG(has_banned_count); + STORE_FLAG(legacy_has_invite_link); + STORE_FLAG(has_sticker_set); + STORE_FLAG(has_linked_channel_id); + STORE_FLAG(has_migrated_from_max_message_id); + STORE_FLAG(has_migrated_from_chat_id); + STORE_FLAG(can_get_participants); + STORE_FLAG(can_set_username); + STORE_FLAG(can_set_sticker_set); + STORE_FLAG(false); // legacy_can_view_statistics + STORE_FLAG(is_all_history_available); + STORE_FLAG(can_set_location); + STORE_FLAG(has_location); + STORE_FLAG(has_bot_user_ids); + STORE_FLAG(is_slow_mode_enabled); + STORE_FLAG(is_slow_mode_delay_active); + STORE_FLAG(has_stats_dc_id); + STORE_FLAG(has_photo); + STORE_FLAG(is_can_view_statistics_inited); + STORE_FLAG(can_view_statistics); + STORE_FLAG(legacy_has_active_group_call_id); + STORE_FLAG(has_invite_link); + STORE_FLAG(has_bot_commands); + STORE_FLAG(can_be_deleted); + STORE_FLAG(has_aggressive_anti_spam_enabled); + STORE_FLAG(has_hidden_participants); + STORE_FLAG(has_flags2); + END_STORE_FLAGS(); + if (has_flags2) { + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_pinned_stories); + STORE_FLAG(has_emoji_sticker_set); + STORE_FLAG(has_boost_count); + STORE_FLAG(has_unrestrict_boost_count); + STORE_FLAG(can_have_sponsored_messages); + STORE_FLAG(can_view_revenue); + STORE_FLAG(has_can_have_sponsored_messages); + END_STORE_FLAGS(); + } + if (has_description) { + store(description, storer); + } + store(participant_count, storer); + if (has_administrator_count) { + store(administrator_count, storer); + } + if (has_restricted_count) { + store(restricted_count, storer); + } + if (has_banned_count) { + store(banned_count, storer); + } + if (has_sticker_set) { + store(sticker_set_id, storer); + } + if (has_linked_channel_id) { + store(linked_channel_id, storer); + } + if (has_location) { + store(location, storer); + } + if (has_bot_user_ids) { + store(bot_user_ids, storer); + } + if (has_migrated_from_max_message_id) { + store(migrated_from_max_message_id, storer); + } + if (has_migrated_from_chat_id) { + store(migrated_from_chat_id, storer); + } + if (is_slow_mode_enabled) { + store(slow_mode_delay, storer); + } + if (is_slow_mode_delay_active) { + store(slow_mode_next_send_date, storer); + } + store_time(expires_at, storer); + if (has_stats_dc_id) { + store(stats_dc_id.get_raw_id(), storer); + } + if (has_photo) { + store(photo, storer); + } + if (has_invite_link) { + store(invite_link, storer); + } + if (has_bot_commands) { + store(bot_commands, storer); + } + if (has_emoji_sticker_set) { + store(emoji_sticker_set_id, storer); + } + if (has_boost_count) { + store(boost_count, storer); + } + if (has_unrestrict_boost_count) { + store(unrestrict_boost_count, storer); + } +} + +template +void ChatManager::ChannelFull::parse(ParserT &parser) { + using td::parse; + bool has_description; + bool has_administrator_count; + bool has_restricted_count; + bool has_banned_count; + bool legacy_has_invite_link; + bool has_sticker_set; + bool has_linked_channel_id; + bool has_migrated_from_max_message_id; + bool has_migrated_from_chat_id; + bool legacy_can_view_statistics; + bool has_location; + bool has_bot_user_ids; + bool is_slow_mode_enabled; + bool is_slow_mode_delay_active; + bool has_stats_dc_id; + bool has_photo; + bool legacy_has_active_group_call_id; + bool has_invite_link; + bool has_bot_commands; + bool has_flags2; + bool has_emoji_sticker_set = false; + bool has_boost_count = false; + bool has_unrestrict_boost_count = false; + bool has_can_have_sponsored_messages = false; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_description); + PARSE_FLAG(has_administrator_count); + PARSE_FLAG(has_restricted_count); + PARSE_FLAG(has_banned_count); + PARSE_FLAG(legacy_has_invite_link); + PARSE_FLAG(has_sticker_set); + PARSE_FLAG(has_linked_channel_id); + PARSE_FLAG(has_migrated_from_max_message_id); + PARSE_FLAG(has_migrated_from_chat_id); + PARSE_FLAG(can_get_participants); + PARSE_FLAG(can_set_username); + PARSE_FLAG(can_set_sticker_set); + PARSE_FLAG(legacy_can_view_statistics); + PARSE_FLAG(is_all_history_available); + PARSE_FLAG(can_set_location); + PARSE_FLAG(has_location); + PARSE_FLAG(has_bot_user_ids); + PARSE_FLAG(is_slow_mode_enabled); + PARSE_FLAG(is_slow_mode_delay_active); + PARSE_FLAG(has_stats_dc_id); + PARSE_FLAG(has_photo); + PARSE_FLAG(is_can_view_statistics_inited); + PARSE_FLAG(can_view_statistics); + PARSE_FLAG(legacy_has_active_group_call_id); + PARSE_FLAG(has_invite_link); + PARSE_FLAG(has_bot_commands); + PARSE_FLAG(can_be_deleted); + PARSE_FLAG(has_aggressive_anti_spam_enabled); + PARSE_FLAG(has_hidden_participants); + PARSE_FLAG(has_flags2); + END_PARSE_FLAGS(); + if (has_flags2) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_pinned_stories); + PARSE_FLAG(has_emoji_sticker_set); + PARSE_FLAG(has_boost_count); + PARSE_FLAG(has_unrestrict_boost_count); + PARSE_FLAG(can_have_sponsored_messages); + PARSE_FLAG(can_view_revenue); + PARSE_FLAG(has_can_have_sponsored_messages); + END_PARSE_FLAGS(); + } + if (has_description) { + parse(description, parser); + } + parse(participant_count, parser); + if (has_administrator_count) { + parse(administrator_count, parser); + } + if (has_restricted_count) { + parse(restricted_count, parser); + } + if (has_banned_count) { + parse(banned_count, parser); + } + if (legacy_has_invite_link) { + string legacy_invite_link; + parse(legacy_invite_link, parser); + } + if (has_sticker_set) { + parse(sticker_set_id, parser); + } + if (has_linked_channel_id) { + parse(linked_channel_id, parser); + } + if (has_location) { + parse(location, parser); + } + if (has_bot_user_ids) { + parse(bot_user_ids, parser); + } + if (has_migrated_from_max_message_id) { + parse(migrated_from_max_message_id, parser); + } + if (has_migrated_from_chat_id) { + parse(migrated_from_chat_id, parser); + } + if (is_slow_mode_enabled) { + parse(slow_mode_delay, parser); + } + if (is_slow_mode_delay_active) { + parse(slow_mode_next_send_date, parser); + } + parse_time(expires_at, parser); + if (has_stats_dc_id) { + stats_dc_id = DcId::create(parser.fetch_int()); + } + if (has_photo) { + parse(photo, parser); + } + if (legacy_has_active_group_call_id) { + InputGroupCallId input_group_call_id; + parse(input_group_call_id, parser); + } + if (has_invite_link) { + parse(invite_link, parser); + } + if (has_bot_commands) { + parse(bot_commands, parser); + } + if (has_emoji_sticker_set) { + parse(emoji_sticker_set_id, parser); + } + if (has_boost_count) { + parse(boost_count, parser); + } + if (has_unrestrict_boost_count) { + parse(unrestrict_boost_count, parser); + } + + if (legacy_can_view_statistics) { + LOG(DEBUG) << "Ignore legacy can view statistics flag"; + } + if (!is_can_view_statistics_inited) { + can_view_statistics = stats_dc_id.is_exact(); + } + if (!has_can_have_sponsored_messages) { + can_have_sponsored_messages = true; + } +} + +tl_object_ptr ChatManager::get_input_channel(ChannelId channel_id) const { + const Channel *c = get_channel(channel_id); + if (c == nullptr) { + if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { + return make_tl_object(channel_id.get(), 0); + } + auto it = channel_messages_.find(channel_id); + if (it != channel_messages_.end()) { + CHECK(!it->second.empty()); + auto message_full_id = *it->second.begin(); + return make_tl_object( + get_simple_input_peer(message_full_id.get_dialog_id()), + message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); + } + return nullptr; + } + + return make_tl_object(channel_id.get(), c->access_hash); +} + +bool ChatManager::have_input_peer_chat(ChatId chat_id, AccessRights access_rights) const { + return have_input_peer_chat(get_chat(chat_id), access_rights); +} + +bool ChatManager::have_input_peer_chat(const Chat *c, AccessRights access_rights) { + if (c == nullptr) { + LOG(DEBUG) << "Have no basic group"; + return false; + } + if (access_rights == AccessRights::Know) { + return true; + } + if (access_rights == AccessRights::Read) { + return true; + } + if (c->status.is_left()) { + LOG(DEBUG) << "Have left basic group"; + return false; + } + if (access_rights == AccessRights::Write && !c->is_active) { + LOG(DEBUG) << "Have inactive basic group"; + return false; + } + return true; +} + +tl_object_ptr ChatManager::get_input_peer_chat(ChatId chat_id, + AccessRights access_rights) const { + auto c = get_chat(chat_id); + if (!have_input_peer_chat(c, access_rights)) { + return nullptr; + } + + return make_tl_object(chat_id.get()); +} + +bool ChatManager::have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const { + const Channel *c = get_channel(channel_id); + return have_input_peer_channel(c, channel_id, access_rights); +} + +tl_object_ptr ChatManager::get_input_peer_channel(ChannelId channel_id, + AccessRights access_rights) const { + const Channel *c = get_channel(channel_id); + if (!have_input_peer_channel(c, channel_id, access_rights)) { + return nullptr; + } + if (c == nullptr) { + if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { + return make_tl_object(channel_id.get(), 0); + } + auto it = channel_messages_.find(channel_id); + CHECK(it != channel_messages_.end()); + CHECK(!it->second.empty()); + auto message_full_id = *it->second.begin(); + return make_tl_object( + get_simple_input_peer(message_full_id.get_dialog_id()), + message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); + } + + return make_tl_object(channel_id.get(), c->access_hash); +} + +tl_object_ptr ChatManager::get_simple_input_peer(DialogId dialog_id) const { + CHECK(dialog_id.get_type() == DialogType::Channel); + auto channel_id = dialog_id.get_channel_id(); + const Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + // if (!have_input_peer_channel(c, channel_id, AccessRights::Read)) { + // return nullptr; + // } + return make_tl_object(channel_id.get(), c->access_hash); +} + +bool ChatManager::have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights, + bool from_linked) const { + if (c == nullptr) { + LOG(DEBUG) << "Have no " << channel_id; + if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { + return true; + } + if (channel_messages_.count(channel_id) != 0) { + return true; + } + return false; + } + if (access_rights == AccessRights::Know) { + return true; + } + if (c->status.is_administrator()) { + return true; + } + if (c->status.is_banned()) { + LOG(DEBUG) << "Was banned in " << channel_id; + return false; + } + if (c->status.is_member()) { + return true; + } + + bool is_public = is_channel_public(c); + if (access_rights == AccessRights::Read) { + if (is_public) { + return true; + } + if (!from_linked && c->has_linked_channel) { + auto linked_channel_id = get_linked_channel_id(channel_id); + if (linked_channel_id.is_valid() && have_channel(linked_channel_id)) { + if (have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, access_rights, true)) { + return true; + } + } else { + return true; + } + } + if (!from_linked && td_->dialog_invite_link_manager_->have_dialog_access_by_invite_link(DialogId(channel_id))) { + return true; + } + } else { + if (!from_linked && c->is_megagroup && !td_->auth_manager_->is_bot() && c->has_linked_channel) { + auto linked_channel_id = get_linked_channel_id(channel_id); + if (linked_channel_id.is_valid() && (is_public || have_channel(linked_channel_id))) { + return is_public || + have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, AccessRights::Read, true); + } else { + return true; + } + } + } + LOG(DEBUG) << "Have no access to " << channel_id; + return false; +} + +bool ChatManager::is_chat_received_from_server(ChatId chat_id) const { + const auto *c = get_chat(chat_id); + return c != nullptr && c->is_received_from_server; +} + +bool ChatManager::is_channel_received_from_server(ChannelId channel_id) const { + const auto *c = get_channel(channel_id); + return c != nullptr && c->is_received_from_server; +} + +const DialogPhoto *ChatManager::get_chat_dialog_photo(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return nullptr; + } + return &c->photo; +} + +const DialogPhoto *ChatManager::get_channel_dialog_photo(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + auto min_channel = get_min_channel(channel_id); + if (min_channel != nullptr) { + return &min_channel->photo_; + } + return nullptr; + } + return &c->photo; +} + +int32 ChatManager::get_chat_accent_color_id_object(ChatId chat_id) const { + return td_->theme_manager_->get_accent_color_id_object(AccentColorId(chat_id)); +} + +AccentColorId ChatManager::get_channel_accent_color_id(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + auto min_channel = get_min_channel(channel_id); + if (min_channel != nullptr && min_channel->accent_color_id_.is_valid()) { + return min_channel->accent_color_id_; + } + return AccentColorId(channel_id); + } + if (!c->accent_color_id.is_valid()) { + return AccentColorId(channel_id); + } + + return c->accent_color_id; +} + +int32 ChatManager::get_channel_accent_color_id_object(ChannelId channel_id) const { + return td_->theme_manager_->get_accent_color_id_object(get_channel_accent_color_id(channel_id), + AccentColorId(channel_id)); +} + +CustomEmojiId ChatManager::get_chat_background_custom_emoji_id(ChatId chat_id) const { + return CustomEmojiId(); +} + +CustomEmojiId ChatManager::get_channel_background_custom_emoji_id(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return CustomEmojiId(); + } + + return c->background_custom_emoji_id; +} + +int32 ChatManager::get_chat_profile_accent_color_id_object(ChatId chat_id) const { + return -1; +} + +int32 ChatManager::get_channel_profile_accent_color_id_object(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return -1; + } + return td_->theme_manager_->get_profile_accent_color_id_object(c->profile_accent_color_id); +} + +CustomEmojiId ChatManager::get_chat_profile_background_custom_emoji_id(ChatId chat_id) const { + return CustomEmojiId(); +} + +CustomEmojiId ChatManager::get_channel_profile_background_custom_emoji_id(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return CustomEmojiId(); + } + + return c->profile_background_custom_emoji_id; +} + +string ChatManager::get_chat_title(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return string(); + } + return c->title; +} + +string ChatManager::get_channel_title(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + auto min_channel = get_min_channel(channel_id); + if (min_channel != nullptr) { + return min_channel->title_; + } + return string(); + } + return c->title; +} + +RestrictedRights ChatManager::get_chat_default_permissions(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, ChannelType::Unknown); + } + return c->default_permissions; +} + +RestrictedRights ChatManager::get_channel_default_permissions(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, ChannelType::Unknown); + } + return c->default_permissions; +} + +td_api::object_ptr ChatManager::get_chat_emoji_status_object(ChatId chat_id) const { + return nullptr; +} + +td_api::object_ptr ChatManager::get_channel_emoji_status_object(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return nullptr; + } + return c->last_sent_emoji_status.get_emoji_status_object(); +} + +bool ChatManager::get_chat_has_protected_content(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return false; + } + return c->noforwards; +} + +bool ChatManager::get_channel_has_protected_content(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->noforwards; +} + +bool ChatManager::get_channel_stories_hidden(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->stories_hidden; +} + +bool ChatManager::can_poll_channel_active_stories(ChannelId channel_id) const { + const Channel *c = get_channel(channel_id); + return need_poll_channel_active_stories(c, channel_id) && Time::now() >= c->max_active_story_id_next_reload_time; +} + +bool ChatManager::can_use_premium_custom_emoji_in_channel(ChannelId channel_id) const { + if (!is_megagroup_channel(channel_id)) { + return false; + } + auto channel_full = get_channel_full_const(channel_id); + return channel_full == nullptr || channel_full->emoji_sticker_set_id.is_valid(); +} + +string ChatManager::get_chat_about(ChatId chat_id) { + auto chat_full = get_chat_full_force(chat_id, "get_chat_about"); + if (chat_full != nullptr) { + return chat_full->description; + } + return string(); +} + +string ChatManager::get_channel_about(ChannelId channel_id) { + auto channel_full = get_channel_full_force(channel_id, false, "get_channel_about"); + if (channel_full != nullptr) { + return channel_full->description; + } + return string(); +} + +string ChatManager::get_channel_search_text(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return get_channel_title(channel_id); + } + return PSTRING() << c->title << ' ' << implode(c->usernames.get_active_usernames()); +} + +string ChatManager::get_channel_first_username(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return string(); + } + return c->usernames.get_first_username(); +} + +string ChatManager::get_channel_editable_username(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return string(); + } + return c->usernames.get_editable_username(); +} + +ChannelId ChatManager::get_unsupported_channel_id() { + return ChannelId(static_cast(G()->is_test_dc() ? 10304875 : 1535424647)); +} + +void ChatManager::set_chat_description(ChatId chat_id, const string &description, Promise &&promise) { + auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); + auto c = get_chat(chat_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!get_chat_permissions(c).can_change_info_and_settings()) { + return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); + } + + td_->create_handler(std::move(promise))->send(DialogId(chat_id), new_description); +} + +void ChatManager::set_channel_username(ChannelId channel_id, const string &username, Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to change supergroup username")); + } + + if (!username.empty() && !is_allowed_username(username)) { + return promise.set_error(Status::Error(400, "Username is invalid")); + } + + td_->create_handler(std::move(promise))->send(channel_id, username); +} + +void ChatManager::toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, + Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to change username")); + } + if (!c->usernames.can_toggle(username)) { + return promise.set_error(Status::Error(400, "Wrong username specified")); + } + td_->create_handler(std::move(promise))->send(channel_id, std::move(username), is_active); +} + +void ChatManager::disable_all_channel_usernames(ChannelId channel_id, Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to disable usernames")); + } + td_->create_handler(std::move(promise))->send(channel_id); +} + +void ChatManager::reorder_channel_usernames(ChannelId channel_id, vector &&usernames, Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to reorder usernames")); + } + if (!c->usernames.can_reorder_to(usernames)) { + return promise.set_error(Status::Error(400, "Invalid username order specified")); + } + if (usernames.size() <= 1) { + return promise.set_value(Unit()); + } + td_->create_handler(std::move(promise))->send(channel_id, std::move(usernames)); +} + +void ChatManager::on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, + Promise &&promise) { + auto *c = get_channel(channel_id); + CHECK(c != nullptr); + if (!c->usernames.can_toggle(username)) { + return reload_channel(channel_id, std::move(promise), "on_update_channel_username_is_active"); + } + on_update_channel_usernames(c, channel_id, c->usernames.toggle(username, is_active)); + update_channel(c, channel_id); + promise.set_value(Unit()); +} + +void ChatManager::on_deactivate_channel_usernames(ChannelId channel_id, Promise &&promise) { + auto *c = get_channel(channel_id); + CHECK(c != nullptr); + on_update_channel_usernames(c, channel_id, c->usernames.deactivate_all()); + update_channel(c, channel_id); + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_active_usernames_order(ChannelId channel_id, vector &&usernames, + Promise &&promise) { + auto *c = get_channel(channel_id); + CHECK(c != nullptr); + if (!c->usernames.can_reorder_to(usernames)) { + return reload_channel(channel_id, std::move(promise), "on_update_channel_active_usernames_order"); + } + on_update_channel_usernames(c, channel_id, c->usernames.reorder_to(std::move(usernames))); + update_channel(c, channel_id); + promise.set_value(Unit()); +} + +void ChatManager::set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise) { + if (!accent_color_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); + } + + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (c->is_megagroup) { + return promise.set_error(Status::Error(400, "Accent color can be changed only in channel chats")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { + return promise.set_error(Status::Error(400, "Not enough rights in the channel")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, false, accent_color_id, background_custom_emoji_id); +} + +void ChatManager::set_channel_profile_accent_color(ChannelId channel_id, AccentColorId profile_accent_color_id, + CustomEmojiId profile_background_custom_emoji_id, + Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { + return promise.set_error(Status::Error(400, "Not enough rights in the chat")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, true, profile_accent_color_id, profile_background_custom_emoji_id); +} + +void ChatManager::set_channel_emoji_status(ChannelId channel_id, const EmojiStatus &emoji_status, + Promise &&promise) { + const auto *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { + return promise.set_error(Status::Error(400, "Not enough rights in the chat")); + } + + add_recent_emoji_status(td_, emoji_status); + + td_->create_handler(std::move(promise))->send(channel_id, emoji_status); +} + +void ChatManager::set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Chat sticker set can be set only for supergroups")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { + return promise.set_error(Status::Error(400, "Not enough rights to change supergroup sticker set")); + } + + telegram_api::object_ptr input_sticker_set; + if (!sticker_set_id.is_valid()) { + input_sticker_set = telegram_api::make_object(); + } else { + input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); + if (input_sticker_set == nullptr) { + return promise.set_error(Status::Error(400, "Sticker set not found")); + } + } + + auto channel_full = get_channel_full(channel_id, false, "set_channel_sticker_set"); + if (channel_full != nullptr && !channel_full->can_set_sticker_set) { + return promise.set_error(Status::Error(400, "Can't set supergroup sticker set")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); +} + +void ChatManager::set_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Cuctom emoji sticker set can be set only for supergroups")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { + return promise.set_error( + Status::Error(400, "Not enough rights to change custom emoji sticker set in the supergroup")); + } + + telegram_api::object_ptr input_sticker_set; + if (!sticker_set_id.is_valid()) { + input_sticker_set = telegram_api::make_object(); + } else { + input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); + if (input_sticker_set == nullptr) { + return promise.set_error(Status::Error(400, "Sticker set not found")); + } + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); +} + +void ChatManager::set_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Unrestrict boost count can be set only for supergroups")); + } + if (!get_channel_status(c).can_restrict_members()) { + return promise.set_error( + Status::Error(400, "Not enough rights to change unrestrict boost count set in the supergroup")); + } + if (unrestrict_boost_count < 0 || unrestrict_boost_count > 8) { + return promise.set_error(Status::Error(400, "Invalid new value for the unrestrict boost count specified")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, unrestrict_boost_count); +} + +void ChatManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (get_channel_type(c) == ChannelType::Megagroup) { + return promise.set_error(Status::Error(400, "Message signatures can't be toggled in supergroups")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { + 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); +} + +void ChatManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { + return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); + } + if (!get_channel_status(c).can_restrict_members()) { + return promise.set_error(Status::Error(400, "Not enough rights")); + } + + td_->create_handler(std::move(promise))->send(channel_id, join_to_send); +} + +void ChatManager::toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { + return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); + } + if (!get_channel_status(c).can_restrict_members()) { + return promise.set_error(Status::Error(400, "Not enough rights")); + } + + td_->create_handler(std::move(promise))->send(channel_id, join_request); +} + +void ChatManager::toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { + return promise.set_error(Status::Error(400, "Not enough rights to toggle all supergroup history availability")); + } + if (get_channel_type(c) != ChannelType::Megagroup) { + return promise.set_error(Status::Error(400, "Message history can be hidden in supergroups only")); + } + if (c->is_forum && !is_all_history_available) { + return promise.set_error(Status::Error(400, "Message history can't be hidden in forum supergroups")); + } + if (c->has_linked_channel && !is_all_history_available) { + return promise.set_error(Status::Error(400, "Message history can't be hidden in discussion supergroups")); + } + // it can be toggled in public chats, but will not affect them + + td_->create_handler(std::move(promise))->send(channel_id, is_all_history_available); +} + +void ChatManager::toggle_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to disable sponsored messages")); + } + if (get_channel_type(c) != ChannelType::Broadcast) { + return promise.set_error(Status::Error(400, "Sponsored messages can be disabled only in channels")); + } + + td_->create_handler(std::move(promise)) + ->send(channel_id, can_have_sponsored_messages); +} + +Status ChatManager::can_hide_chat_participants(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return Status::Error(400, "Basic group not found"); + } + if (!get_chat_permissions(c).is_creator()) { + return Status::Error(400, "Not enough rights to hide group members"); + } + if (c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { + return Status::Error(400, "The basic group is too small"); + } + return Status::OK(); +} + +Status ChatManager::can_hide_channel_participants(ChannelId channel_id, const ChannelFull *channel_full) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return Status::Error(400, "Supergroup not found"); + } + if (!get_channel_status(c).can_restrict_members()) { + return Status::Error(400, "Not enough rights to hide group members"); + } + if (get_channel_type(c) != ChannelType::Megagroup) { + return Status::Error(400, "Group members are hidden by default in channels"); + } + if (channel_full != nullptr && channel_full->has_hidden_participants) { + return Status::OK(); + } + if (c->participant_count > 0 && + c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { + return Status::Error(400, "The supergroup is too small"); + } + return Status::OK(); +} + +void ChatManager::toggle_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, + Promise &&promise) { + auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_hidden_participants"); + TRY_STATUS_PROMISE(promise, can_hide_channel_participants(channel_id, channel_full)); + + td_->create_handler(std::move(promise))->send(channel_id, has_hidden_participants); +} + +Status ChatManager::can_toggle_chat_aggressive_anti_spam(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return Status::Error(400, "Basic group not found"); + } + if (!get_chat_permissions(c).is_creator()) { + return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); + } + if (c->participant_count < + td_->option_manager_->get_option_integer("aggressive_anti_spam_supergroup_member_count_min")) { + return Status::Error(400, "The basic group is too small"); + } + return Status::OK(); +} + +Status ChatManager::can_toggle_channel_aggressive_anti_spam(ChannelId channel_id, + const ChannelFull *channel_full) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return Status::Error(400, "Supergroup not found"); + } + if (!get_channel_status(c).can_delete_messages()) { + return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); + } + if (get_channel_type(c) != ChannelType::Megagroup) { + return Status::Error(400, "Aggressive anti-spam checks can be enabled in supergroups only"); + } + if (c->is_gigagroup) { + return Status::Error(400, "Aggressive anti-spam checks can't be enabled in broadcast supergroups"); + } + if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled) { + return Status::OK(); + } + if (c->has_location || begins_with(c->usernames.get_editable_username(), "translation_")) { + return Status::OK(); + } + if (c->participant_count > 0 && c->participant_count < td_->option_manager_->get_option_integer( + "aggressive_anti_spam_supergroup_member_count_min")) { + return Status::Error(400, "The supergroup is too small"); + } + return Status::OK(); +} + +void ChatManager::toggle_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, + bool has_aggressive_anti_spam_enabled, + Promise &&promise) { + auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_aggressive_anti_spam_enabled"); + TRY_STATUS_PROMISE(promise, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full)); + + td_->create_handler(std::move(promise))->send(channel_id, has_aggressive_anti_spam_enabled); +} + +void ChatManager::toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (c->is_forum == is_forum) { + return promise.set_value(Unit()); + } + if (!get_channel_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to convert the group to a forum")); + } + if (get_channel_type(c) != ChannelType::Megagroup) { + return promise.set_error(Status::Error(400, "Forums can be enabled in supergroups only")); + } + + td_->create_handler(std::move(promise))->send(channel_id, is_forum); +} + +void ChatManager::convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise) { + if (!can_convert_channel_to_gigagroup(channel_id)) { + return promise.set_error(Status::Error(400, "Can't convert the chat to a broadcast group")); + } + + td_->dialog_manager_->remove_dialog_suggested_action( + SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); + + td_->create_handler(std::move(promise))->send(channel_id); +} + +void ChatManager::set_channel_description(ChannelId channel_id, const string &description, Promise &&promise) { + auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { + return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); + } + + td_->create_handler(std::move(promise))->send(DialogId(channel_id), new_description); +} + +void ChatManager::set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, + Promise &&promise) { + if (!dialog_id.is_valid() && !discussion_dialog_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid chat identifiers specified")); + } + + ChannelId broadcast_channel_id; + telegram_api::object_ptr broadcast_input_channel; + if (dialog_id.is_valid()) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_discussion_group 1")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + if (dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + + broadcast_channel_id = dialog_id.get_channel_id(); + const Channel *c = get_channel(broadcast_channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + + if (c->is_megagroup) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + if (!c->status.can_change_info_and_settings_as_administrator()) { + return promise.set_error(Status::Error(400, "Not enough rights in the channel")); + } + + broadcast_input_channel = get_input_channel(broadcast_channel_id); + CHECK(broadcast_input_channel != nullptr); + } else { + broadcast_input_channel = telegram_api::make_object(); + } + + ChannelId group_channel_id; + telegram_api::object_ptr group_input_channel; + if (discussion_dialog_id.is_valid()) { + if (!td_->dialog_manager_->have_dialog_force(discussion_dialog_id, "set_channel_discussion_group 2")) { + return promise.set_error(Status::Error(400, "Discussion chat not found")); + } + if (discussion_dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); + } + + group_channel_id = discussion_dialog_id.get_channel_id(); + const Channel *c = get_channel(group_channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Discussion chat info not found")); + } + + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); + } + if (!c->status.is_administrator() || !c->status.can_pin_messages()) { + return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); + } + + group_input_channel = get_input_channel(group_channel_id); + CHECK(group_input_channel != nullptr); + } else { + group_input_channel = telegram_api::make_object(); + } + + td_->create_handler(std::move(promise)) + ->send(broadcast_channel_id, std::move(broadcast_input_channel), group_channel_id, + std::move(group_input_channel)); +} + +void ChatManager::set_channel_location(ChannelId channel_id, const DialogLocation &location, Promise &&promise) { + if (location.empty()) { + return promise.set_error(Status::Error(400, "Invalid chat location specified")); + } + + const Channel *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Chat is not a supergroup")); + } + if (!c->status.is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); + } + + td_->create_handler(std::move(promise))->send(channel_id, location); +} + +void ChatManager::set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise &&promise) { + vector allowed_slow_mode_delays{0, 10, 30, 60, 300, 900, 3600}; + if (!td::contains(allowed_slow_mode_delays, slow_mode_delay)) { + return promise.set_error(Status::Error(400, "Invalid new value for slow mode delay")); + } + + if (!dialog_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); + } + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_slow_mode_delay")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + if (dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Chat is not a supergroup")); + } + + auto channel_id = dialog_id.get_channel_id(); + const Channel *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Chat is not a supergroup")); + } + if (!get_channel_status(c).can_restrict_members()) { + return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); + } + + td_->create_handler(std::move(promise))->send(channel_id, slow_mode_delay); +} + +void ChatManager::get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise &&promise) { + if (!dialog_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); + } + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_statistics_dc_id")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + + if (dialog_id.get_type() != DialogType::Channel) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + + auto channel_id = dialog_id.get_channel_id(); + const Channel *c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + + auto channel_full = get_channel_full_force(channel_id, false, "get_channel_statistics_dc_id"); + if (channel_full == nullptr || !channel_full->stats_dc_id.is_exact() || + (for_full_statistics && !channel_full->can_view_statistics)) { + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, for_full_statistics, + promise = std::move(promise)](Result result) mutable { + send_closure(actor_id, &ChatManager::get_channel_statistics_dc_id_impl, channel_id, for_full_statistics, + std::move(promise)); + }); + send_get_channel_full_query(channel_full, channel_id, std::move(query_promise), "get_channel_statistics_dc_id"); + return; + } + + promise.set_value(DcId(channel_full->stats_dc_id)); +} + +void ChatManager::get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto channel_full = get_channel_full(channel_id, false, "get_channel_statistics_dc_id_impl"); + if (channel_full == nullptr) { + return promise.set_error(Status::Error(400, "Chat full info not found")); + } + + if (!channel_full->stats_dc_id.is_exact() || (for_full_statistics && !channel_full->can_view_statistics)) { + return promise.set_error(Status::Error(400, "Chat statistics are not available")); + } + + promise.set_value(DcId(channel_full->stats_dc_id)); +} + +bool ChatManager::can_get_channel_message_statistics(ChannelId channel_id) const { + CHECK(!td_->auth_manager_->is_bot()); + const Channel *c = get_channel(channel_id); + if (c == nullptr || c->is_megagroup) { + return false; + } + + auto channel_full = get_channel_full(channel_id); + if (channel_full != nullptr) { + return channel_full->stats_dc_id.is_exact(); + } + + return c->status.can_post_messages(); +} + +bool ChatManager::can_get_channel_story_statistics(ChannelId channel_id) const { + CHECK(!td_->auth_manager_->is_bot()); + const Channel *c = get_channel(channel_id); + if (c == nullptr || c->is_megagroup) { + return false; + } + + auto channel_full = get_channel_full(channel_id); + if (channel_full != nullptr) { + return channel_full->stats_dc_id.is_exact(); + } + + return c->status.can_post_messages(); +} + +bool ChatManager::can_convert_channel_to_gigagroup(ChannelId channel_id) const { + const Channel *c = get_channel(channel_id); + return c == nullptr || get_channel_type(c) != ChannelType::Megagroup || !get_channel_status(c).is_creator() || + c->is_gigagroup || + c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + ChannelType::Unknown); +} + +void ChatManager::report_channel_spam(ChannelId channel_id, const vector &message_ids, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "Spam can be reported only in supergroups")); + } + if (!c->status.is_administrator()) { + return promise.set_error(Status::Error(400, "Spam can be reported only by chat administrators")); + } + + FlatHashMap, DialogIdHash> server_message_ids; + for (auto &message_id : message_ids) { + if (message_id.is_valid_scheduled()) { + return promise.set_error(Status::Error(400, "Can't report scheduled messages")); + } + + if (!message_id.is_valid()) { + return promise.set_error(Status::Error(400, "Message not found")); + } + + if (!message_id.is_server()) { + continue; + } + + auto sender_dialog_id = td_->messages_manager_->get_dialog_message_sender({DialogId(channel_id), message_id}); + CHECK(sender_dialog_id.get_type() != DialogType::SecretChat); + if (sender_dialog_id.is_valid() && sender_dialog_id != td_->dialog_manager_->get_my_dialog_id() && + td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { + server_message_ids[sender_dialog_id].push_back(message_id); + } + } + if (server_message_ids.empty()) { + return promise.set_value(Unit()); + } + + MultiPromiseActorSafe mpas{"ReportSupergroupSpamMultiPromiseActor"}; + mpas.add_promise(std::move(promise)); + auto lock_promise = mpas.get_promise(); + + for (auto &it : server_message_ids) { + td_->create_handler(mpas.get_promise())->send(channel_id, it.first, it.second); + } + + lock_promise.set_value(Unit()); +} + +void ChatManager::report_channel_anti_spam_false_positive(ChannelId channel_id, MessageId message_id, + Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + if (!c->is_megagroup) { + return promise.set_error(Status::Error(400, "The chat is not a supergroup")); + } + if (!c->status.is_administrator()) { + return promise.set_error( + Status::Error(400, "Anti-spam checks false positives can be reported only by chat administrators")); + } + + if (!message_id.is_valid() || !message_id.is_server()) { + return promise.set_error(Status::Error(400, "Invalid message identifier specified")); + } + + td_->create_handler(std::move(promise))->send(channel_id, message_id); +} + +void ChatManager::delete_chat(ChatId chat_id, Promise &&promise) { + auto c = get_chat(chat_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!get_chat_status(c).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to delete the chat")); + } + if (!c->is_active) { + return promise.set_error(Status::Error(400, "Chat is already deactivated")); + } + + td_->create_handler(std::move(promise))->send(chat_id); +} + +void ChatManager::delete_channel(ChannelId channel_id, Promise &&promise) { + auto c = get_channel(channel_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Chat info not found")); + } + if (!get_channel_can_be_deleted(c)) { + return promise.set_error(Status::Error(400, "The chat can't be deleted")); + } + + td_->create_handler(std::move(promise))->send(channel_id); +} + +vector ChatManager::get_channel_ids(vector> &&chats, const char *source) { + vector channel_ids; + for (auto &chat : chats) { + auto channel_id = get_channel_id(chat); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << " in " << to_string(chat); + continue; + } + on_get_chat(std::move(chat), source); + if (have_channel(channel_id)) { + channel_ids.push_back(channel_id); + } + } + return channel_ids; +} + +vector ChatManager::get_dialog_ids(vector> &&chats, const char *source) { + vector dialog_ids; + for (auto &chat : chats) { + auto channel_id = get_channel_id(chat); + if (!channel_id.is_valid()) { + auto chat_id = get_chat_id(chat); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid chat from " << source << " in " << to_string(chat); + } else { + dialog_ids.push_back(DialogId(chat_id)); + } + } else { + dialog_ids.push_back(DialogId(channel_id)); + } + on_get_chat(std::move(chat), source); + } + return dialog_ids; +} + +void ChatManager::return_created_public_dialogs(Promise> &&promise, + const vector &channel_ids) { + if (!promise) { + return; + } + + auto total_count = narrow_cast(channel_ids.size()); + promise.set_value(td_api::make_object( + total_count, transform(channel_ids, [](ChannelId channel_id) { return DialogId(channel_id).get(); }))); +} + +bool ChatManager::is_suitable_created_public_channel(PublicDialogType type, const Channel *c) { + if (c == nullptr || !c->status.is_creator()) { + return false; + } + + switch (type) { + case PublicDialogType::HasUsername: + return c->usernames.has_editable_username(); + case PublicDialogType::IsLocationBased: + return c->has_location; + case PublicDialogType::ForPersonalDialog: + return !c->is_megagroup && c->usernames.has_first_username(); + default: + UNREACHABLE(); + return false; + } +} + +void ChatManager::get_created_public_dialogs(PublicDialogType type, + Promise> &&promise, bool from_binlog) { + auto index = static_cast(type); + if (created_public_channels_inited_[index]) { + return return_created_public_dialogs(std::move(promise), created_public_channels_[index]); + } + + if (get_created_public_channels_queries_[index].empty() && G()->use_message_database()) { + auto pmc_key = PSTRING() << "public_channels" << index; + auto str = G()->td_db()->get_binlog_pmc()->get(pmc_key); + if (!str.empty()) { + auto r_channel_ids = transform(full_split(Slice(str), ','), [](Slice str) -> Result { + TRY_RESULT(channel_id_int, to_integer_safe(str)); + ChannelId channel_id(channel_id_int); + if (!channel_id.is_valid()) { + return Status::Error("Have invalid channel ID"); + } + return channel_id; + }); + if (any_of(r_channel_ids, [](const auto &r_channel_id) { return r_channel_id.is_error(); })) { + LOG(ERROR) << "Can't parse " << str; + G()->td_db()->get_binlog_pmc()->erase(pmc_key); + } else { + Dependencies dependencies; + vector channel_ids; + for (auto &r_channel_id : r_channel_ids) { + auto channel_id = r_channel_id.move_as_ok(); + dependencies.add_dialog_and_dependencies(DialogId(channel_id)); + channel_ids.push_back(channel_id); + } + if (!dependencies.resolve_force(td_, "get_created_public_dialogs")) { + G()->td_db()->get_binlog_pmc()->erase(pmc_key); + } else { + for (auto channel_id : channel_ids) { + if (is_suitable_created_public_channel(type, get_channel(channel_id))) { + created_public_channels_[index].push_back(channel_id); + } + } + created_public_channels_inited_[index] = true; + + if (from_binlog) { + return_created_public_dialogs(std::move(promise), created_public_channels_[index]); + promise = {}; + } + } + } + } + } + + reload_created_public_dialogs(type, std::move(promise)); +} + +void ChatManager::reload_created_public_dialogs(PublicDialogType type, + Promise> &&promise) { + auto index = static_cast(type); + get_created_public_channels_queries_[index].push_back(std::move(promise)); + if (get_created_public_channels_queries_[index].size() == 1) { + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), type](Result &&result) { + send_closure(actor_id, &ChatManager::finish_get_created_public_dialogs, type, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(type, false); + } +} + +void ChatManager::finish_get_created_public_dialogs(PublicDialogType type, Result &&result) { + G()->ignore_result_if_closing(result); + + auto index = static_cast(type); + auto promises = std::move(get_created_public_channels_queries_[index]); + reset_to_empty(get_created_public_channels_queries_[index]); + if (result.is_error()) { + return fail_promises(promises, result.move_as_error()); + } + + CHECK(created_public_channels_inited_[index]); + for (auto &promise : promises) { + return_created_public_dialogs(std::move(promise), created_public_channels_[index]); + } +} + +void ChatManager::update_created_public_channels(Channel *c, ChannelId channel_id) { + for (auto type : + {PublicDialogType::HasUsername, PublicDialogType::IsLocationBased, PublicDialogType::ForPersonalDialog}) { + auto index = static_cast(type); + if (!created_public_channels_inited_[index]) { + continue; + } + bool was_changed = false; + if (!is_suitable_created_public_channel(type, c)) { + was_changed = td::remove(created_public_channels_[index], channel_id); + } else { + if (!td::contains(created_public_channels_[index], channel_id)) { + created_public_channels_[index].push_back(channel_id); + was_changed = true; + } + } + if (was_changed) { + save_created_public_channels(type); + + reload_created_public_dialogs(type, Promise>()); + } + } +} + +void ChatManager::on_get_created_public_channels(PublicDialogType type, + vector> &&chats) { + auto index = static_cast(type); + auto channel_ids = get_channel_ids(std::move(chats), "on_get_created_public_channels"); + if (created_public_channels_inited_[index] && created_public_channels_[index] == channel_ids) { + return; + } + created_public_channels_[index].clear(); + for (auto channel_id : channel_ids) { + td_->dialog_manager_->force_create_dialog(DialogId(channel_id), "on_get_created_public_channels"); + if (is_suitable_created_public_channel(type, get_channel(channel_id))) { + created_public_channels_[index].push_back(channel_id); + } + } + created_public_channels_inited_[index] = true; + + save_created_public_channels(type); +} + +void ChatManager::save_created_public_channels(PublicDialogType type) { + auto index = static_cast(type); + CHECK(created_public_channels_inited_[index]); + if (G()->use_message_database()) { + G()->td_db()->get_binlog_pmc()->set( + PSTRING() << "public_channels" << index, + implode( + transform(created_public_channels_[index], [](auto channel_id) { return PSTRING() << channel_id.get(); }), + ',')); + } +} + +void ChatManager::check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise) { + td_->create_handler(std::move(promise))->send(type, true); +} + +bool ChatManager::are_created_public_broadcasts_inited() const { + return created_public_channels_inited_[2]; +} + +const vector &ChatManager::get_created_public_broadcasts() const { + return created_public_channels_[2]; +} + +vector ChatManager::get_dialogs_for_discussion(Promise &&promise) { + if (dialogs_for_discussion_inited_) { + promise.set_value(Unit()); + return transform(dialogs_for_discussion_, [&](DialogId dialog_id) { + td_->dialog_manager_->force_create_dialog(dialog_id, "get_dialogs_for_discussion"); + return dialog_id; + }); + } + + td_->create_handler(std::move(promise))->send(); + return {}; +} + +void ChatManager::on_get_dialogs_for_discussion(vector> &&chats) { + dialogs_for_discussion_inited_ = true; + dialogs_for_discussion_ = get_dialog_ids(std::move(chats), "on_get_dialogs_for_discussion"); +} + +void ChatManager::update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable) { + if (!dialogs_for_discussion_inited_) { + return; + } + + if (is_suitable) { + if (!td::contains(dialogs_for_discussion_, dialog_id)) { + LOG(DEBUG) << "Add " << dialog_id << " to list of suitable discussion chats"; + dialogs_for_discussion_.insert(dialogs_for_discussion_.begin(), dialog_id); + } + } else { + if (td::remove(dialogs_for_discussion_, dialog_id)) { + LOG(DEBUG) << "Remove " << dialog_id << " from list of suitable discussion chats"; + } + } +} + +vector ChatManager::get_inactive_channels(Promise &&promise) { + if (inactive_channel_ids_inited_) { + promise.set_value(Unit()); + return transform(inactive_channel_ids_, [&](ChannelId channel_id) { return DialogId(channel_id); }); + } + + td_->create_handler(std::move(promise))->send(); + return {}; +} + +void ChatManager::on_get_inactive_channels(vector> &&chats, Promise &&promise) { + auto channel_ids = get_channel_ids(std::move(chats), "on_get_inactive_channels"); + + MultiPromiseActorSafe mpas{"GetInactiveChannelsMultiPromiseActor"}; + mpas.add_promise( + PromiseCreator::lambda([actor_id = actor_id(this), channel_ids, promise = std::move(promise)](Unit) mutable { + send_closure(actor_id, &ChatManager::on_create_inactive_channels, std::move(channel_ids), std::move(promise)); + })); + mpas.set_ignore_errors(true); + auto lock_promise = mpas.get_promise(); + + for (auto channel_id : channel_ids) { + td_->messages_manager_->create_dialog(DialogId(channel_id), false, mpas.get_promise()); + } + + lock_promise.set_value(Unit()); +} + +void ChatManager::on_create_inactive_channels(vector &&channel_ids, Promise &&promise) { + inactive_channel_ids_inited_ = true; + inactive_channel_ids_ = std::move(channel_ids); + promise.set_value(Unit()); +} + +void ChatManager::remove_inactive_channel(ChannelId channel_id) { + if (inactive_channel_ids_inited_ && td::remove(inactive_channel_ids_, channel_id)) { + LOG(DEBUG) << "Remove " << channel_id << " from list of inactive channels"; + } +} + +void ChatManager::register_message_channels(MessageFullId message_full_id, vector channel_ids) { + auto dialog_id = message_full_id.get_dialog_id(); + CHECK(dialog_id.get_type() == DialogType::Channel); + if (!have_channel(dialog_id.get_channel_id())) { + return; + } + for (auto channel_id : channel_ids) { + CHECK(channel_id.is_valid()); + if (!have_channel(channel_id)) { + channel_messages_[channel_id].insert(message_full_id); + + // get info about the channel + get_channel_queries_.add_query(channel_id.get(), Promise(), "register_message_channels"); + } + } +} + +void ChatManager::unregister_message_channels(MessageFullId message_full_id, vector channel_ids) { + if (channel_messages_.empty()) { + // fast path + return; + } + for (auto channel_id : channel_ids) { + auto it = channel_messages_.find(channel_id); + if (it != channel_messages_.end()) { + it->second.erase(message_full_id); + if (it->second.empty()) { + channel_messages_.erase(it); + } + } + } +} + +ChatId ChatManager::get_chat_id(const tl_object_ptr &chat) { + CHECK(chat != nullptr); + switch (chat->get_id()) { + case telegram_api::chatEmpty::ID: + return ChatId(static_cast(chat.get())->id_); + case telegram_api::chat::ID: + return ChatId(static_cast(chat.get())->id_); + case telegram_api::chatForbidden::ID: + return ChatId(static_cast(chat.get())->id_); + default: + return ChatId(); + } +} + +ChannelId ChatManager::get_channel_id(const tl_object_ptr &chat) { + CHECK(chat != nullptr); + switch (chat->get_id()) { + case telegram_api::channel::ID: + return ChannelId(static_cast(chat.get())->id_); + case telegram_api::channelForbidden::ID: + return ChannelId(static_cast(chat.get())->id_); + default: + return ChannelId(); + } +} + +DialogId ChatManager::get_dialog_id(const tl_object_ptr &chat) { + auto channel_id = get_channel_id(chat); + if (channel_id.is_valid()) { + return DialogId(channel_id); + } + return DialogId(get_chat_id(chat)); +} + +class ChatManager::ChatLogEvent { + public: + ChatId chat_id; + const Chat *c_in = nullptr; + unique_ptr c_out; + + ChatLogEvent() = default; + + ChatLogEvent(ChatId chat_id, const Chat *c) : chat_id(chat_id), c_in(c) { + } + + template + void store(StorerT &storer) const { + td::store(chat_id, storer); + td::store(*c_in, storer); + } + + template + void parse(ParserT &parser) { + td::parse(chat_id, parser); + td::parse(c_out, parser); + } +}; + +void ChatManager::save_chat(Chat *c, ChatId chat_id, bool from_binlog) { + if (!G()->use_chat_info_database()) { + return; + } + CHECK(c != nullptr); + if (!c->is_saved) { + if (!from_binlog) { + auto log_event = ChatLogEvent(chat_id, c); + auto storer = get_log_event_storer(log_event); + if (c->log_event_id == 0) { + c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Chats, storer); + } else { + binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Chats, storer); + } + } + + save_chat_to_database(c, chat_id); + return; + } +} + +void ChatManager::on_binlog_chat_event(BinlogEvent &&event) { + if (!G()->use_chat_info_database()) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + ChatLogEvent log_event; + if (log_event_parse(log_event, event.get_data()).is_error()) { + LOG(ERROR) << "Failed to load a basic group from binlog"; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + auto chat_id = log_event.chat_id; + if (have_chat(chat_id) || !chat_id.is_valid()) { + LOG(ERROR) << "Skip adding already added " << chat_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + LOG(INFO) << "Add " << chat_id << " from binlog"; + chats_.set(chat_id, std::move(log_event.c_out)); + + Chat *c = get_chat(chat_id); + CHECK(c != nullptr); + c->log_event_id = event.id_; + + update_chat(c, chat_id, true, false); +} + +string ChatManager::get_chat_database_key(ChatId chat_id) { + return PSTRING() << "gr" << chat_id.get(); +} + +string ChatManager::get_chat_database_value(const Chat *c) { + return log_event_store(*c).as_slice().str(); +} + +void ChatManager::save_chat_to_database(Chat *c, ChatId chat_id) { + CHECK(c != nullptr); + if (c->is_being_saved) { + return; + } + if (loaded_from_database_chats_.count(chat_id)) { + save_chat_to_database_impl(c, chat_id, get_chat_database_value(c)); + return; + } + if (load_chat_from_database_queries_.count(chat_id) != 0) { + return; + } + + load_chat_from_database_impl(chat_id, Auto()); +} + +void ChatManager::save_chat_to_database_impl(Chat *c, ChatId chat_id, string value) { + CHECK(c != nullptr); + CHECK(load_chat_from_database_queries_.count(chat_id) == 0); + CHECK(!c->is_being_saved); + c->is_being_saved = true; + c->is_saved = true; + LOG(INFO) << "Trying to save to database " << chat_id; + G()->td_db()->get_sqlite_pmc()->set( + get_chat_database_key(chat_id), std::move(value), PromiseCreator::lambda([chat_id](Result<> result) { + send_closure(G()->chat_manager(), &ChatManager::on_save_chat_to_database, chat_id, result.is_ok()); + })); +} + +void ChatManager::on_save_chat_to_database(ChatId chat_id, bool success) { + if (G()->close_flag()) { + return; + } + + Chat *c = get_chat(chat_id); + CHECK(c != nullptr); + CHECK(c->is_being_saved); + CHECK(load_chat_from_database_queries_.count(chat_id) == 0); + c->is_being_saved = false; + + if (!success) { + LOG(ERROR) << "Failed to save " << chat_id << " to database"; + c->is_saved = false; + } else { + LOG(INFO) << "Successfully saved " << chat_id << " to database"; + } + if (c->is_saved) { + if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } else { + save_chat(c, chat_id, c->log_event_id != 0); + } +} + +void ChatManager::load_chat_from_database(Chat *c, ChatId chat_id, Promise promise) { + if (loaded_from_database_chats_.count(chat_id)) { + promise.set_value(Unit()); + return; + } + + CHECK(c == nullptr || !c->is_being_saved); + load_chat_from_database_impl(chat_id, std::move(promise)); +} + +void ChatManager::load_chat_from_database_impl(ChatId chat_id, Promise promise) { + LOG(INFO) << "Load " << chat_id << " from database"; + auto &load_chat_queries = load_chat_from_database_queries_[chat_id]; + load_chat_queries.push_back(std::move(promise)); + if (load_chat_queries.size() == 1u) { + G()->td_db()->get_sqlite_pmc()->get(get_chat_database_key(chat_id), PromiseCreator::lambda([chat_id](string value) { + send_closure(G()->chat_manager(), &ChatManager::on_load_chat_from_database, + chat_id, std::move(value), false); + })); + } +} + +void ChatManager::on_load_chat_from_database(ChatId chat_id, string value, bool force) { + if (G()->close_flag() && !force) { + // the chat is in Binlog and will be saved after restart + return; + } + + CHECK(chat_id.is_valid()); + if (!loaded_from_database_chats_.insert(chat_id).second) { + return; + } + + auto it = load_chat_from_database_queries_.find(chat_id); + vector> promises; + if (it != load_chat_from_database_queries_.end()) { + promises = std::move(it->second); + CHECK(!promises.empty()); + load_chat_from_database_queries_.erase(it); + } + + LOG(INFO) << "Successfully loaded " << chat_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_chat_database_key(chat_id), Auto()); + // return; + + Chat *c = get_chat(chat_id); + if (c == nullptr) { + if (!value.empty()) { + c = add_chat(chat_id); + + if (log_event_parse(*c, value).is_error()) { + LOG(ERROR) << "Failed to load " << chat_id << " from database"; + chats_.erase(chat_id); + } else { + c->is_saved = true; + update_chat(c, chat_id, true, true); + } + } + } else { + CHECK(!c->is_saved); // chat can't be saved before load completes + CHECK(!c->is_being_saved); + auto new_value = get_chat_database_value(c); + if (value != new_value) { + save_chat_to_database_impl(c, chat_id, std::move(new_value)); + } else if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } + + if (c != nullptr && c->migrated_to_channel_id.is_valid() && + !have_channel_force(c->migrated_to_channel_id, "on_load_chat_from_database")) { + LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id; + } + + set_promises(promises); +} + +bool ChatManager::have_chat_force(ChatId chat_id, const char *source) { + return get_chat_force(chat_id, source) != nullptr; +} + +ChatManager::Chat *ChatManager::get_chat_force(ChatId chat_id, const char *source) { + if (!chat_id.is_valid()) { + return nullptr; + } + + Chat *c = get_chat(chat_id); + if (c != nullptr) { + if (c->migrated_to_channel_id.is_valid() && !have_channel_force(c->migrated_to_channel_id, source)) { + LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id << " from " << source; + } + + return c; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (loaded_from_database_chats_.count(chat_id)) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << chat_id << " from database from " << source; + on_load_chat_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_database_key(chat_id)), true); + return get_chat(chat_id); +} + +class ChatManager::ChannelLogEvent { + public: + ChannelId channel_id; + const Channel *c_in = nullptr; + unique_ptr c_out; + + ChannelLogEvent() = default; + + ChannelLogEvent(ChannelId channel_id, const Channel *c) : channel_id(channel_id), c_in(c) { + } + + template + void store(StorerT &storer) const { + td::store(channel_id, storer); + td::store(*c_in, storer); + } + + template + void parse(ParserT &parser) { + td::parse(channel_id, parser); + td::parse(c_out, parser); + } +}; + +void ChatManager::save_channel(Channel *c, ChannelId channel_id, bool from_binlog) { + if (!G()->use_chat_info_database()) { + return; + } + CHECK(c != nullptr); + if (!c->is_saved) { + if (!from_binlog) { + auto log_event = ChannelLogEvent(channel_id, c); + auto storer = get_log_event_storer(log_event); + if (c->log_event_id == 0) { + c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Channels, storer); + } else { + binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Channels, storer); + } + } + + save_channel_to_database(c, channel_id); + return; + } +} + +void ChatManager::on_binlog_channel_event(BinlogEvent &&event) { + if (!G()->use_chat_info_database()) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + ChannelLogEvent log_event; + if (log_event_parse(log_event, event.get_data()).is_error()) { + LOG(ERROR) << "Failed to load a supergroup from binlog"; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + auto channel_id = log_event.channel_id; + if (have_channel(channel_id) || !channel_id.is_valid()) { + LOG(ERROR) << "Skip adding already added " << channel_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + LOG(INFO) << "Add " << channel_id << " from binlog"; + channels_.set(channel_id, std::move(log_event.c_out)); + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + c->log_event_id = event.id_; + + update_channel(c, channel_id, true, false); +} + +string ChatManager::get_channel_database_key(ChannelId channel_id) { + return PSTRING() << "ch" << channel_id.get(); +} + +string ChatManager::get_channel_database_value(const Channel *c) { + return log_event_store(*c).as_slice().str(); +} + +void ChatManager::save_channel_to_database(Channel *c, ChannelId channel_id) { + CHECK(c != nullptr); + if (c->is_being_saved) { + return; + } + if (loaded_from_database_channels_.count(channel_id)) { + save_channel_to_database_impl(c, channel_id, get_channel_database_value(c)); + return; + } + if (load_channel_from_database_queries_.count(channel_id) != 0) { + return; + } + + load_channel_from_database_impl(channel_id, Auto()); +} + +void ChatManager::save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value) { + CHECK(c != nullptr); + CHECK(load_channel_from_database_queries_.count(channel_id) == 0); + CHECK(!c->is_being_saved); + c->is_being_saved = true; + c->is_saved = true; + LOG(INFO) << "Trying to save to database " << channel_id; + G()->td_db()->get_sqlite_pmc()->set( + get_channel_database_key(channel_id), std::move(value), PromiseCreator::lambda([channel_id](Result<> result) { + send_closure(G()->chat_manager(), &ChatManager::on_save_channel_to_database, channel_id, result.is_ok()); + })); +} + +void ChatManager::on_save_channel_to_database(ChannelId channel_id, bool success) { + if (G()->close_flag()) { + return; + } + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + CHECK(c->is_being_saved); + CHECK(load_channel_from_database_queries_.count(channel_id) == 0); + c->is_being_saved = false; + + if (!success) { + LOG(ERROR) << "Failed to save " << channel_id << " to database"; + c->is_saved = false; + } else { + LOG(INFO) << "Successfully saved " << channel_id << " to database"; + } + if (c->is_saved) { + if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } else { + save_channel(c, channel_id, c->log_event_id != 0); + } +} + +void ChatManager::load_channel_from_database(Channel *c, ChannelId channel_id, Promise promise) { + if (loaded_from_database_channels_.count(channel_id)) { + promise.set_value(Unit()); + return; + } + + CHECK(c == nullptr || !c->is_being_saved); + load_channel_from_database_impl(channel_id, std::move(promise)); +} + +void ChatManager::load_channel_from_database_impl(ChannelId channel_id, Promise promise) { + LOG(INFO) << "Load " << channel_id << " from database"; + auto &load_channel_queries = load_channel_from_database_queries_[channel_id]; + load_channel_queries.push_back(std::move(promise)); + if (load_channel_queries.size() == 1u) { + G()->td_db()->get_sqlite_pmc()->get(get_channel_database_key(channel_id), + PromiseCreator::lambda([channel_id](string value) { + send_closure(G()->chat_manager(), &ChatManager::on_load_channel_from_database, + channel_id, std::move(value), false); + })); + } +} + +void ChatManager::on_load_channel_from_database(ChannelId channel_id, string value, bool force) { + if (G()->close_flag() && !force) { + // the channel is in Binlog and will be saved after restart + return; + } + + CHECK(channel_id.is_valid()); + if (!loaded_from_database_channels_.insert(channel_id).second) { + return; + } + + auto it = load_channel_from_database_queries_.find(channel_id); + vector> promises; + if (it != load_channel_from_database_queries_.end()) { + promises = std::move(it->second); + CHECK(!promises.empty()); + load_channel_from_database_queries_.erase(it); + } + + LOG(INFO) << "Successfully loaded " << channel_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_channel_database_key(channel_id), Auto()); + // return; + + Channel *c = get_channel(channel_id); + if (c == nullptr) { + if (!value.empty()) { + c = add_channel(channel_id, "on_load_channel_from_database"); + + if (log_event_parse(*c, value).is_error()) { + LOG(ERROR) << "Failed to load " << channel_id << " from database"; + channels_.erase(channel_id); + } else { + c->is_saved = true; + update_channel(c, channel_id, true, true); + } + } + } else { + CHECK(!c->is_saved); // channel can't be saved before load completes + CHECK(!c->is_being_saved); + if (!value.empty()) { + Channel temp_c; + if (log_event_parse(temp_c, value).is_ok()) { + if (c->participant_count == 0 && temp_c.participant_count != 0) { + c->participant_count = temp_c.participant_count; + CHECK(c->is_update_supergroup_sent); + send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); + } + + c->status.update_restrictions(); + temp_c.status.update_restrictions(); + if (temp_c.status != c->status) { + on_channel_status_changed(c, channel_id, temp_c.status, c->status); + CHECK(!c->is_being_saved); + } + + if (temp_c.usernames != c->usernames) { + on_channel_usernames_changed(c, channel_id, temp_c.usernames, c->usernames); + CHECK(!c->is_being_saved); + } + } + } + auto new_value = get_channel_database_value(c); + if (value != new_value) { + save_channel_to_database_impl(c, channel_id, std::move(new_value)); + } else if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } + + set_promises(promises); +} + +bool ChatManager::have_channel_force(ChannelId channel_id, const char *source) { + return get_channel_force(channel_id, source) != nullptr; +} + +ChatManager::Channel *ChatManager::get_channel_force(ChannelId channel_id, const char *source) { + if (!channel_id.is_valid()) { + return nullptr; + } + + Channel *c = get_channel(channel_id); + if (c != nullptr) { + return c; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (loaded_from_database_channels_.count(channel_id)) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << channel_id << " from database from " << source; + on_load_channel_from_database(channel_id, + G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_database_key(channel_id)), true); + return get_channel(channel_id); +} + +void ChatManager::save_chat_full(const ChatFull *chat_full, ChatId chat_id) { + if (!G()->use_chat_info_database()) { + return; + } + + LOG(INFO) << "Trying to save to database full " << chat_id; + CHECK(chat_full != nullptr); + G()->td_db()->get_sqlite_pmc()->set(get_chat_full_database_key(chat_id), get_chat_full_database_value(chat_full), + Auto()); +} + +string ChatManager::get_chat_full_database_key(ChatId chat_id) { + return PSTRING() << "grf" << chat_id.get(); +} + +string ChatManager::get_chat_full_database_value(const ChatFull *chat_full) { + return log_event_store(*chat_full).as_slice().str(); +} + +void ChatManager::on_load_chat_full_from_database(ChatId chat_id, string value) { + LOG(INFO) << "Successfully loaded full " << chat_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); + // return; + + if (get_chat_full(chat_id) != nullptr || value.empty()) { + return; + } + + ChatFull *chat_full = add_chat_full(chat_id); + auto status = log_event_parse(*chat_full, value); + if (status.is_error()) { + // can't happen unless database is broken + LOG(ERROR) << "Repair broken full " << chat_id << ' ' << format::as_hex_dump<4>(Slice(value)); + + // just clean all known data about the chat and pretend that there was nothing in the database + chats_full_.erase(chat_id); + G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); + return; + } + + Dependencies dependencies; + dependencies.add(chat_id); + dependencies.add(chat_full->creator_user_id); + for (auto &participant : chat_full->participants) { + dependencies.add_message_sender_dependencies(participant.dialog_id_); + dependencies.add(participant.inviter_user_id_); + } + dependencies.add(chat_full->invite_link.get_creator_user_id()); + if (!dependencies.resolve_force(td_, "on_load_chat_full_from_database")) { + chats_full_.erase(chat_id); + G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); + return; + } + + Chat *c = get_chat(chat_id); + CHECK(c != nullptr); + + bool need_invite_link = c->is_active && c->status.can_manage_invite_links(); + bool have_invite_link = chat_full->invite_link.is_valid(); + if (need_invite_link != have_invite_link) { + if (need_invite_link) { + // ignore ChatFull without invite link + chats_full_.erase(chat_id); + return; + } else { + chat_full->invite_link = DialogInviteLink(); + } + } + + if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { + chat_full->photo = Photo(); + if (c->photo.small_file_id.is_valid()) { + reload_chat_full(chat_id, Auto(), "on_load_chat_full_from_database"); + } + } + + auto photo = std::move(chat_full->photo); + chat_full->photo = Photo(); + on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); + + td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, false); + + chat_full->is_update_chat_full_sent = true; + update_chat_full(chat_full, chat_id, "on_load_chat_full_from_database", true); +} + +ChatManager::ChatFull *ChatManager::get_chat_full_force(ChatId chat_id, const char *source) { + if (!have_chat_force(chat_id, source)) { + return nullptr; + } + + ChatFull *chat_full = get_chat_full(chat_id); + if (chat_full != nullptr) { + return chat_full; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (!unavailable_chat_fulls_.insert(chat_id).second) { + return nullptr; + } + + LOG(INFO) << "Trying to load full " << chat_id << " from database from " << source; + on_load_chat_full_from_database(chat_id, + G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_full_database_key(chat_id))); + return get_chat_full(chat_id); +} + +void ChatManager::save_channel_full(const ChannelFull *channel_full, ChannelId channel_id) { + if (!G()->use_chat_info_database()) { + return; + } + + LOG(INFO) << "Trying to save to database full " << channel_id; + CHECK(channel_full != nullptr); + G()->td_db()->get_sqlite_pmc()->set(get_channel_full_database_key(channel_id), + get_channel_full_database_value(channel_full), Auto()); +} + +string ChatManager::get_channel_full_database_key(ChannelId channel_id) { + return PSTRING() << "chf" << channel_id.get(); +} + +string ChatManager::get_channel_full_database_value(const ChannelFull *channel_full) { + return log_event_store(*channel_full).as_slice().str(); +} + +void ChatManager::on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source) { + LOG(INFO) << "Successfully loaded full " << channel_id << " of size " << value.size() << " from database from " + << source; + // G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); + // return; + + if (get_channel_full(channel_id, true, "on_load_channel_full_from_database") != nullptr || value.empty()) { + return; + } + + ChannelFull *channel_full = add_channel_full(channel_id); + auto status = log_event_parse(*channel_full, value); + if (status.is_error()) { + // can't happen unless database is broken + LOG(ERROR) << "Repair broken full " << channel_id << ' ' << format::as_hex_dump<4>(Slice(value)); + + // just clean all known data about the channel and pretend that there was nothing in the database + channels_full_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); + return; + } + + Dependencies dependencies; + dependencies.add(channel_id); + // must not depend on the linked_dialog_id itself, because message database can be disabled + // the Dialog will be forcely created in update_channel_full + dependencies.add_dialog_dependencies(DialogId(channel_full->linked_channel_id)); + dependencies.add(channel_full->migrated_from_chat_id); + for (auto bot_user_id : channel_full->bot_user_ids) { + dependencies.add(bot_user_id); + } + dependencies.add(channel_full->invite_link.get_creator_user_id()); + if (!dependencies.resolve_force(td_, source)) { + channels_full_.erase(channel_id); + G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); + return; + } + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + + bool need_invite_link = c->status.can_manage_invite_links(); + bool have_invite_link = channel_full->invite_link.is_valid(); + if (need_invite_link != have_invite_link) { + if (need_invite_link) { + // ignore ChannelFull without invite link + channels_full_.erase(channel_id); + return; + } else { + channel_full->invite_link = DialogInviteLink(); + } + } + + if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { + channel_full->photo = Photo(); + if (c->photo.small_file_id.is_valid()) { + channel_full->expires_at = 0.0; + } + } + auto photo = std::move(channel_full->photo); + channel_full->photo = Photo(); + on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); + + if (channel_full->participant_count < channel_full->administrator_count) { + channel_full->participant_count = channel_full->administrator_count; + } + if (c->participant_count != 0 && c->participant_count != channel_full->participant_count) { + channel_full->participant_count = c->participant_count; + + if (channel_full->participant_count < channel_full->administrator_count) { + channel_full->participant_count = channel_full->administrator_count; + channel_full->expires_at = 0.0; + + c->participant_count = channel_full->participant_count; + c->is_changed = true; + } + } + if (c->can_be_deleted != channel_full->can_be_deleted) { + c->can_be_deleted = channel_full->can_be_deleted; + c->need_save_to_database = true; + } + + if (invalidated_channels_full_.erase(channel_id) > 0 || + (!c->is_slow_mode_enabled && channel_full->slow_mode_delay != 0)) { + do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); + } + + td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, false); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, true); + + update_channel(c, channel_id); + + channel_full->is_update_channel_full_sent = true; + update_channel_full(channel_full, channel_id, "on_load_channel_full_from_database", true); + + if (channel_full->expires_at == 0.0) { + load_channel_full(channel_id, true, Auto(), "on_load_channel_full_from_database"); + } +} + +ChatManager::ChannelFull *ChatManager::get_channel_full_force(ChannelId channel_id, bool only_local, + const char *source) { + if (!have_channel_force(channel_id, source)) { + return nullptr; + } + + ChannelFull *channel_full = get_channel_full(channel_id, only_local, source); + if (channel_full != nullptr) { + return channel_full; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (!unavailable_channel_fulls_.insert(channel_id).second) { + return nullptr; + } + + LOG(INFO) << "Trying to load full " << channel_id << " from database from " << source; + on_load_channel_full_from_database( + channel_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_full_database_key(channel_id)), source); + return get_channel_full(channel_id, only_local, source); +} + +void ChatManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, bool from_database) { + CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << chat_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + + bool need_update_chat_full = false; + if (c->is_photo_changed) { + td_->messages_manager_->on_dialog_photo_updated(DialogId(chat_id)); + c->is_photo_changed = false; + + auto chat_full = get_chat_full(chat_id); // must not load ChatFull + if (chat_full != nullptr && + !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { + on_update_chat_full_photo(chat_full, chat_id, Photo()); + if (chat_full->is_update_chat_full_sent) { + need_update_chat_full = true; + } + if (c->photo.small_file_id.is_valid()) { + reload_chat_full(chat_id, Auto(), "update_chat"); + } + } + } + if (c->is_title_changed) { + td_->messages_manager_->on_dialog_title_updated(DialogId(chat_id)); + c->is_title_changed = false; + } + if (c->is_default_permissions_changed) { + td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(chat_id)); + c->is_default_permissions_changed = false; + } + if (c->is_is_active_changed) { + update_dialogs_for_discussion(DialogId(chat_id), c->is_active && c->status.is_creator()); + c->is_is_active_changed = false; + } + if (c->is_status_changed) { + if (!c->status.can_manage_invite_links()) { + td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(chat_id)); + } + if (!from_database) { + // if the chat is empty, this can add it to a chat list or remove it from a chat list + send_closure_later(G()->messages_manager(), &MessagesManager::try_update_dialog_pos, DialogId(chat_id)); + + if (c->is_update_basic_group_sent) { + // reload the chat to repair its status if it is changed back after receiving of outdated data + create_actor( + "ReloadChatSleepActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), chat_id](Unit) { + send_closure(actor_id, &ChatManager::reload_chat, chat_id, Promise(), "ReloadChatSleepActor"); + })) + .release(); + } + } + c->is_status_changed = false; + } + if (c->is_noforwards_changed) { + td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(chat_id)); + c->is_noforwards_changed = false; + } + + if (need_update_chat_full) { + auto chat_full = get_chat_full(chat_id); + CHECK(chat_full != nullptr); + update_chat_full(chat_full, chat_id, "update_chat"); + } + + LOG(DEBUG) << "Update " << chat_id << ": need_save_to_database = " << c->need_save_to_database + << ", is_changed = " << c->is_changed; + c->need_save_to_database |= c->is_changed; + if (c->need_save_to_database) { + if (!from_database) { + c->is_saved = false; + } + c->need_save_to_database = false; + } + if (c->is_changed) { + send_closure(G()->td(), &Td::send_update, get_update_basic_group_object(chat_id, c)); + c->is_changed = false; + c->is_update_basic_group_sent = true; + } + + if (!from_database) { + save_chat(c, chat_id, from_binlog); + } + + if (c->cache_version != Chat::CACHE_VERSION && !c->is_repaired && have_input_peer_chat(c, AccessRights::Read) && + !G()->close_flag()) { + c->is_repaired = true; + + LOG(INFO) << "Repairing cache of " << chat_id; + reload_chat(chat_id, Promise(), "update_chat"); + } +} + +void ChatManager::update_channel(Channel *c, ChannelId channel_id, bool from_binlog, bool from_database) { + CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << channel_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + + bool need_update_channel_full = false; + if (c->is_photo_changed) { + td_->messages_manager_->on_dialog_photo_updated(DialogId(channel_id)); + c->is_photo_changed = false; + + auto channel_full = get_channel_full(channel_id, true, "update_channel"); + if (channel_full != nullptr && + !is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { + on_update_channel_full_photo(channel_full, channel_id, Photo()); + if (channel_full->is_update_channel_full_sent) { + need_update_channel_full = true; + } + if (c->photo.small_file_id.is_valid()) { + if (channel_full->expires_at > 0.0) { + channel_full->expires_at = 0.0; + channel_full->need_save_to_database = true; + } + send_get_channel_full_query(channel_full, channel_id, Auto(), "update_channel"); + } + } + } + if (c->is_accent_color_changed) { + td_->messages_manager_->on_dialog_accent_colors_updated(DialogId(channel_id)); + c->is_accent_color_changed = false; + } + if (c->is_title_changed) { + td_->messages_manager_->on_dialog_title_updated(DialogId(channel_id)); + c->is_title_changed = false; + } + if (c->is_status_changed) { + c->status.update_restrictions(); + auto until_date = c->status.get_until_date(); + double left_time = 0; + if (until_date > 0) { + left_time = until_date - G()->server_time() + 2; + if (left_time <= 0) { + c->status.update_restrictions(); + CHECK(c->status.get_until_date() == 0); + } + } + if (left_time > 0 && left_time < 366 * 86400) { + channel_unban_timeout_.set_timeout_in(channel_id.get(), left_time); + } else { + channel_unban_timeout_.cancel_timeout(channel_id.get()); + } + + if (c->is_megagroup) { + update_dialogs_for_discussion(DialogId(channel_id), c->status.is_administrator() && c->status.can_pin_messages()); + } + if (!c->status.is_member()) { + remove_inactive_channel(channel_id); + } + if (!c->status.can_manage_invite_links()) { + td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(channel_id)); + } + if (!from_database && c->is_update_supergroup_sent) { + // reload the channel to repair its status if it is changed back after receiving of outdated data + create_actor("ReloadChannelSleepActor", 1.0, + PromiseCreator::lambda([actor_id = actor_id(this), channel_id](Unit) { + send_closure(actor_id, &ChatManager::reload_channel, channel_id, Promise(), + "ReloadChannelSleepActor"); + })) + .release(); + } + c->is_status_changed = false; + } + if (c->is_username_changed) { + if (c->status.is_creator()) { + update_created_public_channels(c, channel_id); + } + c->is_username_changed = false; + } + if (c->is_default_permissions_changed) { + td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(channel_id)); + if (c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, + ChannelType::Unknown)) { + td_->dialog_manager_->remove_dialog_suggested_action( + SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); + } + c->is_default_permissions_changed = false; + } + if (c->is_has_location_changed) { + if (c->status.is_creator()) { + update_created_public_channels(c, channel_id); + } + c->is_has_location_changed = false; + } + if (c->is_creator_changed) { + update_created_public_channels(c, channel_id); + c->is_creator_changed = false; + } + if (c->is_noforwards_changed) { + td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(channel_id)); + c->is_noforwards_changed = false; + } + if (c->is_stories_hidden_changed) { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(channel_id), "stories_hidden"); + c->is_stories_hidden_changed = false; + } + auto unix_time = G()->unix_time(); + auto effective_emoji_status = c->emoji_status.get_effective_emoji_status(true, unix_time); + if (effective_emoji_status != c->last_sent_emoji_status) { + if (!c->last_sent_emoji_status.is_empty()) { + channel_emoji_status_timeout_.cancel_timeout(channel_id.get()); + } + c->last_sent_emoji_status = effective_emoji_status; + if (!c->last_sent_emoji_status.is_empty()) { + auto until_date = c->last_sent_emoji_status.get_until_date(); + auto left_time = until_date - unix_time; + if (left_time >= 0 && left_time < 30 * 86400) { + channel_emoji_status_timeout_.set_timeout_in(channel_id.get(), left_time); + } + } + + td_->messages_manager_->on_dialog_emoji_status_updated(DialogId(channel_id)); + } + c->is_emoji_status_changed = false; + + if (!td_->auth_manager_->is_bot()) { + if (c->restriction_reasons.empty()) { + restricted_channel_ids_.erase(channel_id); + } else { + restricted_channel_ids_.insert(channel_id); + } + } + + if (from_binlog || from_database) { + td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), c->usernames, true); + } + + if (!is_channel_public(c) && !c->has_linked_channel) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, + DialogId(channel_id), DialogId(), false); + } + + if (need_update_channel_full) { + auto channel_full = get_channel_full(channel_id, true, "update_channel"); + CHECK(channel_full != nullptr); + update_channel_full(channel_full, channel_id, "update_channel"); + } + + LOG(DEBUG) << "Update " << channel_id << ": need_save_to_database = " << c->need_save_to_database + << ", is_changed = " << c->is_changed; + c->need_save_to_database |= c->is_changed; + if (c->need_save_to_database) { + if (!from_database) { + c->is_saved = false; + } + c->need_save_to_database = false; + } + if (c->is_changed) { + send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); + c->is_changed = false; + c->is_update_supergroup_sent = true; + } + + if (!from_database) { + save_channel(c, channel_id, from_binlog); + } + + bool have_read_access = have_input_peer_channel(c, channel_id, AccessRights::Read); + if (c->had_read_access && !have_read_access) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), + Promise()); + } + c->had_read_access = have_read_access; + + if (c->cache_version != Channel::CACHE_VERSION && !c->is_repaired && + have_input_peer_channel(c, channel_id, AccessRights::Read) && !G()->close_flag()) { + c->is_repaired = true; + + LOG(INFO) << "Repairing cache of " << channel_id; + reload_channel(channel_id, Promise(), "update_channel"); + } +} + +void ChatManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database) { + CHECK(chat_full != nullptr); + + if (chat_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << chat_id << " from " << source; + } + chat_full->is_being_updated = true; + SCOPE_EXIT { + chat_full->is_being_updated = false; + }; + + unavailable_chat_fulls_.erase(chat_id); // don't needed anymore + + chat_full->need_send_update |= chat_full->is_changed; + chat_full->need_save_to_database |= chat_full->is_changed; + chat_full->is_changed = false; + if (chat_full->need_send_update || chat_full->need_save_to_database) { + LOG(INFO) << "Update full " << chat_id << " from " << source; + } + if (chat_full->need_send_update) { + vector administrators; + vector bot_user_ids; + for (const auto &participant : chat_full->participants) { + if (participant.status_.is_administrator() && participant.dialog_id_.get_type() == DialogType::User) { + administrators.emplace_back(participant.dialog_id_.get_user_id(), participant.status_.get_rank(), + participant.status_.is_creator()); + } + if (participant.dialog_id_.get_type() == DialogType::User) { + auto user_id = participant.dialog_id_.get_user_id(); + if (td_->user_manager_->is_user_bot(user_id)) { + bot_user_ids.push_back(user_id); + } + } + } + td::remove_if(chat_full->bot_commands, [&bot_user_ids](const BotCommands &commands) { + return !td::contains(bot_user_ids, commands.get_bot_user_id()); + }); + + td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), + chat_full->version != -1, from_database); + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(chat_id), + std::move(bot_user_ids), from_database); + + { + Chat *c = get_chat(chat_id); + CHECK(c == nullptr || c->is_update_basic_group_sent); + } + if (!chat_full->is_update_chat_full_sent) { + LOG(ERROR) << "Send partial updateBasicGroupFullInfo for " << chat_id << " from " << source; + chat_full->is_update_chat_full_sent = true; + } + send_closure( + G()->td(), &Td::send_update, + make_tl_object(get_basic_group_id_object(chat_id, "update_chat_full"), + get_basic_group_full_info_object(chat_id, chat_full))); + chat_full->need_send_update = false; + } + if (chat_full->need_save_to_database) { + if (!from_database) { + save_chat_full(chat_full, chat_id); + } + chat_full->need_save_to_database = false; + } +} + +void ChatManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, + bool from_database) { + CHECK(channel_full != nullptr); + + if (channel_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << channel_id << " from " << source; + } + channel_full->is_being_updated = true; + SCOPE_EXIT { + channel_full->is_being_updated = false; + }; + + unavailable_channel_fulls_.erase(channel_id); // don't needed anymore + + CHECK(channel_full->participant_count >= channel_full->administrator_count); + + if (channel_full->is_slow_mode_next_send_date_changed) { + auto now = G()->server_time(); + if (channel_full->slow_mode_next_send_date > now + 3601) { + channel_full->slow_mode_next_send_date = static_cast(now) + 3601; + } + if (channel_full->slow_mode_next_send_date <= now) { + channel_full->slow_mode_next_send_date = 0; + } + if (channel_full->slow_mode_next_send_date == 0) { + slow_mode_delay_timeout_.cancel_timeout(channel_id.get()); + } else { + slow_mode_delay_timeout_.set_timeout_in(channel_id.get(), channel_full->slow_mode_next_send_date - now + 0.002); + } + channel_full->is_slow_mode_next_send_date_changed = false; + } + + if (channel_full->need_save_to_database) { + channel_full->is_changed |= td::remove_if( + channel_full->bot_commands, [bot_user_ids = &channel_full->bot_user_ids](const BotCommands &commands) { + return !td::contains(*bot_user_ids, commands.get_bot_user_id()); + }); + } + + channel_full->need_send_update |= channel_full->is_changed; + channel_full->need_save_to_database |= channel_full->is_changed; + channel_full->is_changed = false; + if (channel_full->need_send_update || channel_full->need_save_to_database) { + LOG(INFO) << "Update full " << channel_id << " from " << source; + } + if (channel_full->need_send_update) { + if (channel_full->linked_channel_id.is_valid()) { + td_->dialog_manager_->force_create_dialog(DialogId(channel_full->linked_channel_id), "update_channel_full", true); + } + + { + Channel *c = get_channel(channel_id); + CHECK(c == nullptr || c->is_update_supergroup_sent); + } + if (!channel_full->is_update_channel_full_sent) { + LOG(ERROR) << "Send partial updateSupergroupFullInfo for " << channel_id << " from " << source; + channel_full->is_update_channel_full_sent = true; + } + send_closure( + G()->td(), &Td::send_update, + make_tl_object(get_supergroup_id_object(channel_id, "update_channel_full"), + get_supergroup_full_info_object(channel_id, channel_full))); + channel_full->need_send_update = false; + } + if (channel_full->need_save_to_database) { + if (!from_database) { + save_channel_full(channel_full, channel_id); + } + channel_full->need_save_to_database = false; + } +} + +void ChatManager::on_get_chat(tl_object_ptr &&chat, const char *source) { + LOG(DEBUG) << "Receive from " << source << ' ' << to_string(chat); + switch (chat->get_id()) { + case telegram_api::chatEmpty::ID: + on_get_chat_empty(static_cast(*chat), source); + break; + case telegram_api::chat::ID: + on_get_chat(static_cast(*chat), source); + break; + case telegram_api::chatForbidden::ID: + on_get_chat_forbidden(static_cast(*chat), source); + break; + case telegram_api::channel::ID: + on_get_channel(static_cast(*chat), source); + break; + case telegram_api::channelForbidden::ID: + on_get_channel_forbidden(static_cast(*chat), source); + break; + default: + UNREACHABLE(); + } +} + +void ChatManager::on_get_chats(vector> &&chats, const char *source) { + for (auto &chat : chats) { + auto constuctor_id = chat->get_id(); + if (constuctor_id == telegram_api::channel::ID || constuctor_id == telegram_api::channelForbidden::ID) { + // apply info about megagroups before corresponding chats + on_get_chat(std::move(chat), source); + chat = nullptr; + } + } + for (auto &chat : chats) { + if (chat != nullptr) { + on_get_chat(std::move(chat), source); + chat = nullptr; + } + } +} + +void ChatManager::on_get_chat_full(tl_object_ptr &&chat_full_ptr, Promise &&promise) { + LOG(INFO) << "Receive " << to_string(chat_full_ptr); + if (chat_full_ptr->get_id() == telegram_api::chatFull::ID) { + auto chat = move_tl_object_as(chat_full_ptr); + ChatId chat_id(chat->id_); + Chat *c = get_chat(chat_id); + if (c == nullptr) { + LOG(ERROR) << "Can't find " << chat_id; + return promise.set_value(Unit()); + } + if (c->version >= c->pinned_message_version) { + auto pinned_message_id = MessageId(ServerMessageId(chat->pinned_msg_id_)); + LOG(INFO) << "Receive pinned " << pinned_message_id << " in " << chat_id << " with version " << c->version + << ". Current version is " << c->pinned_message_version; + td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); + if (c->version > c->pinned_message_version) { + c->pinned_message_version = c->version; + c->need_save_to_database = true; + update_chat(c, chat_id); + } + } + + td_->messages_manager_->on_update_dialog_folder_id(DialogId(chat_id), FolderId(chat->folder_id_)); + + td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(chat_id), chat->has_scheduled_); + + { + InputGroupCallId input_group_call_id; + if (chat->call_ != nullptr) { + input_group_call_id = InputGroupCallId(chat->call_); + } + td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id); + } + + { + DialogId default_join_group_call_as_dialog_id; + if (chat->groupcall_default_join_as_ != nullptr) { + default_join_group_call_as_dialog_id = DialogId(chat->groupcall_default_join_as_); + } + // use send closure later to not create synchronously default_join_group_call_as_dialog_id + send_closure_later(G()->messages_manager(), + &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(chat_id), + default_join_group_call_as_dialog_id, false); + } + + td_->messages_manager_->on_update_dialog_message_ttl(DialogId(chat_id), MessageTtl(chat->ttl_period_)); + + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(chat_id), !chat->translations_disabled_); + + ChatFull *chat_full = add_chat_full(chat_id); + on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_)); + auto photo = get_photo(td_, std::move(chat->chat_photo_), DialogId(chat_id)); + // on_update_chat_photo should be a no-op if server sent consistent data + on_update_chat_photo(c, chat_id, as_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, photo, false), + false); + on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); + if (chat_full->description != chat->about_) { + chat_full->description = std::move(chat->about_); + chat_full->is_changed = true; + td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); + } + if (chat_full->can_set_username != chat->can_set_username_) { + chat_full->can_set_username = chat->can_set_username_; + chat_full->need_save_to_database = true; + } + + on_get_chat_participants(std::move(chat->participants_), false); + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_), + "on_get_chat_full"); + + td_->messages_manager_->on_update_dialog_available_reactions( + DialogId(chat_id), std::move(chat->available_reactions_), chat->reactions_limit_); + + td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_)); + + td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(chat_id), chat->requests_pending_, + std::move(chat->recent_requesters_)); + + auto bot_commands = td_->user_manager_->get_bot_commands(std::move(chat->bot_info_), &chat_full->participants); + if (chat_full->bot_commands != bot_commands) { + chat_full->bot_commands = std::move(bot_commands); + chat_full->is_changed = true; + } + + if (c->is_changed) { + LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << chat_id; + update_chat(c, chat_id); + } + + chat_full->is_update_chat_full_sent = true; + update_chat_full(chat_full, chat_id, "on_get_chat_full"); + } else { + CHECK(chat_full_ptr->get_id() == telegram_api::channelFull::ID); + auto channel = move_tl_object_as(chat_full_ptr); + ChannelId channel_id(channel->id_); + auto c = get_channel(channel_id); + if (c == nullptr) { + LOG(ERROR) << "Can't find " << channel_id; + return promise.set_value(Unit()); + } + + invalidated_channels_full_.erase(channel_id); + + if (!G()->close_flag()) { + auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); + if (channel_full != nullptr) { + if (channel_full->repair_request_version != 0 && + channel_full->repair_request_version < channel_full->speculative_version) { + LOG(INFO) << "Receive ChannelFull with request version " << channel_full->repair_request_version + << ", but current speculative version is " << channel_full->speculative_version; + + channel_full->repair_request_version = channel_full->speculative_version; + + auto input_channel = get_input_channel(channel_id); + CHECK(input_channel != nullptr); + td_->create_handler(std::move(promise))->send(channel_id, std::move(input_channel)); + return; + } + channel_full->repair_request_version = 0; + } + } + + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_), + "on_get_channel_full"); + + 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_); + + td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_)); + + td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(channel_id), channel->requests_pending_, + std::move(channel->recent_requesters_)); + + td_->messages_manager_->on_update_dialog_message_ttl(DialogId(channel_id), MessageTtl(channel->ttl_period_)); + + td_->messages_manager_->on_update_dialog_view_as_messages(DialogId(channel_id), channel->view_forum_as_messages_); + + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(channel_id), !channel->translations_disabled_); + + send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(channel_id), + std::move(channel->stories_), Promise()); + + ChannelFull *channel_full = add_channel_full(channel_id); + + bool have_participant_count = (channel->flags_ & telegram_api::channelFull::PARTICIPANTS_COUNT_MASK) != 0; + auto participant_count = have_participant_count ? channel->participants_count_ : channel_full->participant_count; + auto administrator_count = 0; + if ((channel->flags_ & telegram_api::channelFull::ADMINS_COUNT_MASK) != 0) { + administrator_count = channel->admins_count_; + } else if (c->is_megagroup || c->status.is_administrator()) { + // in megagroups and administered channels don't drop known number of administrators + administrator_count = channel_full->administrator_count; + } + if (participant_count < administrator_count) { + participant_count = administrator_count; + } + auto restricted_count = channel->banned_count_; + auto banned_count = channel->kicked_count_; + auto can_get_participants = channel->can_view_participants_; + auto has_hidden_participants = channel->participants_hidden_; + auto can_set_username = channel->can_set_username_; + auto can_set_sticker_set = channel->can_set_stickers_; + auto can_set_location = channel->can_set_location_; + auto is_all_history_available = !channel->hidden_prehistory_; + auto can_have_sponsored_messages = !channel->restricted_sponsored_; + auto has_aggressive_anti_spam_enabled = channel->antispam_; + auto can_view_statistics = channel->can_view_stats_; + auto can_view_revenue = channel->can_view_revenue_; + bool has_pinned_stories = channel->stories_pinned_available_; + auto boost_count = channel->boosts_applied_; + auto unrestrict_boost_count = channel->boosts_unrestrict_; + StickerSetId sticker_set_id; + if (channel->stickerset_ != nullptr) { + sticker_set_id = + td_->stickers_manager_->on_get_sticker_set(std::move(channel->stickerset_), true, "on_get_channel_full"); + } + StickerSetId emoji_sticker_set_id; + if (channel->emojiset_ != nullptr) { + emoji_sticker_set_id = + td_->stickers_manager_->on_get_sticker_set(std::move(channel->emojiset_), true, "on_get_channel_full"); + } + DcId stats_dc_id; + if ((channel->flags_ & telegram_api::channelFull::STATS_DC_MASK) != 0) { + stats_dc_id = DcId::create(channel->stats_dc_); + } + if (!stats_dc_id.is_exact() && can_view_statistics) { + LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id; + can_view_statistics = false; + } + + channel_full->repair_request_version = 0; + channel_full->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME; + if (channel_full->participant_count != participant_count || + channel_full->administrator_count != administrator_count || + channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count || + channel_full->can_get_participants != can_get_participants || + channel_full->can_set_sticker_set != can_set_sticker_set || + channel_full->can_set_location != can_set_location || + channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id || + channel_full->sticker_set_id != sticker_set_id || channel_full->emoji_sticker_set_id != emoji_sticker_set_id || + channel_full->is_all_history_available != is_all_history_available || + channel_full->can_have_sponsored_messages != can_have_sponsored_messages || + channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled || + channel_full->has_hidden_participants != has_hidden_participants || + channel_full->has_pinned_stories != has_pinned_stories || channel_full->boost_count != boost_count || + channel_full->unrestrict_boost_count != unrestrict_boost_count || + channel_full->can_view_revenue != can_view_revenue) { + channel_full->participant_count = participant_count; + channel_full->administrator_count = administrator_count; + channel_full->restricted_count = restricted_count; + channel_full->banned_count = banned_count; + channel_full->can_get_participants = can_get_participants; + channel_full->has_hidden_participants = has_hidden_participants; + channel_full->can_set_sticker_set = can_set_sticker_set; + channel_full->can_set_location = can_set_location; + channel_full->can_view_statistics = can_view_statistics; + channel_full->stats_dc_id = stats_dc_id; + channel_full->sticker_set_id = sticker_set_id; + channel_full->emoji_sticker_set_id = emoji_sticker_set_id; + channel_full->is_all_history_available = is_all_history_available; + channel_full->can_have_sponsored_messages = can_have_sponsored_messages; + channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; + channel_full->has_pinned_stories = has_pinned_stories; + channel_full->boost_count = boost_count; + channel_full->unrestrict_boost_count = unrestrict_boost_count; + channel_full->can_view_revenue = can_view_revenue; + + channel_full->is_changed = true; + } + if (channel_full->description != channel->about_) { + channel_full->description = std::move(channel->about_); + channel_full->is_changed = true; + td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); + } + + if (have_participant_count && c->participant_count != participant_count) { + c->participant_count = participant_count; + c->is_changed = true; + update_channel(c, channel_id); + } + if (!channel_full->is_can_view_statistics_inited) { + channel_full->is_can_view_statistics_inited = true; + channel_full->need_save_to_database = true; + } + if (channel_full->can_set_username != can_set_username) { + channel_full->can_set_username = can_set_username; + channel_full->need_save_to_database = true; + } + + auto photo = get_photo(td_, std::move(channel->chat_photo_), DialogId(channel_id)); + // on_update_channel_photo should be a no-op if server sent consistent data + on_update_channel_photo( + c, channel_id, as_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, photo, false), + false); + on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); + + td_->messages_manager_->on_read_channel_outbox(channel_id, + MessageId(ServerMessageId(channel->read_outbox_max_id_))); + if ((channel->flags_ & telegram_api::channelFull::AVAILABLE_MIN_ID_MASK) != 0) { + td_->messages_manager_->on_update_channel_max_unavailable_message_id( + channel_id, MessageId(ServerMessageId(channel->available_min_id_)), "ChannelFull"); + } + td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)), + channel->unread_count_, channel->pts_, "ChannelFull"); + + on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_)); + + td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), channel->blocked_, false); + + td_->messages_manager_->on_update_dialog_last_pinned_message_id( + DialogId(channel_id), MessageId(ServerMessageId(channel->pinned_msg_id_))); + + td_->messages_manager_->on_update_dialog_folder_id(DialogId(channel_id), FolderId(channel->folder_id_)); + + td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(channel_id), + channel->has_scheduled_); + { + InputGroupCallId input_group_call_id; + if (channel->call_ != nullptr) { + input_group_call_id = InputGroupCallId(channel->call_); + } + td_->messages_manager_->on_update_dialog_group_call_id(DialogId(channel_id), input_group_call_id); + } + { + DialogId default_join_group_call_as_dialog_id; + if (channel->groupcall_default_join_as_ != nullptr) { + default_join_group_call_as_dialog_id = DialogId(channel->groupcall_default_join_as_); + } + // use send closure later to not create synchronously default_join_group_call_as_dialog_id + send_closure_later(G()->messages_manager(), + &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(channel_id), + default_join_group_call_as_dialog_id, false); + } + { + DialogId default_send_message_as_dialog_id; + if (channel->default_send_as_ != nullptr) { + default_send_message_as_dialog_id = DialogId(channel->default_send_as_); + } + // use send closure later to not create synchronously default_send_message_as_dialog_id + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, + DialogId(channel_id), default_send_message_as_dialog_id, false); + } + + if (participant_count >= 190 || !can_get_participants || has_hidden_participants) { + td_->dialog_participant_manager_->on_update_dialog_online_member_count(DialogId(channel_id), + channel->online_count_, true); + } + + vector bot_user_ids; + for (const auto &bot_info : channel->bot_info_) { + UserId user_id(bot_info->user_id_); + if (!td_->user_manager_->is_user_bot(user_id)) { + continue; + } + + bot_user_ids.push_back(user_id); + } + on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); + + auto bot_commands = td_->user_manager_->get_bot_commands(std::move(channel->bot_info_), nullptr); + if (channel_full->bot_commands != bot_commands) { + channel_full->bot_commands = std::move(bot_commands); + channel_full->is_changed = true; + } + + ChannelId linked_channel_id; + if ((channel->flags_ & telegram_api::channelFull::LINKED_CHAT_ID_MASK) != 0) { + linked_channel_id = ChannelId(channel->linked_chat_id_); + auto linked_channel = get_channel_force(linked_channel_id, "ChannelFull"); + if (linked_channel == nullptr || c->is_megagroup == linked_channel->is_megagroup || + channel_id == linked_channel_id) { + LOG(ERROR) << "Failed to add a link between " << channel_id << " and " << linked_channel_id; + linked_channel_id = ChannelId(); + } + } + on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id); + + on_update_channel_full_location(channel_full, channel_id, DialogLocation(td_, std::move(channel->location_))); + + if (c->is_megagroup) { + on_update_channel_full_slow_mode_delay(channel_full, channel_id, channel->slowmode_seconds_, + channel->slowmode_next_send_date_); + } + if (channel_full->can_be_deleted != channel->can_delete_channel_) { + channel_full->can_be_deleted = channel->can_delete_channel_; + channel_full->need_save_to_database = true; + } + if (c->can_be_deleted != channel_full->can_be_deleted) { + c->can_be_deleted = channel_full->can_be_deleted; + c->need_save_to_database = true; + } + + auto migrated_from_chat_id = ChatId(channel->migrated_from_chat_id_); + auto migrated_from_max_message_id = MessageId(ServerMessageId(channel->migrated_from_max_id_)); + if (channel_full->migrated_from_chat_id != migrated_from_chat_id || + channel_full->migrated_from_max_message_id != migrated_from_max_message_id) { + channel_full->migrated_from_chat_id = migrated_from_chat_id; + channel_full->migrated_from_max_message_id = migrated_from_max_message_id; + channel_full->is_changed = true; + } + + if (c->is_changed) { + LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << channel_id; + update_channel(c, channel_id); + } + + channel_full->is_update_channel_full_sent = true; + update_channel_full(channel_full, channel_id, "on_get_channel_full"); + + if (linked_channel_id.is_valid()) { + auto linked_channel_full = get_channel_full_force(linked_channel_id, true, "on_get_channel_full"); + on_update_channel_full_linked_channel_id(linked_channel_full, linked_channel_id, channel_id); + if (linked_channel_full != nullptr) { + update_channel_full(linked_channel_full, linked_channel_id, "on_get_channel_full 2"); + } + } + + td_->dialog_manager_->set_dialog_pending_suggestions(DialogId(channel_id), + std::move(channel->pending_suggestions_)); + } + promise.set_value(Unit()); +} + +void ChatManager::on_get_chat_full_failed(ChatId chat_id) { + if (G()->close_flag()) { + return; + } + + LOG(INFO) << "Failed to get full " << chat_id; +} + +void ChatManager::on_get_channel_full_failed(ChannelId channel_id) { + if (G()->close_flag()) { + return; + } + + LOG(INFO) << "Failed to get full " << channel_id; + auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); + if (channel_full != nullptr) { + channel_full->repair_request_version = 0; + } +} + +void ChatManager::on_ignored_restriction_reasons_changed() { + restricted_channel_ids_.foreach([&](const ChannelId &channel_id) { + send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, get_channel(channel_id))); + }); +} + +void ChatManager::update_chat_online_member_count(ChatId chat_id, bool is_from_server) { + auto chat_full = get_chat_full(chat_id); + if (chat_full != nullptr) { + update_chat_online_member_count(chat_full, chat_id, false); + } +} + +void ChatManager::update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server) { + td_->dialog_participant_manager_->update_dialog_online_member_count(chat_full->participants, DialogId(chat_id), + is_from_server); +} + +void ChatManager::on_get_chat_participants(tl_object_ptr &&participants_ptr, + bool from_update) { + switch (participants_ptr->get_id()) { + case telegram_api::chatParticipantsForbidden::ID: { + auto participants = move_tl_object_as(participants_ptr); + ChatId chat_id(participants->chat_id_); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + + if (!have_chat_force(chat_id, "on_get_chat_participants")) { + LOG(ERROR) << chat_id << " not found"; + return; + } + + if (from_update) { + drop_chat_full(chat_id); + } + break; + } + case telegram_api::chatParticipants::ID: { + auto participants = move_tl_object_as(participants_ptr); + ChatId chat_id(participants->chat_id_); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + + const Chat *c = get_chat_force(chat_id, "chatParticipants"); + if (c == nullptr) { + LOG(ERROR) << chat_id << " not found"; + return; + } + + ChatFull *chat_full = get_chat_full_force(chat_id, "telegram_api::chatParticipants"); + if (chat_full == nullptr) { + LOG(INFO) << "Ignore update of members for unknown full " << chat_id; + return; + } + + UserId new_creator_user_id; + vector new_participants; + new_participants.reserve(participants->participants_.size()); + + for (auto &participant_ptr : participants->participants_) { + DialogParticipant dialog_participant(std::move(participant_ptr), c->date, c->status.is_creator()); + if (!dialog_participant.is_valid()) { + LOG(ERROR) << "Receive invalid " << dialog_participant; + continue; + } + + LOG_IF(ERROR, !td_->dialog_manager_->have_dialog_info(dialog_participant.dialog_id_)) + << "Have no information about " << dialog_participant.dialog_id_ << " as a member of " << chat_id; + LOG_IF(ERROR, !td_->user_manager_->have_user(dialog_participant.inviter_user_id_)) + << "Have no information about " << dialog_participant.inviter_user_id_ << " as a member of " << chat_id; + if (dialog_participant.joined_date_ < c->date) { + LOG_IF(ERROR, dialog_participant.joined_date_ < c->date - 30 && c->date >= 1486000000) + << "Wrong join date = " << dialog_participant.joined_date_ << " for " << dialog_participant.dialog_id_ + << ", " << chat_id << " was created at " << c->date; + dialog_participant.joined_date_ = c->date; + } + if (dialog_participant.status_.is_creator() && dialog_participant.dialog_id_.get_type() == DialogType::User) { + new_creator_user_id = dialog_participant.dialog_id_.get_user_id(); + } + new_participants.push_back(std::move(dialog_participant)); + } + + if (chat_full->creator_user_id != new_creator_user_id) { + if (new_creator_user_id.is_valid() && chat_full->creator_user_id.is_valid()) { + LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to " << new_creator_user_id + << " in " << chat_id; + } + chat_full->creator_user_id = new_creator_user_id; + chat_full->is_changed = true; + } + + on_update_chat_full_participants(chat_full, chat_id, std::move(new_participants), participants->version_, + from_update); + if (from_update) { + update_chat_full(chat_full, chat_id, "on_get_chat_participants"); + } + break; + } + default: + UNREACHABLE(); + } +} + +const DialogParticipant *ChatManager::get_chat_participant(ChatId chat_id, UserId user_id) const { + auto chat_full = get_chat_full(chat_id); + if (chat_full == nullptr) { + return nullptr; + } + return get_chat_full_participant(chat_full, DialogId(user_id)); +} + +const DialogParticipant *ChatManager::get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id) { + for (const auto &dialog_participant : chat_full->participants) { + if (dialog_participant.dialog_id_ == dialog_id) { + return &dialog_participant; + } + } + return nullptr; +} + +const vector *ChatManager::get_chat_participants(ChatId chat_id) const { + auto chat_full = get_chat_full(chat_id); + if (chat_full == nullptr) { + return nullptr; + } + return &chat_full->participants; +} + +tl_object_ptr ChatManager::get_chat_member_object(const DialogParticipant &dialog_participant, + const char *source) const { + return td_api::make_object( + get_message_sender_object(td_, dialog_participant.dialog_id_, source), + td_->user_manager_->get_user_id_object(dialog_participant.inviter_user_id_, "chatMember.inviter_user_id"), + dialog_participant.joined_date_, dialog_participant.status_.get_chat_member_status_object()); +} + +bool ChatManager::on_get_channel_error(ChannelId channel_id, const Status &status, const char *source) { + LOG(INFO) << "Receive " << status << " in " << channel_id << " from " << source; + if (status.message() == CSlice("BOT_METHOD_INVALID")) { + LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source; + return true; + } + if (G()->is_expected_error(status)) { + return true; + } + if (status.message() == "CHANNEL_PRIVATE" || status.message() == "CHANNEL_PUBLIC_GROUP_NA") { + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive " << status.message() << " in invalid " << channel_id << " from " << source; + return false; + } + + auto c = get_channel(channel_id); + if (c == nullptr) { + if (Slice(source) == Slice("GetChannelDifferenceQuery") || Slice(source) == Slice("GetChannelsQuery")) { + // get channel difference after restart + // get channel from server by its identifier + return true; + } + LOG(ERROR) << "Receive " << status.message() << " in not found " << channel_id << " from " << source; + return false; + } + + auto debug_channel_object = oneline(to_string(get_supergroup_object(channel_id, c))); + if (c->status.is_member()) { + LOG(INFO) << "Emulate leaving " << channel_id; + // TODO we also may try to write to a public channel + int32 flags = 0; + if (c->is_megagroup) { + flags |= CHANNEL_FLAG_IS_MEGAGROUP; + } else { + flags |= CHANNEL_FLAG_IS_BROADCAST; + } + telegram_api::channelForbidden channel_forbidden(flags, false /*ignored*/, false /*ignored*/, channel_id.get(), + c->access_hash, c->title, 0); + on_get_channel_forbidden(channel_forbidden, "CHANNEL_PRIVATE"); + } else if (!c->status.is_banned()) { + if (!c->usernames.is_empty()) { + LOG(INFO) << "Drop usernames of " << channel_id; + on_update_channel_usernames(c, channel_id, Usernames()); + } + + on_update_channel_has_location(c, channel_id, false); + + on_update_channel_linked_channel_id(channel_id, ChannelId()); + + update_channel(c, channel_id); + + td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); + } + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, source); + LOG_IF(ERROR, have_input_peer_channel(c, channel_id, AccessRights::Read)) + << "Have read access to channel after receiving CHANNEL_PRIVATE. Channel state: " + << oneline(to_string(get_supergroup_object(channel_id, c))) + << ". Previous channel state: " << debug_channel_object; + + return true; + } + return false; +} + +bool ChatManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) { + auto new_count = count + delta_count; + if (new_count < min_count) { + new_count = min_count; + } + if (new_count == count) { + return false; + } + + count = new_count; + return true; +} + +void ChatManager::speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, + UserId inviter_user_id, int32 date, bool by_me) { + td_->dialog_participant_manager_->add_cached_channel_participants(channel_id, added_user_ids, inviter_user_id, date); + auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participants"); + + int32 delta_participant_count = 0; + for (auto user_id : added_user_ids) { + if (!user_id.is_valid()) { + continue; + } + + delta_participant_count++; + if (channel_full != nullptr && td_->user_manager_->is_user_bot(user_id) && + !td::contains(channel_full->bot_user_ids, user_id)) { + channel_full->bot_user_ids.push_back(user_id); + channel_full->need_save_to_database = true; + reload_channel_full(channel_id, Promise(), "speculative_add_channel_participants"); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); + } + } + if (channel_full != nullptr) { + if (channel_full->is_changed) { + channel_full->speculative_version++; + } + update_channel_full(channel_full, channel_id, "speculative_add_channel_participants"); + } + if (delta_participant_count == 0) { + return; + } + + speculative_add_channel_participant_count(channel_id, delta_participant_count, by_me); +} + +void ChatManager::speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me) { + if (!deleted_user_id.is_valid()) { + return; + } + + td_->dialog_participant_manager_->delete_cached_channel_participant(channel_id, deleted_user_id); + + if (td_->user_manager_->is_user_bot(deleted_user_id)) { + auto channel_full = get_channel_full_force(channel_id, true, "speculative_delete_channel_participant"); + if (channel_full != nullptr && td::remove(channel_full->bot_user_ids, deleted_user_id)) { + channel_full->need_save_to_database = true; + update_channel_full(channel_full, channel_id, "speculative_delete_channel_participant"); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); + } + } + + speculative_add_channel_participant_count(channel_id, -1, by_me); +} + +void ChatManager::speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, + bool by_me) { + if (by_me) { + // Currently, ignore all changes made by the current user, because they may have been already counted + invalidate_channel_full(channel_id, false, "speculative_add_channel_participant_count"); // just in case + return; + } + + auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participant_count"); + auto min_count = channel_full == nullptr ? 0 : channel_full->administrator_count; + + auto c = get_channel_force(channel_id, "speculative_add_channel_participant_count"); + if (c != nullptr && c->participant_count != 0 && + speculative_add_count(c->participant_count, delta_participant_count, min_count)) { + c->is_changed = true; + update_channel(c, channel_id); + } + + if (channel_full == nullptr) { + return; + } + + channel_full->is_changed |= + speculative_add_count(channel_full->participant_count, delta_participant_count, min_count); + + if (channel_full->is_changed) { + channel_full->speculative_version++; + } + + update_channel_full(channel_full, channel_id, "speculative_add_channel_participant_count"); +} + +void ChatManager::speculative_add_channel_user(ChannelId channel_id, UserId user_id, + const DialogParticipantStatus &new_status, + const DialogParticipantStatus &old_status) { + auto c = get_channel_force(channel_id, "speculative_add_channel_user"); + // channel full must be loaded before c->participant_count is updated, because on_load_channel_full_from_database + // must copy the initial c->participant_count before it is speculatibely updated + auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_user"); + int32 min_count = 0; + LOG(INFO) << "Speculatively change status of " << user_id << " in " << channel_id << " from " << old_status << " to " + << new_status; + if (channel_full != nullptr) { + channel_full->is_changed |= speculative_add_count( + channel_full->administrator_count, new_status.is_administrator_member() - old_status.is_administrator_member()); + min_count = channel_full->administrator_count; + } + + if (c != nullptr && c->participant_count != 0 && + speculative_add_count(c->participant_count, new_status.is_member() - old_status.is_member(), min_count)) { + c->is_changed = true; + update_channel(c, channel_id); + } + + td_->dialog_participant_manager_->update_cached_channel_participant_status(channel_id, user_id, new_status); + + if (channel_full == nullptr) { + return; + } + + channel_full->is_changed |= speculative_add_count(channel_full->participant_count, + new_status.is_member() - old_status.is_member(), min_count); + channel_full->is_changed |= + speculative_add_count(channel_full->restricted_count, new_status.is_restricted() - old_status.is_restricted()); + channel_full->is_changed |= + speculative_add_count(channel_full->banned_count, new_status.is_banned() - old_status.is_banned()); + + if (channel_full->is_changed) { + channel_full->speculative_version++; + } + + if (new_status.is_member() != old_status.is_member() && td_->user_manager_->is_user_bot(user_id)) { + if (new_status.is_member()) { + if (!td::contains(channel_full->bot_user_ids, user_id)) { + channel_full->bot_user_ids.push_back(user_id); + channel_full->need_save_to_database = true; + reload_channel_full(channel_id, Promise(), "speculative_add_channel_user"); + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); + } + } else { + if (td::remove(channel_full->bot_user_ids, user_id)) { + channel_full->need_save_to_database = true; + + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + channel_full->bot_user_ids, false); + } + } + } + + update_channel_full(channel_full, channel_id, "speculative_add_channel_user"); +} + +void ChatManager::invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, const char *source) { + LOG(INFO) << "Invalidate supergroup full for " << channel_id << " from " << source; + auto channel_full = get_channel_full(channel_id, true, "invalidate_channel_full"); // must not load ChannelFull + if (channel_full != nullptr) { + do_invalidate_channel_full(channel_full, channel_id, need_drop_slow_mode_delay); + update_channel_full(channel_full, channel_id, source); + } else if (channel_id.is_valid()) { + invalidated_channels_full_.insert(channel_id); + } +} + +void ChatManager::do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, + bool need_drop_slow_mode_delay) { + CHECK(channel_full != nullptr); + td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(channel_id)); + if (channel_full->expires_at >= Time::now()) { + channel_full->expires_at = 0.0; + channel_full->need_save_to_database = true; + } + if (need_drop_slow_mode_delay && channel_full->slow_mode_delay != 0) { + channel_full->slow_mode_delay = 0; + channel_full->slow_mode_next_send_date = 0; + channel_full->is_slow_mode_next_send_date_changed = true; + channel_full->is_changed = true; + } +} + +void ChatManager::on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo) { + CHECK(chat_full != nullptr); + if (photo != chat_full->photo) { + chat_full->photo = std::move(photo); + chat_full->is_changed = true; + } + + auto photo_file_ids = photo_get_file_ids(chat_full->photo); + if (chat_full->registered_photo_file_ids == photo_file_ids) { + return; + } + + auto &file_source_id = chat_full->file_source_id; + if (!file_source_id.is_valid()) { + file_source_id = chat_full_file_source_ids_.get(chat_id); + if (file_source_id.is_valid()) { + VLOG(file_references) << "Move " << file_source_id << " inside of " << chat_id; + chat_full_file_source_ids_.erase(chat_id); + } else { + VLOG(file_references) << "Need to create new file source for full " << chat_id; + file_source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); + } + } + + td_->file_manager_->change_files_source(file_source_id, chat_full->registered_photo_file_ids, photo_file_ids); + chat_full->registered_photo_file_ids = std::move(photo_file_ids); +} + +void ChatManager::on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo) { + CHECK(channel_full != nullptr); + if (photo != channel_full->photo) { + channel_full->photo = std::move(photo); + channel_full->is_changed = true; + } + + auto photo_file_ids = photo_get_file_ids(channel_full->photo); + if (channel_full->registered_photo_file_ids == photo_file_ids) { + return; + } + + auto &file_source_id = channel_full->file_source_id; + if (!file_source_id.is_valid()) { + file_source_id = channel_full_file_source_ids_.get(channel_id); + if (file_source_id.is_valid()) { + VLOG(file_references) << "Move " << file_source_id << " inside of " << channel_id; + channel_full_file_source_ids_.erase(channel_id); + } else { + VLOG(file_references) << "Need to create new file source for full " << channel_id; + file_source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); + } + } + + td_->file_manager_->change_files_source(file_source_id, channel_full->registered_photo_file_ids, photo_file_ids); + channel_full->registered_photo_file_ids = std::move(photo_file_ids); +} + +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"))) { + chat_full->is_changed = true; + } +} + +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"))) { + channel_full->is_changed = true; + } +} + +void ChatManager::remove_linked_channel_id(ChannelId channel_id) { + if (!channel_id.is_valid()) { + return; + } + + auto linked_channel_id = linked_channel_ids_.get(channel_id); + if (linked_channel_id.is_valid()) { + linked_channel_ids_.erase(channel_id); + linked_channel_ids_.erase(linked_channel_id); + } +} + +ChannelId ChatManager::get_linked_channel_id(ChannelId channel_id) const { + auto channel_full = get_channel_full(channel_id); + if (channel_full != nullptr) { + return channel_full->linked_channel_id; + } + + return linked_channel_ids_.get(channel_id); +} + +void ChatManager::on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, + ChannelId linked_channel_id) { + auto old_linked_channel_id = get_linked_channel_id(channel_id); + LOG(INFO) << "Uplate linked channel in " << channel_id << " from " << old_linked_channel_id << " to " + << linked_channel_id; + + if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id && + channel_full->linked_channel_id.is_valid()) { + get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 10"); + get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 0"); + } + auto old_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); + + remove_linked_channel_id(channel_id); + remove_linked_channel_id(linked_channel_id); + if (channel_id.is_valid() && linked_channel_id.is_valid()) { + linked_channel_ids_.set(channel_id, linked_channel_id); + linked_channel_ids_.set(linked_channel_id, channel_id); + } + + if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id) { + if (channel_full->linked_channel_id.is_valid()) { + // remove link from a previously linked channel_full + auto linked_channel = + get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 11"); + if (linked_channel != nullptr && linked_channel->has_linked_channel) { + linked_channel->has_linked_channel = false; + linked_channel->is_changed = true; + update_channel(linked_channel, channel_full->linked_channel_id); + reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 21"); + } + auto linked_channel_full = + get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 1"); + if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id == channel_id) { + linked_channel_full->linked_channel_id = ChannelId(); + linked_channel_full->is_changed = true; + update_channel_full(linked_channel_full, channel_full->linked_channel_id, + "on_update_channel_full_linked_channel_id 3"); + } + } + + channel_full->linked_channel_id = linked_channel_id; + channel_full->is_changed = true; + + if (channel_full->linked_channel_id.is_valid()) { + // add link from a newly linked channel_full + auto linked_channel = + get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 12"); + if (linked_channel != nullptr && !linked_channel->has_linked_channel) { + linked_channel->has_linked_channel = true; + linked_channel->is_changed = true; + update_channel(linked_channel, channel_full->linked_channel_id); + reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 22"); + } + auto linked_channel_full = + get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 2"); + if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id != channel_id) { + linked_channel_full->linked_channel_id = channel_id; + linked_channel_full->is_changed = true; + update_channel_full(linked_channel_full, channel_full->linked_channel_id, + "on_update_channel_full_linked_channel_id 4"); + } + } + } + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + if (linked_channel_id.is_valid() != c->has_linked_channel) { + c->has_linked_channel = linked_channel_id.is_valid(); + c->is_changed = true; + update_channel(c, channel_id); + } + + if (old_linked_channel_id != linked_channel_id) { + // must be called after the linked channel is changed + td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(channel_id), old_linked_channel_id, + linked_channel_id); + } + + if (linked_channel_id.is_valid()) { + auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); + LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to " + << new_linked_linked_channel_id; + if (old_linked_linked_channel_id != new_linked_linked_channel_id) { + // must be called after the linked channel is changed + td_->messages_manager_->on_dialog_linked_channel_updated( + DialogId(linked_channel_id), old_linked_linked_channel_id, new_linked_linked_channel_id); + } + } +} + +void ChatManager::on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, + const DialogLocation &location) { + if (channel_full->location != location) { + channel_full->location = location; + channel_full->is_changed = true; + } + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + on_update_channel_has_location(c, channel_id, !location.empty()); + update_channel(c, channel_id); +} + +void ChatManager::on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, + int32 slow_mode_delay, int32 slow_mode_next_send_date) { + if (slow_mode_delay < 0) { + LOG(ERROR) << "Receive slow mode delay " << slow_mode_delay << " in " << channel_id; + slow_mode_delay = 0; + } + + if (channel_full->slow_mode_delay != slow_mode_delay) { + channel_full->slow_mode_delay = slow_mode_delay; + channel_full->is_changed = true; + } + on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); + + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + bool is_slow_mode_enabled = slow_mode_delay != 0; + if (is_slow_mode_enabled != c->is_slow_mode_enabled) { + c->is_slow_mode_enabled = is_slow_mode_enabled; + c->is_changed = true; + update_channel(c, channel_id); + } +} + +void ChatManager::on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full, + int32 slow_mode_next_send_date) { + if (slow_mode_next_send_date < 0) { + LOG(ERROR) << "Receive slow mode next send date " << slow_mode_next_send_date; + slow_mode_next_send_date = 0; + } + if (channel_full->slow_mode_delay == 0 && slow_mode_next_send_date > 0) { + LOG(ERROR) << "Slow mode is disabled, but next send date is " << slow_mode_next_send_date; + slow_mode_next_send_date = 0; + } + + if (slow_mode_next_send_date != 0) { + auto now = G()->unix_time(); + if (slow_mode_next_send_date <= now) { + slow_mode_next_send_date = 0; + } + if (slow_mode_next_send_date > now + 3601) { + slow_mode_next_send_date = now + 3601; + } + } + if (channel_full->slow_mode_next_send_date != slow_mode_next_send_date) { + channel_full->slow_mode_next_send_date = slow_mode_next_send_date; + channel_full->is_slow_mode_next_send_date_changed = true; + if (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count) { + channel_full->is_changed = true; + } else { + channel_full->need_save_to_database = true; + } + } +} + +bool ChatManager::update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link) { + if (new_invite_link != invite_link) { + if (invite_link.is_valid() && invite_link.get_invite_link() != new_invite_link.get_invite_link()) { + // old link was invalidated + td_->dialog_invite_link_manager_->invalidate_invite_link_info(invite_link.get_invite_link()); + } + + invite_link = std::move(new_invite_link); + return true; + } + return false; +} + +void ChatManager::repair_chat_participants(ChatId chat_id) { + send_get_chat_full_query(chat_id, Auto(), "repair_chat_participants"); +} + +void ChatManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, + int32 version) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + if (!td_->user_manager_->have_user(user_id)) { + LOG(ERROR) << "Can't find " << user_id; + return; + } + if (!td_->user_manager_->have_user(inviter_user_id)) { + LOG(ERROR) << "Can't find " << inviter_user_id; + return; + } + LOG(INFO) << "Receive updateChatParticipantAdd to " << chat_id << " with " << user_id << " invited by " + << inviter_user_id << " at " << date << " with version " << version; + + ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_add_user"); + if (chat_full == nullptr) { + LOG(INFO) << "Ignoring update about members of " << chat_id; + return; + } + auto c = get_chat(chat_id); + if (c == nullptr) { + LOG(ERROR) << "Receive updateChatParticipantAdd for unknown " << chat_id << ". Couldn't apply it"; + repair_chat_participants(chat_id); + return; + } + if (c->status.is_left()) { + // possible if updates come out of order + LOG(WARNING) << "Receive updateChatParticipantAdd for left " << chat_id << ". Couldn't apply it"; + + repair_chat_participants(chat_id); // just in case + return; + } + if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { + for (auto &participant : chat_full->participants) { + if (participant.dialog_id_ == DialogId(user_id)) { + if (participant.inviter_user_id_ != inviter_user_id) { + LOG(ERROR) << user_id << " was readded to " << chat_id << " by " << inviter_user_id + << ", previously invited by " << participant.inviter_user_id_; + participant.inviter_user_id_ = inviter_user_id; + participant.joined_date_ = date; + repair_chat_participants(chat_id); + } else { + // Possible if update comes twice + LOG(INFO) << user_id << " was readded to " << chat_id; + } + return; + } + } + 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()}); + 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"); + + // Chat is already updated + if (chat_full->version == c->version && + narrow_cast(chat_full->participants.size()) != c->participant_count) { + LOG(ERROR) << "Number of members in " << chat_id << " with version " << c->version << " is " + << c->participant_count << " but there are " << chat_full->participants.size() + << " members in the ChatFull"; + repair_chat_participants(chat_id); + } + } +} + +void ChatManager::on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, + int32 version) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + if (!td_->user_manager_->have_user(user_id)) { + LOG(ERROR) << "Can't find " << user_id; + return; + } + LOG(INFO) << "Receive updateChatParticipantAdmin in " << chat_id << " with " << user_id << ", administrator rights " + << (is_administrator ? "enabled" : "disabled") << " with version " << version; + + auto c = get_chat_force(chat_id, "on_update_chat_edit_administrator"); + if (c == nullptr) { + LOG(INFO) << "Ignoring update about members of unknown " << chat_id; + return; + } + + if (c->status.is_left()) { + // possible if updates come out of order + LOG(WARNING) << "Receive updateChatParticipantAdmin for left " << chat_id << ". Couldn't apply it"; + + repair_chat_participants(chat_id); // just in case + return; + } + if (version <= -1) { + LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; + return; + } + CHECK(c->version >= 0); + + auto status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->status.is_creator()) + : DialogParticipantStatus::Member(); + if (version > c->version) { + if (version != c->version + 1) { + LOG(INFO) << "Administrators of " << chat_id << " with version " << c->version + << " has changed, but new version is " << version; + repair_chat_participants(chat_id); + return; + } + + c->version = version; + c->need_save_to_database = true; + if (user_id == td_->user_manager_->get_my_id() && !c->status.is_creator()) { + // if chat with version was already received, then the update is already processed + // so we need to call on_update_chat_status only if version > c->version + on_update_chat_status(c, chat_id, status); + } + update_chat(c, chat_id); + } + + ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_edit_administrator"); + if (chat_full != nullptr) { + if (chat_full->version + 1 == version) { + for (auto &participant : chat_full->participants) { + if (participant.dialog_id_ == DialogId(user_id)) { + participant.status_ = std::move(status); + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id, "on_update_chat_edit_administrator"); + return; + } + } + } + + // can't find chat member or version have increased too much + repair_chat_participants(chat_id); + } +} + +void ChatManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + if (!td_->user_manager_->have_user(user_id)) { + LOG(ERROR) << "Can't find " << user_id; + return; + } + LOG(INFO) << "Receive updateChatParticipantDelete from " << chat_id << " with " << user_id << " and version " + << version; + + ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_delete_user"); + if (chat_full == nullptr) { + LOG(INFO) << "Ignoring update about members of " << chat_id; + return; + } + const Chat *c = get_chat_force(chat_id, "on_update_chat_delete_user"); + if (c == nullptr) { + LOG(ERROR) << "Receive updateChatParticipantDelete for unknown " << chat_id; + repair_chat_participants(chat_id); + return; + } + if (user_id == td_->user_manager_->get_my_id()) { + LOG_IF(WARNING, c->status.is_member()) << "User was removed from " << chat_id + << " but it is not left the group. Possible if updates comes out of order"; + return; + } + if (c->status.is_left()) { + // possible if updates come out of order + LOG(INFO) << "Receive updateChatParticipantDelete for left " << chat_id; + + repair_chat_participants(chat_id); + return; + } + if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { + for (size_t i = 0; i < chat_full->participants.size(); i++) { + if (chat_full->participants[i].dialog_id_ == DialogId(user_id)) { + chat_full->participants[i] = chat_full->participants.back(); + chat_full->participants.resize(chat_full->participants.size() - 1); + chat_full->is_changed = true; + update_chat_online_member_count(chat_full, chat_id, false); + update_chat_full(chat_full, chat_id, "on_update_chat_delete_user"); + + if (static_cast(chat_full->participants.size()) != c->participant_count) { + repair_chat_participants(chat_id); + } + return; + } + } + LOG(ERROR) << "Can't find basic group member " << user_id << " in " << chat_id << " to be removed"; + repair_chat_participants(chat_id); + } +} + +void ChatManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status) { + if (c->status != status) { + LOG(INFO) << "Update " << chat_id << " status from " << c->status << " to " << status; + bool need_reload_group_call = c->status.can_manage_calls() != status.can_manage_calls(); + bool need_drop_invite_link = c->status.can_manage_invite_links() && !status.can_manage_invite_links(); + + c->status = std::move(status); + c->is_status_changed = true; + + if (c->status.is_left()) { + c->participant_count = 0; + c->version = -1; + c->default_permissions_version = -1; + c->pinned_message_version = -1; + + drop_chat_full(chat_id); + } else if (need_drop_invite_link) { + ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_status"); + if (chat_full != nullptr) { + on_update_chat_full_invite_link(chat_full, nullptr); + update_chat_full(chat_full, chat_id, "on_update_chat_status"); + } + } + if (need_reload_group_call) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, + DialogId(chat_id)); + } + + c->is_changed = true; + } +} + +void ChatManager::on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, + int32 version) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + auto c = get_chat_force(chat_id, "on_update_chat_default_permissions"); + if (c == nullptr) { + LOG(INFO) << "Ignoring update about unknown " << chat_id; + return; + } + + LOG(INFO) << "Receive updateChatDefaultBannedRights in " << chat_id << " with " << default_permissions + << " and version " << version << ". Current version is " << c->version; + + if (c->status.is_left()) { + // possible if updates come out of order + LOG(WARNING) << "Receive updateChatDefaultBannedRights for left " << chat_id << ". Couldn't apply it"; + + repair_chat_participants(chat_id); // just in case + return; + } + if (version <= -1) { + LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; + return; + } + CHECK(c->version >= 0); + + if (version > c->version) { + // this should be unreachable, because version and default permissions must be already updated from + // the chat object in on_get_chat + if (version != c->version + 1) { + LOG(INFO) << "Default permissions of " << chat_id << " with version " << c->version + << " has changed, but new version is " << version; + repair_chat_participants(chat_id); + return; + } + + LOG_IF(ERROR, default_permissions == c->default_permissions) + << "Receive updateChatDefaultBannedRights in " << chat_id << " with version " << version + << " and default_permissions = " << default_permissions + << ", but default_permissions are not changed. Current version is " << c->version; + c->version = version; + c->need_save_to_database = true; + on_update_chat_default_permissions(c, chat_id, default_permissions, version); + update_chat(c, chat_id); + } +} + +void ChatManager::on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions, + int32 version) { + if (c->default_permissions != default_permissions && version >= c->default_permissions_version) { + LOG(INFO) << "Update " << chat_id << " default permissions from " << c->default_permissions << " to " + << default_permissions << " and version from " << c->default_permissions_version << " to " << version; + c->default_permissions = default_permissions; + c->default_permissions_version = version; + c->is_default_permissions_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards) { + if (c->noforwards != noforwards) { + LOG(INFO) << "Update " << chat_id << " has_protected_content from " << c->noforwards << " to " << noforwards; + c->noforwards = noforwards; + c->is_noforwards_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + auto c = get_chat_force(chat_id, "on_update_chat_pinned_message"); + if (c == nullptr) { + LOG(INFO) << "Ignoring update about unknown " << chat_id; + return; + } + + LOG(INFO) << "Receive updateChatPinnedMessage in " << chat_id << " with " << pinned_message_id << " and version " + << version << ". Current version is " << c->version << "/" << c->pinned_message_version; + + if (c->status.is_left()) { + // possible if updates come out of order + repair_chat_participants(chat_id); // just in case + return; + } + if (version <= -1) { + LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; + return; + } + CHECK(c->version >= 0); + + if (version >= c->pinned_message_version) { + if (version != c->version + 1 && version != c->version) { + LOG(INFO) << "Pinned message of " << chat_id << " with version " << c->version + << " has changed, but new version is " << version; + repair_chat_participants(chat_id); + } else if (version == c->version + 1) { + c->version = version; + c->need_save_to_database = true; + } + td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); + if (version > c->pinned_message_version) { + LOG(INFO) << "Change pinned message version of " << chat_id << " from " << c->pinned_message_version << " to " + << version; + c->pinned_message_version = version; + c->need_save_to_database = true; + } + update_chat(c, chat_id); + } +} + +void ChatManager::on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version, + const string &debug_str) { + if (version <= -1) { + LOG(ERROR) << "Receive wrong version " << version << " in " << chat_id << debug_str; + return; + } + + if (version < c->version) { + // some outdated data + LOG(INFO) << "Receive number of members in " << chat_id << " with version " << version << debug_str + << ", but current version is " << c->version; + return; + } + + if (c->participant_count != participant_count) { + if (version == c->version && participant_count != 0) { + // version is not changed when deleted user is removed from the chat + LOG_IF(ERROR, c->participant_count != participant_count + 1) + << "Number of members in " << chat_id << " has changed from " << c->participant_count << " to " + << participant_count << ", but version " << c->version << " remains unchanged" << debug_str; + repair_chat_participants(chat_id); + } + + c->participant_count = participant_count; + c->version = version; + c->is_changed = true; + return; + } + + if (version > c->version) { + c->version = version; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_chat_photo(Chat *c, ChatId chat_id, + tl_object_ptr &&chat_photo_ptr) { + on_update_chat_photo( + c, chat_id, get_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, std::move(chat_photo_ptr)), true); +} + +void ChatManager::on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache) { + if (td_->auth_manager_->is_bot()) { + photo.minithumbnail.clear(); + } + + if (need_update_dialog_photo(c->photo, photo)) { + c->photo = std::move(photo); + c->is_photo_changed = true; + c->need_save_to_database = true; + + if (invalidate_photo_cache) { + auto chat_full = get_chat_full(chat_id); // must not load ChatFull + if (chat_full != nullptr) { + if (!chat_full->photo.is_empty()) { + chat_full->photo = Photo(); + chat_full->is_changed = true; + } + if (c->photo.small_file_id.is_valid()) { + reload_chat_full(chat_id, Auto(), "on_update_chat_photo"); + } + update_chat_full(chat_full, chat_id, "on_update_chat_photo"); + } + } + } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { + c->photo.minithumbnail = std::move(photo.minithumbnail); + c->is_photo_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_chat_title(Chat *c, ChatId chat_id, string &&title) { + if (c->title != title) { + c->title = std::move(title); + c->is_title_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_chat_active(Chat *c, ChatId chat_id, bool is_active) { + if (c->is_active != is_active) { + c->is_active = is_active; + c->is_is_active_changed = true; + c->is_changed = true; + } +} + +void ChatManager::on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id) { + if (c->migrated_to_channel_id != migrated_to_channel_id && migrated_to_channel_id.is_valid()) { + LOG_IF(ERROR, c->migrated_to_channel_id.is_valid()) + << "Upgraded supergroup ID for " << chat_id << " has changed from " << c->migrated_to_channel_id << " to " + << migrated_to_channel_id; + c->migrated_to_channel_id = migrated_to_channel_id; + c->is_changed = true; + } +} + +void ChatManager::on_update_chat_description(ChatId chat_id, string &&description) { + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id; + return; + } + + auto chat_full = get_chat_full_force(chat_id, "on_update_chat_description"); + if (chat_full == nullptr) { + return; + } + if (chat_full->description != description) { + chat_full->description = std::move(description); + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id, "on_update_chat_description"); + td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); + } +} + +bool ChatManager::on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version) { + if (version <= -1) { + LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; + return false; + } + if (chat_full->version == -1) { + // chat members are unknown, nothing to update + return false; + } + + if (chat_full->version + 1 == version) { + chat_full->version = version; + return true; + } + + LOG(INFO) << "Number of members in " << chat_id << " with version " << chat_full->version + << " has changed, but new version is " << version; + repair_chat_participants(chat_id); + return false; +} + +void ChatManager::on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, + vector participants, int32 version, + bool from_update) { + if (version <= -1) { + LOG(ERROR) << "Receive members with wrong version " << version << " in " << chat_id; + return; + } + + if (version < chat_full->version) { + // some outdated data + LOG(WARNING) << "Receive members of " << chat_id << " with version " << version << " but current version is " + << chat_full->version; + return; + } + + if ((chat_full->participants.size() != participants.size() && version == chat_full->version) || + (from_update && version != chat_full->version + 1)) { + LOG(INFO) << "Members of " << chat_id << " has changed"; + // this is possible in very rare situations + repair_chat_participants(chat_id); + } + + chat_full->participants = std::move(participants); + chat_full->version = version; + chat_full->is_changed = true; + update_chat_online_member_count(chat_full, chat_id, true); +} + +void ChatManager::drop_chat_full(ChatId chat_id) { + ChatFull *chat_full = get_chat_full_force(chat_id, "drop_chat_full"); + if (chat_full == nullptr) { + return; + } + + LOG(INFO) << "Drop basicGroupFullInfo of " << chat_id; + on_update_chat_full_photo(chat_full, chat_id, Photo()); + // chat_full->creator_user_id = UserId(); + chat_full->participants.clear(); + chat_full->bot_commands.clear(); + chat_full->version = -1; + on_update_chat_full_invite_link(chat_full, nullptr); + update_chat_online_member_count(chat_full, chat_id, true); + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id, "drop_chat_full"); +} + +void ChatManager::on_update_channel_photo(Channel *c, ChannelId channel_id, + tl_object_ptr &&chat_photo_ptr) { + on_update_channel_photo( + c, channel_id, + get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, std::move(chat_photo_ptr)), + true); +} + +void ChatManager::on_update_chat_bot_commands(ChatId chat_id, BotCommands &&bot_commands) { + auto chat_full = get_chat_full_force(chat_id, "on_update_chat_bot_commands"); + if (chat_full != nullptr && BotCommands::update_all_bot_commands(chat_full->bot_commands, std::move(bot_commands))) { + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id, "on_update_chat_bot_commands"); + } +} + +void ChatManager::on_update_chat_permanent_invite_link(ChatId chat_id, const DialogInviteLink &invite_link) { + auto chat_full = get_chat_full_force(chat_id, "on_update_chat_permanent_invite_link"); + if (chat_full != nullptr && update_permanent_invite_link(chat_full->invite_link, invite_link)) { + chat_full->is_changed = true; + update_chat_full(chat_full, chat_id, "on_update_chat_permanent_invite_link"); + } +} + +void ChatManager::on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, + bool invalidate_photo_cache) { + if (td_->auth_manager_->is_bot()) { + photo.minithumbnail.clear(); + } + + if (need_update_dialog_photo(c->photo, photo)) { + c->photo = std::move(photo); + c->is_photo_changed = true; + c->need_save_to_database = true; + + if (invalidate_photo_cache) { + auto channel_full = get_channel_full(channel_id, true, "on_update_channel_photo"); // must not load ChannelFull + if (channel_full != nullptr) { + if (!channel_full->photo.is_empty()) { + channel_full->photo = Photo(); + channel_full->is_changed = true; + } + if (c->photo.small_file_id.is_valid()) { + if (channel_full->expires_at > 0.0) { + channel_full->expires_at = 0.0; + channel_full->need_save_to_database = true; + } + reload_channel_full(channel_id, Auto(), "on_update_channel_photo"); + } + update_channel_full(channel_full, channel_id, "on_update_channel_photo"); + } + } + } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { + c->photo.minithumbnail = std::move(photo.minithumbnail); + c->is_photo_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_emoji_status(Channel *c, ChannelId channel_id, EmojiStatus emoji_status) { + if (c->emoji_status != emoji_status) { + LOG(DEBUG) << "Change emoji status of " << channel_id << " from " << c->emoji_status << " to " << emoji_status; + c->emoji_status = std::move(emoji_status); + c->is_emoji_status_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId accent_color_id) { + if (accent_color_id == AccentColorId(channel_id) || !accent_color_id.is_valid()) { + accent_color_id = AccentColorId(); + } + if (c->accent_color_id != accent_color_id) { + c->accent_color_id = accent_color_id; + c->is_accent_color_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, + CustomEmojiId background_custom_emoji_id) { + if (c->background_custom_emoji_id != background_custom_emoji_id) { + c->background_custom_emoji_id = background_custom_emoji_id; + c->is_accent_color_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_profile_accent_color_id(Channel *c, ChannelId channel_id, + AccentColorId profile_accent_color_id) { + if (!profile_accent_color_id.is_valid()) { + profile_accent_color_id = AccentColorId(); + } + if (c->profile_accent_color_id != profile_accent_color_id) { + c->profile_accent_color_id = profile_accent_color_id; + c->is_accent_color_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_profile_background_custom_emoji_id( + Channel *c, ChannelId channel_id, CustomEmojiId profile_background_custom_emoji_id) { + if (c->profile_background_custom_emoji_id != profile_background_custom_emoji_id) { + c->profile_background_custom_emoji_id = profile_background_custom_emoji_id; + c->is_accent_color_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_title(Channel *c, ChannelId channel_id, string &&title) { + if (c->title != title) { + c->title = std::move(title); + c->is_title_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status) { + if (c->status != status) { + LOG(INFO) << "Update " << channel_id << " status from " << c->status << " to " << status; + if (c->is_update_supergroup_sent) { + on_channel_status_changed(c, channel_id, c->status, status); + } + c->status = status; + c->is_status_changed = true; + c->is_changed = true; + } +} + +void ChatManager::on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status, + const DialogParticipantStatus &new_status) { + CHECK(c->is_update_supergroup_sent); + bool have_channel_full = get_channel_full(channel_id) != nullptr; + + if (old_status.can_post_stories() != new_status.can_post_stories()) { + td_->story_manager_->update_dialogs_to_send_stories(channel_id, new_status.can_post_stories()); + } + + bool need_reload_group_call = old_status.can_manage_calls() != new_status.can_manage_calls(); + if (old_status.can_manage_invite_links() && !new_status.can_manage_invite_links()) { + auto channel_full = get_channel_full(channel_id, true, "on_channel_status_changed"); + if (channel_full != nullptr) { // otherwise invite_link will be dropped when the channel is loaded + on_update_channel_full_invite_link(channel_full, nullptr); + do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); + update_channel_full(channel_full, channel_id, "on_channel_status_changed"); + } + } else { + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_status_changed"); + } + + if (old_status.is_creator() != new_status.is_creator()) { + c->is_creator_changed = true; + + send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner"); + td_->dialog_participant_manager_->reload_dialog_administrators(DialogId(channel_id), {}, Auto()); + td_->dialog_manager_->remove_dialog_suggested_action( + SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); + } + + if (old_status.is_member() != new_status.is_member() || new_status.is_banned()) { + td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); + + if (new_status.is_member() || new_status.is_creator()) { + reload_channel_full(channel_id, + PromiseCreator::lambda([channel_id](Unit) { LOG(INFO) << "Reloaded full " << channel_id; }), + "on_channel_status_changed"); + } + } + if (need_reload_group_call) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, + DialogId(channel_id)); + } + bool is_bot = td_->auth_manager_->is_bot(); + if (is_bot && old_status.is_administrator() && !new_status.is_administrator()) { + td_->dialog_participant_manager_->drop_channel_participant_cache(channel_id); + } + if (is_bot && old_status.is_member() && !new_status.is_member() && !G()->use_message_database()) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), + Promise()); + } + if (!is_bot && old_status.is_member() != new_status.is_member()) { + DialogId dialog_id(channel_id); + if (new_status.is_member()) { + send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, dialog_id); + } else { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, dialog_id, + "on_channel_status_changed"); + } + + send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, + "on_channel_status_changed", true, true); + } + + // must not load ChannelFull, because must not change the Channel + CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); +} + +void ChatManager::on_update_channel_default_permissions(Channel *c, ChannelId channel_id, + RestrictedRights default_permissions) { + if (c->is_megagroup && c->default_permissions != default_permissions) { + LOG(INFO) << "Update " << channel_id << " default permissions from " << c->default_permissions << " to " + << default_permissions; + c->default_permissions = default_permissions; + c->is_default_permissions_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location) { + if (c->has_location != has_location) { + LOG(INFO) << "Update " << channel_id << " has_location from " << c->has_location << " to " << has_location; + c->has_location = has_location; + c->is_has_location_changed = true; + c->is_changed = true; + } +} + +void ChatManager::on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards) { + if (c->noforwards != noforwards) { + LOG(INFO) << "Update " << channel_id << " has_protected_content from " << c->noforwards << " to " << noforwards; + c->noforwards = noforwards; + c->is_noforwards_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_story_ids(ChannelId channel_id, StoryId max_active_story_id, + StoryId max_read_story_id) { + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + + Channel *c = get_channel_force(channel_id, "on_update_channel_story_ids"); + if (c != nullptr) { + on_update_channel_story_ids_impl(c, channel_id, max_active_story_id, max_read_story_id); + update_channel(c, channel_id); + } else { + LOG(INFO) << "Ignore update channel story identifiers about unknown " << channel_id; + } +} + +void ChatManager::on_update_channel_story_ids_impl(Channel *c, ChannelId channel_id, StoryId max_active_story_id, + StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { + LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << channel_id; + return; + } + if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { + LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << channel_id; + return; + } + + auto has_unread_stories = get_channel_has_unread_stories(c); + if (c->max_active_story_id != max_active_story_id) { + LOG(DEBUG) << "Change last active story of " << channel_id << " from " << c->max_active_story_id << " to " + << max_active_story_id; + c->max_active_story_id = max_active_story_id; + c->need_save_to_database = true; + } + if (need_poll_channel_active_stories(c, channel_id)) { + auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; + if (max_active_story_id_next_reload_time > + c->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { + LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << channel_id; + c->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; + c->need_save_to_database = true; + } + } + if (!max_active_story_id.is_valid()) { + CHECK(max_read_story_id == StoryId()); + if (c->max_read_story_id != StoryId()) { + LOG(DEBUG) << "Drop last read " << c->max_read_story_id << " of " << channel_id; + c->max_read_story_id = StoryId(); + c->need_save_to_database = true; + } + } else if (max_read_story_id.get() > c->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " + << max_read_story_id; + c->max_read_story_id = max_read_story_id; + c->need_save_to_database = true; + } + if (has_unread_stories != get_channel_has_unread_stories(c)) { + LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; + c->is_changed = true; + } +} + +void ChatManager::on_update_channel_max_read_story_id(ChannelId channel_id, StoryId max_read_story_id) { + CHECK(channel_id.is_valid()); + + Channel *c = get_channel(channel_id); + if (c != nullptr) { + on_update_channel_max_read_story_id(c, channel_id, max_read_story_id); + update_channel(c, channel_id); + } +} + +void ChatManager::on_update_channel_max_read_story_id(Channel *c, ChannelId channel_id, StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot()) { + return; + } + + auto has_unread_stories = get_channel_has_unread_stories(c); + if (max_read_story_id.get() > c->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " + << max_read_story_id; + c->max_read_story_id = max_read_story_id; + c->need_save_to_database = true; + } + if (has_unread_stories != get_channel_has_unread_stories(c)) { + LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; + c->is_changed = true; + } +} + +void ChatManager::on_update_channel_stories_hidden(ChannelId channel_id, bool stories_hidden) { + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + + Channel *c = get_channel_force(channel_id, "on_update_channel_stories_hidden"); + if (c != nullptr) { + on_update_channel_stories_hidden(c, channel_id, stories_hidden); + update_channel(c, channel_id); + } else { + LOG(INFO) << "Ignore update channel stories are archived about unknown " << channel_id; + } +} + +void ChatManager::on_update_channel_stories_hidden(Channel *c, ChannelId channel_id, bool stories_hidden) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (c->stories_hidden != stories_hidden) { + LOG(DEBUG) << "Change stories are archived of " << channel_id << " to " << stories_hidden; + c->stories_hidden = stories_hidden; + c->is_stories_hidden_changed = true; + c->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_participant_count(ChannelId channel_id, int32 participant_count) { + Channel *c = get_channel(channel_id); + if (c == nullptr || c->participant_count == participant_count) { + return; + } + + c->participant_count = participant_count; + c->is_changed = true; + update_channel(c, channel_id); + + auto channel_full = get_channel_full(channel_id, true, "on_update_channel_participant_count"); + if (channel_full != nullptr && channel_full->participant_count != participant_count) { + if (channel_full->administrator_count > participant_count) { + channel_full->administrator_count = participant_count; + } + channel_full->participant_count = participant_count; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_participant_count"); + } +} + +void ChatManager::on_update_channel_editable_username(ChannelId channel_id, string &&username) { + Channel *c = get_channel(channel_id); + CHECK(c != nullptr); + on_update_channel_usernames(c, channel_id, c->usernames.change_editable_username(std::move(username))); + update_channel(c, channel_id); +} + +void ChatManager::on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames) { + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + + Channel *c = get_channel_force(channel_id, "on_update_channel_usernames"); + if (c != nullptr) { + on_update_channel_usernames(c, channel_id, std::move(usernames)); + update_channel(c, channel_id); + } else { + LOG(INFO) << "Ignore update channel usernames about unknown " << channel_id; + } +} + +void ChatManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) { + if (c->usernames != usernames) { + td_->dialog_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); + td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); + if (c->is_update_supergroup_sent) { + on_channel_usernames_changed(c, channel_id, c->usernames, usernames); + } + + c->usernames = std::move(usernames); + c->is_username_changed = true; + c->is_changed = true; + } else { + td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), usernames, false); + } +} + +void ChatManager::on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames, + const Usernames &new_usernames) { + bool have_channel_full = get_channel_full(channel_id) != nullptr; + if (!old_usernames.has_first_username() || !new_usernames.has_first_username()) { + // moving channel from private to public can change availability of chat members + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_usernames_changed"); + } + + // must not load ChannelFull, because must not change the Channel + CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); +} + +void ChatManager::on_update_channel_description(ChannelId channel_id, string &&description) { + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_description"); + if (channel_full == nullptr) { + return; + } + if (channel_full->description != description) { + channel_full->description = std::move(description); + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_description"); + td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); + } +} + +void ChatManager::on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_sticker_set"); + if (channel_full == nullptr) { + return; + } + if (channel_full->sticker_set_id != sticker_set_id) { + channel_full->sticker_set_id = sticker_set_id; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_sticker_set"); + } +} + +void ChatManager::on_update_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_emoji_sticker_set"); + if (channel_full == nullptr) { + return; + } + if (channel_full->emoji_sticker_set_id != sticker_set_id) { + channel_full->emoji_sticker_set_id = sticker_set_id; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_emoji_sticker_set"); + } +} + +void ChatManager::on_update_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count) { + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_unrestrict_boost_count"); + if (channel_full == nullptr) { + return; + } + if (channel_full->unrestrict_boost_count != unrestrict_boost_count) { + channel_full->unrestrict_boost_count = unrestrict_boost_count; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_unrestrict_boost_count"); + } +} + +void ChatManager::on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id) { + if (channel_id.is_valid()) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_linked_channel_id 1"); + on_update_channel_full_linked_channel_id(channel_full, channel_id, group_channel_id); + if (channel_full != nullptr) { + update_channel_full(channel_full, channel_id, "on_update_channel_linked_channel_id 3"); + } + } + if (group_channel_id.is_valid()) { + auto channel_full = get_channel_full_force(group_channel_id, true, "on_update_channel_linked_channel_id 2"); + on_update_channel_full_linked_channel_id(channel_full, group_channel_id, channel_id); + if (channel_full != nullptr) { + update_channel_full(channel_full, group_channel_id, "on_update_channel_linked_channel_id 4"); + } + } +} + +void ChatManager::on_update_channel_location(ChannelId channel_id, const DialogLocation &location) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_location"); + if (channel_full != nullptr) { + on_update_channel_full_location(channel_full, channel_id, location); + update_channel_full(channel_full, channel_id, "on_update_channel_location"); + } +} + +void ChatManager::on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_delay"); + if (channel_full != nullptr) { + on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, 0); + update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_delay"); + } + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_next_send_date"); + if (channel_full != nullptr) { + on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); + update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_next_send_date"); + } +} + +void ChatManager::on_update_channel_bot_user_ids(ChannelId channel_id, vector &&bot_user_ids) { + CHECK(channel_id.is_valid()); + if (!have_channel(channel_id)) { + LOG(ERROR) << channel_id << " not found"; + return; + } + + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_user_ids"); + if (channel_full == nullptr) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + std::move(bot_user_ids), false); + return; + } + on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); + update_channel_full(channel_full, channel_id, "on_update_channel_bot_user_ids"); +} + +void ChatManager::on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id, + vector &&bot_user_ids) { + CHECK(channel_full != nullptr); + send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), + bot_user_ids, false); + if (channel_full->bot_user_ids != bot_user_ids) { + channel_full->bot_user_ids = std::move(bot_user_ids); + channel_full->need_save_to_database = true; + } +} + +void ChatManager::on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_is_all_history_available"); + if (channel_full != nullptr && channel_full->is_all_history_available != is_all_history_available) { + channel_full->is_all_history_available = is_all_history_available; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_is_all_history_available"); + } + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_can_have_sponsored_messages"); + if (channel_full != nullptr && channel_full->can_have_sponsored_messages != can_have_sponsored_messages) { + channel_full->can_have_sponsored_messages = can_have_sponsored_messages; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_can_have_sponsored_messages"); + } + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_hidden_participants"); + if (channel_full != nullptr && channel_full->has_hidden_participants != has_hidden_participants) { + channel_full->has_hidden_participants = has_hidden_participants; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_has_hidden_participants"); + } + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, + bool has_aggressive_anti_spam_enabled, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(channel_id.is_valid()); + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_aggressive_anti_spam_enabled"); + if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled) { + channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_has_aggressive_anti_spam_enabled"); + } + promise.set_value(Unit()); +} + +void ChatManager::on_update_channel_has_pinned_stories(ChannelId channel_id, bool has_pinned_stories) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + + ChannelFull *channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_pinned_stories"); + if (channel_full == nullptr || channel_full->has_pinned_stories == has_pinned_stories) { + return; + } + channel_full->has_pinned_stories = has_pinned_stories; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_has_pinned_stories"); +} + +void ChatManager::on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions) { + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id; + return; + } + + Channel *c = get_channel_force(channel_id, "on_update_channel_default_permissions"); + if (c != nullptr) { + on_update_channel_default_permissions(c, channel_id, std::move(default_permissions)); + update_channel(c, channel_id); + } else { + LOG(INFO) << "Ignore update channel default permissions about unknown " << channel_id; + } +} + +FileSourceId ChatManager::get_chat_full_file_source_id(ChatId chat_id) { + if (!chat_id.is_valid()) { + return FileSourceId(); + } + + auto chat_full = get_chat_full(chat_id); + if (chat_full != nullptr) { + VLOG(file_references) << "Don't need to create file source for full " << chat_id; + // chat full was already added, source ID was registered and shouldn't be needed + return chat_full->is_update_chat_full_sent ? FileSourceId() : chat_full->file_source_id; + } + + auto &source_id = chat_full_file_source_ids_[chat_id]; + if (!source_id.is_valid()) { + source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); + } + VLOG(file_references) << "Return " << source_id << " for full " << chat_id; + return source_id; +} + +FileSourceId ChatManager::get_channel_full_file_source_id(ChannelId channel_id) { + if (!channel_id.is_valid()) { + return FileSourceId(); + } + + auto channel_full = get_channel_full(channel_id); + if (channel_full != nullptr) { + VLOG(file_references) << "Don't need to create file source for full " << channel_id; + // channel full was already added, source ID was registered and shouldn't be needed + return channel_full->is_update_channel_full_sent ? FileSourceId() : channel_full->file_source_id; + } + + auto &source_id = channel_full_file_source_ids_[channel_id]; + if (!source_id.is_valid()) { + source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); + } + VLOG(file_references) << "Return " << source_id << " for full " << channel_id; + return source_id; +} + +void ChatManager::create_new_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, + Promise> &&promise) { + auto new_title = clean_name(title, MAX_TITLE_LENGTH); + if (new_title.empty()) { + return promise.set_error(Status::Error(400, "Title must be non-empty")); + } + + vector> input_users; + for (auto user_id : user_ids) { + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); + input_users.push_back(std::move(input_user)); + } + + td_->create_handler(std::move(promise))->send(std::move(input_users), new_title, message_ttl); +} + +void ChatManager::create_new_channel(const string &title, bool is_forum, bool is_megagroup, const string &description, + const DialogLocation &location, bool for_import, MessageTtl message_ttl, + Promise> &&promise) { + auto new_title = clean_name(title, MAX_TITLE_LENGTH); + if (new_title.empty()) { + return promise.set_error(Status::Error(400, "Title must be non-empty")); + } + + td_->create_handler(std::move(promise)) + ->send(new_title, is_forum, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, + for_import, message_ttl); +} + +bool ChatManager::have_chat(ChatId chat_id) const { + return chats_.count(chat_id) > 0; +} + +const ChatManager::Chat *ChatManager::get_chat(ChatId chat_id) const { + return chats_.get_pointer(chat_id); +} + +ChatManager::Chat *ChatManager::get_chat(ChatId chat_id) { + return chats_.get_pointer(chat_id); +} + +ChatManager::Chat *ChatManager::add_chat(ChatId chat_id) { + CHECK(chat_id.is_valid()); + auto &chat_ptr = chats_[chat_id]; + if (chat_ptr == nullptr) { + chat_ptr = make_unique(); + } + return chat_ptr.get(); +} + +bool ChatManager::get_chat(ChatId chat_id, int left_tries, Promise &&promise) { + if (!chat_id.is_valid()) { + promise.set_error(Status::Error(400, "Invalid basic group identifier")); + return false; + } + + if (!have_chat(chat_id)) { + if (left_tries > 2 && G()->use_chat_info_database()) { + send_closure_later(actor_id(this), &ChatManager::load_chat_from_database, nullptr, chat_id, std::move(promise)); + return false; + } + + if (left_tries > 1) { + get_chat_queries_.add_query(chat_id.get(), std::move(promise), "get_chat"); + return false; + } + + promise.set_error(Status::Error(400, "Group not found")); + return false; + } + + promise.set_value(Unit()); + return true; +} + +void ChatManager::reload_chat(ChatId chat_id, Promise &&promise, const char *source) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!chat_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid basic group identifier")); + } + + get_chat_queries_.add_query(chat_id.get(), std::move(promise), source); +} + +const ChatManager::ChatFull *ChatManager::get_chat_full(ChatId chat_id) const { + return chats_full_.get_pointer(chat_id); +} + +ChatManager::ChatFull *ChatManager::get_chat_full(ChatId chat_id) { + return chats_full_.get_pointer(chat_id); +} + +ChatManager::ChatFull *ChatManager::add_chat_full(ChatId chat_id) { + CHECK(chat_id.is_valid()); + auto &chat_full_ptr = chats_full_[chat_id]; + if (chat_full_ptr == nullptr) { + chat_full_ptr = make_unique(); + } + return chat_full_ptr.get(); +} + +bool ChatManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, + bool only_participants) const { + CHECK(c != nullptr); + CHECK(chat_full != nullptr); + if (!c->is_active && chat_full->version == -1) { + return false; + } + + if (chat_full->version != c->version) { + LOG(INFO) << "Have outdated ChatFull " << chat_id << " with current version " << chat_full->version + << " and chat version " << c->version; + return true; + } + + if (!only_participants && c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) { + LOG(INFO) << "Have outdated invite link in " << chat_id; + return true; + } + + if (!only_participants && + !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { + LOG(INFO) << "Have outdated chat photo in " << chat_id; + return true; + } + + LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version << " and photos " << c->photo + << '/' << chat_full->photo; + return false; +} + +void ChatManager::load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source) { + auto c = get_chat(chat_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Group not found")); + } + + auto chat_full = get_chat_full_force(chat_id, source); + if (chat_full == nullptr) { + LOG(INFO) << "Full " << chat_id << " not found"; + return send_get_chat_full_query(chat_id, std::move(promise), source); + } + + if (is_chat_full_outdated(chat_full, c, chat_id, false)) { + LOG(INFO) << "Have outdated full " << chat_id; + if (td_->auth_manager_->is_bot() && !force) { + return send_get_chat_full_query(chat_id, std::move(promise), source); + } + + send_get_chat_full_query(chat_id, Auto(), source); + } + + vector participant_dialog_ids; + for (const auto &dialog_participant : chat_full->participants) { + participant_dialog_ids.push_back(dialog_participant.dialog_id_); + } + td_->story_manager_->on_view_dialog_active_stories(std::move(participant_dialog_ids)); + + promise.set_value(Unit()); +} + +void ChatManager::reload_chat_full(ChatId chat_id, Promise &&promise, const char *source) { + send_get_chat_full_query(chat_id, std::move(promise), source); +} + +void ChatManager::send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source) { + LOG(INFO) << "Get full " << chat_id << " from " << source; + if (!chat_id.is_valid()) { + return promise.set_error(Status::Error(500, "Invalid chat_id")); + } + auto send_query = PromiseCreator::lambda([td = td_, chat_id](Result> &&promise) { + if (promise.is_ok() && !G()->close_flag()) { + td->create_handler(promise.move_as_ok())->send(chat_id); + } + }); + + get_chat_full_queries_.add_query(DialogId(chat_id).get(), std::move(send_query), std::move(promise)); +} + +int32 ChatManager::get_chat_date(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return 0; + } + return c->date; +} + +int32 ChatManager::get_chat_participant_count(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return 0; + } + return c->participant_count; +} + +bool ChatManager::get_chat_is_active(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return false; + } + return c->is_active; +} + +ChannelId ChatManager::get_chat_migrated_to_channel_id(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return ChannelId(); + } + return c->migrated_to_channel_id; +} + +DialogParticipantStatus ChatManager::get_chat_status(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return DialogParticipantStatus::Banned(0); + } + return get_chat_status(c); +} + +DialogParticipantStatus ChatManager::get_chat_status(const Chat *c) { + if (!c->is_active) { + return DialogParticipantStatus::Banned(0); + } + return c->status; +} + +DialogParticipantStatus ChatManager::get_chat_permissions(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return DialogParticipantStatus::Banned(0); + } + return get_chat_permissions(c); +} + +DialogParticipantStatus ChatManager::get_chat_permissions(const Chat *c) const { + if (!c->is_active) { + return DialogParticipantStatus::Banned(0); + } + return c->status.apply_restrictions(c->default_permissions, false, td_->auth_manager_->is_bot()); +} + +bool ChatManager::is_appointed_chat_administrator(ChatId chat_id) const { + auto c = get_chat(chat_id); + if (c == nullptr) { + return false; + } + return c->status.is_administrator(); +} + +bool ChatManager::is_channel_public(ChannelId channel_id) const { + return is_channel_public(get_channel(channel_id)); +} + +bool ChatManager::is_channel_public(const Channel *c) { + return c != nullptr && (c->usernames.has_first_username() || c->has_location); +} + +ChannelType ChatManager::get_channel_type(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + auto min_channel = get_min_channel(channel_id); + if (min_channel != nullptr) { + return min_channel->is_megagroup_ ? ChannelType::Megagroup : ChannelType::Broadcast; + } + return ChannelType::Unknown; + } + return get_channel_type(c); +} + +ChannelType ChatManager::get_channel_type(const Channel *c) { + if (c->is_megagroup) { + return ChannelType::Megagroup; + } + return ChannelType::Broadcast; +} + +bool ChatManager::is_broadcast_channel(ChannelId channel_id) const { + return get_channel_type(channel_id) == ChannelType::Broadcast; +} + +bool ChatManager::is_megagroup_channel(ChannelId channel_id) const { + return get_channel_type(channel_id) == ChannelType::Megagroup; +} + +bool ChatManager::is_forum_channel(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->is_forum; +} + +int32 ChatManager::get_channel_date(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return 0; + } + return c->date; +} + +DialogParticipantStatus ChatManager::get_channel_status(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return DialogParticipantStatus::Banned(0); + } + return get_channel_status(c); +} + +DialogParticipantStatus ChatManager::get_channel_status(const Channel *c) { + c->status.update_restrictions(); + return c->status; +} + +DialogParticipantStatus ChatManager::get_channel_permissions(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return DialogParticipantStatus::Banned(0); + } + return get_channel_permissions(channel_id, c); +} + +DialogParticipantStatus ChatManager::get_channel_permissions(ChannelId channel_id, const Channel *c) const { + c->status.update_restrictions(); + bool is_booster = false; + if (!td_->auth_manager_->is_bot() && c->is_megagroup) { + auto channel_full = get_channel_full_const(channel_id); + if (channel_full == nullptr || (channel_full->unrestrict_boost_count > 0 && + channel_full->boost_count >= channel_full->unrestrict_boost_count)) { + is_booster = true; + } + } + return c->status.apply_restrictions(c->default_permissions, is_booster, td_->auth_manager_->is_bot()); +} + +int32 ChatManager::get_channel_participant_count(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return 0; + } + return c->participant_count; +} + +bool ChatManager::get_channel_is_verified(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->is_verified; +} + +bool ChatManager::get_channel_is_scam(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->is_scam; +} + +bool ChatManager::get_channel_is_fake(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return c->is_fake; +} + +bool ChatManager::get_channel_sign_messages(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_sign_messages(c); +} + +bool ChatManager::get_channel_sign_messages(const Channel *c) { + return c->sign_messages; +} + +bool ChatManager::get_channel_has_linked_channel(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_has_linked_channel(c); +} + +bool ChatManager::get_channel_has_linked_channel(const Channel *c) { + return c->has_linked_channel; +} + +bool ChatManager::get_channel_can_be_deleted(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_can_be_deleted(c); +} + +bool ChatManager::get_channel_can_be_deleted(const Channel *c) { + return c->can_be_deleted; +} + +bool ChatManager::get_channel_join_to_send(const Channel *c) { + return c->join_to_send || !c->is_megagroup || !c->has_linked_channel; +} + +bool ChatManager::get_channel_join_request(ChannelId channel_id) const { + auto c = get_channel(channel_id); + if (c == nullptr) { + return false; + } + return get_channel_join_request(c); +} + +bool ChatManager::get_channel_join_request(const Channel *c) { + return c->join_request && c->is_megagroup && (is_channel_public(c) || c->has_linked_channel); +} + +ChannelId ChatManager::get_channel_linked_channel_id(ChannelId channel_id, const char *source) { + auto channel_full = get_channel_full_const(channel_id); + if (channel_full == nullptr) { + channel_full = get_channel_full_force(channel_id, true, source); + if (channel_full == nullptr) { + return ChannelId(); + } + } + return channel_full->linked_channel_id; +} + +int32 ChatManager::get_channel_slow_mode_delay(ChannelId channel_id, const char *source) { + auto channel_full = get_channel_full_const(channel_id); + if (channel_full == nullptr) { + channel_full = get_channel_full_force(channel_id, true, source); + if (channel_full == nullptr) { + return 0; + } + } + return channel_full->slow_mode_delay; +} + +bool ChatManager::get_channel_effective_has_hidden_participants(ChannelId channel_id, const char *source) { + auto c = get_channel_force(channel_id, "get_channel_effective_has_hidden_participants"); + if (c == nullptr) { + return true; + } + if (get_channel_status(c).is_administrator()) { + return false; + } + + auto channel_full = get_channel_full_const(channel_id); + if (channel_full == nullptr) { + channel_full = get_channel_full_force(channel_id, true, source); + if (channel_full == nullptr) { + return true; + } + } + return channel_full->has_hidden_participants || !channel_full->can_get_participants; +} + +int32 ChatManager::get_channel_my_boost_count(ChannelId channel_id) { + auto channel_full = get_channel_full_const(channel_id); + if (channel_full == nullptr) { + channel_full = get_channel_full_force(channel_id, true, "get_channel_my_boost_count"); + if (channel_full == nullptr) { + return 0; + } + } + return channel_full->boost_count; +} + +bool ChatManager::have_channel(ChannelId channel_id) const { + return channels_.count(channel_id) > 0; +} + +bool ChatManager::have_min_channel(ChannelId channel_id) const { + return min_channels_.count(channel_id) > 0; +} + +const MinChannel *ChatManager::get_min_channel(ChannelId channel_id) const { + return min_channels_.get_pointer(channel_id); +} + +void ChatManager::add_min_channel(ChannelId channel_id, const MinChannel &min_channel) { + if (have_channel(channel_id) || have_min_channel(channel_id) || !channel_id.is_valid()) { + return; + } + min_channels_.set(channel_id, td::make_unique(min_channel)); +} + +const ChatManager::Channel *ChatManager::get_channel(ChannelId channel_id) const { + return channels_.get_pointer(channel_id); +} + +ChatManager::Channel *ChatManager::get_channel(ChannelId channel_id) { + return channels_.get_pointer(channel_id); +} + +ChatManager::Channel *ChatManager::add_channel(ChannelId channel_id, const char *source) { + CHECK(channel_id.is_valid()); + auto &channel_ptr = channels_[channel_id]; + if (channel_ptr == nullptr) { + channel_ptr = make_unique(); + min_channels_.erase(channel_id); + } + return channel_ptr.get(); +} + +bool ChatManager::get_channel(ChannelId channel_id, int left_tries, Promise &&promise) { + if (!channel_id.is_valid()) { + promise.set_error(Status::Error(400, "Invalid supergroup identifier")); + return false; + } + + if (!have_channel(channel_id)) { + if (left_tries > 2 && G()->use_chat_info_database()) { + send_closure_later(actor_id(this), &ChatManager::load_channel_from_database, nullptr, channel_id, + std::move(promise)); + return false; + } + + if (left_tries > 1 && td_->auth_manager_->is_bot()) { + get_channel_queries_.add_query(channel_id.get(), std::move(promise), "get_channel"); + return false; + } + + promise.set_error(Status::Error(400, "Supergroup not found")); + return false; + } + + promise.set_value(Unit()); + return true; +} + +void ChatManager::reload_channel(ChannelId channel_id, Promise &&promise, const char *source) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!channel_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid supergroup identifier")); + } + + have_channel_force(channel_id, source); + auto input_channel = get_input_channel(channel_id); + if (input_channel == nullptr) { + // requests with 0 access_hash must not be merged + td_->create_handler(std::move(promise)) + ->send(telegram_api::make_object(channel_id.get(), 0)); + return; + } + + get_channel_queries_.add_query(channel_id.get(), std::move(promise), source); +} + +const ChatManager::ChannelFull *ChatManager::get_channel_full_const(ChannelId channel_id) const { + return channels_full_.get_pointer(channel_id); +} + +const ChatManager::ChannelFull *ChatManager::get_channel_full(ChannelId channel_id) const { + return channels_full_.get_pointer(channel_id); +} + +ChatManager::ChannelFull *ChatManager::get_channel_full(ChannelId channel_id, bool only_local, const char *source) { + auto channel_full = channels_full_.get_pointer(channel_id); + if (channel_full == nullptr) { + return nullptr; + } + + if (!only_local && channel_full->is_expired() && !td_->auth_manager_->is_bot()) { + send_get_channel_full_query(channel_full, channel_id, Auto(), source); + } + + return channel_full; +} + +ChatManager::ChannelFull *ChatManager::add_channel_full(ChannelId channel_id) { + CHECK(channel_id.is_valid()); + auto &channel_full_ptr = channels_full_[channel_id]; + if (channel_full_ptr == nullptr) { + channel_full_ptr = make_unique(); + } + return channel_full_ptr.get(); +} + +void ChatManager::load_channel_full(ChannelId channel_id, bool force, Promise &&promise, const char *source) { + auto channel_full = get_channel_full_force(channel_id, true, source); + if (channel_full == nullptr) { + return send_get_channel_full_query(channel_full, channel_id, std::move(promise), source); + } + if (channel_full->is_expired()) { + if (td_->auth_manager_->is_bot() && !force) { + return send_get_channel_full_query(channel_full, channel_id, std::move(promise), "load expired channel_full"); + } + + Promise new_promise; + if (promise) { + new_promise = PromiseCreator::lambda([channel_id](Result result) { + if (result.is_error()) { + LOG(INFO) << "Failed to reload expired " << channel_id << ": " << result.error(); + } else { + LOG(INFO) << "Reloaded expired " << channel_id; + } + }); + } + send_get_channel_full_query(channel_full, channel_id, std::move(new_promise), "load expired channel_full"); + } + + promise.set_value(Unit()); +} + +void ChatManager::reload_channel_full(ChannelId channel_id, Promise &&promise, const char *source) { + send_get_channel_full_query(get_channel_full(channel_id, true, "reload_channel_full"), channel_id, std::move(promise), + source); +} + +void ChatManager::send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, Promise &&promise, + const char *source) { + auto input_channel = get_input_channel(channel_id); + if (input_channel == nullptr) { + return promise.set_error(Status::Error(400, "Supergroup not found")); + } + + if (!have_input_peer_channel(channel_id, AccessRights::Read)) { + return promise.set_error(Status::Error(400, "Can't access the chat")); + } + + if (channel_full != nullptr) { + if (!promise) { + if (channel_full->repair_request_version != 0) { + LOG(INFO) << "Skip get full " << channel_id << " request from " << source; + return; + } + channel_full->repair_request_version = channel_full->speculative_version; + } else { + channel_full->repair_request_version = std::numeric_limits::max(); + } + } + + LOG(INFO) << "Get full " << channel_id << " from " << source; + auto send_query = PromiseCreator::lambda( + [td = td_, channel_id, input_channel = std::move(input_channel)](Result> &&promise) mutable { + if (promise.is_ok() && !G()->close_flag()) { + td->create_handler(promise.move_as_ok())->send(channel_id, std::move(input_channel)); + } + }); + get_chat_full_queries_.add_query(DialogId(channel_id).get(), std::move(send_query), std::move(promise)); +} + +void ChatManager::get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { + LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id; + + auto c = get_chat(chat_id); + if (c == nullptr) { + return promise.set_error(Status::Error(400, "Group not found")); + } + + if (td_->auth_manager_->is_bot() && user_id == td_->user_manager_->get_my_id()) { + // bots don't need inviter information + reload_chat(chat_id, Auto(), "get_chat_participant"); + return promise.set_value(DialogParticipant{DialogId(user_id), user_id, c->date, c->status}); + } + + auto chat_full = get_chat_full_force(chat_id, "get_chat_participant"); + if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id, true))) { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result &&result) mutable { + TRY_STATUS_PROMISE(promise, std::move(result)); + send_closure(actor_id, &ChatManager::finish_get_chat_participant, chat_id, user_id, std::move(promise)); + }); + send_get_chat_full_query(chat_id, std::move(query_promise), "get_chat_participant"); + return; + } + + if (is_chat_full_outdated(chat_full, c, chat_id, true)) { + send_get_chat_full_query(chat_id, Auto(), "get_chat_participant lazy"); + } + + finish_get_chat_participant(chat_id, user_id, std::move(promise)); +} + +void ChatManager::finish_get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + const auto *participant = get_chat_participant(chat_id, user_id); + if (participant == nullptr) { + return promise.set_value(DialogParticipant::left(DialogId(user_id))); + } + + promise.set_value(DialogParticipant(*participant)); +} + +void ChatManager::on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_administrator_count"); + if (channel_full != nullptr && channel_full->administrator_count != administrator_count) { + channel_full->administrator_count = administrator_count; + channel_full->is_changed = true; + + if (channel_full->participant_count < channel_full->administrator_count) { + channel_full->participant_count = channel_full->administrator_count; + + auto c = get_channel(channel_id); + if (c != nullptr && c->participant_count != channel_full->participant_count) { + c->participant_count = channel_full->participant_count; + c->is_changed = true; + update_channel(c, channel_id); + } + } + + update_channel_full(channel_full, channel_id, "on_update_channel_administrator_count"); + } +} + +void ChatManager::on_update_channel_bot_commands(ChannelId channel_id, BotCommands &&bot_commands) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_commands"); + if (channel_full != nullptr && + BotCommands::update_all_bot_commands(channel_full->bot_commands, std::move(bot_commands))) { + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_bot_commands"); + } +} + +void ChatManager::on_update_channel_permanent_invite_link(ChannelId channel_id, const DialogInviteLink &invite_link) { + auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_permanent_invite_link"); + if (channel_full != nullptr && update_permanent_invite_link(channel_full->invite_link, invite_link)) { + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_update_channel_permanent_invite_link"); + } +} + +void ChatManager::on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source) { + ChatId chat_id(chat.id_); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; + return; + } + + if (!have_chat(chat_id)) { + LOG(ERROR) << "Have no information about " << chat_id << " but received chatEmpty from " << source; + } +} + +void ChatManager::on_get_chat(telegram_api::chat &chat, const char *source) { + auto debug_str = PSTRING() << " from " << source << " in " << oneline(to_string(chat)); + ChatId chat_id(chat.id_); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id << debug_str; + return; + } + + DialogParticipantStatus status = [&] { + bool is_creator = 0 != (chat.flags_ & CHAT_FLAG_USER_IS_CREATOR); + bool has_left = 0 != (chat.flags_ & CHAT_FLAG_USER_HAS_LEFT); + if (is_creator) { + return DialogParticipantStatus::Creator(!has_left, false, string()); + } else if (chat.admin_rights_ != nullptr) { + return DialogParticipantStatus(false, std::move(chat.admin_rights_), string(), ChannelType::Unknown); + } else if (has_left) { + return DialogParticipantStatus::Left(); + } else { + return DialogParticipantStatus::Member(); + } + }(); + + bool is_active = 0 == (chat.flags_ & CHAT_FLAG_IS_DEACTIVATED); + + ChannelId migrated_to_channel_id; + if (chat.flags_ & CHAT_FLAG_WAS_MIGRATED) { + switch (chat.migrated_to_->get_id()) { + case telegram_api::inputChannelFromMessage::ID: + case telegram_api::inputChannelEmpty::ID: + LOG(ERROR) << "Receive invalid information about upgraded supergroup for " << chat_id << debug_str; + break; + case telegram_api::inputChannel::ID: { + auto input_channel = move_tl_object_as(chat.migrated_to_); + migrated_to_channel_id = ChannelId(input_channel->channel_id_); + if (!have_channel_force(migrated_to_channel_id, source)) { + if (!migrated_to_channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << migrated_to_channel_id << debug_str; + } else { + // temporarily create the channel + Channel *c = add_channel(migrated_to_channel_id, "on_get_chat"); + c->access_hash = input_channel->access_hash_; + c->title = chat.title_; + c->status = DialogParticipantStatus::Left(); + c->is_megagroup = true; + + // we definitely need to call update_channel, because client should know about every added channel + update_channel(c, migrated_to_channel_id); + + // get info about the channel + get_channel_queries_.add_query(migrated_to_channel_id.get(), Promise(), "on_get_chat"); + } + } + break; + } + default: + UNREACHABLE(); + } + } + + Chat *c = get_chat_force(chat_id, source); // to load versions + if (c == nullptr) { + c = add_chat(chat_id); + } + on_update_chat_title(c, chat_id, std::move(chat.title_)); + if (!status.is_left()) { + on_update_chat_participant_count(c, chat_id, chat.participants_count_, chat.version_, debug_str); + } else { + chat.photo_ = nullptr; + } + if (c->date != chat.date_) { + LOG_IF(ERROR, c->date != 0) << "Chat creation date has changed from " << c->date << " to " << chat.date_ + << debug_str; + c->date = chat.date_; + c->need_save_to_database = true; + } + on_update_chat_status(c, chat_id, std::move(status)); + on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_, ChannelType::Unknown), + chat.version_); + on_update_chat_photo(c, chat_id, std::move(chat.photo_)); + on_update_chat_active(c, chat_id, is_active); + on_update_chat_noforwards(c, chat_id, chat.noforwards_); + on_update_chat_migrated_to_channel_id(c, chat_id, migrated_to_channel_id); + LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated" << debug_str; + if (c->cache_version != Chat::CACHE_VERSION) { + c->cache_version = Chat::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_chat(c, chat_id); + + bool has_active_group_call = (chat.flags_ & CHAT_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; + bool is_group_call_empty = (chat.flags_ & CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; + td_->messages_manager_->on_update_dialog_group_call(DialogId(chat_id), has_active_group_call, is_group_call_empty, + "receive chat"); +} + +void ChatManager::on_get_chat_forbidden(telegram_api::chatForbidden &chat, const char *source) { + ChatId chat_id(chat.id_); + if (!chat_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; + return; + } + + bool is_uninited = get_chat_force(chat_id, source) == nullptr; + Chat *c = add_chat(chat_id); + on_update_chat_title(c, chat_id, std::move(chat.title_)); + // chat participant count will be updated in on_update_chat_status + on_update_chat_photo(c, chat_id, nullptr); + if (c->date != 0) { + c->date = 0; // removed in 38-th layer + c->need_save_to_database = true; + } + on_update_chat_status(c, chat_id, DialogParticipantStatus::Banned(0)); + if (is_uninited) { + on_update_chat_active(c, chat_id, true); + on_update_chat_migrated_to_channel_id(c, chat_id, ChannelId()); + } else { + // leave active and migrated to as is + } + if (c->cache_version != Chat::CACHE_VERSION) { + c->cache_version = Chat::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_chat(c, chat_id); +} + +void ChatManager::on_get_channel(telegram_api::channel &channel, const char *source) { + ChannelId channel_id(channel.id_); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); + return; + } + + if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { + Channel *c = get_channel_force(channel_id, source); + LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " + << to_string(get_supergroup_object(channel_id, c)); + if (c == nullptr && !have_min_channel(channel_id)) { + min_channels_.set(channel_id, td::make_unique()); + } + return; + } + + bool is_min = (channel.flags_ & CHANNEL_FLAG_IS_MIN) != 0; + bool has_access_hash = (channel.flags_ & CHANNEL_FLAG_HAS_ACCESS_HASH) != 0; + auto access_hash = has_access_hash ? channel.access_hash_ : 0; + + bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0; + bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0; + bool join_to_send = (channel.flags_ & CHANNEL_FLAG_JOIN_TO_SEND) != 0; + bool join_request = (channel.flags_ & CHANNEL_FLAG_JOIN_REQUEST) != 0; + bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0; + bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; + bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0; + bool is_scam = (channel.flags_ & CHANNEL_FLAG_IS_SCAM) != 0; + bool is_fake = (channel.flags_ & CHANNEL_FLAG_IS_FAKE) != 0; + bool is_gigagroup = (channel.flags_ & CHANNEL_FLAG_IS_GIGAGROUP) != 0; + bool is_forum = (channel.flags_ & CHANNEL_FLAG_IS_FORUM) != 0; + bool have_participant_count = (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0; + int32 participant_count = have_participant_count ? channel.participants_count_ : 0; + bool stories_available = channel.stories_max_id_ > 0; + bool stories_unavailable = channel.stories_unavailable_; + auto boost_level = channel.level_; + + if (have_participant_count) { + auto channel_full = get_channel_full_const(channel_id); + if (channel_full != nullptr && channel_full->administrator_count > participant_count) { + participant_count = channel_full->administrator_count; + } + } + + { + bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; + LOG_IF(ERROR, is_broadcast == is_megagroup) + << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " + << oneline(to_string(channel)); + } + + if (is_megagroup) { + LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id << " from " << source; + sign_messages = true; + } else { + LOG_IF(ERROR, is_slow_mode_enabled) << "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; + is_gigagroup = false; + is_forum = false; + } + if (is_gigagroup) { + td_->dialog_manager_->remove_dialog_suggested_action( + 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) { + LOG(DEBUG) << "Receive known min " << channel_id; + + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); + on_update_channel_title(c, channel_id, std::move(channel.title_)); + on_update_channel_usernames(c, channel_id, + Usernames(std::move(channel.username_), std::move(channel.usernames_))); + if (!c->status.is_banned()) { + on_update_channel_photo(c, channel_id, std::move(channel.photo_)); + } + on_update_channel_has_location(c, channel_id, channel.has_geo_); + on_update_channel_noforwards(c, channel_id, channel.noforwards_); + on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); + + if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || + c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || + c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { + c->has_linked_channel = has_linked_channel; + c->is_slow_mode_enabled = is_slow_mode_enabled; + c->is_megagroup = is_megagroup; + c->is_scam = is_scam; + c->is_fake = is_fake; + c->is_gigagroup = is_gigagroup; + if (c->is_forum != is_forum) { + c->is_forum = is_forum; + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), + is_forum); + } + c->boost_level = boost_level; + + c->is_changed = true; + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_min_channel"); + } + if (!td_->auth_manager_->is_bot()) { + auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); + if (restriction_reasons != c->restriction_reasons) { + c->restriction_reasons = std::move(restriction_reasons); + c->is_changed = true; + } + } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } + // sign_messages isn't known for min-channels + if (c->is_verified != is_verified) { + c->is_verified = is_verified; + + c->is_changed = true; + } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } + + // must be after setting of c->is_megagroup + on_update_channel_default_permissions(c, channel_id, + RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); + + update_channel(c, channel_id); + } else { + auto min_channel = td::make_unique(); + min_channel->photo_ = + get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), access_hash, std::move(channel.photo_)); + if (td_->auth_manager_->is_bot()) { + min_channel->photo_.minithumbnail.clear(); + } + PeerColor peer_color(channel.color_); + min_channel->accent_color_id_ = peer_color.accent_color_id_; + min_channel->title_ = std::move(channel.title_); + min_channel->is_megagroup_ = is_megagroup; + + min_channels_.set(channel_id, std::move(min_channel)); + } + return; + } + if (!has_access_hash) { + LOG(ERROR) << "Receive non-min " << channel_id << " without access_hash from " << source; + return; + } + + if (status.is_creator()) { + // to correctly calculate is_ownership_transferred in on_update_channel_status + get_channel_force(channel_id, source); + } + + Channel *c = add_channel(channel_id, "on_get_channel"); + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); + if (c->access_hash != access_hash) { + c->access_hash = access_hash; + c->need_save_to_database = true; + } + if (c->date != channel.date_) { + c->date = channel.date_; + c->is_changed = true; + } + + bool need_update_participant_count = have_participant_count && participant_count != c->participant_count; + if (need_update_participant_count) { + c->participant_count = participant_count; + c->is_changed = true; + } + + bool need_invalidate_channel_full = false; + if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || + c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || + c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { + c->has_linked_channel = has_linked_channel; + c->is_slow_mode_enabled = is_slow_mode_enabled; + c->is_megagroup = is_megagroup; + c->is_scam = is_scam; + c->is_fake = is_fake; + c->is_gigagroup = is_gigagroup; + if (c->is_forum != is_forum) { + c->is_forum = is_forum; + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), + is_forum); + } + c->boost_level = boost_level; + + c->is_changed = true; + need_invalidate_channel_full = true; + } + if (!td_->auth_manager_->is_bot()) { + auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); + if (restriction_reasons != c->restriction_reasons) { + c->restriction_reasons = std::move(restriction_reasons); + c->is_changed = true; + } + } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } + if (c->is_verified != is_verified || c->sign_messages != sign_messages) { + c->is_verified = is_verified; + c->sign_messages = sign_messages; + + c->is_changed = true; + } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } + + on_update_channel_title(c, channel_id, std::move(channel.title_)); + on_update_channel_photo(c, channel_id, std::move(channel.photo_)); + PeerColor peer_color(channel.color_); + on_update_channel_accent_color_id(c, channel_id, peer_color.accent_color_id_); + on_update_channel_background_custom_emoji_id(c, channel_id, peer_color.background_custom_emoji_id_); + PeerColor profile_peer_color(channel.profile_color_); + on_update_channel_profile_accent_color_id(c, channel_id, profile_peer_color.accent_color_id_); + on_update_channel_profile_background_custom_emoji_id(c, channel_id, profile_peer_color.background_custom_emoji_id_); + on_update_channel_status(c, channel_id, std::move(status)); + on_update_channel_usernames( + c, channel_id, + Usernames(std::move(channel.username_), + std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status + on_update_channel_has_location(c, channel_id, channel.has_geo_); + on_update_channel_noforwards(c, channel_id, channel.noforwards_); + on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); + if (!td_->auth_manager_->is_bot() && !channel.stories_hidden_min_) { + on_update_channel_stories_hidden(c, channel_id, channel.stories_hidden_); + } + // must be after setting of c->is_megagroup + on_update_channel_default_permissions(c, channel_id, + RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); + if (!td_->auth_manager_->is_bot() && (stories_available || stories_unavailable)) { + // update at the end, because it calls need_poll_channel_active_stories + on_update_channel_story_ids_impl(c, channel_id, StoryId(channel.stories_max_id_), StoryId()); + } + + if (c->cache_version != Channel::CACHE_VERSION) { + c->cache_version = Channel::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_channel(c, channel_id); + + if (need_update_participant_count) { + auto channel_full = get_channel_full(channel_id, true, "on_get_channel"); + if (channel_full != nullptr && channel_full->participant_count != participant_count) { + channel_full->participant_count = participant_count; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_get_channel"); + } + } + + if (need_invalidate_channel_full) { + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel"); + } + + bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; + bool is_group_call_empty = (channel.flags_ & CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; + td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), has_active_group_call, is_group_call_empty, + "receive channel"); +} + +void ChatManager::on_get_channel_forbidden(telegram_api::channelForbidden &channel, const char *source) { + ChannelId channel_id(channel.id_); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); + return; + } + + if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { + Channel *c = get_channel_force(channel_id, source); + LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " + << to_string(get_supergroup_object(channel_id, c)); + if (c == nullptr && !have_min_channel(channel_id)) { + min_channels_.set(channel_id, td::make_unique()); + } + return; + } + + Channel *c = add_channel(channel_id, "on_get_channel_forbidden"); + auto old_join_to_send = get_channel_join_to_send(c); + auto old_join_request = get_channel_join_request(c); + if (c->access_hash != channel.access_hash_) { + c->access_hash = channel.access_hash_; + c->need_save_to_database = true; + } + if (c->date != 0) { + c->date = 0; + c->is_changed = true; + } + + bool sign_messages = false; + bool join_to_send = false; + bool join_request = false; + bool is_slow_mode_enabled = false; + bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; + bool is_verified = false; + bool is_scam = false; + bool is_fake = false; + + { + bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; + LOG_IF(ERROR, is_broadcast == is_megagroup) + << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " + << oneline(to_string(channel)); + } + + if (is_megagroup) { + sign_messages = true; + } + + bool need_invalidate_channel_full = false; + if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || + !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake || + c->join_to_send != join_to_send || c->join_request != join_request) { + // c->has_linked_channel = has_linked_channel; + c->is_slow_mode_enabled = is_slow_mode_enabled; + c->is_megagroup = is_megagroup; + c->restriction_reasons.clear(); + c->is_scam = is_scam; + c->is_fake = is_fake; + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->is_changed = true; + need_invalidate_channel_full = true; + } + if (c->join_to_send != join_to_send || c->join_request != join_request) { + c->join_to_send = join_to_send; + c->join_request = join_request; + + c->need_save_to_database = true; + } + if (c->sign_messages != sign_messages || c->is_verified != is_verified) { + c->sign_messages = sign_messages; + c->is_verified = is_verified; + + c->is_changed = true; + } + if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { + c->is_changed = true; + } + + on_update_channel_title(c, channel_id, std::move(channel.title_)); + on_update_channel_photo(c, channel_id, nullptr); + on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(channel.until_date_)); + // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it + // on_update_channel_has_location(c, channel_id, false); + on_update_channel_noforwards(c, channel_id, false); + on_update_channel_emoji_status(c, channel_id, EmojiStatus()); + td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "on_get_channel_forbidden"); + // must be after setting of c->is_megagroup + tl_object_ptr banned_rights; // == nullptr + on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights, ChannelType::Megagroup)); + + bool need_drop_participant_count = c->participant_count != 0; + if (need_drop_participant_count) { + c->participant_count = 0; + c->is_changed = true; + } + + if (c->cache_version != Channel::CACHE_VERSION) { + c->cache_version = Channel::CACHE_VERSION; + c->need_save_to_database = true; + } + c->is_received_from_server = true; + update_channel(c, channel_id); + + if (need_drop_participant_count) { + auto channel_full = get_channel_full(channel_id, true, "on_get_channel_forbidden"); + if (channel_full != nullptr && channel_full->participant_count != 0) { + channel_full->participant_count = 0; + channel_full->administrator_count = 0; + channel_full->is_changed = true; + update_channel_full(channel_full, channel_id, "on_get_channel_forbidden 2"); + } + } + if (need_invalidate_channel_full) { + invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel_forbidden 3"); + } +} + +td_api::object_ptr ChatManager::get_update_basic_group_object(ChatId chat_id, const Chat *c) { + if (c == nullptr) { + return get_update_unknown_basic_group_object(chat_id); + } + return td_api::make_object(get_basic_group_object(chat_id, c)); +} + +td_api::object_ptr ChatManager::get_update_unknown_basic_group_object(ChatId chat_id) { + return td_api::make_object(td_api::make_object( + chat_id.get(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), true, 0)); +} + +int64 ChatManager::get_basic_group_id_object(ChatId chat_id, const char *source) const { + if (chat_id.is_valid() && get_chat(chat_id) == nullptr && unknown_chats_.count(chat_id) == 0) { + LOG(ERROR) << "Have no information about " << chat_id << " from " << source; + unknown_chats_.insert(chat_id); + send_closure(G()->td(), &Td::send_update, get_update_unknown_basic_group_object(chat_id)); + } + return chat_id.get(); +} + +tl_object_ptr ChatManager::get_basic_group_object(ChatId chat_id) { + return get_basic_group_object(chat_id, get_chat(chat_id)); +} + +tl_object_ptr ChatManager::get_basic_group_object(ChatId chat_id, const Chat *c) { + if (c == nullptr) { + return nullptr; + } + if (c->migrated_to_channel_id.is_valid()) { + get_channel_force(c->migrated_to_channel_id, "get_basic_group_object"); + } + return get_basic_group_object_const(chat_id, c); +} + +tl_object_ptr ChatManager::get_basic_group_object_const(ChatId chat_id, const Chat *c) const { + return make_tl_object( + chat_id.get(), c->participant_count, get_chat_status(c).get_chat_member_status_object(), c->is_active, + get_supergroup_id_object(c->migrated_to_channel_id, "get_basic_group_object")); +} + +tl_object_ptr ChatManager::get_basic_group_full_info_object(ChatId chat_id) const { + return get_basic_group_full_info_object(chat_id, get_chat_full(chat_id)); +} + +tl_object_ptr ChatManager::get_basic_group_full_info_object( + ChatId chat_id, const ChatFull *chat_full) const { + CHECK(chat_full != nullptr); + auto bot_commands = transform(chat_full->bot_commands, [td = td_](const BotCommands &commands) { + return commands.get_bot_commands_object(td); + }); + auto members = transform(chat_full->participants, [this](const DialogParticipant &dialog_participant) { + return get_chat_member_object(dialog_participant, "get_basic_group_full_info_object"); + }); + return make_tl_object( + get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description, + td_->user_manager_->get_user_id_object(chat_full->creator_user_id, "basicGroupFullInfo"), std::move(members), + can_hide_chat_participants(chat_id).is_ok(), can_toggle_chat_aggressive_anti_spam(chat_id).is_ok(), + chat_full->invite_link.get_chat_invite_link_object(td_->user_manager_.get()), std::move(bot_commands)); +} + +td_api::object_ptr ChatManager::get_update_supergroup_object(ChannelId channel_id, + const Channel *c) const { + if (c == nullptr) { + return get_update_unknown_supergroup_object(channel_id); + } + return td_api::make_object(get_supergroup_object(channel_id, c)); +} + +td_api::object_ptr ChatManager::get_update_unknown_supergroup_object( + ChannelId channel_id) const { + auto min_channel = get_min_channel(channel_id); + 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)); +} + +int64 ChatManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { + if (channel_id.is_valid() && get_channel(channel_id) == nullptr && unknown_channels_.count(channel_id) == 0) { + if (have_min_channel(channel_id)) { + LOG(INFO) << "Have only min " << channel_id << " received from " << source; + } else { + LOG(ERROR) << "Have no information about " << channel_id << " received from " << source; + } + unknown_channels_.insert(channel_id); + send_closure(G()->td(), &Td::send_update, get_update_unknown_supergroup_object(channel_id)); + } + return channel_id.get(); +} + +bool ChatManager::need_poll_channel_active_stories(const Channel *c, ChannelId channel_id) const { + return c != nullptr && !get_channel_status(c).is_member() && + have_input_peer_channel(c, channel_id, AccessRights::Read); +} + +bool ChatManager::get_channel_has_unread_stories(const Channel *c) { + CHECK(c != nullptr); + return c->max_active_story_id.get() > c->max_read_story_id.get(); +} + +tl_object_ptr ChatManager::get_supergroup_object(ChannelId channel_id) const { + return get_supergroup_object(channel_id, get_channel(channel_id)); +} + +tl_object_ptr ChatManager::get_supergroup_object(ChannelId channel_id, const Channel *c) { + if (c == nullptr) { + return nullptr; + } + 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), + 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->max_active_story_id.is_valid(), get_channel_has_unread_stories(c)); +} + +tl_object_ptr ChatManager::get_supergroup_full_info_object(ChannelId channel_id) const { + return get_supergroup_full_info_object(channel_id, get_channel_full(channel_id)); +} + +tl_object_ptr ChatManager::get_supergroup_full_info_object( + ChannelId channel_id, const ChannelFull *channel_full) const { + CHECK(channel_full != nullptr); + double slow_mode_delay_expires_in = 0; + if (channel_full->slow_mode_next_send_date != 0 && + (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count)) { + slow_mode_delay_expires_in = max(channel_full->slow_mode_next_send_date - G()->server_time(), 1e-3); + } + auto bot_commands = transform(channel_full->bot_commands, [td = td_](const BotCommands &commands) { + return commands.get_bot_commands_object(td); + }); + bool has_hidden_participants = channel_full->has_hidden_participants || !channel_full->can_get_participants; + return td_api::make_object( + 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, + 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_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()); +} + +void ChatManager::get_current_state(vector> &updates) const { + for (auto chat_id : unknown_chats_) { + if (!have_chat(chat_id)) { + updates.push_back(get_update_unknown_basic_group_object(chat_id)); + } + } + for (auto channel_id : unknown_channels_) { + if (!have_channel(channel_id)) { + updates.push_back(get_update_unknown_supergroup_object(channel_id)); + } + } + + channels_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel) { + updates.push_back(get_update_supergroup_object(channel_id, channel.get())); + }); + // chat objects can contain channel_id, so they must be sent after channels + chats_.foreach([&](const ChatId &chat_id, const unique_ptr &chat) { + updates.push_back(td_api::make_object(get_basic_group_object_const(chat_id, chat.get()))); + }); + + channels_full_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel_full) { + updates.push_back(td_api::make_object( + channel_id.get(), get_supergroup_full_info_object(channel_id, channel_full.get()))); + }); + chats_full_.foreach([&](const ChatId &chat_id, const unique_ptr &chat_full) { + updates.push_back(td_api::make_object( + chat_id.get(), get_basic_group_full_info_object(chat_id, chat_full.get()))); + }); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ChatManager.h b/lib/tgchat/ext/td/td/telegram/ChatManager.h new file mode 100644 index 00000000..09596758 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ChatManager.h @@ -0,0 +1,949 @@ +// +// 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/AccentColorId.h" +#include "td/telegram/AccessRights.h" +#include "td/telegram/BotCommand.h" +#include "td/telegram/ChannelId.h" +#include "td/telegram/ChannelType.h" +#include "td/telegram/ChatId.h" +#include "td/telegram/CustomEmojiId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/DialogInviteLink.h" +#include "td/telegram/DialogLocation.h" +#include "td/telegram/DialogParticipant.h" +#include "td/telegram/EmojiStatus.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileSourceId.h" +#include "td/telegram/MessageFullId.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessageTtl.h" +#include "td/telegram/net/DcId.h" +#include "td/telegram/Photo.h" +#include "td/telegram/PublicDialogType.h" +#include "td/telegram/QueryCombiner.h" +#include "td/telegram/QueryMerger.h" +#include "td/telegram/RestrictionReason.h" +#include "td/telegram/StickerSetId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" +#include "td/telegram/Usernames.h" + +#include "td/actor/actor.h" +#include "td/actor/MultiTimeout.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/WaitFreeHashMap.h" +#include "td/utils/WaitFreeHashSet.h" + +#include + +namespace td { + +struct BinlogEvent; +struct MinChannel; +class Td; + +class ChatManager final : public Actor { + public: + ChatManager(Td *td, ActorShared<> parent); + ChatManager(const ChatManager &) = delete; + ChatManager &operator=(const ChatManager &) = delete; + ChatManager(ChatManager &&) = delete; + ChatManager &operator=(ChatManager &&) = delete; + ~ChatManager() final; + + static ChatId get_chat_id(const tl_object_ptr &chat); + static ChannelId get_channel_id(const tl_object_ptr &chat); + static DialogId get_dialog_id(const tl_object_ptr &chat); + + vector get_channel_ids(vector> &&chats, const char *source); + + // TODO get_input_chat ??? + + tl_object_ptr get_input_channel(ChannelId channel_id) const; + + tl_object_ptr get_input_peer_chat(ChatId chat_id, AccessRights access_rights) const; + bool have_input_peer_chat(ChatId chat_id, AccessRights access_rights) const; + + tl_object_ptr get_simple_input_peer(DialogId dialog_id) const; + tl_object_ptr get_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const; + bool have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const; + + bool is_chat_received_from_server(ChatId chat_id) const; + bool is_channel_received_from_server(ChannelId channel_id) const; + + const DialogPhoto *get_chat_dialog_photo(ChatId chat_id) const; + const DialogPhoto *get_channel_dialog_photo(ChannelId channel_id) const; + + AccentColorId get_channel_accent_color_id(ChannelId channel_id) const; + + int32 get_chat_accent_color_id_object(ChatId chat_id) const; + int32 get_channel_accent_color_id_object(ChannelId channel_id) const; + + CustomEmojiId get_chat_background_custom_emoji_id(ChatId chat_id) const; + CustomEmojiId get_channel_background_custom_emoji_id(ChannelId channel_id) const; + + int32 get_chat_profile_accent_color_id_object(ChatId chat_id) const; + int32 get_channel_profile_accent_color_id_object(ChannelId channel_id) const; + + CustomEmojiId get_chat_profile_background_custom_emoji_id(ChatId chat_id) const; + CustomEmojiId get_channel_profile_background_custom_emoji_id(ChannelId channel_id) const; + + string get_chat_title(ChatId chat_id) const; + string get_channel_title(ChannelId channel_id) const; + + RestrictedRights get_chat_default_permissions(ChatId chat_id) const; + RestrictedRights get_channel_default_permissions(ChannelId channel_id) const; + + td_api::object_ptr get_chat_emoji_status_object(ChatId chat_id) const; + td_api::object_ptr get_channel_emoji_status_object(ChannelId channel_id) const; + + string get_chat_about(ChatId chat_id); + string get_channel_about(ChannelId channel_id); + + bool get_chat_has_protected_content(ChatId chat_id) const; + bool get_channel_has_protected_content(ChannelId channel_id) const; + + bool get_channel_stories_hidden(ChannelId channel_id) const; + + bool can_poll_channel_active_stories(ChannelId channel_id) const; + + bool can_use_premium_custom_emoji_in_channel(ChannelId channel_id) const; + + string get_channel_search_text(ChannelId channel_id) const; + + string get_channel_first_username(ChannelId channel_id) const; + string get_channel_editable_username(ChannelId channel_id) const; + + void on_binlog_chat_event(BinlogEvent &&event); + void on_binlog_channel_event(BinlogEvent &&event); + + void on_get_chat(tl_object_ptr &&chat, const char *source); + void on_get_chats(vector> &&chats, const char *source); + + void on_get_chat_full(tl_object_ptr &&chat_full, Promise &&promise); + void on_get_chat_full_failed(ChatId chat_id); + void on_get_channel_full_failed(ChannelId channel_id); + + void on_ignored_restriction_reasons_changed(); + + void on_get_chat_participants(tl_object_ptr &&participants, bool from_update); + void on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, int32 version); + void on_update_chat_description(ChatId chat_id, string &&description); + void on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, int32 version); + void on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version); + void on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, int32 version); + void on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version); + void on_update_chat_bot_commands(ChatId chat_id, BotCommands &&bot_commands); + void on_update_chat_permanent_invite_link(ChatId chat_id, const DialogInviteLink &invite_link); + + void on_update_channel_participant_count(ChannelId channel_id, int32 participant_count); + void on_update_channel_editable_username(ChannelId channel_id, string &&username); + void on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames); + void on_update_channel_story_ids(ChannelId channel_id, StoryId max_active_story_id, StoryId max_read_story_id); + void on_update_channel_max_read_story_id(ChannelId channel_id, StoryId max_read_story_id); + void on_update_channel_stories_hidden(ChannelId channel_id, bool stories_hidden); + void on_update_channel_description(ChannelId channel_id, string &&description); + void on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id); + void on_update_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id); + void on_update_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count); + void on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id); + void on_update_channel_location(ChannelId channel_id, const DialogLocation &location); + void on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, Promise &&promise); + void on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date); + void on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, + Promise &&promise); + void on_update_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, + Promise &&promise); + void on_update_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, + Promise &&promise); + void on_update_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, + Promise &&promise); + void on_update_channel_has_pinned_stories(ChannelId channel_id, bool has_pinned_stories); + void on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions); + void on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count); + void on_update_channel_bot_commands(ChannelId channel_id, BotCommands &&bot_commands); + void on_update_channel_permanent_invite_link(ChannelId channel_id, const DialogInviteLink &invite_link); + + void speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, + UserId inviter_user_id, int32 date, bool by_me); + + void speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me); + + void invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, const char *source); + + bool on_get_channel_error(ChannelId channel_id, const Status &status, const char *source); + + void on_get_created_public_channels(PublicDialogType type, vector> &&chats); + + bool are_created_public_broadcasts_inited() const; + + const vector &get_created_public_broadcasts() const; + + void on_get_dialogs_for_discussion(vector> &&chats); + + void on_get_inactive_channels(vector> &&chats, Promise &&promise); + + void remove_inactive_channel(ChannelId channel_id); + + void register_message_channels(MessageFullId message_full_id, vector channel_ids); + + void unregister_message_channels(MessageFullId message_full_id, vector channel_ids); + + static ChannelId get_unsupported_channel_id(); + + void update_chat_online_member_count(ChatId chat_id, bool is_from_server); + + void on_update_channel_bot_user_ids(ChannelId channel_id, vector &&bot_user_ids); + + void on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, + Promise &&promise); + + void on_deactivate_channel_usernames(ChannelId channel_id, Promise &&promise); + + void on_update_channel_active_usernames_order(ChannelId channel_id, vector &&usernames, + Promise &&promise); + + void set_chat_description(ChatId chat_id, const string &description, Promise &&promise); + + void set_channel_username(ChannelId channel_id, const string &username, Promise &&promise); + + void toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, + Promise &&promise); + + void disable_all_channel_usernames(ChannelId channel_id, Promise &&promise); + + void reorder_channel_usernames(ChannelId channel_id, vector &&usernames, Promise &&promise); + + void set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id, Promise &&promise); + + void set_channel_profile_accent_color(ChannelId channel_id, AccentColorId profile_accent_color_id, + CustomEmojiId profile_background_custom_emoji_id, Promise &&promise); + + void set_channel_emoji_status(ChannelId channel_id, const EmojiStatus &emoji_status, Promise &&promise); + + void set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise); + + void set_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise); + + 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_join_to_send(ChannelId channel_id, bool joint_to_send, Promise &&promise); + + void toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise); + + void toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, + Promise &&promise); + + void toggle_channel_can_have_sponsored_messages(ChannelId channel_id, bool can_have_sponsored_messages, + Promise &&promise); + + void toggle_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, + Promise &&promise); + + void toggle_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, + Promise &&promise); + + void toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise &&promise); + + void convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise); + + void set_channel_description(ChannelId channel_id, const string &description, Promise &&promise); + + void set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, Promise &&promise); + + void set_channel_location(ChannelId dialog_id, const DialogLocation &location, Promise &&promise); + + void set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise &&promise); + + void report_channel_spam(ChannelId channel_id, const vector &message_ids, Promise &&promise); + + void report_channel_anti_spam_false_positive(ChannelId channel_id, MessageId message_id, Promise &&promise); + + void delete_chat(ChatId chat_id, Promise &&promise); + + void delete_channel(ChannelId channel_id, Promise &&promise); + + void get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise &&promise); + + bool can_get_channel_message_statistics(ChannelId channel_id) const; + + bool can_get_channel_story_statistics(ChannelId channel_id) const; + + bool can_convert_channel_to_gigagroup(ChannelId channel_id) const; + + void get_created_public_dialogs(PublicDialogType type, Promise> &&promise, + bool from_binlog); + + void check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise); + + void reload_created_public_dialogs(PublicDialogType type, Promise> &&promise); + + vector get_dialogs_for_discussion(Promise &&promise); + + vector get_inactive_channels(Promise &&promise); + + void create_new_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, + Promise> &&promise); + + bool have_chat(ChatId chat_id) const; + bool have_chat_force(ChatId chat_id, const char *source); + bool get_chat(ChatId chat_id, int left_tries, Promise &&promise); + void reload_chat(ChatId chat_id, Promise &&promise, const char *source); + void load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source); + FileSourceId get_chat_full_file_source_id(ChatId chat_id); + void reload_chat_full(ChatId chat_id, Promise &&promise, const char *source); + + int32 get_chat_date(ChatId chat_id) const; + int32 get_chat_participant_count(ChatId chat_id) const; + bool get_chat_is_active(ChatId chat_id) const; + ChannelId get_chat_migrated_to_channel_id(ChatId chat_id) const; + DialogParticipantStatus get_chat_status(ChatId chat_id) const; + DialogParticipantStatus get_chat_permissions(ChatId chat_id) const; + bool is_appointed_chat_administrator(ChatId chat_id) const; + const DialogParticipant *get_chat_participant(ChatId chat_id, UserId user_id) const; + const vector *get_chat_participants(ChatId chat_id) const; + + void create_new_channel(const string &title, bool is_forum, bool is_megagroup, const string &description, + const DialogLocation &location, bool for_import, MessageTtl message_ttl, + Promise> &&promise); + + bool have_min_channel(ChannelId channel_id) const; + const MinChannel *get_min_channel(ChannelId channel_id) const; + void add_min_channel(ChannelId channel_id, const MinChannel &min_channel); + + bool have_channel(ChannelId channel_id) const; + bool have_channel_force(ChannelId channel_id, const char *source); + bool get_channel(ChannelId channel_id, int left_tries, Promise &&promise); + void reload_channel(ChannelId channel_id, Promise &&promise, const char *source); + void load_channel_full(ChannelId channel_id, bool force, Promise &&promise, const char *source); + FileSourceId get_channel_full_file_source_id(ChannelId channel_id); + void reload_channel_full(ChannelId channel_id, Promise &&promise, const char *source); + + bool is_channel_public(ChannelId channel_id) const; + + ChannelType get_channel_type(ChannelId channel_id) const; + bool is_broadcast_channel(ChannelId channel_id) const; + bool is_megagroup_channel(ChannelId channel_id) const; + bool is_forum_channel(ChannelId channel_id) const; + int32 get_channel_date(ChannelId channel_id) const; + DialogParticipantStatus get_channel_status(ChannelId channel_id) const; + DialogParticipantStatus get_channel_permissions(ChannelId channel_id) const; + bool get_channel_is_verified(ChannelId channel_id) const; + bool get_channel_is_scam(ChannelId channel_id) const; + 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_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; + ChannelId get_channel_linked_channel_id(ChannelId channel_id, const char *source); + int32 get_channel_slow_mode_delay(ChannelId channel_id, const char *source); + bool get_channel_effective_has_hidden_participants(ChannelId channel_id, const char *source); + int32 get_channel_my_boost_count(ChannelId channel_id); + + void get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise); + + void speculative_add_channel_user(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status, + const DialogParticipantStatus &old_status); + + int64 get_basic_group_id_object(ChatId chat_id, const char *source) const; + + tl_object_ptr get_basic_group_object(ChatId chat_id); + + tl_object_ptr get_basic_group_full_info_object(ChatId chat_id) const; + + int64 get_supergroup_id_object(ChannelId channel_id, const char *source) const; + + tl_object_ptr get_supergroup_object(ChannelId channel_id) const; + + tl_object_ptr get_supergroup_full_info_object(ChannelId channel_id) const; + + tl_object_ptr get_chat_member_object(const DialogParticipant &dialog_participant, + const char *source) const; + + void repair_chat_participants(ChatId chat_id); + + void get_current_state(vector> &updates) const; + + private: + struct Chat { + string title; + DialogPhoto photo; + int32 participant_count = 0; + int32 date = 0; + int32 version = -1; + int32 default_permissions_version = -1; + int32 pinned_message_version = -1; + ChannelId migrated_to_channel_id; + + DialogParticipantStatus status = DialogParticipantStatus::Banned(0); + RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, ChannelType::Unknown}; + + static constexpr uint32 CACHE_VERSION = 4; + uint32 cache_version = 0; + + bool is_active = false; + bool noforwards = false; + + bool is_title_changed = true; + bool is_photo_changed = true; + bool is_default_permissions_changed = true; + bool is_status_changed = true; + bool is_is_active_changed = true; + bool is_noforwards_changed = true; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_basic_group_sent = false; + + bool is_repaired = false; // whether cached value is rechecked + + bool is_saved = false; // is current chat version being saved/is saved to the database + bool is_being_saved = false; // is current chat being saved to the database + + bool is_received_from_server = false; // true, if the chat was received from the server and not the database + + uint64 log_event_id = 0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + // do not forget to update drop_chat_full and on_get_chat_full + struct ChatFull { + int32 version = -1; + UserId creator_user_id; + vector participants; + + Photo photo; + vector registered_photo_file_ids; + FileSourceId file_source_id; + + string description; + + DialogInviteLink invite_link; + + vector bot_commands; + + bool can_set_username = false; + + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_send_update = true; // have new changes that need only to be sent to the client + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_chat_full_sent = false; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct Channel { + int64 access_hash = 0; + string title; + DialogPhoto photo; + EmojiStatus emoji_status; + EmojiStatus last_sent_emoji_status; + AccentColorId accent_color_id; + CustomEmojiId background_custom_emoji_id; + AccentColorId profile_accent_color_id; + CustomEmojiId profile_background_custom_emoji_id; + Usernames usernames; + vector restriction_reasons; + DialogParticipantStatus status = DialogParticipantStatus::Banned(0); + RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, ChannelType::Unknown}; + int32 date = 0; + int32 participant_count = 0; + int32 boost_level = 0; + + double max_active_story_id_next_reload_time = 0.0; + StoryId max_active_story_id; + StoryId max_read_story_id; + + static constexpr uint32 CACHE_VERSION = 10; + uint32 cache_version = 0; + + bool has_linked_channel = false; + bool has_location = false; + bool sign_messages = false; + bool is_slow_mode_enabled = false; + bool noforwards = false; + bool can_be_deleted = false; + bool join_to_send = false; + bool join_request = false; + bool stories_hidden = false; + + bool is_megagroup = false; + bool is_gigagroup = false; + bool is_forum = false; + bool is_verified = false; + bool is_scam = false; + bool is_fake = false; + + bool is_title_changed = true; + bool is_username_changed = true; + bool is_photo_changed = true; + bool is_emoji_status_changed = true; + bool is_accent_color_changed = true; + bool is_default_permissions_changed = true; + bool is_status_changed = true; + bool is_stories_hidden_changed = true; + bool is_has_location_changed = true; + bool is_noforwards_changed = true; + bool is_creator_changed = true; + bool had_read_access = true; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_supergroup_sent = false; + + bool is_repaired = false; // whether cached value is rechecked + + bool is_saved = false; // is current channel version being saved/is saved to the database + bool is_being_saved = false; // is current channel being saved to the database + + bool is_received_from_server = false; // true, if the channel was received from the server and not the database + + uint64 log_event_id = 0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + // do not forget to update invalidate_channel_full and on_get_chat_full + struct ChannelFull { + Photo photo; + vector registered_photo_file_ids; + FileSourceId file_source_id; + + string description; + int32 participant_count = 0; + int32 administrator_count = 0; + int32 restricted_count = 0; + int32 banned_count = 0; + int32 boost_count = 0; + int32 unrestrict_boost_count = 0; + + DialogInviteLink invite_link; + + vector bot_commands; + + uint32 speculative_version = 1; + uint32 repair_request_version = 0; + + StickerSetId sticker_set_id; + StickerSetId emoji_sticker_set_id; + + ChannelId linked_channel_id; + + DialogLocation location; + + DcId stats_dc_id; + + int32 slow_mode_delay = 0; + int32 slow_mode_next_send_date = 0; + + MessageId migrated_from_max_message_id; + ChatId migrated_from_chat_id; + + vector bot_user_ids; + + bool can_get_participants = false; + bool has_hidden_participants = false; + bool can_set_username = false; + bool can_set_sticker_set = false; + bool can_set_location = false; + bool can_view_statistics = false; + bool is_can_view_statistics_inited = false; + bool can_view_revenue = false; + bool is_all_history_available = true; + bool can_have_sponsored_messages = true; + bool has_aggressive_anti_spam_enabled = false; + bool can_be_deleted = false; + bool has_pinned_stories = false; + + bool is_slow_mode_next_send_date_changed = true; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_send_update = true; // have new changes that need only to be sent to the client + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_channel_full_sent = false; + + double expires_at = 0.0; + + bool is_expired() const { + return expires_at < Time::now(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + class ChatLogEvent; + class ChannelLogEvent; + + static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title + static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description + + static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit + + static constexpr int32 CHAT_FLAG_USER_IS_CREATOR = 1 << 0; + static constexpr int32 CHAT_FLAG_USER_HAS_LEFT = 1 << 2; + // static constexpr int32 CHAT_FLAG_ADMINISTRATORS_ENABLED = 1 << 3; + // static constexpr int32 CHAT_FLAG_IS_ADMINISTRATOR = 1 << 4; + static constexpr int32 CHAT_FLAG_IS_DEACTIVATED = 1 << 5; + static constexpr int32 CHAT_FLAG_WAS_MIGRATED = 1 << 6; + static constexpr int32 CHAT_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23; + static constexpr int32 CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24; + static constexpr int32 CHAT_FLAG_NOFORWARDS = 1 << 25; + + static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0; + static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2; + static constexpr int32 CHANNEL_FLAG_IS_BROADCAST = 1 << 5; + static constexpr int32 CHANNEL_FLAG_HAS_USERNAME = 1 << 6; + static constexpr int32 CHANNEL_FLAG_IS_VERIFIED = 1 << 7; + static constexpr int32 CHANNEL_FLAG_IS_MEGAGROUP = 1 << 8; + static constexpr int32 CHANNEL_FLAG_IS_RESTRICTED = 1 << 9; + // static constexpr int32 CHANNEL_FLAG_ANYONE_CAN_INVITE = 1 << 10; + static constexpr int32 CHANNEL_FLAG_SIGN_MESSAGES = 1 << 11; + static constexpr int32 CHANNEL_FLAG_IS_MIN = 1 << 12; + static constexpr int32 CHANNEL_FLAG_HAS_ACCESS_HASH = 1 << 13; + static constexpr int32 CHANNEL_FLAG_HAS_ADMIN_RIGHTS = 1 << 14; + static constexpr int32 CHANNEL_FLAG_HAS_BANNED_RIGHTS = 1 << 15; + static constexpr int32 CHANNEL_FLAG_HAS_UNBAN_DATE = 1 << 16; + static constexpr int32 CHANNEL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 17; + static constexpr int32 CHANNEL_FLAG_IS_SCAM = 1 << 19; + static constexpr int32 CHANNEL_FLAG_HAS_LINKED_CHAT = 1 << 20; + static constexpr int32 CHANNEL_FLAG_HAS_LOCATION = 1 << 21; + static constexpr int32 CHANNEL_FLAG_IS_SLOW_MODE_ENABLED = 1 << 22; + static constexpr int32 CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23; + static constexpr int32 CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24; + static constexpr int32 CHANNEL_FLAG_IS_FAKE = 1 << 25; + static constexpr int32 CHANNEL_FLAG_IS_GIGAGROUP = 1 << 26; + static constexpr int32 CHANNEL_FLAG_NOFORWARDS = 1 << 27; + static constexpr int32 CHANNEL_FLAG_JOIN_TO_SEND = 1 << 28; + static constexpr int32 CHANNEL_FLAG_JOIN_REQUEST = 1 << 29; + static constexpr int32 CHANNEL_FLAG_IS_FORUM = 1 << 30; + static constexpr int32 CHANNEL_FLAG_HAS_USERNAMES = 1 << 0; + + static constexpr int32 CHANNEL_FULL_EXPIRE_TIME = 60; + + static bool have_input_peer_chat(const Chat *c, AccessRights access_rights); + + bool have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights, + bool from_linked = false) const; + + const Chat *get_chat(ChatId chat_id) const; + Chat *get_chat(ChatId chat_id); + Chat *get_chat_force(ChatId chat_id, const char *source); + + Chat *add_chat(ChatId chat_id); + + const ChatFull *get_chat_full(ChatId chat_id) const; + ChatFull *get_chat_full(ChatId chat_id); + ChatFull *get_chat_full_force(ChatId chat_id, const char *source); + + ChatFull *add_chat_full(ChatId chat_id); + + void send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source); + + const Channel *get_channel(ChannelId channel_id) const; + Channel *get_channel(ChannelId channel_id); + Channel *get_channel_force(ChannelId channel_id, const char *source); + + Channel *add_channel(ChannelId channel_id, const char *source); + + const ChannelFull *get_channel_full(ChannelId channel_id) const; + const ChannelFull *get_channel_full_const(ChannelId channel_id) const; + ChannelFull *get_channel_full(ChannelId channel_id, bool only_local, const char *source); + ChannelFull *get_channel_full_force(ChannelId channel_id, bool only_local, const char *source); + + ChannelFull *add_channel_full(ChannelId channel_id); + + void send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, Promise &&promise, + const char *source); + + static DialogParticipantStatus get_chat_status(const Chat *c); + DialogParticipantStatus get_chat_permissions(const Chat *c) const; + + static ChannelType get_channel_type(const Channel *c); + 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_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); + static bool get_channel_join_request(const Channel *c); + + void on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status); + static void on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions, + int32 version); + void on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version, + const string &debug_str); + void on_update_chat_photo(Chat *c, ChatId chat_id, tl_object_ptr &&chat_photo_ptr); + void on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache); + static void on_update_chat_title(Chat *c, ChatId chat_id, string &&title); + static void on_update_chat_active(Chat *c, ChatId chat_id, bool is_active); + static void on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id); + static void on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards); + + void on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo); + bool on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version); + void on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector participants, + int32 version, bool from_update); + void on_update_chat_full_invite_link(ChatFull *chat_full, + tl_object_ptr &&invite_link); + + void on_update_channel_photo(Channel *c, ChannelId channel_id, + tl_object_ptr &&chat_photo_ptr); + void on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache); + void on_update_channel_emoji_status(Channel *c, ChannelId channel_id, EmojiStatus emoji_status); + void on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId accent_color_id); + void on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, + CustomEmojiId background_custom_emoji_id); + void on_update_channel_profile_accent_color_id(Channel *c, ChannelId channel_id, + AccentColorId profile_accent_color_id); + void on_update_channel_profile_background_custom_emoji_id(Channel *c, ChannelId channel_id, + CustomEmojiId profile_background_custom_emoji_id); + static void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title); + void on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames); + void on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status); + static void on_update_channel_default_permissions(Channel *c, ChannelId channel_id, + RestrictedRights default_permissions); + static void on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location); + static void on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards); + void on_update_channel_stories_hidden(Channel *c, ChannelId channel_id, bool stories_hidden); + void on_update_channel_story_ids_impl(Channel *c, ChannelId channel_id, StoryId max_active_story_id, + StoryId max_read_story_id); + void on_update_channel_max_read_story_id(Channel *c, ChannelId channel_id, StoryId max_read_story_id); + + void on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo); + void on_update_channel_full_invite_link(ChannelFull *channel_full, + tl_object_ptr &&invite_link); + void on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, + ChannelId linked_channel_id); + void on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location); + void on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, int32 slow_mode_delay, + int32 slow_mode_next_send_date); + static void on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full, + int32 slow_mode_next_send_date); + static void on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id, + vector &&bot_user_ids); + + void on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status, + const DialogParticipantStatus &new_status); + void on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames, + const Usernames &new_usernames); + + void remove_linked_channel_id(ChannelId channel_id); + ChannelId get_linked_channel_id(ChannelId channel_id) const; + + static bool speculative_add_count(int32 &count, int32 delta_count, int32 min_count = 0); + + void speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, bool by_me); + + void drop_chat_full(ChatId chat_id); + + void do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, bool need_drop_slow_mode_delay); + + void update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server); + + void on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source); + void on_get_chat(telegram_api::chat &chat, const char *source); + void on_get_chat_forbidden(telegram_api::chatForbidden &chat, const char *source); + void on_get_channel(telegram_api::channel &channel, const char *source); + void on_get_channel_forbidden(telegram_api::channelForbidden &channel, const char *source); + + void save_chat(Chat *c, ChatId chat_id, bool from_binlog); + static string get_chat_database_key(ChatId chat_id); + static string get_chat_database_value(const Chat *c); + void save_chat_to_database(Chat *c, ChatId chat_id); + void save_chat_to_database_impl(Chat *c, ChatId chat_id, string value); + void on_save_chat_to_database(ChatId chat_id, bool success); + void load_chat_from_database(Chat *c, ChatId chat_id, Promise promise); + void load_chat_from_database_impl(ChatId chat_id, Promise promise); + void on_load_chat_from_database(ChatId chat_id, string value, bool force); + + void save_channel(Channel *c, ChannelId channel_id, bool from_binlog); + static string get_channel_database_key(ChannelId channel_id); + static string get_channel_database_value(const Channel *c); + void save_channel_to_database(Channel *c, ChannelId channel_id); + void save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value); + void on_save_channel_to_database(ChannelId channel_id, bool success); + void load_channel_from_database(Channel *c, ChannelId channel_id, Promise promise); + void load_channel_from_database_impl(ChannelId channel_id, Promise promise); + void on_load_channel_from_database(ChannelId channel_id, string value, bool force); + + static void save_chat_full(const ChatFull *chat_full, ChatId chat_id); + static string get_chat_full_database_key(ChatId chat_id); + static string get_chat_full_database_value(const ChatFull *chat_full); + void on_load_chat_full_from_database(ChatId chat_id, string value); + + static void save_channel_full(const ChannelFull *channel_full, ChannelId channel_id); + static string get_channel_full_database_key(ChannelId channel_id); + static string get_channel_full_database_value(const ChannelFull *channel_full); + void on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source); + + void update_chat(Chat *c, ChatId chat_id, bool from_binlog = false, bool from_database = false); + void update_channel(Channel *c, ChannelId channel_id, bool from_binlog = false, bool from_database = false); + + void update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database = false); + void update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, + bool from_database = false); + + bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, bool only_participants) const; + + static bool is_channel_public(const Channel *c); + + static bool is_suitable_created_public_channel(PublicDialogType type, const Channel *c); + + static void return_created_public_dialogs(Promise> &&promise, + const vector &channel_ids); + + void finish_get_created_public_dialogs(PublicDialogType type, Result &&result); + + void update_created_public_channels(Channel *c, ChannelId channel_id); + + void save_created_public_channels(PublicDialogType type); + + bool update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link); + + static const DialogParticipant *get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id); + + void finish_get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise); + + td_api::object_ptr get_update_basic_group_object(ChatId chat_id, const Chat *c); + + static td_api::object_ptr get_update_unknown_basic_group_object(ChatId chat_id); + + tl_object_ptr get_basic_group_object(ChatId chat_id, const Chat *c); + + tl_object_ptr get_basic_group_object_const(ChatId chat_id, const Chat *c) const; + + tl_object_ptr get_basic_group_full_info_object(ChatId chat_id, + const ChatFull *chat_full) const; + + bool need_poll_channel_active_stories(const Channel *c, ChannelId channel_id) const; + + static bool get_channel_has_unread_stories(const Channel *c); + + td_api::object_ptr get_update_supergroup_object(ChannelId channel_id, + const Channel *c) const; + + td_api::object_ptr get_update_unknown_supergroup_object(ChannelId channel_id) const; + + static tl_object_ptr get_supergroup_object(ChannelId channel_id, const Channel *c); + + Status can_hide_chat_participants(ChatId chat_id) const; + + Status can_hide_channel_participants(ChannelId channel_id, const ChannelFull *channel_full) const; + + Status can_toggle_chat_aggressive_anti_spam(ChatId chat_id) const; + + Status can_toggle_channel_aggressive_anti_spam(ChannelId channel_id, const ChannelFull *channel_full) const; + + tl_object_ptr get_supergroup_full_info_object(ChannelId channel_id, + const ChannelFull *channel_full) const; + + vector get_dialog_ids(vector> &&chats, const char *source); + + void on_create_inactive_channels(vector &&channel_ids, Promise &&promise); + + void update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable); + + void get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, Promise &&promise); + + static void on_channel_emoji_status_timeout_callback(void *chat_manager_ptr, int64 channel_id_long); + + static void on_channel_unban_timeout_callback(void *chat_manager_ptr, int64 channel_id_long); + + static void on_slow_mode_delay_timeout_callback(void *chat_manager_ptr, int64 channel_id_long); + + void on_channel_emoji_status_timeout(ChannelId channel_id); + + void on_channel_unban_timeout(ChannelId channel_id); + + void on_slow_mode_delay_timeout(ChannelId channel_id); + + void tear_down() final; + + Td *td_; + ActorShared<> parent_; + + WaitFreeHashMap, ChatIdHash> chats_; + WaitFreeHashMap, ChatIdHash> chats_full_; + mutable FlatHashSet unknown_chats_; + WaitFreeHashMap chat_full_file_source_ids_; + + WaitFreeHashMap, ChannelIdHash> min_channels_; + WaitFreeHashMap, ChannelIdHash> channels_; + WaitFreeHashMap, ChannelIdHash> channels_full_; + mutable FlatHashSet unknown_channels_; + WaitFreeHashSet invalidated_channels_full_; + WaitFreeHashMap channel_full_file_source_ids_; + + bool created_public_channels_inited_[3] = {false, false, false}; + vector created_public_channels_[3]; + vector>> get_created_public_channels_queries_[3]; + + bool dialogs_for_discussion_inited_ = false; + vector dialogs_for_discussion_; + + bool inactive_channel_ids_inited_ = false; + vector inactive_channel_ids_; + + FlatHashMap>, ChatIdHash> load_chat_from_database_queries_; + FlatHashSet loaded_from_database_chats_; + FlatHashSet unavailable_chat_fulls_; + + FlatHashMap>, ChannelIdHash> load_channel_from_database_queries_; + FlatHashSet loaded_from_database_channels_; + FlatHashSet unavailable_channel_fulls_; + + QueryMerger get_chat_queries_{"GetChatMerger", 3, 50}; + QueryMerger get_channel_queries_{"GetChannelMerger", 100, 1}; // can't merge getChannel queries without access hash + + QueryCombiner get_chat_full_queries_{"GetChatFullCombiner", 2.0}; + + FlatHashMap, ChannelIdHash> channel_messages_; + + WaitFreeHashMap linked_channel_ids_; + + WaitFreeHashSet restricted_channel_ids_; + + MultiTimeout channel_emoji_status_timeout_{"ChannelEmojiStatusTimeout"}; + MultiTimeout channel_unban_timeout_{"ChannelUnbanTimeout"}; + MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"}; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp b/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp index c0648cad..fa75c49f 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.cpp @@ -6,11 +6,15 @@ // #include "td/telegram/ChatReactions.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" + #include "td/utils/algorithm.h" namespace td { -ChatReactions::ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr) { +ChatReactions::ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, + int32 reactions_limit) { if (chat_reactions_ptr == nullptr) { return; } @@ -31,6 +35,7 @@ ChatReactions::ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, @@ -39,13 +44,17 @@ ChatReactions::ChatReactions(td_api::object_ptr return; } switch (chat_reactions_ptr->get_id()) { - case td_api::chatAvailableReactionsAll::ID: + case td_api::chatAvailableReactionsAll::ID: { + auto chat_reactions = move_tl_object_as(chat_reactions_ptr); allow_all_regular_ = true; allow_all_custom_ = allow_all_custom; + reactions_limit_ = chat_reactions->max_reaction_count_; break; + } case td_api::chatAvailableReactionsSome::ID: { 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_; break; } default: @@ -74,12 +83,16 @@ bool ChatReactions::is_allowed_reaction_type(const ReactionType &reaction_type) return td::contains(reaction_types_, reaction_type); } -td_api::object_ptr ChatReactions::get_chat_available_reactions_object() const { +td_api::object_ptr ChatReactions::get_chat_available_reactions_object(Td *td) const { + auto reactions_uniq_max = static_cast(td->option_manager_->get_option_integer("reactions_uniq_max", 11)); + if (0 < reactions_limit_ && reactions_limit_ < reactions_uniq_max) { + reactions_uniq_max = reactions_limit_; + } if (allow_all_regular_) { - return td_api::make_object(); + return td_api::make_object(reactions_uniq_max); } return td_api::make_object( - ReactionType::get_reaction_types_object(reaction_types_)); + ReactionType::get_reaction_types_object(reaction_types_), reactions_uniq_max); } telegram_api::object_ptr ChatReactions::get_input_chat_reactions() const { @@ -99,10 +112,14 @@ telegram_api::object_ptr ChatReactions::get_input_c 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_; + return lhs.reaction_types_ == rhs.reaction_types_ && lhs.allow_all_regular_ == rhs.allow_all_regular_ && + lhs.reactions_limit_ == rhs.reactions_limit_; } StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions) { + if (reactions.reactions_limit_ != 0) { + string_builder << '[' << reactions.reactions_limit_ << "] "; + } 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 e46f2997..fc69b09d 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.h +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.h @@ -16,17 +16,23 @@ namespace td { +class Td; + struct ChatReactions { vector reaction_types_; bool allow_all_regular_ = false; // implies empty reaction_types_ bool allow_all_custom_ = false; // implies allow_all_regular_ + int32 reactions_limit_ = 0; ChatReactions() = default; - explicit ChatReactions(vector &&reactions) : reaction_types_(std::move(reactions)) { + static ChatReactions legacy(vector &&reactions) { + ChatReactions result; + result.reaction_types_ = std::move(reactions); + return result; } - explicit ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr); + ChatReactions(telegram_api::object_ptr &&chat_reactions_ptr, int32 reactions_limit); ChatReactions(td_api::object_ptr &&chat_reactions_ptr, bool allow_all_custom); @@ -41,7 +47,7 @@ struct ChatReactions { telegram_api::object_ptr get_input_chat_reactions() const; - td_api::object_ptr get_chat_available_reactions_object() const; + td_api::object_ptr get_chat_available_reactions_object(Td *td) const; bool empty() const { return reaction_types_.empty() && !allow_all_regular_; diff --git a/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp b/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp index 998e82d7..0a3d40ef 100644 --- a/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp +++ b/lib/tgchat/ext/td/td/telegram/ChatReactions.hpp @@ -17,27 +17,37 @@ namespace td { template void ChatReactions::store(StorerT &storer) const { bool has_reactions = !reaction_types_.empty(); + bool has_reactions_limit = reactions_limit_ != 0; BEGIN_STORE_FLAGS(); STORE_FLAG(allow_all_regular_); STORE_FLAG(allow_all_custom_); STORE_FLAG(has_reactions); + STORE_FLAG(has_reactions_limit); END_STORE_FLAGS(); if (has_reactions) { td::store(reaction_types_, storer); } + if (has_reactions_limit) { + td::store(reactions_limit_, storer); + } } template void ChatReactions::parse(ParserT &parser) { bool has_reactions; + bool has_reactions_limit; BEGIN_PARSE_FLAGS(); PARSE_FLAG(allow_all_regular_); PARSE_FLAG(allow_all_custom_); PARSE_FLAG(has_reactions); + PARSE_FLAG(has_reactions_limit); END_PARSE_FLAGS(); if (has_reactions) { td::parse(reaction_types_, parser); } + if (has_reactions_limit) { + td::parse(reactions_limit_, parser); + } } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Client.cpp b/lib/tgchat/ext/td/td/telegram/Client.cpp index 1c6f59a6..39340e67 100644 --- a/lib/tgchat/ext/td/td/telegram/Client.cpp +++ b/lib/tgchat/ext/td/td/telegram/Client.cpp @@ -380,6 +380,7 @@ class MultiImpl { } void create(int32 td_id, unique_ptr callback) { + LOG(INFO) << "Initialize client " << td_id; auto guard = concurrent_scheduler_->get_send_guard(); send_closure(multi_td_, &MultiTd::create, td_id, std::move(callback)); } @@ -395,6 +396,7 @@ class MultiImpl { } void close(ClientManager::ClientId client_id) { + LOG(INFO) << "Close client"; auto guard = concurrent_scheduler_->get_send_guard(); send_closure(multi_td_, &MultiTd::close, client_id); } @@ -478,6 +480,7 @@ class ClientManager::Impl final { public: ClientId create_client_id() { auto client_id = MultiImpl::create_id(); + LOG(INFO) << "Created managed client " << client_id; { auto lock = impls_mutex_.lock_write().move_as_ok(); impls_[client_id]; // create empty MultiImplInfo @@ -521,6 +524,7 @@ class ClientManager::Impl final { response.object->get_id() == td_api::updateAuthorizationState::ID && static_cast(response.object.get())->authorization_state_->get_id() == td_api::authorizationStateClosed::ID) { + LOG(INFO) << "Release closed client"; auto lock = impls_mutex_.lock_write().move_as_ok(); close_impl(response.client_id); @@ -567,6 +571,7 @@ class ClientManager::Impl final { if (ExitGuard::is_exited()) { return; } + LOG(INFO) << "Destroy ClientManager"; for (auto &it : impls_) { close_impl(it.first); } @@ -592,6 +597,7 @@ class Client::Impl final { static MultiImplPool pool; multi_impl_ = pool.get(); td_id_ = MultiImpl::create_id(); + LOG(INFO) << "Create client " << td_id_; multi_impl_->create(td_id_, receiver_.create_callback(td_id_)); } @@ -618,6 +624,7 @@ class Client::Impl final { Impl(Impl &&) = delete; Impl &operator=(Impl &&) = delete; ~Impl() { + LOG(INFO) << "Destroy Client"; multi_impl_->close(td_id_); while (!ExitGuard::is_exited()) { auto response = receiver_.receive(0.1, false); diff --git a/lib/tgchat/ext/td/td/telegram/CommonDialogManager.cpp b/lib/tgchat/ext/td/td/telegram/CommonDialogManager.cpp index 92d0a890..118f9560 100644 --- a/lib/tgchat/ext/td/td/telegram/CommonDialogManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/CommonDialogManager.cpp @@ -6,11 +6,12 @@ // #include "td/telegram/CommonDialogManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -94,13 +95,13 @@ void CommonDialogManager::drop_common_dialogs_cache(UserId user_id) { std::pair> CommonDialogManager::get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force, Promise &&promise) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); return {}; } - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { promise.set_error(Status::Error(400, "Can't get common chats with self")); return {}; } @@ -178,7 +179,7 @@ std::pair> CommonDialogManager::get_common_dialogs(UserI void CommonDialogManager::on_get_common_dialogs(UserId user_id, int64 offset_chat_id, vector> &&chats, int32 total_count) { CHECK(user_id.is_valid()); - td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count); + td_->user_manager_->on_update_user_common_chat_count(user_id, total_count); auto &common_dialogs = found_common_dialogs_[user_id]; if (common_dialogs.is_outdated && offset_chat_id == 0 && @@ -196,12 +197,12 @@ void CommonDialogManager::on_get_common_dialogs(UserId user_id, int64 offset_cha } bool is_last = chats.empty() && offset_chat_id == 0; for (auto &chat : chats) { - auto dialog_id = ContactsManager::get_dialog_id(chat); + auto dialog_id = ChatManager::get_dialog_id(chat); if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive invalid " << to_string(chat); continue; } - td_->contacts_manager_->on_get_chat(std::move(chat), "on_get_common_dialogs"); + td_->chat_manager_->on_get_chat(std::move(chat), "on_get_common_dialogs"); if (!td::contains(result, dialog_id)) { td_->dialog_manager_->force_create_dialog(dialog_id, "get common dialogs"); @@ -213,7 +214,7 @@ void CommonDialogManager::on_get_common_dialogs(UserId user_id, int64 offset_cha LOG(ERROR) << "Fix total count of common groups with " << user_id << " from " << total_count << " to " << result.size(); total_count = narrow_cast(result.size()); - td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count); + td_->user_manager_->on_update_user_common_chat_count(user_id, total_count); } result.emplace_back(); diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp index 80e62206..85e8af65 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.cpp @@ -8,7 +8,6 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/ConnectionState.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/JsonValue.h" #include "td/telegram/LinkManager.h" @@ -27,10 +26,12 @@ #include "td/telegram/Premium.h" #include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" +#include "td/telegram/SuggestedAction.hpp" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/TranscriptionManager.h" +#include "td/telegram/UserManager.h" #include "td/mtproto/AuthData.h" #include "td/mtproto/AuthKey.h" @@ -55,10 +56,10 @@ #include "td/utils/emoji.h" #include "td/utils/FlatHashMap.h" #include "td/utils/format.h" +#include "td/utils/HttpDate.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" -#include "td/utils/Parser.h" #include "td/utils/port/Clocks.h" #include "td/utils/Random.h" #include "td/utils/SliceBuilder.h" @@ -76,83 +77,6 @@ namespace td { int VERBOSITY_NAME(config_recoverer) = VERBOSITY_NAME(INFO); -Result HttpDate::to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second) { - if (year < 1970 || year > 2037) { - return Status::Error("Invalid year"); - } - if (month < 1 || month > 12) { - return Status::Error("Invalid month"); - } - if (day < 1 || day > days_in_month(year, month)) { - return Status::Error("Invalid day"); - } - if (hour < 0 || hour >= 24) { - return Status::Error("Invalid hour"); - } - if (minute < 0 || minute >= 60) { - return Status::Error("Invalid minute"); - } - if (second < 0 || second > 60) { - return Status::Error("Invalid second"); - } - - int32 res = 0; - for (int32 y = 1970; y < year; y++) { - res += (is_leap(y) + 365) * seconds_in_day(); - } - for (int32 m = 1; m < month; m++) { - res += days_in_month(year, m) * seconds_in_day(); - } - res += (day - 1) * seconds_in_day(); - res += hour * 60 * 60; - res += minute * 60; - res += second; - return res; -} - -Result HttpDate::parse_http_date(string slice) { - Parser p(slice); - p.read_till(','); // ignore week day - p.skip(','); - p.skip_whitespaces(); - p.skip_nofail('0'); - TRY_RESULT(day, to_integer_safe(p.read_word())); - auto month_name = p.read_word(); - to_lower_inplace(month_name); - TRY_RESULT(year, to_integer_safe(p.read_word())); - p.skip_whitespaces(); - p.skip_nofail('0'); - TRY_RESULT(hour, to_integer_safe(p.read_till(':'))); - p.skip(':'); - p.skip_nofail('0'); - TRY_RESULT(minute, to_integer_safe(p.read_till(':'))); - p.skip(':'); - p.skip_nofail('0'); - TRY_RESULT(second, to_integer_safe(p.read_word())); - auto gmt = p.read_word(); - TRY_STATUS(std::move(p.status())); - if (gmt != "GMT") { - return Status::Error("Timezone must be GMT"); - } - - static Slice month_names[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; - - int month = 0; - - for (int m = 1; m <= 12; m++) { - if (month_names[m - 1] == month_name) { - month = m; - break; - } - } - - if (month == 0) { - return Status::Error("Unknown month name"); - } - - return HttpDate::to_unix_time(year, month, day, hour, minute, second); -} - Result decode_config(Slice input) { static auto rsa = mtproto::RSA::from_pem_public_key( "-----BEGIN RSA PUBLIC KEY-----\n" @@ -950,6 +874,18 @@ void ConfigManager::start_up() { expire_time_ = expire_time; set_timeout_in(expire_time_.in()); } + + auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_suggested_actions_database_key()); + if (!log_event_string.empty()) { + vector suggested_actions; + auto status = log_event_parse(suggested_actions, log_event_string); + if (status.is_error()) { + LOG(ERROR) << "Failed to parse suggested actions from binlog: " << status; + save_suggested_actions(); + } else { + update_suggested_actions(suggested_actions_, std::move(suggested_actions)); + } + } } ActorShared<> ConfigManager::create_reference() { @@ -1115,7 +1051,9 @@ void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sen } void ConfigManager::hide_suggested_action(SuggestedAction suggested_action) { - remove_suggested_action(suggested_actions_, suggested_action); + if (remove_suggested_action(suggested_actions_, suggested_action)) { + save_suggested_actions(); + } } void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, Promise &&promise) { @@ -1156,7 +1094,9 @@ void ConfigManager::on_result(NetQueryPtr net_query) { fail_promises(promises, result_ptr.move_as_error()); return; } - remove_suggested_action(suggested_actions_, suggested_action); + if (remove_suggested_action(suggested_actions_, suggested_action)) { + save_suggested_actions(); + } reget_app_config(Auto()); set_promises(promises); @@ -1465,6 +1405,7 @@ void ConfigManager::process_app_config(tl_object_ptr &c string animation_search_provider; string animation_search_emojis; vector suggested_actions; + vector dismissed_suggestions; bool can_archive_and_mute_new_chats_from_unknown_users = false; int32 chat_read_mark_expire_period = 0; int32 chat_read_mark_size_threshold = 0; @@ -1485,10 +1426,15 @@ void ConfigManager::process_app_config(tl_object_ptr &c int32 dialog_filter_update_period = 300; // bool archive_all_stories = false; int32 story_viewers_expire_period = 86400; - int64 stories_changelog_user_id = ContactsManager::get_service_notifications_user_id().get(); + int64 stories_changelog_user_id = UserManager::get_service_notifications_user_id().get(); int32 transcribe_audio_trial_weekly_number = 0; int32 transcribe_audio_trial_duration_max = 0; int32 transcribe_audio_trial_cooldown_until = 0; + vector business_features; + string premium_manage_subscription_url; + bool need_premium_for_new_chat_privacy = true; + bool channel_revenue_withdrawal_enabled = false; + bool can_edit_fact_check = false; if (config->get_id() == telegram_api::jsonObject::ID) { for (auto &key_value : static_cast(config.get())->value_) { Slice key = key_value->key_; @@ -1656,12 +1602,16 @@ void ConfigManager::process_app_config(tl_object_ptr &c } continue; } - if (key == "pending_suggestions") { + if (key == "pending_suggestions" || key == "dismissed_suggestions") { if (value->get_id() == telegram_api::jsonArray::ID) { auto actions = std::move(static_cast(value)->value_); auto otherwise_relogin_days = G()->get_option_integer("otherwise_relogin_days"); for (auto &action : actions) { auto action_str = get_json_value_string(std::move(action), key); + if (key == "dismissed_suggestions") { + dismissed_suggestions.push_back(action_str); + continue; + } SuggestedAction suggested_action(action_str); if (!suggested_action.is_empty()) { if (otherwise_relogin_days > 0 && @@ -1973,10 +1923,11 @@ void ConfigManager::process_app_config(tl_object_ptr &c } if (key == "channel_bg_icon_level_min" || key == "channel_custom_wallpaper_level_min" || key == "channel_emoji_status_level_min" || key == "channel_profile_bg_icon_level_min" || - key == "channel_wallpaper_level_min" || key == "pm_read_date_expire_period" || - key == "group_transcribe_level_min" || key == "group_emoji_stickers_level_min" || - key == "group_profile_bg_icon_level_min" || key == "group_emoji_status_level_min" || - key == "group_wallpaper_level_min" || key == "group_custom_wallpaper_level_min") { + key == "channel_restrict_sponsored_level_min" || key == "channel_wallpaper_level_min" || + key == "pm_read_date_expire_period" || key == "group_transcribe_level_min" || + key == "group_emoji_stickers_level_min" || key == "group_profile_bg_icon_level_min" || + key == "group_emoji_status_level_min" || key == "group_wallpaper_level_min" || + key == "group_custom_wallpaper_level_min") { G()->set_option_integer(key, get_json_value_int(std::move(key_value->value_), key)); continue; } @@ -1990,6 +1941,71 @@ void ConfigManager::process_app_config(tl_object_ptr &c get_json_value_int(std::move(key_value->value_), key)); continue; } + if (key == "intro_title_length_limit") { + G()->set_option_integer("business_start_page_title_length_max", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "intro_description_length_limit") { + G()->set_option_integer("business_start_page_message_length_max", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "business_promo_order") { + if (value->get_id() == telegram_api::jsonArray::ID) { + auto features = std::move(static_cast(value)->value_); + for (auto &feature : features) { + auto business_feature = get_json_value_string(std::move(feature), key); + if (!td::contains(business_feature, ',')) { + business_features.push_back(std::move(business_feature)); + } + } + } else { + LOG(ERROR) << "Receive unexpected business_promo_order " << to_string(*value); + } + continue; + } + if (key == "new_noncontact_peers_require_premium_without_ownpremium") { + need_premium_for_new_chat_privacy = !get_json_value_bool(std::move(key_value->value_), key); + continue; + } + if (key == "channel_revenue_withdrawal_enabled") { + channel_revenue_withdrawal_enabled = get_json_value_bool(std::move(key_value->value_), key); + continue; + } + if (key == "upload_premium_speedup_download") { + G()->set_option_integer("premium_download_speedup", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "upload_premium_speedup_upload") { + G()->set_option_integer("premium_upload_speedup", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "upload_premium_speedup_notify_period") { + G()->set_option_integer(key, get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "business_chat_links_limit") { + G()->set_option_integer("business_chat_link_count_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "premium_manage_subscription_url") { + premium_manage_subscription_url = get_json_value_string(std::move(key_value->value_), key); + continue; + } + if (key == "stories_pinned_to_top_count_max") { + G()->set_option_integer("pinned_story_count_max", get_json_value_int(std::move(key_value->value_), key)); + continue; + } + if (key == "can_edit_factcheck") { + can_edit_fact_check = get_json_value_bool(std::move(key_value->value_), key); + continue; + } + if (key == "factcheck_length_limit") { + G()->set_option_integer("fact_check_length_max", + get_json_value_int(std::move(key_value->value_), key)); + continue; + } new_values.push_back(std::move(key_value)); } @@ -2088,16 +2104,23 @@ void ConfigManager::process_app_config(tl_object_ptr &c if (dialog_filter_update_period > 0) { options.set_option_integer("chat_folder_new_chats_update_period", dialog_filter_update_period); } + if (td::contains(dismissed_suggestions, "BIRTHDAY_CONTACTS_TODAY")) { + options.set_option_boolean("dismiss_birthday_contact_today", true); + } else { + options.set_option_empty("dismiss_birthday_contact_today"); + } if (!is_premium_available) { premium_bot_username.clear(); // just in case premium_invoice_slug.clear(); // just in case premium_features.clear(); // just in case + business_features.clear(); // just in case options.set_option_empty("is_premium_available"); } else { options.set_option_boolean("is_premium_available", is_premium_available); } options.set_option_string("premium_features", implode(premium_features, ',')); + options.set_option_string("business_features", implode(business_features, ',')); if (premium_bot_username.empty()) { options.set_option_empty("premium_bot_username"); } else { @@ -2124,11 +2147,16 @@ void ConfigManager::process_app_config(tl_object_ptr &c } else { options.set_option_empty("gift_premium_from_input_field"); } - if (stories_changelog_user_id != ContactsManager::get_service_notifications_user_id().get()) { + if (stories_changelog_user_id != UserManager::get_service_notifications_user_id().get()) { options.set_option_integer("stories_changelog_user_id", stories_changelog_user_id); } else { options.set_option_empty("stories_changelog_user_id"); } + if (can_edit_fact_check) { + options.set_option_boolean("can_edit_fact_check", can_edit_fact_check); + } else { + options.set_option_empty("can_edit_fact_check"); + } if (story_viewers_expire_period >= 0) { options.set_option_integer("story_viewers_expiration_delay", story_viewers_expire_period); @@ -2142,13 +2170,37 @@ void ConfigManager::process_app_config(tl_object_ptr &c options.set_option_integer("stickers_premium_by_emoji_num", stickers_premium_by_emoji_num); options.set_option_integer("stickers_normal_by_emoji_per_premium_num", stickers_normal_by_emoji_per_premium_num); + options.set_option_boolean("can_withdraw_chat_revenue", channel_revenue_withdrawal_enabled); + options.set_option_boolean("need_premium_for_new_chat_privacy", need_premium_for_new_chat_privacy); + options.set_option_empty("default_ton_blockchain_config"); options.set_option_empty("default_ton_blockchain_name"); options.set_option_empty("story_viewers_expire_period"); + if (premium_manage_subscription_url.empty()) { + G()->set_option_empty("premium_manage_subscription_url"); + } else { + G()->set_option_string("premium_manage_subscription_url", premium_manage_subscription_url); + } + // do not update suggested actions while changing content settings or dismissing an action if (!is_set_content_settings_request_sent_ && dismiss_suggested_action_request_count_ == 0) { - update_suggested_actions(suggested_actions_, std::move(suggested_actions)); + if (update_suggested_actions(suggested_actions_, std::move(suggested_actions))) { + save_suggested_actions(); + } + } +} + +string ConfigManager::get_suggested_actions_database_key() { + return "suggested_actions"; +} + +void ConfigManager::save_suggested_actions() { + if (suggested_actions_.empty()) { + G()->td_db()->get_binlog_pmc()->erase(get_suggested_actions_database_key()); + } else { + G()->td_db()->get_binlog_pmc()->set(get_suggested_actions_database_key(), + log_event_store(suggested_actions_).as_slice().str()); } } diff --git a/lib/tgchat/ext/td/td/telegram/ConfigManager.h b/lib/tgchat/ext/td/td/telegram/ConfigManager.h index 3f4289b1..0acfb299 100644 --- a/lib/tgchat/ext/td/td/telegram/ConfigManager.h +++ b/lib/tgchat/ext/td/td/telegram/ConfigManager.h @@ -56,23 +56,6 @@ ActorOwn<> get_simple_config_firebase_realtime(Promise promi ActorOwn<> get_simple_config_firebase_firestore(Promise promise, bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id); -class HttpDate { - static bool is_leap(int32 year) { - return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); - } - static int32 days_in_month(int32 year, int32 month) { - static int cnt[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - return cnt[month - 1] + (month == 2 && is_leap(year)); - } - static int32 seconds_in_day() { - return 24 * 60 * 60; - } - - public: - static Result to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second); - static Result parse_http_date(std::string slice); -}; - class ConfigRecoverer; class ConfigManager final : public NetQueryCallback { public: @@ -102,7 +85,7 @@ class ConfigManager final : public NetQueryCallback { private: struct AppConfig { - static constexpr int32 CURRENT_VERSION = 32; + static constexpr int32 CURRENT_VERSION = 46; int32 version_ = 0; int32 hash_ = 0; telegram_api::object_ptr config_; @@ -158,6 +141,10 @@ class ConfigManager final : public NetQueryCallback { void do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions); + static string get_suggested_actions_database_key(); + + void save_suggested_actions(); + static Timestamp load_config_expire_time(); static void save_config_expire(Timestamp timestamp); static void save_dc_options_update(const DcOptions &dc_options); diff --git a/lib/tgchat/ext/td/td/telegram/Contact.cpp b/lib/tgchat/ext/td/td/telegram/Contact.cpp index 029b6100..8ab83f2e 100644 --- a/lib/tgchat/ext/td/td/telegram/Contact.cpp +++ b/lib/tgchat/ext/td/td/telegram/Contact.cpp @@ -8,6 +8,8 @@ #include "td/telegram/misc.h" #include "td/telegram/secret_api.h" +#include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/common.h" @@ -46,8 +48,9 @@ const string &Contact::get_last_name() const { return last_name_; } -tl_object_ptr Contact::get_contact_object() const { - return make_tl_object(phone_number_, first_name_, last_name_, vcard_, user_id_.get()); +tl_object_ptr Contact::get_contact_object(Td *td) const { + return make_tl_object(phone_number_, first_name_, last_name_, vcard_, + td->user_manager_->get_user_id_object(user_id_, "contact")); } tl_object_ptr Contact::get_input_media_contact() const { @@ -88,7 +91,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Contact &contact) << ", vCard size = " << contact.vcard_.size() << contact.user_id_ << "]"; } -Result get_contact(td_api::object_ptr &&contact) { +Result get_contact(Td *td, td_api::object_ptr &&contact) { if (contact == nullptr) { return Status::Error(400, "Contact must be non-empty"); } @@ -105,15 +108,20 @@ Result get_contact(td_api::object_ptr &&contact) { if (!clean_input_string(contact->vcard_)) { return Status::Error(400, "vCard must be encoded in UTF-8"); } + UserId user_id(contact->user_id_); + if (user_id != UserId() && !td->user_manager_->have_user_force(user_id, "get_contact")) { + return Status::Error(400, "User not found"); + } return Contact(std::move(contact->phone_number_), std::move(contact->first_name_), std::move(contact->last_name_), - std::move(contact->vcard_), UserId(contact->user_id_)); + std::move(contact->vcard_), user_id); } -Result process_input_message_contact(tl_object_ptr &&input_message_content) { +Result process_input_message_contact(Td *td, + td_api::object_ptr &&input_message_content) { CHECK(input_message_content != nullptr); CHECK(input_message_content->get_id() == td_api::inputMessageContact::ID); - return get_contact(std::move(static_cast(input_message_content.get())->contact_)); + return get_contact(td, std::move(static_cast(input_message_content.get())->contact_)); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Contact.h b/lib/tgchat/ext/td/td/telegram/Contact.h index 28e9d547..a93538e1 100644 --- a/lib/tgchat/ext/td/td/telegram/Contact.h +++ b/lib/tgchat/ext/td/td/telegram/Contact.h @@ -22,6 +22,8 @@ namespace td { +class Td; + class Contact { string phone_number_; string first_name_; @@ -52,7 +54,7 @@ class Contact { const string &get_last_name() const; - tl_object_ptr get_contact_object() const; + tl_object_ptr get_contact_object(Td *td) const; tl_object_ptr get_input_media_contact() const; @@ -141,9 +143,9 @@ struct ContactHash { } }; -Result get_contact(td_api::object_ptr &&contact) TD_WARN_UNUSED_RESULT; +Result get_contact(Td *td, td_api::object_ptr &&contact) TD_WARN_UNUSED_RESULT; -Result process_input_message_contact(tl_object_ptr &&input_message_content) - TD_WARN_UNUSED_RESULT; +Result process_input_message_contact( + Td *td, td_api::object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp b/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp deleted file mode 100644 index e17a989c..00000000 --- a/lib/tgchat/ext/td/td/telegram/ContactsManager.cpp +++ /dev/null @@ -1,17435 +0,0 @@ -// -// 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/ContactsManager.h" - -#include "td/telegram/AnimationsManager.h" -#include "td/telegram/Application.h" -#include "td/telegram/AuthManager.h" -#include "td/telegram/BlockListId.h" -#include "td/telegram/BotMenuButton.h" -#include "td/telegram/BusinessAwayMessage.h" -#include "td/telegram/BusinessGreetingMessage.h" -#include "td/telegram/BusinessInfo.h" -#include "td/telegram/BusinessInfo.hpp" -#include "td/telegram/BusinessWorkHours.h" -#include "td/telegram/CommonDialogManager.h" -#include "td/telegram/ConfigManager.h" -#include "td/telegram/Dependencies.h" -#include "td/telegram/DialogAdministrator.h" -#include "td/telegram/DialogInviteLink.h" -#include "td/telegram/DialogInviteLinkManager.h" -#include "td/telegram/DialogLocation.h" -#include "td/telegram/DialogManager.h" -#include "td/telegram/DialogParticipantManager.h" -#include "td/telegram/Document.h" -#include "td/telegram/DocumentsManager.h" -#include "td/telegram/FileReferenceManager.h" -#include "td/telegram/files/FileManager.h" -#include "td/telegram/files/FileType.h" -#include "td/telegram/FolderId.h" -#include "td/telegram/Global.h" -#include "td/telegram/GroupCallManager.h" -#include "td/telegram/InlineQueriesManager.h" -#include "td/telegram/InputGroupCallId.h" -#include "td/telegram/LinkManager.h" -#include "td/telegram/logevent/LogEvent.h" -#include "td/telegram/logevent/LogEventHelper.h" -#include "td/telegram/MessageSender.h" -#include "td/telegram/MessagesManager.h" -#include "td/telegram/MessageTtl.h" -#include "td/telegram/MinChannel.h" -#include "td/telegram/misc.h" -#include "td/telegram/net/NetQuery.h" -#include "td/telegram/NotificationManager.h" -#include "td/telegram/OptionManager.h" -#include "td/telegram/PeerColor.h" -#include "td/telegram/Photo.h" -#include "td/telegram/Photo.hpp" -#include "td/telegram/PhotoSize.h" -#include "td/telegram/PremiumGiftOption.hpp" -#include "td/telegram/ReactionListType.h" -#include "td/telegram/ReactionManager.h" -#include "td/telegram/SecretChatLayer.h" -#include "td/telegram/SecretChatsManager.h" -#include "td/telegram/ServerMessageId.h" -#include "td/telegram/StickerPhotoSize.h" -#include "td/telegram/StickersManager.h" -#include "td/telegram/StoryManager.h" -#include "td/telegram/Td.h" -#include "td/telegram/TdDb.h" -#include "td/telegram/telegram_api.h" -#include "td/telegram/ThemeManager.h" -#include "td/telegram/UpdatesManager.h" -#include "td/telegram/Version.h" - -#include "td/db/binlog/BinlogEvent.h" -#include "td/db/binlog/BinlogHelper.h" -#include "td/db/SqliteKeyValue.h" -#include "td/db/SqliteKeyValueAsync.h" - -#include "td/actor/SleepActor.h" - -#include "td/utils/algorithm.h" -#include "td/utils/buffer.h" -#include "td/utils/format.h" -#include "td/utils/logging.h" -#include "td/utils/misc.h" -#include "td/utils/Random.h" -#include "td/utils/ScopeGuard.h" -#include "td/utils/Slice.h" -#include "td/utils/SliceBuilder.h" -#include "td/utils/StringBuilder.h" -#include "td/utils/Time.h" -#include "td/utils/tl_helpers.h" -#include "td/utils/utf8.h" - -#include -#include -#include -#include -#include -#include - -namespace td { - -class DismissSuggestionQuery final : public Td::ResultHandler { - Promise promise_; - DialogId dialog_id_; - - public: - explicit DismissSuggestionQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(SuggestedAction action) { - dialog_id_ = action.dialog_id_; - auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); - CHECK(input_peer != nullptr); - - send_query(G()->net_query_creator().create( - telegram_api::help_dismissSuggestion(std::move(input_peer), action.get_suggested_action_str()))); - } - - 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, "DismissSuggestionQuery"); - promise_.set_error(std::move(status)); - } -}; - -class GetContactsQuery final : public Td::ResultHandler { - public: - void send(int64 hash) { - send_query(G()->net_query_creator().create(telegram_api::contacts_getContacts(hash))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetContactsQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_contacts(std::move(ptr)); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_contacts_failed(std::move(status)); - } -}; - -class GetContactsStatusesQuery final : public Td::ResultHandler { - public: - void send() { - send_query(G()->net_query_creator().create(telegram_api::contacts_getStatuses())); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->contacts_manager_->on_get_contacts_statuses(result_ptr.move_as_ok()); - } - - void on_error(Status status) final { - if (!G()->is_expected_error(status)) { - LOG(ERROR) << "Receive error for GetContactsStatusesQuery: " << status; - } - } -}; - -class AddContactQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - - public: - explicit AddContactQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, tl_object_ptr &&input_user, const Contact &contact, - bool share_phone_number) { - user_id_ = user_id; - int32 flags = 0; - if (share_phone_number) { - flags |= telegram_api::contacts_addContact::ADD_PHONE_PRIVACY_EXCEPTION_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::contacts_addContact(flags, false /*ignored*/, std::move(input_user), contact.get_first_name(), - contact.get_last_name(), contact.get_phone_number()), - {{DialogId(user_id)}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for AddContactQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - td_->contacts_manager_->reload_contacts(true); - td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AddContactQuery"); - } -}; - -class EditCloseFriendsQuery final : public Td::ResultHandler { - Promise promise_; - vector user_ids_; - - public: - explicit EditCloseFriendsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector user_ids) { - user_ids_ = std::move(user_ids); - send_query(G()->net_query_creator().create( - telegram_api::contacts_editCloseFriends(UserId::get_input_user_ids(user_ids_)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->contacts_manager_->on_set_close_friends(user_ids_, std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class ResolvePhoneQuery final : public Td::ResultHandler { - Promise promise_; - string phone_number_; - - public: - explicit ResolvePhoneQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(const string &phone_number) { - phone_number_ = phone_number; - send_query(G()->net_query_creator().create(telegram_api::contacts_resolvePhone(phone_number))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for ResolvePhoneQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolvePhoneQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolvePhoneQuery"); - - DialogId dialog_id(ptr->peer_); - if (dialog_id.get_type() != DialogType::User) { - LOG(ERROR) << "Receive " << dialog_id << " by " << phone_number_; - return on_error(Status::Error(500, "Receive invalid response")); - } - - td_->contacts_manager_->on_resolved_phone_number(phone_number_, dialog_id.get_user_id()); - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == Slice("PHONE_NOT_OCCUPIED")) { - td_->contacts_manager_->on_resolved_phone_number(phone_number_, UserId()); - return promise_.set_value(Unit()); - } - promise_.set_error(std::move(status)); - } -}; - -class AcceptContactQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - - public: - explicit AcceptContactQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, tl_object_ptr &&input_user) { - user_id_ = user_id; - send_query(G()->net_query_creator().create(telegram_api::contacts_acceptContact(std::move(input_user)))); - } - - 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 AcceptContactQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - td_->contacts_manager_->reload_contacts(true); - td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AcceptContactQuery"); - } -}; - -class ImportContactsQuery final : public Td::ResultHandler { - int64 random_id_ = 0; - size_t sent_size_ = 0; - - public: - void send(vector> &&input_phone_contacts, int64 random_id) { - random_id_ = random_id; - sent_size_ = input_phone_contacts.size(); - send_query(G()->net_query_creator().create(telegram_api::contacts_importContacts(std::move(input_phone_contacts)))); - } - - 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 ImportContactsQuery: " << to_string(ptr); - if (sent_size_ == ptr->retry_contacts_.size()) { - return on_error(Status::Error(429, "Too Many Requests: retry after 3600")); - } - td_->contacts_manager_->on_imported_contacts(random_id_, std::move(ptr)); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_imported_contacts(random_id_, std::move(status)); - } -}; - -class DeleteContactsQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit DeleteContactsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector> &&input_users) { - send_query(G()->net_query_creator().create(telegram_api::contacts_deleteContacts(std::move(input_users)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for DeleteContactsQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - td_->contacts_manager_->reload_contacts(true); - } -}; - -class DeleteContactsByPhoneNumberQuery final : public Td::ResultHandler { - Promise promise_; - vector user_ids_; - - public: - explicit DeleteContactsByPhoneNumberQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector &&user_phone_numbers, vector &&user_ids) { - if (user_phone_numbers.empty()) { - return promise_.set_value(Unit()); - } - user_ids_ = std::move(user_ids); - send_query(G()->net_query_creator().create(telegram_api::contacts_deleteByPhones(std::move(user_phone_numbers)))); - } - - 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(); - if (!result) { - return on_error(Status::Error(500, "Some contacts can't be deleted")); - } - - td_->contacts_manager_->on_deleted_contacts(user_ids_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - td_->contacts_manager_->reload_contacts(true); - } -}; - -class ResetContactsQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit ResetContactsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::contacts_resetSaved())); - } - - 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(); - if (!result) { - LOG(ERROR) << "Failed to delete imported contacts"; - td_->contacts_manager_->reload_contacts(true); - } else { - td_->contacts_manager_->on_update_contacts_reset(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - td_->contacts_manager_->reload_contacts(true); - } -}; - -class SearchDialogsNearbyQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit SearchDialogsNearbyQuery(Promise> &&promise) - : promise_(std::move(promise)) { - } - - void send(const Location &location, bool from_background, int32 expire_date) { - int32 flags = 0; - if (from_background) { - flags |= telegram_api::contacts_getLocated::BACKGROUND_MASK; - } - if (expire_date != -1) { - flags |= telegram_api::contacts_getLocated::SELF_EXPIRES_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::contacts_getLocated(flags, false /*ignored*/, location.get_input_geo_point(), expire_date))); - } - - 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 UploadProfilePhotoQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - FileId file_id_; - bool is_fallback_; - bool only_suggest_; - - public: - explicit UploadProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, FileId file_id, tl_object_ptr &&input_file, bool is_fallback, - bool only_suggest, bool is_animation, double main_frame_timestamp) { - CHECK(input_file != nullptr); - CHECK(file_id.is_valid()); - - user_id_ = user_id; - file_id_ = file_id; - is_fallback_ = is_fallback; - only_suggest_ = only_suggest; - - static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_MASK) == - static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_MASK), - ""); - static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK) == - static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_START_TS_MASK), - ""); - static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::FILE_MASK) == - static_cast(telegram_api::photos_uploadContactProfilePhoto::FILE_MASK), - ""); - - int32 flags = 0; - tl_object_ptr photo_input_file; - tl_object_ptr video_input_file; - if (is_animation) { - flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_MASK; - video_input_file = std::move(input_file); - - if (main_frame_timestamp != 0.0) { - flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK; - } - } else { - flags |= telegram_api::photos_uploadProfilePhoto::FILE_MASK; - photo_input_file = std::move(input_file); - } - if (td_->contacts_manager_->is_user_bot(user_id)) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), - std::move(photo_input_file), std::move(video_input_file), - main_frame_timestamp, nullptr), - {{user_id}})); - } else if (user_id == td_->contacts_manager_->get_my_id()) { - if (is_fallback) { - flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, std::move(photo_input_file), - std::move(video_input_file), main_frame_timestamp, nullptr), - {{"me"}})); - } else { - if (only_suggest) { - flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; - } else { - flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; - } - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, - r_input_user.move_as_ok(), std::move(photo_input_file), - std::move(video_input_file), main_frame_timestamp, nullptr), - {{user_id}})); - } - } - - void send(UserId user_id, unique_ptr sticker_photo_size, bool is_fallback, bool only_suggest) { - CHECK(sticker_photo_size != nullptr); - user_id_ = user_id; - file_id_ = FileId(); - is_fallback_ = is_fallback; - only_suggest_ = only_suggest; - - if (td_->contacts_manager_->is_user_bot(user_id)) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; - flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), nullptr, nullptr, - 0.0, sticker_photo_size->get_input_video_size_object(td_)), - {{user_id}})); - } else if (user_id == td_->contacts_manager_->get_my_id()) { - int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; - if (is_fallback) { - flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, nullptr, nullptr, 0.0, - sticker_photo_size->get_input_video_size_object(td_)), - {{"me"}})); - } else { - int32 flags = telegram_api::photos_uploadContactProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; - if (only_suggest) { - flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; - } else { - flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; - } - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, - r_input_user.move_as_ok(), nullptr, nullptr, 0.0, - sticker_photo_size->get_input_video_size_object(td_)), - {{user_id}})); - } - } - - void on_result(BufferSlice packet) final { - static_assert(std::is_same::value, - ""); - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - if (!only_suggest_) { - td_->contacts_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, 0, - std::move(promise_)); - } else { - promise_.set_value(Unit()); - } - - if (file_id_.is_valid()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - if (file_id_.is_valid()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } - } -}; - -class UpdateProfilePhotoQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - FileId file_id_; - int64 old_photo_id_; - bool is_fallback_; - string file_reference_; - - public: - explicit UpdateProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, - tl_object_ptr &&input_photo) { - CHECK(input_photo != nullptr); - user_id_ = user_id; - file_id_ = file_id; - old_photo_id_ = old_photo_id; - is_fallback_ = is_fallback; - file_reference_ = FileManager::extract_file_reference(input_photo); - int32 flags = 0; - if (is_fallback) { - flags |= telegram_api::photos_updateProfilePhoto::FALLBACK_MASK; - } - if (td_->contacts_manager_->is_user_bot(user_id)) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - flags |= telegram_api::photos_updateProfilePhoto::BOT_MASK; - send_query(G()->net_query_creator().create( - telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), - std::move(input_photo)), - {{user_id}})); - } else { - send_query(G()->net_query_creator().create( - telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, nullptr, std::move(input_photo)), - {{"me"}})); - } - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->contacts_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, old_photo_id_, - std::move(promise_)); - } - - void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { - if (file_id_.is_valid()) { - VLOG(file_references) << "Receive " << status << " for " << file_id_; - td_->file_manager_->delete_file_reference(file_id_, file_reference_); - td_->file_reference_manager_->repair_file_reference( - file_id_, PromiseCreator::lambda([user_id = user_id_, file_id = file_id_, is_fallback = is_fallback_, - old_photo_id = old_photo_id_, - promise = std::move(promise_)](Result result) mutable { - if (result.is_error()) { - return promise.set_error(Status::Error(400, "Can't find the photo")); - } - - send_closure(G()->contacts_manager(), &ContactsManager::send_update_profile_photo_query, user_id, file_id, - old_photo_id, is_fallback, std::move(promise)); - })); - return; - } else { - LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_; - } - } - - promise_.set_error(std::move(status)); - } -}; - -class DeleteContactProfilePhotoQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - - public: - explicit DeleteContactProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, tl_object_ptr &&input_user) { - CHECK(input_user != nullptr); - user_id_ = user_id; - - int32 flags = 0; - flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; - send_query(G()->net_query_creator().create( - telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, - std::move(input_user), nullptr, nullptr, 0, nullptr), - {{user_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - ptr->photo_ = nullptr; - td_->contacts_manager_->on_set_profile_photo(user_id_, std::move(ptr), false, 0, std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class DeleteProfilePhotoQuery final : public Td::ResultHandler { - Promise promise_; - int64 profile_photo_id_; - - public: - explicit DeleteProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(int64 profile_photo_id) { - profile_photo_id_ = profile_photo_id; - vector> input_photo_ids; - input_photo_ids.push_back(make_tl_object(profile_photo_id, 0, BufferSlice())); - send_query(G()->net_query_creator().create(telegram_api::photos_deletePhotos(std::move(input_photo_ids)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto result = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for DeleteProfilePhotoQuery: " << format::as_array(result); - if (result.size() != 1u) { - LOG(WARNING) << "Photo can't be deleted"; - return on_error(Status::Error(400, "Photo can't be deleted")); - } - - td_->contacts_manager_->on_delete_profile_photo(profile_photo_id_, std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class UpdateColorQuery final : public Td::ResultHandler { - Promise promise_; - bool for_profile_; - AccentColorId accent_color_id_; - CustomEmojiId background_custom_emoji_id_; - - public: - explicit UpdateColorQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(bool for_profile, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { - for_profile_ = for_profile; - accent_color_id_ = accent_color_id; - background_custom_emoji_id_ = background_custom_emoji_id; - int32 flags = 0; - if (for_profile) { - flags |= telegram_api::account_updateColor::FOR_PROFILE_MASK; - } - if (accent_color_id.is_valid()) { - flags |= telegram_api::account_updateColor::COLOR_MASK; - } - if (background_custom_emoji_id.is_valid()) { - flags |= telegram_api::account_updateColor::BACKGROUND_EMOJI_ID_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::account_updateColor(flags, false /*ignored*/, accent_color_id.get(), - background_custom_emoji_id.get()), - {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - LOG(DEBUG) << "Receive result for UpdateColorQuery: " << result_ptr.ok(); - td_->contacts_manager_->on_update_accent_color_success(for_profile_, accent_color_id_, background_custom_emoji_id_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class UpdateProfileQuery final : public Td::ResultHandler { - Promise promise_; - int32 flags_; - string first_name_; - string last_name_; - string about_; - - public: - explicit UpdateProfileQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(int32 flags, const string &first_name, const string &last_name, const string &about) { - flags_ = flags; - first_name_ = first_name; - last_name_ = last_name; - about_ = about; - send_query(G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about), - {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - LOG(DEBUG) << "Receive result for UpdateProfileQuery: " << to_string(result_ptr.ok()); - td_->contacts_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateProfileQuery"); - td_->contacts_manager_->on_update_profile_success(flags_, first_name_, last_name_, about_); - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class UpdateUsernameQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit UpdateUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(const string &username) { - send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username), {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - LOG(DEBUG) << "Receive result for UpdateUsernameQuery: " << to_string(result_ptr.ok()); - td_->contacts_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateUsernameQuery"); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleUsernameQuery final : public Td::ResultHandler { - Promise promise_; - string username_; - bool is_active_; - - public: - explicit ToggleUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(string &&username, bool is_active) { - username_ = std::move(username); - is_active_ = is_active; - send_query(G()->net_query_creator().create(telegram_api::account_toggleUsername(username_, is_active_), {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ToggleUsernameQuery: " << result; - td_->contacts_manager_->on_update_username_is_active(td_->contacts_manager_->get_my_id(), std::move(username_), - is_active_, std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED") { - td_->contacts_manager_->on_update_username_is_active(td_->contacts_manager_->get_my_id(), std::move(username_), - is_active_, std::move(promise_)); - return; - } - promise_.set_error(std::move(status)); - } -}; - -class ReorderUsernamesQuery final : public Td::ResultHandler { - Promise promise_; - vector usernames_; - - public: - explicit ReorderUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector &&usernames) { - usernames_ = usernames; - send_query(G()->net_query_creator().create(telegram_api::account_reorderUsernames(std::move(usernames)), {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ReorderUsernamesQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Usernames weren't updated")); - } - - td_->contacts_manager_->on_update_active_usernames_order(td_->contacts_manager_->get_my_id(), std::move(usernames_), - std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED") { - td_->contacts_manager_->on_update_active_usernames_order(td_->contacts_manager_->get_my_id(), - std::move(usernames_), std::move(promise_)); - return; - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleBotUsernameQuery final : public Td::ResultHandler { - Promise promise_; - UserId bot_user_id_; - string username_; - bool is_active_; - - public: - explicit ToggleBotUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId bot_user_id, string &&username, bool is_active) { - bot_user_id_ = bot_user_id; - username_ = std::move(username); - is_active_ = is_active; - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id_); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - send_query(G()->net_query_creator().create( - telegram_api::bots_toggleUsername(r_input_user.move_as_ok(), username_, is_active_), {{bot_user_id_}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ToggleBotUsernameQuery: " << result; - td_->contacts_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, - std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED") { - td_->contacts_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, - std::move(promise_)); - return; - } - promise_.set_error(std::move(status)); - } -}; - -class ReorderBotUsernamesQuery final : public Td::ResultHandler { - Promise promise_; - UserId bot_user_id_; - vector usernames_; - - public: - explicit ReorderBotUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId bot_user_id, vector &&usernames) { - bot_user_id_ = bot_user_id; - usernames_ = usernames; - auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id_); - if (r_input_user.is_error()) { - return on_error(r_input_user.move_as_error()); - } - send_query(G()->net_query_creator().create( - telegram_api::bots_reorderUsernames(r_input_user.move_as_ok(), std::move(usernames)), {{bot_user_id_}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ReorderBotUsernamesQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Usernames weren't updated")); - } - - td_->contacts_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED") { - td_->contacts_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), - std::move(promise_)); - return; - } - promise_.set_error(std::move(status)); - } -}; - -class UpdateEmojiStatusQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit UpdateEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(const EmojiStatus &emoji_status) { - send_query(G()->net_query_creator().create( - telegram_api::account_updateEmojiStatus(emoji_status.get_input_emoji_status()), {{"me"}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - LOG(DEBUG) << "Receive result for UpdateEmojiStatusQuery: " << result_ptr.ok(); - if (result_ptr.ok()) { - promise_.set_value(Unit()); - } else { - promise_.set_error(Status::Error(400, "Failed to change Premium badge")); - } - } - - void on_error(Status status) final { - get_recent_emoji_statuses(td_, Auto()); - promise_.set_error(std::move(status)); - } -}; - -class CreateChatQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit CreateChatQuery(Promise> &&promise) : promise_(std::move(promise)) { - } - - void send(vector> &&input_users, const string &title, MessageTtl message_ttl) { - int32 flags = telegram_api::messages_createChat::TTL_PERIOD_MASK; - send_query(G()->net_query_creator().create( - telegram_api::messages_createChat(flags, std::move(input_users), title, message_ttl.get_input_ttl_period()))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->messages_manager_->on_create_new_dialog(result_ptr.move_as_ok(), DialogType::Chat, std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class CreateChannelQuery final : public Td::ResultHandler { - Promise> promise_; - - public: - explicit CreateChannelQuery(Promise> &&promise) : promise_(std::move(promise)) { - } - - void send(const string &title, bool is_forum, bool is_megagroup, const string &about, const DialogLocation &location, - bool for_import, MessageTtl message_ttl) { - int32 flags = telegram_api::channels_createChannel::TTL_PERIOD_MASK; - if (is_forum) { - flags |= telegram_api::channels_createChannel::FORUM_MASK; - } else if (is_megagroup) { - flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK; - } else { - flags |= telegram_api::channels_createChannel::BROADCAST_MASK; - } - if (!location.empty()) { - flags |= telegram_api::channels_createChannel::GEO_POINT_MASK; - } - if (for_import) { - flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK; - } - - send_query(G()->net_query_creator().create(telegram_api::channels_createChannel( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, title, about, - location.get_input_geo_point(), location.get_address(), message_ttl.get_input_ttl_period()))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->messages_manager_->on_create_new_dialog(result_ptr.move_as_ok(), DialogType::Channel, std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class UpdateChannelUsernameQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - string username_; - - public: - explicit UpdateChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, const string &username) { - channel_id_ = channel_id; - username_ = username; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_updateUsername(std::move(input_channel), username), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for UpdateChannelUsernameQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Supergroup username is not updated")); - } - - td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_)); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelUsernameQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleChannelUsernameQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - string username_; - bool is_active_; - - public: - explicit ToggleChannelUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, string &&username, bool is_active) { - channel_id_ = channel_id; - username_ = std::move(username); - is_active_ = is_active; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleUsername(std::move(input_channel), username_, is_active_), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ToggleChannelUsernameQuery: " << result; - td_->contacts_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, - std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_, - std::move(promise_)); - return; - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelUsernameQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class DeactivateAllChannelUsernamesQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit DeactivateAllChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create(telegram_api::channels_deactivateAllUsernames(std::move(input_channel)), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for DeactivateAllChannelUsernamesQuery: " << result; - td_->contacts_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_)); - return; - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeactivateAllChannelUsernamesQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ReorderChannelUsernamesQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - vector usernames_; - - public: - explicit ReorderChannelUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, vector &&usernames) { - channel_id_ = channel_id; - usernames_ = usernames; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_reorderUsernames(std::move(input_channel), std::move(usernames)), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for ReorderChannelUsernamesQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Supergroup usernames weren't updated")); - } - - td_->contacts_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), - std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_), - std::move(promise_)); - return; - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReorderChannelUsernamesQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class UpdateChannelColorQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit UpdateChannelColorQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool for_profile, AccentColorId accent_color_id, - CustomEmojiId background_custom_emoji_id) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - int32 flags = 0; - if (for_profile) { - flags |= telegram_api::channels_updateColor::FOR_PROFILE_MASK; - } - if (accent_color_id.is_valid()) { - flags |= telegram_api::channels_updateColor::COLOR_MASK; - } - if (background_custom_emoji_id.is_valid()) { - flags |= telegram_api::channels_updateColor::BACKGROUND_EMOJI_ID_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::channels_updateColor(flags, false /*ignored*/, std::move(input_channel), accent_color_id.get(), - background_custom_emoji_id.get()), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for UpdateChannelColorQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelColorQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class UpdateChannelEmojiStatusQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit UpdateChannelEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, const EmojiStatus &emoji_status) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_updateEmojiStatus(std::move(input_channel), emoji_status.get_input_emoji_status()), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for UpdateChannelEmojiStatusQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelEmojiStatusQuery"); - get_recent_emoji_statuses(td_, Auto()); - } - promise_.set_error(std::move(status)); - } -}; - -class SetChannelStickerSetQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - StickerSetId sticker_set_id_; - - public: - explicit SetChannelStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, StickerSetId sticker_set_id, - telegram_api::object_ptr &&input_sticker_set) { - channel_id_ = channel_id; - sticker_set_id_ = sticker_set_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_setStickers(std::move(input_channel), std::move(input_sticker_set)), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for SetChannelStickerSetQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Supergroup sticker set not updated")); - } - - td_->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "SetChannelStickerSetQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class SetChannelEmojiStickerSetQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - StickerSetId sticker_set_id_; - - public: - explicit SetChannelEmojiStickerSetQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, StickerSetId sticker_set_id, - telegram_api::object_ptr &&input_sticker_set) { - channel_id_ = channel_id; - sticker_set_id_ = sticker_set_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_setEmojiStickers(std::move(input_channel), std::move(input_sticker_set)), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for SetChannelEmojiStickerSetQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Supergroup custom emoji sticker set not updated")); - } - - td_->contacts_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_emoji_sticker_set(channel_id_, sticker_set_id_); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "SetChannelEmojiStickerSetQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class SetChannelBoostsToUnblockRestrictionsQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - int32 unrestrict_boost_count_; - - public: - explicit SetChannelBoostsToUnblockRestrictionsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, int32 unrestrict_boost_count) { - channel_id_ = channel_id; - unrestrict_boost_count_ = unrestrict_boost_count; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_setBoostsToUnblockRestrictions(std::move(input_channel), unrestrict_boost_count), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for SetChannelBoostsToUnblockRestrictionsQuery: " << to_string(ptr); - td_->contacts_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_unrestrict_boost_count(channel_id_, unrestrict_boost_count_); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "SetChannelBoostsToUnblockRestrictionsQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleChannelSignaturesQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ToggleChannelSignaturesQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool sign_messages) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleChannelSignaturesQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelSignaturesQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleChannelJoinToSendQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ToggleChannelJoinToSendQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool join_to_send) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleJoinToSend(std::move(input_channel), join_to_send), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleChannelJoinToSendQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinToSendQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleChannelJoinRequestQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ToggleChannelJoinRequestQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool join_request) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleJoinRequest(std::move(input_channel), join_request), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleChannelJoinRequestQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinRequestQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class TogglePrehistoryHiddenQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - bool is_all_history_available_; - - public: - explicit TogglePrehistoryHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool is_all_history_available) { - channel_id_ = channel_id; - is_all_history_available_ = is_all_history_available; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_togglePreHistoryHidden(std::move(input_channel), !is_all_history_available), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for TogglePrehistoryHiddenQuery: " << to_string(ptr); - - td_->updates_manager_->on_get_updates( - std::move(ptr), - PromiseCreator::lambda([actor_id = G()->contacts_manager(), promise = std::move(promise_), - channel_id = channel_id_, - is_all_history_available = is_all_history_available_](Unit result) mutable { - send_closure(actor_id, &ContactsManager::on_update_channel_is_all_history_available, channel_id, - is_all_history_available, std::move(promise)); - })); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "TogglePrehistoryHiddenQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleParticipantsHiddenQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - bool has_hidden_participants_; - - public: - explicit ToggleParticipantsHiddenQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool has_hidden_participants) { - channel_id_ = channel_id; - has_hidden_participants_ = has_hidden_participants; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleParticipantsHidden(std::move(input_channel), has_hidden_participants), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleParticipantsHiddenQuery: " << to_string(ptr); - - td_->updates_manager_->on_get_updates( - std::move(ptr), - PromiseCreator::lambda([actor_id = G()->contacts_manager(), promise = std::move(promise_), - channel_id = channel_id_, - has_hidden_participants = has_hidden_participants_](Unit result) mutable { - send_closure(actor_id, &ContactsManager::on_update_channel_has_hidden_participants, channel_id, - has_hidden_participants, std::move(promise)); - })); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleParticipantsHiddenQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleAntiSpamQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - bool has_aggressive_anti_spam_enabled_; - - public: - explicit ToggleAntiSpamQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool has_aggressive_anti_spam_enabled) { - channel_id_ = channel_id; - has_aggressive_anti_spam_enabled_ = has_aggressive_anti_spam_enabled; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleAntiSpam(std::move(input_channel), has_aggressive_anti_spam_enabled), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleAntiSpamQuery: " << to_string(ptr); - - td_->updates_manager_->on_get_updates( - std::move(ptr), - PromiseCreator::lambda( - [actor_id = G()->contacts_manager(), promise = std::move(promise_), channel_id = channel_id_, - has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled_](Unit result) mutable { - send_closure(actor_id, &ContactsManager::on_update_channel_has_aggressive_anti_spam_enabled, channel_id, - has_aggressive_anti_spam_enabled, std::move(promise)); - })); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleAntiSpamQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ToggleForumQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ToggleForumQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, bool is_forum) { - channel_id_ = channel_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create(telegram_api::channels_toggleForum(std::move(input_channel), is_forum), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleForumQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleForumQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ConvertToGigagroupQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ConvertToGigagroupQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id) { - channel_id_ = channel_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create(telegram_api::channels_convertToGigagroup(std::move(input_channel)), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ConvertToGigagroupQuery: " << to_string(ptr); - - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - promise_.set_value(Unit()); - return; - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ConvertToGigagroupQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class EditChatAboutQuery final : public Td::ResultHandler { - Promise promise_; - DialogId dialog_id_; - string about_; - - void on_success() { - switch (dialog_id_.get_type()) { - case DialogType::Chat: - return td_->contacts_manager_->on_update_chat_description(dialog_id_.get_chat_id(), std::move(about_)); - case DialogType::Channel: - return td_->contacts_manager_->on_update_channel_description(dialog_id_.get_channel_id(), std::move(about_)); - case DialogType::User: - case DialogType::SecretChat: - case DialogType::None: - UNREACHABLE(); - } - } - - public: - explicit EditChatAboutQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(DialogId dialog_id, const string &about) { - dialog_id_ = dialog_id; - about_ = about; - auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } - send_query(G()->net_query_creator().create(telegram_api::messages_editChatAbout(std::move(input_peer), about), - {{dialog_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.ok(); - LOG(DEBUG) << "Receive result for EditChatAboutQuery: " << result; - if (!result) { - return on_error(Status::Error(500, "Chat description is not updated")); - } - - on_success(); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_ABOUT_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") { - on_success(); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "EditChatAboutQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class SetDiscussionGroupQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId broadcast_channel_id_; - ChannelId group_channel_id_; - - public: - explicit SetDiscussionGroupQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId broadcast_channel_id, - telegram_api::object_ptr broadcast_input_channel, ChannelId group_channel_id, - telegram_api::object_ptr group_input_channel) { - broadcast_channel_id_ = broadcast_channel_id; - group_channel_id_ = group_channel_id; - send_query(G()->net_query_creator().create( - telegram_api::channels_setDiscussionGroup(std::move(broadcast_input_channel), std::move(group_input_channel)), - {{broadcast_channel_id}, {group_channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.move_as_ok(); - LOG_IF(INFO, !result) << "Set discussion group has failed"; - - td_->contacts_manager_->on_update_channel_linked_channel_id(broadcast_channel_id_, group_channel_id_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (status.message() == "LINK_NOT_MODIFIED") { - return promise_.set_value(Unit()); - } - promise_.set_error(std::move(status)); - } -}; - -class EditLocationQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - DialogLocation location_; - - public: - explicit EditLocationQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, const DialogLocation &location) { - channel_id_ = channel_id; - location_ = location; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - - send_query(G()->net_query_creator().create( - telegram_api::channels_editLocation(std::move(input_channel), location_.get_input_geo_point(), - location_.get_address()), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.move_as_ok(); - LOG_IF(INFO, !result) << "Edit chat location has failed"; - - td_->contacts_manager_->on_update_channel_location(channel_id_, location_); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditLocationQuery"); - promise_.set_error(std::move(status)); - } -}; - -class ToggleSlowModeQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - int32 slow_mode_delay_ = 0; - - public: - explicit ToggleSlowModeQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, int32 slow_mode_delay) { - channel_id_ = channel_id; - slow_mode_delay_ = slow_mode_delay; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - - send_query(G()->net_query_creator().create( - telegram_api::channels_toggleSlowMode(std::move(input_channel), slow_mode_delay), {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for ToggleSlowModeQuery: " << to_string(ptr); - - td_->updates_manager_->on_get_updates( - std::move(ptr), - PromiseCreator::lambda([actor_id = G()->contacts_manager(), promise = std::move(promise_), - channel_id = channel_id_, slow_mode_delay = slow_mode_delay_](Unit result) mutable { - send_closure(actor_id, &ContactsManager::on_update_channel_slow_mode_delay, channel_id, slow_mode_delay, - std::move(promise)); - })); - } - - void on_error(Status status) final { - if (status.message() == "CHAT_NOT_MODIFIED") { - td_->contacts_manager_->on_update_channel_slow_mode_delay(channel_id_, slow_mode_delay_, Promise()); - if (!td_->auth_manager_->is_bot()) { - promise_.set_value(Unit()); - return; - } - } else { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleSlowModeQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ReportChannelSpamQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - DialogId sender_dialog_id_; - - public: - explicit ReportChannelSpamQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, DialogId sender_dialog_id, const vector &message_ids) { - channel_id_ = channel_id; - sender_dialog_id_ = sender_dialog_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - - auto input_peer = td_->dialog_manager_->get_input_peer(sender_dialog_id, AccessRights::Know); - CHECK(input_peer != nullptr); - - send_query(G()->net_query_creator().create(telegram_api::channels_reportSpam( - std::move(input_channel), std::move(input_peer), MessageId::get_server_message_ids(message_ids)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.move_as_ok(); - LOG_IF(INFO, !result) << "Report spam has failed in " << channel_id_; - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - if (sender_dialog_id_.get_type() != DialogType::Channel) { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReportChannelSpamQuery"); - } - promise_.set_error(std::move(status)); - } -}; - -class ReportChannelAntiSpamFalsePositiveQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit ReportChannelAntiSpamFalsePositiveQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, MessageId message_id) { - channel_id_ = channel_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - - send_query(G()->net_query_creator().create(telegram_api::channels_reportAntiSpamFalsePositive( - std::move(input_channel), message_id.get_server_message_id().get()))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - bool result = result_ptr.move_as_ok(); - LOG_IF(INFO, !result) << "Report anti-spam false positive has failed in " << channel_id_; - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReportChannelAntiSpamFalsePositiveQuery"); - promise_.set_error(std::move(status)); - } -}; - -class DeleteChatQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit DeleteChatQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChatId chat_id) { - send_query(G()->net_query_creator().create(telegram_api::messages_deleteChat(chat_id.get()), {{chat_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()); - } - - LOG(INFO) << "Receive result for DeleteChatQuery: " << result_ptr.ok(); - td_->updates_manager_->get_difference("DeleteChatQuery"); - td_->updates_manager_->on_get_updates(make_tl_object(), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class DeleteChannelQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit DeleteChannelQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id) { - channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query(G()->net_query_creator().create(telegram_api::channels_deleteChannel(std::move(input_channel)), - {{channel_id}})); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for DeleteChannelQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelQuery"); - promise_.set_error(std::move(status)); - } -}; - -class MigrateChatQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit MigrateChatQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChatId chat_id) { - send_query(G()->net_query_creator().create(telegram_api::messages_migrateChat(chat_id.get()), {{chat_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 MigrateChatQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetChannelRecommendationsQuery final : public Td::ResultHandler { - Promise>>> promise_; - ChannelId channel_id_; - - public: - explicit GetChannelRecommendationsQuery( - Promise>>> &&promise) - : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id) { - channel_id_ = channel_id; - - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); - CHECK(input_channel != nullptr); - send_query( - G()->net_query_creator().create(telegram_api::channels_getChannelRecommendations(std::move(input_channel)))); - } - - 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 chats_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetChannelRecommendationsQuery: " << to_string(chats_ptr); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - auto total_count = static_cast(chats->chats_.size()); - return promise_.set_value({total_count, std::move(chats->chats_)}); - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - return promise_.set_value({chats->count_, std::move(chats->chats_)}); - } - default: - UNREACHABLE(); - return promise_.set_error(Status::Error("Unreachable")); - } - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelRecommendationsQuery"); - promise_.set_error(std::move(status)); - } -}; - -class GetCreatedPublicChannelsQuery final : public Td::ResultHandler { - Promise promise_; - PublicDialogType type_; - - public: - explicit GetCreatedPublicChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(PublicDialogType type, bool check_limit) { - type_ = type; - int32 flags = 0; - if (type_ == PublicDialogType::IsLocationBased) { - flags |= telegram_api::channels_getAdminedPublicChannels::BY_LOCATION_MASK; - } - if (check_limit) { - flags |= telegram_api::channels_getAdminedPublicChannels::CHECK_LIMIT_MASK; - } - send_query(G()->net_query_creator().create( - telegram_api::channels_getAdminedPublicChannels(flags, false /*ignored*/, false /*ignored*/))); - } - - 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 chats_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetCreatedPublicChannelsQuery: " << to_string(chats_ptr); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - td_->contacts_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); - break; - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - LOG(ERROR) << "Receive chatsSlice in result of GetCreatedPublicChannelsQuery"; - td_->contacts_manager_->on_get_created_public_channels(type_, std::move(chats->chats_)); - break; - } - default: - UNREACHABLE(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetGroupsForDiscussionQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetGroupsForDiscussionQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::channels_getGroupsForDiscussion())); - } - - 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 chats_ptr = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetGroupsForDiscussionQuery: " << to_string(chats_ptr); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - td_->contacts_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); - break; - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - LOG(ERROR) << "Receive chatsSlice in result of GetGroupsForDiscussionQuery"; - td_->contacts_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_)); - break; - } - default: - UNREACHABLE(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetInactiveChannelsQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetInactiveChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::channels_getInactiveChannels())); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto result = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetInactiveChannelsQuery: " << to_string(result); - // don't need to use result->dates_, because chat.last_message.date is more reliable - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetInactiveChannelsQuery"); - td_->contacts_manager_->on_get_inactive_channels(std::move(result->chats_), std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetUsersQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetUsersQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector> &&input_users) { - send_query(G()->net_query_creator().create(telegram_api::users_getUsers(std::move(input_users)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->contacts_manager_->on_get_users(result_ptr.move_as_ok(), "GetUsersQuery"); - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetFullUserQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetFullUserQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(tl_object_ptr &&input_user) { - send_query(G()->net_query_creator().create(telegram_api::users_getFullUser(std::move(input_user)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - LOG(DEBUG) << "Receive result for GetFullUserQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullUserQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullUserQuery"); - td_->contacts_manager_->on_get_user_full(std::move(ptr->full_user_)); - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetUserPhotosQuery final : public Td::ResultHandler { - Promise promise_; - UserId user_id_; - int32 offset_; - int32 limit_; - - public: - explicit GetUserPhotosQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(UserId user_id, tl_object_ptr &&input_user, int32 offset, int32 limit, - int64 photo_id) { - user_id_ = user_id; - offset_ = offset; - limit_ = limit; - send_query(G()->net_query_creator().create( - telegram_api::photos_getUserPhotos(std::move(input_user), offset, photo_id, limit))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - - LOG(INFO) << "Receive result for GetUserPhotosQuery: " << to_string(ptr); - int32 constructor_id = ptr->get_id(); - if (constructor_id == telegram_api::photos_photos::ID) { - auto photos = move_tl_object_as(ptr); - - td_->contacts_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery"); - auto photos_size = narrow_cast(photos->photos_.size()); - td_->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos_size, std::move(photos->photos_)); - } else { - CHECK(constructor_id == telegram_api::photos_photosSlice::ID); - auto photos = move_tl_object_as(ptr); - - td_->contacts_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery slice"); - td_->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos->count_, std::move(photos->photos_)); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetChatsQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetChatsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector &&chat_ids) { - send_query(G()->net_query_creator().create(telegram_api::messages_getChats(std::move(chat_ids)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto chats_ptr = result_ptr.move_as_ok(); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery"); - break; - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - LOG(ERROR) << "Receive chatsSlice in result of GetChatsQuery"; - td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery slice"); - break; - } - default: - UNREACHABLE(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetFullChatQuery final : public Td::ResultHandler { - Promise promise_; - ChatId chat_id_; - - public: - explicit GetFullChatQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChatId chat_id) { - send_query(G()->net_query_creator().create(telegram_api::messages_getFullChat(chat_id.get()))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - auto ptr = result_ptr.move_as_ok(); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullChatQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChatQuery"); - td_->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_chat_full_failed(chat_id_); - promise_.set_error(std::move(status)); - } -}; - -class GetChannelsQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit GetChannelsQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(tl_object_ptr &&input_channel) { - CHECK(input_channel != nullptr); - if (input_channel->get_id() == telegram_api::inputChannel::ID) { - channel_id_ = ChannelId(static_cast(input_channel.get())->channel_id_); - } else if (input_channel->get_id() == telegram_api::inputChannelFromMessage::ID) { - channel_id_ = - ChannelId(static_cast(input_channel.get())->channel_id_); - } - - vector> input_channels; - input_channels.push_back(std::move(input_channel)); - send_query(G()->net_query_creator().create(telegram_api::channels_getChannels(std::move(input_channels)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - // LOG(INFO) << "Receive result for GetChannelsQuery: " << to_string(result_ptr.ok()); - auto chats_ptr = result_ptr.move_as_ok(); - switch (chats_ptr->get_id()) { - case telegram_api::messages_chats::ID: { - auto chats = move_tl_object_as(chats_ptr); - td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery"); - break; - } - case telegram_api::messages_chatsSlice::ID: { - auto chats = move_tl_object_as(chats_ptr); - LOG(ERROR) << "Receive chatsSlice in result of GetChannelsQuery"; - td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery slice"); - break; - } - default: - UNREACHABLE(); - } - - promise_.set_value(Unit()); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelsQuery"); - promise_.set_error(std::move(status)); - } -}; - -class GetFullChannelQuery final : public Td::ResultHandler { - Promise promise_; - ChannelId channel_id_; - - public: - explicit GetFullChannelQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(ChannelId channel_id, tl_object_ptr &&input_channel) { - channel_id_ = channel_id; - send_query(G()->net_query_creator().create(telegram_api::channels_getFullChannel(std::move(input_channel)))); - } - - 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(); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullChannelQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChannelQuery"); - td_->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_)); - } - - void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetFullChannelQuery"); - td_->contacts_manager_->on_get_channel_full_failed(channel_id_); - promise_.set_error(std::move(status)); - } -}; - -class GetSupportUserQuery final : public Td::ResultHandler { - Promise promise_; - - public: - explicit GetSupportUserQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send() { - send_query(G()->net_query_creator().create(telegram_api::help_getSupport())); - } - - 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 GetSupportUserQuery: " << to_string(ptr); - - auto user_id = ContactsManager::get_user_id(ptr->user_); - td_->contacts_manager_->on_get_user(std::move(ptr->user_), "GetSupportUserQuery"); - - promise_.set_value(std::move(user_id)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class GetIsPremiumRequiredToContactQuery final : public Td::ResultHandler { - Promise promise_; - vector user_ids_; - - public: - explicit GetIsPremiumRequiredToContactQuery(Promise &&promise) : promise_(std::move(promise)) { - } - - void send(vector &&user_ids, vector> &&input_users) { - user_ids_ = std::move(user_ids); - send_query( - G()->net_query_creator().create(telegram_api::users_getIsPremiumRequiredToContact(std::move(input_users)))); - } - - void on_result(BufferSlice packet) final { - auto result_ptr = fetch_result(packet); - if (result_ptr.is_error()) { - return on_error(result_ptr.move_as_error()); - } - - td_->contacts_manager_->on_get_is_premium_required_to_contact_users(std::move(user_ids_), result_ptr.move_as_ok(), - std::move(promise_)); - } - - void on_error(Status status) final { - promise_.set_error(std::move(status)); - } -}; - -class ContactsManager::UploadProfilePhotoCallback final : public FileManager::UploadCallback { - public: - void on_upload_ok(FileId file_id, tl_object_ptr input_file) final { - send_closure_later(G()->contacts_manager(), &ContactsManager::on_upload_profile_photo, file_id, - std::move(input_file)); - } - void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) final { - UNREACHABLE(); - } - void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) final { - UNREACHABLE(); - } - void on_upload_error(FileId file_id, Status error) final { - send_closure_later(G()->contacts_manager(), &ContactsManager::on_upload_profile_photo_error, file_id, - std::move(error)); - } -}; - -ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { - upload_profile_photo_callback_ = std::make_shared(); - - my_id_ = load_my_id(); - - if (G()->use_chat_info_database()) { - auto next_contacts_sync_date_string = G()->td_db()->get_binlog_pmc()->get("next_contacts_sync_date"); - if (!next_contacts_sync_date_string.empty()) { - next_contacts_sync_date_ = min(to_integer(next_contacts_sync_date_string), G()->unix_time() + 100000); - } - - auto saved_contact_count_string = G()->td_db()->get_binlog_pmc()->get("saved_contact_count"); - if (!saved_contact_count_string.empty()) { - saved_contact_count_ = to_integer(saved_contact_count_string); - } - } else if (!td_->auth_manager_->is_bot()) { - G()->td_db()->get_binlog_pmc()->erase("next_contacts_sync_date"); - G()->td_db()->get_binlog_pmc()->erase("saved_contact_count"); - } - if (G()->use_sqlite_pmc()) { - G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto()); - if (!G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase_by_prefix("channel_recommendations", Auto()); - } - } - - if (!td_->auth_manager_->is_bot()) { - 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()) { - was_online_local_ = unix_time - 1; - } - - location_visibility_expire_date_ = - to_integer(G()->td_db()->get_binlog_pmc()->get("location_visibility_expire_date")); - if (location_visibility_expire_date_ != 0 && location_visibility_expire_date_ <= G()->unix_time()) { - location_visibility_expire_date_ = 0; - G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date"); - } - auto pending_location_visibility_expire_date_string = - G()->td_db()->get_binlog_pmc()->get("pending_location_visibility_expire_date"); - if (!pending_location_visibility_expire_date_string.empty()) { - pending_location_visibility_expire_date_ = to_integer(pending_location_visibility_expire_date_string); - } - update_is_location_visible(); - LOG(INFO) << "Loaded location_visibility_expire_date = " << location_visibility_expire_date_ - << " and pending_location_visibility_expire_date = " << pending_location_visibility_expire_date_; - } - - user_online_timeout_.set_callback(on_user_online_timeout_callback); - user_online_timeout_.set_callback_data(static_cast(this)); - - user_emoji_status_timeout_.set_callback(on_user_emoji_status_timeout_callback); - user_emoji_status_timeout_.set_callback_data(static_cast(this)); - - channel_emoji_status_timeout_.set_callback(on_channel_emoji_status_timeout_callback); - channel_emoji_status_timeout_.set_callback_data(static_cast(this)); - - channel_unban_timeout_.set_callback(on_channel_unban_timeout_callback); - channel_unban_timeout_.set_callback_data(static_cast(this)); - - user_nearby_timeout_.set_callback(on_user_nearby_timeout_callback); - user_nearby_timeout_.set_callback_data(static_cast(this)); - - slow_mode_delay_timeout_.set_callback(on_slow_mode_delay_timeout_callback); - slow_mode_delay_timeout_.set_callback_data(static_cast(this)); - - get_user_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - auto input_users = transform(query_ids, [this](int64 query_id) { return get_input_user_force(UserId(query_id)); }); - td_->create_handler(std::move(promise))->send(std::move(input_users)); - }); - get_chat_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - td_->create_handler(std::move(promise))->send(std::move(query_ids)); - }); - get_channel_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(query_ids.size() == 1); - auto input_channel = get_input_channel(ChannelId(query_ids[0])); - if (input_channel == nullptr) { - return promise.set_error(Status::Error(400, "Channel not found")); - } - td_->create_handler(std::move(promise))->send(std::move(input_channel)); - }); - get_is_premium_required_to_contact_queries_.set_merge_function( - [this](vector query_ids, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - auto user_ids = UserId::get_user_ids(query_ids); - auto input_users = transform(user_ids, [this](UserId user_id) { return get_input_user_force(user_id); }); - td_->create_handler(std::move(promise)) - ->send(std::move(user_ids), std::move(input_users)); - }); -} - -ContactsManager::~ContactsManager() { - Scheduler::instance()->destroy_on_scheduler( - G()->get_gc_scheduler_id(), users_, users_full_, user_photos_, unknown_users_, pending_user_photos_, - user_profile_photo_file_source_ids_, my_photo_file_id_, user_full_file_source_ids_, chats_, chats_full_, - unknown_chats_, chat_full_file_source_ids_, min_channels_, channels_, channels_full_, unknown_channels_, - invalidated_channels_full_, channel_full_file_source_ids_, secret_chats_, unknown_secret_chats_, - secret_chats_with_user_); - Scheduler::instance()->destroy_on_scheduler( - G()->get_gc_scheduler_id(), loaded_from_database_users_, unavailable_user_fulls_, loaded_from_database_chats_, - unavailable_chat_fulls_, loaded_from_database_channels_, unavailable_channel_fulls_, - loaded_from_database_secret_chats_, resolved_phone_numbers_, all_imported_contacts_, linked_channel_ids_, - restricted_user_ids_, restricted_channel_ids_); -} - -void ContactsManager::start_up() { - if (!pending_location_visibility_expire_date_) { - try_send_set_location_visibility_query(); - } -} - -void ContactsManager::tear_down() { - parent_.reset(); - - LOG(DEBUG) << "Have " << users_.calc_size() << " users, " << chats_.calc_size() << " basic groups, " - << channels_.calc_size() << " supergroups and " << secret_chats_.calc_size() << " secret chats to free"; - LOG(DEBUG) << "Have " << users_full_.calc_size() << " full users, " << chats_full_.calc_size() - << " full basic groups and " << channels_full_.calc_size() << " full supergroups to free"; -} - -UserId ContactsManager::load_my_id() { - auto id_string = G()->td_db()->get_binlog_pmc()->get("my_id"); - if (!id_string.empty()) { - UserId my_id(to_integer(id_string)); - if (my_id.is_valid()) { - return my_id; - } - - my_id = UserId(to_integer(Slice(id_string).substr(5))); - if (my_id.is_valid()) { - G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); - return my_id; - } - - LOG(ERROR) << "Wrong my ID = \"" << id_string << "\" stored in database"; - } - return UserId(); -} - -void ContactsManager::on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_online_timeout, - UserId(user_id_long)); -} - -void ContactsManager::on_user_online_timeout(UserId user_id) { - if (G()->close_flag()) { - return; - } - - auto u = get_user(user_id); - CHECK(u != nullptr); - CHECK(u->is_update_user_sent); - - LOG(INFO) << "Update " << user_id << " online status to offline"; - send_closure(G()->td(), &Td::send_update, - td_api::make_object(user_id.get(), - get_user_status_object(user_id, u, G()->unix_time()))); - - td_->dialog_participant_manager_->update_user_online_member_count(user_id); -} - -void ContactsManager::on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_emoji_status_timeout, - UserId(user_id_long)); -} - -void ContactsManager::on_user_emoji_status_timeout(UserId user_id) { - if (G()->close_flag()) { - return; - } - - auto u = get_user(user_id); - CHECK(u != nullptr); - CHECK(u->is_update_user_sent); - - update_user(u, user_id); -} - -void ContactsManager::on_channel_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_channel_emoji_status_timeout, - ChannelId(channel_id_long)); -} - -void ContactsManager::on_channel_emoji_status_timeout(ChannelId channel_id) { - if (G()->close_flag()) { - return; - } - - auto c = get_channel(channel_id); - CHECK(c != nullptr); - CHECK(c->is_update_supergroup_sent); - - update_channel(c, channel_id); -} - -void ContactsManager::on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_channel_unban_timeout, - ChannelId(channel_id_long)); -} - -void ContactsManager::on_channel_unban_timeout(ChannelId channel_id) { - if (G()->close_flag()) { - return; - } - - auto c = get_channel(channel_id); - CHECK(c != nullptr); - - auto old_status = c->status; - c->status.update_restrictions(); - if (c->status == old_status) { - LOG_IF(ERROR, c->status.is_restricted() || c->status.is_banned()) - << "Status of " << channel_id << " wasn't updated: " << c->status; - } else { - c->is_changed = true; - } - - LOG(INFO) << "Update " << channel_id << " status"; - c->is_status_changed = true; - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_unban_timeout"); - update_channel(c, channel_id); // always call, because in case of failure we need to reactivate timeout -} - -void ContactsManager::on_user_nearby_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_nearby_timeout, - UserId(user_id_long)); -} - -void ContactsManager::on_user_nearby_timeout(UserId user_id) { - if (G()->close_flag()) { - return; - } - - auto u = get_user(user_id); - CHECK(u != nullptr); - - LOG(INFO) << "Remove " << user_id << " from nearby list"; - DialogId dialog_id(user_id); - for (size_t i = 0; i < users_nearby_.size(); i++) { - if (users_nearby_[i].dialog_id == dialog_id) { - users_nearby_.erase(users_nearby_.begin() + i); - send_update_users_nearby(); - return; - } - } -} - -void ContactsManager::on_slow_mode_delay_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) { - if (G()->close_flag()) { - return; - } - - auto contacts_manager = static_cast(contacts_manager_ptr); - send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_slow_mode_delay_timeout, - ChannelId(channel_id_long)); -} - -void ContactsManager::on_slow_mode_delay_timeout(ChannelId channel_id) { - if (G()->close_flag()) { - return; - } - - on_update_channel_slow_mode_next_send_date(channel_id, 0); -} - -template -void ContactsManager::User::store(StorerT &storer) const { - using td::store; - bool has_last_name = !last_name.empty(); - bool legacy_has_username = false; - bool has_photo = photo.small_file_id.is_valid(); - bool has_language_code = !language_code.empty(); - bool have_access_hash = access_hash != -1; - bool has_cache_version = cache_version != 0; - bool has_is_contact = true; - bool has_restriction_reasons = !restriction_reasons.empty(); - bool has_emoji_status = !emoji_status.is_empty(); - bool has_usernames = !usernames.is_empty(); - bool has_flags2 = true; - bool has_max_active_story_id = max_active_story_id.is_valid(); - bool has_max_read_story_id = max_read_story_id.is_valid(); - bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); - bool has_accent_color_id = accent_color_id.is_valid(); - bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); - bool has_profile_accent_color_id = profile_accent_color_id.is_valid(); - bool has_profile_background_custom_emoji_id = profile_background_custom_emoji_id.is_valid(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(is_received); - STORE_FLAG(is_verified); - STORE_FLAG(is_deleted); - STORE_FLAG(is_bot); - STORE_FLAG(can_join_groups); - STORE_FLAG(can_read_all_group_messages); - STORE_FLAG(is_inline_bot); - STORE_FLAG(need_location_bot); - STORE_FLAG(has_last_name); - STORE_FLAG(legacy_has_username); - STORE_FLAG(has_photo); - STORE_FLAG(false); // legacy is_restricted - STORE_FLAG(has_language_code); - STORE_FLAG(have_access_hash); - STORE_FLAG(is_support); - STORE_FLAG(is_min_access_hash); - STORE_FLAG(is_scam); - STORE_FLAG(has_cache_version); - STORE_FLAG(has_is_contact); - STORE_FLAG(is_contact); - STORE_FLAG(is_mutual_contact); - STORE_FLAG(has_restriction_reasons); - STORE_FLAG(need_apply_min_photo); - STORE_FLAG(is_fake); - STORE_FLAG(can_be_added_to_attach_menu); - STORE_FLAG(is_premium); - STORE_FLAG(attach_menu_enabled); - STORE_FLAG(has_emoji_status); - STORE_FLAG(has_usernames); - STORE_FLAG(can_be_edited_bot); - END_STORE_FLAGS(); - if (has_flags2) { - BEGIN_STORE_FLAGS(); - STORE_FLAG(is_close_friend); - STORE_FLAG(stories_hidden); - STORE_FLAG(false); - STORE_FLAG(has_max_active_story_id); - STORE_FLAG(has_max_read_story_id); - STORE_FLAG(has_max_active_story_id_next_reload_time); - STORE_FLAG(has_accent_color_id); - STORE_FLAG(has_background_custom_emoji_id); - STORE_FLAG(has_profile_accent_color_id); - STORE_FLAG(has_profile_background_custom_emoji_id); - STORE_FLAG(contact_require_premium); - END_STORE_FLAGS(); - } - store(first_name, storer); - if (has_last_name) { - store(last_name, storer); - } - store(phone_number, storer); - if (have_access_hash) { - store(access_hash, storer); - } - if (has_photo) { - store(photo, storer); - } - store(was_online, storer); - if (has_restriction_reasons) { - store(restriction_reasons, storer); - } - if (is_inline_bot) { - store(inline_query_placeholder, storer); - } - if (is_bot) { - store(bot_info_version, storer); - } - if (has_language_code) { - store(language_code, storer); - } - if (has_cache_version) { - store(cache_version, storer); - } - if (has_emoji_status) { - store(emoji_status, storer); - } - if (has_usernames) { - store(usernames, storer); - } - if (has_max_active_story_id) { - store(max_active_story_id, storer); - } - if (has_max_read_story_id) { - store(max_read_story_id, storer); - } - if (has_max_active_story_id_next_reload_time) { - store_time(max_active_story_id_next_reload_time, storer); - } - if (has_accent_color_id) { - store(accent_color_id, storer); - } - if (has_background_custom_emoji_id) { - store(background_custom_emoji_id, storer); - } - if (has_profile_accent_color_id) { - store(profile_accent_color_id, storer); - } - if (has_profile_background_custom_emoji_id) { - store(profile_background_custom_emoji_id, storer); - } -} - -template -void ContactsManager::User::parse(ParserT &parser) { - using td::parse; - bool has_last_name; - bool legacy_has_username; - bool has_photo; - bool legacy_is_restricted; - bool has_language_code; - bool have_access_hash; - bool has_cache_version; - bool has_is_contact; - bool has_restriction_reasons; - bool has_emoji_status; - bool has_usernames; - bool has_flags2 = parser.version() >= static_cast(Version::AddUserFlags2); - bool legacy_has_stories = false; - bool has_max_active_story_id = false; - bool has_max_read_story_id = false; - bool has_max_active_story_id_next_reload_time = false; - bool has_accent_color_id = false; - bool has_background_custom_emoji_id = false; - bool has_profile_accent_color_id = false; - bool has_profile_background_custom_emoji_id = false; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(is_received); - PARSE_FLAG(is_verified); - PARSE_FLAG(is_deleted); - PARSE_FLAG(is_bot); - PARSE_FLAG(can_join_groups); - PARSE_FLAG(can_read_all_group_messages); - PARSE_FLAG(is_inline_bot); - PARSE_FLAG(need_location_bot); - PARSE_FLAG(has_last_name); - PARSE_FLAG(legacy_has_username); - PARSE_FLAG(has_photo); - PARSE_FLAG(legacy_is_restricted); - PARSE_FLAG(has_language_code); - PARSE_FLAG(have_access_hash); - PARSE_FLAG(is_support); - PARSE_FLAG(is_min_access_hash); - PARSE_FLAG(is_scam); - PARSE_FLAG(has_cache_version); - PARSE_FLAG(has_is_contact); - PARSE_FLAG(is_contact); - PARSE_FLAG(is_mutual_contact); - PARSE_FLAG(has_restriction_reasons); - PARSE_FLAG(need_apply_min_photo); - PARSE_FLAG(is_fake); - PARSE_FLAG(can_be_added_to_attach_menu); - PARSE_FLAG(is_premium); - PARSE_FLAG(attach_menu_enabled); - PARSE_FLAG(has_emoji_status); - PARSE_FLAG(has_usernames); - PARSE_FLAG(can_be_edited_bot); - END_PARSE_FLAGS(); - if (has_flags2) { - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(is_close_friend); - PARSE_FLAG(stories_hidden); - PARSE_FLAG(legacy_has_stories); - PARSE_FLAG(has_max_active_story_id); - PARSE_FLAG(has_max_read_story_id); - PARSE_FLAG(has_max_active_story_id_next_reload_time); - PARSE_FLAG(has_accent_color_id); - PARSE_FLAG(has_background_custom_emoji_id); - PARSE_FLAG(has_profile_accent_color_id); - PARSE_FLAG(has_profile_background_custom_emoji_id); - PARSE_FLAG(contact_require_premium); - END_PARSE_FLAGS(); - } - parse(first_name, parser); - if (has_last_name) { - parse(last_name, parser); - } - if (legacy_has_username) { - CHECK(!has_usernames); - string username; - parse(username, parser); - usernames = Usernames(std::move(username), vector>()); - } - parse(phone_number, parser); - if (parser.version() < static_cast(Version::FixMinUsers)) { - have_access_hash = is_received; - } - if (have_access_hash) { - parse(access_hash, parser); - } else { - is_min_access_hash = true; - } - if (has_photo) { - parse(photo, parser); - } - if (!has_is_contact) { - // enum class LinkState : uint8 { Unknown, None, KnowsPhoneNumber, Contact }; - - uint32 link_state_inbound; - uint32 link_state_outbound; - parse(link_state_inbound, parser); - parse(link_state_outbound, parser); - - is_contact = link_state_outbound == 3; - is_mutual_contact = is_contact && link_state_inbound == 3; - is_close_friend = false; - } - parse(was_online, parser); - if (legacy_is_restricted) { - string restriction_reason; - parse(restriction_reason, parser); - restriction_reasons = get_restriction_reasons(restriction_reason); - } else if (has_restriction_reasons) { - parse(restriction_reasons, parser); - } - if (is_inline_bot) { - parse(inline_query_placeholder, parser); - } - if (is_bot) { - parse(bot_info_version, parser); - } - if (has_language_code) { - parse(language_code, parser); - } - if (has_cache_version) { - parse(cache_version, parser); - } - if (has_emoji_status) { - parse(emoji_status, parser); - } - if (has_usernames) { - CHECK(!legacy_has_username); - parse(usernames, parser); - } - if (has_max_active_story_id) { - parse(max_active_story_id, parser); - } - if (has_max_read_story_id) { - parse(max_read_story_id, parser); - } - if (has_max_active_story_id_next_reload_time) { - parse_time(max_active_story_id_next_reload_time, parser); - } - if (has_accent_color_id) { - parse(accent_color_id, parser); - } - if (has_background_custom_emoji_id) { - parse(background_custom_emoji_id, parser); - } - if (has_profile_accent_color_id) { - parse(profile_accent_color_id, parser); - } - if (has_profile_background_custom_emoji_id) { - parse(profile_background_custom_emoji_id, parser); - } - - if (!check_utf8(first_name)) { - LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; - first_name.clear(); - cache_version = 0; - } - if (!check_utf8(last_name)) { - LOG(ERROR) << "Have invalid last name \"" << last_name << '"'; - last_name.clear(); - cache_version = 0; - } - - clean_phone_number(phone_number); - if (first_name.empty() && last_name.empty()) { - first_name = phone_number; - } - if (!is_contact && is_mutual_contact) { - LOG(ERROR) << "Have invalid flag is_mutual_contact"; - is_mutual_contact = false; - cache_version = 0; - } - if (!is_contact && is_close_friend) { - LOG(ERROR) << "Have invalid flag is_close_friend"; - is_close_friend = false; - cache_version = 0; - } -} - -template -void ContactsManager::UserFull::store(StorerT &storer) const { - using td::store; - bool has_about = !about.empty(); - bool has_photo = !photo.is_empty(); - bool has_description = !description.empty(); - bool has_commands = !commands.empty(); - bool has_private_forward_name = !private_forward_name.empty(); - bool has_group_administrator_rights = group_administrator_rights != AdministratorRights(); - bool has_broadcast_administrator_rights = broadcast_administrator_rights != AdministratorRights(); - bool has_menu_button = menu_button != nullptr; - bool has_description_photo = !description_photo.is_empty(); - bool has_description_animation = description_animation_file_id.is_valid(); - bool has_premium_gift_options = !premium_gift_options.empty(); - bool has_personal_photo = !personal_photo.is_empty(); - bool has_fallback_photo = !fallback_photo.is_empty(); - bool has_business_info = business_info != nullptr && !business_info->is_empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_about); - STORE_FLAG(is_blocked); - STORE_FLAG(can_be_called); - STORE_FLAG(has_private_calls); - STORE_FLAG(can_pin_messages); - STORE_FLAG(need_phone_number_privacy_exception); // 5 - STORE_FLAG(has_photo); - STORE_FLAG(supports_video_calls); - STORE_FLAG(has_description); - STORE_FLAG(has_commands); - STORE_FLAG(has_private_forward_name); // 10 - STORE_FLAG(has_group_administrator_rights); - STORE_FLAG(has_broadcast_administrator_rights); - STORE_FLAG(has_menu_button); - STORE_FLAG(has_description_photo); - STORE_FLAG(has_description_animation); // 15 - STORE_FLAG(has_premium_gift_options); - STORE_FLAG(voice_messages_forbidden); - STORE_FLAG(has_personal_photo); - STORE_FLAG(has_fallback_photo); - STORE_FLAG(has_pinned_stories); // 20 - STORE_FLAG(is_blocked_for_stories); - STORE_FLAG(wallpaper_overridden); - STORE_FLAG(read_dates_private); - STORE_FLAG(contact_require_premium); - STORE_FLAG(has_business_info); // 25 - END_STORE_FLAGS(); - if (has_about) { - store(about, storer); - } - store(common_chat_count, storer); - store_time(expires_at, storer); - if (has_photo) { - store(photo, storer); - } - if (has_description) { - store(description, storer); - } - if (has_commands) { - store(commands, storer); - } - if (has_private_forward_name) { - store(private_forward_name, storer); - } - if (has_group_administrator_rights) { - store(group_administrator_rights, storer); - } - if (has_broadcast_administrator_rights) { - store(broadcast_administrator_rights, storer); - } - if (has_menu_button) { - store(menu_button, storer); - } - if (has_description_photo) { - store(description_photo, storer); - } - if (has_description_animation) { - storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(description_animation_file_id, - storer); - } - if (has_premium_gift_options) { - store(premium_gift_options, storer); - } - if (has_personal_photo) { - store(personal_photo, storer); - } - if (has_fallback_photo) { - store(fallback_photo, storer); - } - if (has_business_info) { - store(business_info, storer); - } -} - -template -void ContactsManager::UserFull::parse(ParserT &parser) { - using td::parse; - bool has_about; - bool has_photo; - bool has_description; - bool has_commands; - bool has_private_forward_name; - bool has_group_administrator_rights; - bool has_broadcast_administrator_rights; - bool has_menu_button; - bool has_description_photo; - bool has_description_animation; - bool has_premium_gift_options; - bool has_personal_photo; - bool has_fallback_photo; - bool has_business_info; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_about); - PARSE_FLAG(is_blocked); - PARSE_FLAG(can_be_called); - PARSE_FLAG(has_private_calls); - PARSE_FLAG(can_pin_messages); - PARSE_FLAG(need_phone_number_privacy_exception); - PARSE_FLAG(has_photo); - PARSE_FLAG(supports_video_calls); - PARSE_FLAG(has_description); - PARSE_FLAG(has_commands); - PARSE_FLAG(has_private_forward_name); - PARSE_FLAG(has_group_administrator_rights); - PARSE_FLAG(has_broadcast_administrator_rights); - PARSE_FLAG(has_menu_button); - PARSE_FLAG(has_description_photo); - PARSE_FLAG(has_description_animation); - PARSE_FLAG(has_premium_gift_options); - PARSE_FLAG(voice_messages_forbidden); - PARSE_FLAG(has_personal_photo); - PARSE_FLAG(has_fallback_photo); - PARSE_FLAG(has_pinned_stories); - PARSE_FLAG(is_blocked_for_stories); - PARSE_FLAG(wallpaper_overridden); - PARSE_FLAG(read_dates_private); - PARSE_FLAG(contact_require_premium); - PARSE_FLAG(has_business_info); - END_PARSE_FLAGS(); - if (has_about) { - parse(about, parser); - } - parse(common_chat_count, parser); - parse_time(expires_at, parser); - if (has_photo) { - parse(photo, parser); - } - if (has_description) { - parse(description, parser); - } - if (has_commands) { - parse(commands, parser); - } - if (has_private_forward_name) { - parse(private_forward_name, parser); - } - if (has_group_administrator_rights) { - parse(group_administrator_rights, parser); - } - if (has_broadcast_administrator_rights) { - parse(broadcast_administrator_rights, parser); - } - if (has_menu_button) { - parse(menu_button, parser); - } - if (has_description_photo) { - parse(description_photo, parser); - } - if (has_description_animation) { - description_animation_file_id = - parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); - } - if (has_premium_gift_options) { - parse(premium_gift_options, parser); - } - if (has_personal_photo) { - parse(personal_photo, parser); - } - if (has_fallback_photo) { - parse(fallback_photo, parser); - } - if (has_business_info) { - parse(business_info, parser); - } -} - -template -void ContactsManager::Chat::store(StorerT &storer) const { - using td::store; - bool has_photo = photo.small_file_id.is_valid(); - bool use_new_rights = true; - bool has_default_permissions_version = default_permissions_version != -1; - bool has_pinned_message_version = pinned_message_version != -1; - bool has_cache_version = cache_version != 0; - BEGIN_STORE_FLAGS(); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(is_active); - STORE_FLAG(has_photo); - STORE_FLAG(use_new_rights); - STORE_FLAG(has_default_permissions_version); - STORE_FLAG(has_pinned_message_version); - STORE_FLAG(has_cache_version); - STORE_FLAG(noforwards); - END_STORE_FLAGS(); - - store(title, storer); - if (has_photo) { - store(photo, storer); - } - store(participant_count, storer); - store(date, storer); - store(migrated_to_channel_id, storer); - store(version, storer); - store(status, storer); - store(default_permissions, storer); - if (has_default_permissions_version) { - store(default_permissions_version, storer); - } - if (has_pinned_message_version) { - store(pinned_message_version, storer); - } - if (has_cache_version) { - store(cache_version, storer); - } -} - -template -void ContactsManager::Chat::parse(ParserT &parser) { - using td::parse; - bool has_photo; - bool left; - bool kicked; - bool is_creator; - bool is_administrator; - bool everyone_is_administrator; - bool can_edit; - bool use_new_rights; - bool has_default_permissions_version; - bool has_pinned_message_version; - bool has_cache_version; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(left); - PARSE_FLAG(kicked); - PARSE_FLAG(is_creator); - PARSE_FLAG(is_administrator); - PARSE_FLAG(everyone_is_administrator); - PARSE_FLAG(can_edit); - PARSE_FLAG(is_active); - PARSE_FLAG(has_photo); - PARSE_FLAG(use_new_rights); - PARSE_FLAG(has_default_permissions_version); - PARSE_FLAG(has_pinned_message_version); - PARSE_FLAG(has_cache_version); - PARSE_FLAG(noforwards); - END_PARSE_FLAGS(); - - parse(title, parser); - if (has_photo) { - parse(photo, parser); - } - parse(participant_count, parser); - parse(date, parser); - parse(migrated_to_channel_id, parser); - parse(version, parser); - if (use_new_rights) { - parse(status, parser); - parse(default_permissions, parser); - } else { - if (can_edit != (is_creator || is_administrator || everyone_is_administrator)) { - LOG(ERROR) << "Have wrong can_edit flag"; - } - - if (kicked || !is_active) { - status = DialogParticipantStatus::Banned(0); - } else if (left) { - status = DialogParticipantStatus::Left(); - } else if (is_creator) { - status = DialogParticipantStatus::Creator(true, false, string()); - } else if (is_administrator && !everyone_is_administrator) { - status = DialogParticipantStatus::GroupAdministrator(false); - } else { - status = DialogParticipantStatus::Member(); - } - default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, - everyone_is_administrator, everyone_is_administrator, - everyone_is_administrator, false, ChannelType::Unknown); - } - if (has_default_permissions_version) { - parse(default_permissions_version, parser); - } - if (has_pinned_message_version) { - parse(pinned_message_version, parser); - } - if (has_cache_version) { - parse(cache_version, parser); - } - - if (!check_utf8(title)) { - LOG(ERROR) << "Have invalid title \"" << title << '"'; - title.clear(); - cache_version = 0; - } - - if (status.is_administrator() && !status.is_creator()) { - status = DialogParticipantStatus::GroupAdministrator(false); - } -} - -template -void ContactsManager::ChatFull::store(StorerT &storer) const { - using td::store; - bool has_description = !description.empty(); - bool has_legacy_invite_link = false; - bool has_photo = !photo.is_empty(); - bool has_invite_link = invite_link.is_valid(); - bool has_bot_commands = !bot_commands.empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_description); - STORE_FLAG(has_legacy_invite_link); - STORE_FLAG(can_set_username); - STORE_FLAG(has_photo); - STORE_FLAG(has_invite_link); - STORE_FLAG(has_bot_commands); - END_STORE_FLAGS(); - store(version, storer); - store(creator_user_id, storer); - store(participants, storer); - if (has_description) { - store(description, storer); - } - if (has_photo) { - store(photo, storer); - } - if (has_invite_link) { - store(invite_link, storer); - } - if (has_bot_commands) { - store(bot_commands, storer); - } -} - -template -void ContactsManager::ChatFull::parse(ParserT &parser) { - using td::parse; - bool has_description; - bool legacy_has_invite_link; - bool has_photo; - bool has_invite_link; - bool has_bot_commands; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_description); - PARSE_FLAG(legacy_has_invite_link); - PARSE_FLAG(can_set_username); - PARSE_FLAG(has_photo); - PARSE_FLAG(has_invite_link); - PARSE_FLAG(has_bot_commands); - END_PARSE_FLAGS(); - parse(version, parser); - parse(creator_user_id, parser); - parse(participants, parser); - if (has_description) { - parse(description, parser); - } - if (legacy_has_invite_link) { - string legacy_invite_link; - parse(legacy_invite_link, parser); - } - if (has_photo) { - parse(photo, parser); - } - if (has_invite_link) { - parse(invite_link, parser); - } - if (has_bot_commands) { - parse(bot_commands, parser); - } -} - -template -void ContactsManager::Channel::store(StorerT &storer) const { - using td::store; - bool has_photo = photo.small_file_id.is_valid(); - bool legacy_has_username = false; - bool use_new_rights = true; - bool has_participant_count = participant_count != 0; - bool have_default_permissions = true; - bool has_cache_version = cache_version != 0; - bool has_restriction_reasons = !restriction_reasons.empty(); - bool legacy_has_active_group_call = false; - bool has_usernames = !usernames.is_empty(); - bool has_flags2 = true; - bool has_max_active_story_id = max_active_story_id.is_valid(); - bool has_max_read_story_id = max_read_story_id.is_valid(); - bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); - bool has_accent_color_id = accent_color_id.is_valid(); - bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); - bool has_profile_accent_color_id = profile_accent_color_id.is_valid(); - bool has_profile_background_custom_emoji_id = profile_background_custom_emoji_id.is_valid(); - bool has_boost_level = boost_level != 0; - bool has_emoji_status = !emoji_status.is_empty(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(false); - STORE_FLAG(sign_messages); - STORE_FLAG(false); - STORE_FLAG(false); // 5 - STORE_FLAG(false); - STORE_FLAG(is_megagroup); - STORE_FLAG(is_verified); - STORE_FLAG(has_photo); - STORE_FLAG(legacy_has_username); // 10 - STORE_FLAG(false); - STORE_FLAG(use_new_rights); - STORE_FLAG(has_participant_count); - STORE_FLAG(have_default_permissions); - STORE_FLAG(is_scam); // 15 - STORE_FLAG(has_cache_version); - STORE_FLAG(has_linked_channel); - STORE_FLAG(has_location); - STORE_FLAG(is_slow_mode_enabled); - STORE_FLAG(has_restriction_reasons); // 20 - STORE_FLAG(legacy_has_active_group_call); - STORE_FLAG(is_fake); - STORE_FLAG(is_gigagroup); - STORE_FLAG(noforwards); - STORE_FLAG(can_be_deleted); // 25 - STORE_FLAG(join_to_send); - STORE_FLAG(join_request); - STORE_FLAG(has_usernames); - STORE_FLAG(has_flags2); - END_STORE_FLAGS(); - if (has_flags2) { - BEGIN_STORE_FLAGS(); - STORE_FLAG(is_forum); - STORE_FLAG(has_max_active_story_id); - STORE_FLAG(has_max_read_story_id); - STORE_FLAG(has_max_active_story_id_next_reload_time); - STORE_FLAG(stories_hidden); - STORE_FLAG(has_accent_color_id); - STORE_FLAG(has_background_custom_emoji_id); - STORE_FLAG(has_profile_accent_color_id); - STORE_FLAG(has_profile_background_custom_emoji_id); - STORE_FLAG(has_boost_level); - STORE_FLAG(has_emoji_status); - END_STORE_FLAGS(); - } - - store(status, storer); - store(access_hash, storer); - store(title, storer); - if (has_photo) { - store(photo, storer); - } - store(date, storer); - if (has_restriction_reasons) { - store(restriction_reasons, storer); - } - if (has_participant_count) { - store(participant_count, storer); - } - if (is_megagroup) { - store(default_permissions, storer); - } - if (has_cache_version) { - store(cache_version, storer); - } - if (has_usernames) { - store(usernames, storer); - } - if (has_max_active_story_id) { - store(max_active_story_id, storer); - } - if (has_max_read_story_id) { - store(max_read_story_id, storer); - } - if (has_max_active_story_id_next_reload_time) { - store_time(max_active_story_id_next_reload_time, storer); - } - if (has_accent_color_id) { - store(accent_color_id, storer); - } - if (has_background_custom_emoji_id) { - store(background_custom_emoji_id, storer); - } - if (has_profile_accent_color_id) { - store(profile_accent_color_id, storer); - } - if (has_profile_background_custom_emoji_id) { - store(profile_background_custom_emoji_id, storer); - } - if (has_boost_level) { - store(boost_level, storer); - } - if (has_emoji_status) { - store(emoji_status, storer); - } -} - -template -void ContactsManager::Channel::parse(ParserT &parser) { - using td::parse; - bool has_photo; - bool legacy_has_username; - bool legacy_is_restricted; - bool left; - bool kicked; - bool is_creator; - bool can_edit; - bool can_moderate; - bool anyone_can_invite; - bool use_new_rights; - bool has_participant_count; - bool have_default_permissions; - bool has_cache_version; - bool has_restriction_reasons; - bool legacy_has_active_group_call; - bool has_usernames; - bool has_flags2; - bool has_max_active_story_id = false; - bool has_max_read_story_id = false; - bool has_max_active_story_id_next_reload_time = false; - bool has_accent_color_id = false; - bool has_background_custom_emoji_id = false; - bool has_profile_accent_color_id = false; - bool has_profile_background_custom_emoji_id = false; - bool has_boost_level = false; - bool has_emoji_status = false; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(left); - PARSE_FLAG(kicked); - PARSE_FLAG(anyone_can_invite); - PARSE_FLAG(sign_messages); - PARSE_FLAG(is_creator); - PARSE_FLAG(can_edit); - PARSE_FLAG(can_moderate); - PARSE_FLAG(is_megagroup); - PARSE_FLAG(is_verified); - PARSE_FLAG(has_photo); - PARSE_FLAG(legacy_has_username); - PARSE_FLAG(legacy_is_restricted); - PARSE_FLAG(use_new_rights); - PARSE_FLAG(has_participant_count); - PARSE_FLAG(have_default_permissions); - PARSE_FLAG(is_scam); - PARSE_FLAG(has_cache_version); - PARSE_FLAG(has_linked_channel); - PARSE_FLAG(has_location); - PARSE_FLAG(is_slow_mode_enabled); - PARSE_FLAG(has_restriction_reasons); - PARSE_FLAG(legacy_has_active_group_call); - PARSE_FLAG(is_fake); - PARSE_FLAG(is_gigagroup); - PARSE_FLAG(noforwards); - PARSE_FLAG(can_be_deleted); - PARSE_FLAG(join_to_send); - PARSE_FLAG(join_request); - PARSE_FLAG(has_usernames); - PARSE_FLAG(has_flags2); - END_PARSE_FLAGS(); - if (has_flags2) { - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(is_forum); - PARSE_FLAG(has_max_active_story_id); - PARSE_FLAG(has_max_read_story_id); - PARSE_FLAG(has_max_active_story_id_next_reload_time); - PARSE_FLAG(stories_hidden); - PARSE_FLAG(has_accent_color_id); - PARSE_FLAG(has_background_custom_emoji_id); - PARSE_FLAG(has_profile_accent_color_id); - PARSE_FLAG(has_profile_background_custom_emoji_id); - PARSE_FLAG(has_boost_level); - PARSE_FLAG(has_emoji_status); - END_PARSE_FLAGS(); - } - - if (use_new_rights) { - parse(status, parser); - } else { - if (kicked) { - status = DialogParticipantStatus::Banned(0); - } else if (left) { - status = DialogParticipantStatus::Left(); - } else if (is_creator) { - status = DialogParticipantStatus::Creator(true, false, string()); - } else if (can_edit || can_moderate) { - status = DialogParticipantStatus::ChannelAdministrator(false, is_megagroup); - } else { - status = DialogParticipantStatus::Member(); - } - } - parse(access_hash, parser); - parse(title, parser); - if (has_photo) { - parse(photo, parser); - } - if (legacy_has_username) { - CHECK(!has_usernames); - string username; - parse(username, parser); - usernames = Usernames(std::move(username), vector>()); - } - parse(date, parser); - if (legacy_is_restricted) { - string restriction_reason; - parse(restriction_reason, parser); - restriction_reasons = get_restriction_reasons(restriction_reason); - } else if (has_restriction_reasons) { - parse(restriction_reasons, parser); - } - if (has_participant_count) { - parse(participant_count, parser); - } - if (is_megagroup) { - if (have_default_permissions) { - parse(default_permissions, parser); - } else { - default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, - true, false, anyone_can_invite, false, false, ChannelType::Megagroup); - } - } - if (has_cache_version) { - parse(cache_version, parser); - } - if (has_usernames) { - CHECK(!legacy_has_username); - parse(usernames, parser); - } - if (has_max_active_story_id) { - parse(max_active_story_id, parser); - } - if (has_max_read_story_id) { - parse(max_read_story_id, parser); - } - if (has_max_active_story_id_next_reload_time) { - parse_time(max_active_story_id_next_reload_time, parser); - } - if (has_accent_color_id) { - parse(accent_color_id, parser); - } - if (has_background_custom_emoji_id) { - parse(background_custom_emoji_id, parser); - } - if (has_profile_accent_color_id) { - parse(profile_accent_color_id, parser); - } - if (has_profile_background_custom_emoji_id) { - parse(profile_background_custom_emoji_id, parser); - } - if (has_boost_level) { - parse(boost_level, parser); - } - if (has_emoji_status) { - parse(emoji_status, parser); - } - - if (!check_utf8(title)) { - LOG(ERROR) << "Have invalid title \"" << title << '"'; - title.clear(); - cache_version = 0; - } - if (legacy_has_active_group_call) { - cache_version = 0; - } - if (!is_megagroup && status.is_restricted()) { - if (status.is_member()) { - status = DialogParticipantStatus::Member(); - } else { - status = DialogParticipantStatus::Left(); - } - } -} - -template -void ContactsManager::ChannelFull::store(StorerT &storer) const { - using td::store; - bool has_description = !description.empty(); - bool has_administrator_count = administrator_count != 0; - bool has_restricted_count = restricted_count != 0; - bool has_banned_count = banned_count != 0; - bool legacy_has_invite_link = false; - bool has_sticker_set = sticker_set_id.is_valid(); - bool has_linked_channel_id = linked_channel_id.is_valid(); - bool has_migrated_from_max_message_id = migrated_from_max_message_id.is_valid(); - bool has_migrated_from_chat_id = migrated_from_chat_id.is_valid(); - bool has_location = !location.empty(); - bool has_bot_user_ids = !bot_user_ids.empty(); - bool is_slow_mode_enabled = slow_mode_delay != 0; - bool is_slow_mode_delay_active = slow_mode_next_send_date != 0; - bool has_stats_dc_id = stats_dc_id.is_exact(); - bool has_photo = !photo.is_empty(); - bool legacy_has_active_group_call_id = false; - bool has_invite_link = invite_link.is_valid(); - bool has_bot_commands = !bot_commands.empty(); - bool has_flags2 = true; - bool has_emoji_sticker_set = emoji_sticker_set_id.is_valid(); - bool has_boost_count = boost_count != 0; - bool has_unrestrict_boost_count = unrestrict_boost_count != 0; - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_description); - STORE_FLAG(has_administrator_count); - STORE_FLAG(has_restricted_count); - STORE_FLAG(has_banned_count); - STORE_FLAG(legacy_has_invite_link); - STORE_FLAG(has_sticker_set); // 5 - STORE_FLAG(has_linked_channel_id); - STORE_FLAG(has_migrated_from_max_message_id); - STORE_FLAG(has_migrated_from_chat_id); - STORE_FLAG(can_get_participants); - STORE_FLAG(can_set_username); // 10 - STORE_FLAG(can_set_sticker_set); - STORE_FLAG(false); // legacy_can_view_statistics - STORE_FLAG(is_all_history_available); - STORE_FLAG(can_set_location); - STORE_FLAG(has_location); // 15 - STORE_FLAG(has_bot_user_ids); - STORE_FLAG(is_slow_mode_enabled); - STORE_FLAG(is_slow_mode_delay_active); - STORE_FLAG(has_stats_dc_id); - STORE_FLAG(has_photo); // 20 - STORE_FLAG(is_can_view_statistics_inited); - STORE_FLAG(can_view_statistics); - STORE_FLAG(legacy_has_active_group_call_id); - STORE_FLAG(has_invite_link); - STORE_FLAG(has_bot_commands); // 25 - STORE_FLAG(can_be_deleted); - STORE_FLAG(has_aggressive_anti_spam_enabled); - STORE_FLAG(has_hidden_participants); - STORE_FLAG(has_flags2); - END_STORE_FLAGS(); - if (has_flags2) { - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_pinned_stories); - STORE_FLAG(has_emoji_sticker_set); - STORE_FLAG(has_boost_count); - STORE_FLAG(has_unrestrict_boost_count); - END_STORE_FLAGS(); - } - if (has_description) { - store(description, storer); - } - store(participant_count, storer); - if (has_administrator_count) { - store(administrator_count, storer); - } - if (has_restricted_count) { - store(restricted_count, storer); - } - if (has_banned_count) { - store(banned_count, storer); - } - if (has_sticker_set) { - store(sticker_set_id, storer); - } - if (has_linked_channel_id) { - store(linked_channel_id, storer); - } - if (has_location) { - store(location, storer); - } - if (has_bot_user_ids) { - store(bot_user_ids, storer); - } - if (has_migrated_from_max_message_id) { - store(migrated_from_max_message_id, storer); - } - if (has_migrated_from_chat_id) { - store(migrated_from_chat_id, storer); - } - if (is_slow_mode_enabled) { - store(slow_mode_delay, storer); - } - if (is_slow_mode_delay_active) { - store(slow_mode_next_send_date, storer); - } - store_time(expires_at, storer); - if (has_stats_dc_id) { - store(stats_dc_id.get_raw_id(), storer); - } - if (has_photo) { - store(photo, storer); - } - if (has_invite_link) { - store(invite_link, storer); - } - if (has_bot_commands) { - store(bot_commands, storer); - } - if (has_emoji_sticker_set) { - store(emoji_sticker_set_id, storer); - } - if (has_boost_count) { - store(boost_count, storer); - } - if (has_unrestrict_boost_count) { - store(unrestrict_boost_count, storer); - } -} - -template -void ContactsManager::ChannelFull::parse(ParserT &parser) { - using td::parse; - bool has_description; - bool has_administrator_count; - bool has_restricted_count; - bool has_banned_count; - bool legacy_has_invite_link; - bool has_sticker_set; - bool has_linked_channel_id; - bool has_migrated_from_max_message_id; - bool has_migrated_from_chat_id; - bool legacy_can_view_statistics; - bool has_location; - bool has_bot_user_ids; - bool is_slow_mode_enabled; - bool is_slow_mode_delay_active; - bool has_stats_dc_id; - bool has_photo; - bool legacy_has_active_group_call_id; - bool has_invite_link; - bool has_bot_commands; - bool has_flags2; - bool has_emoji_sticker_set = false; - bool has_boost_count = false; - bool has_unrestrict_boost_count = false; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_description); - PARSE_FLAG(has_administrator_count); - PARSE_FLAG(has_restricted_count); - PARSE_FLAG(has_banned_count); - PARSE_FLAG(legacy_has_invite_link); - PARSE_FLAG(has_sticker_set); - PARSE_FLAG(has_linked_channel_id); - PARSE_FLAG(has_migrated_from_max_message_id); - PARSE_FLAG(has_migrated_from_chat_id); - PARSE_FLAG(can_get_participants); - PARSE_FLAG(can_set_username); - PARSE_FLAG(can_set_sticker_set); - PARSE_FLAG(legacy_can_view_statistics); - PARSE_FLAG(is_all_history_available); - PARSE_FLAG(can_set_location); - PARSE_FLAG(has_location); - PARSE_FLAG(has_bot_user_ids); - PARSE_FLAG(is_slow_mode_enabled); - PARSE_FLAG(is_slow_mode_delay_active); - PARSE_FLAG(has_stats_dc_id); - PARSE_FLAG(has_photo); - PARSE_FLAG(is_can_view_statistics_inited); - PARSE_FLAG(can_view_statistics); - PARSE_FLAG(legacy_has_active_group_call_id); - PARSE_FLAG(has_invite_link); - PARSE_FLAG(has_bot_commands); - PARSE_FLAG(can_be_deleted); - PARSE_FLAG(has_aggressive_anti_spam_enabled); - PARSE_FLAG(has_hidden_participants); - PARSE_FLAG(has_flags2); - END_PARSE_FLAGS(); - if (has_flags2) { - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_pinned_stories); - PARSE_FLAG(has_emoji_sticker_set); - PARSE_FLAG(has_boost_count); - PARSE_FLAG(has_unrestrict_boost_count); - END_PARSE_FLAGS(); - } - if (has_description) { - parse(description, parser); - } - parse(participant_count, parser); - if (has_administrator_count) { - parse(administrator_count, parser); - } - if (has_restricted_count) { - parse(restricted_count, parser); - } - if (has_banned_count) { - parse(banned_count, parser); - } - if (legacy_has_invite_link) { - string legacy_invite_link; - parse(legacy_invite_link, parser); - } - if (has_sticker_set) { - parse(sticker_set_id, parser); - } - if (has_linked_channel_id) { - parse(linked_channel_id, parser); - } - if (has_location) { - parse(location, parser); - } - if (has_bot_user_ids) { - parse(bot_user_ids, parser); - } - if (has_migrated_from_max_message_id) { - parse(migrated_from_max_message_id, parser); - } - if (has_migrated_from_chat_id) { - parse(migrated_from_chat_id, parser); - } - if (is_slow_mode_enabled) { - parse(slow_mode_delay, parser); - } - if (is_slow_mode_delay_active) { - parse(slow_mode_next_send_date, parser); - } - parse_time(expires_at, parser); - if (has_stats_dc_id) { - stats_dc_id = DcId::create(parser.fetch_int()); - } - if (has_photo) { - parse(photo, parser); - } - if (legacy_has_active_group_call_id) { - InputGroupCallId input_group_call_id; - parse(input_group_call_id, parser); - } - if (has_invite_link) { - parse(invite_link, parser); - } - if (has_bot_commands) { - parse(bot_commands, parser); - } - if (has_emoji_sticker_set) { - parse(emoji_sticker_set_id, parser); - } - if (has_boost_count) { - parse(boost_count, parser); - } - if (has_unrestrict_boost_count) { - parse(unrestrict_boost_count, parser); - } - - if (legacy_can_view_statistics) { - LOG(DEBUG) << "Ignore legacy can view statistics flag"; - } - if (!is_can_view_statistics_inited) { - can_view_statistics = stats_dc_id.is_exact(); - } -} - -template -void ContactsManager::SecretChat::store(StorerT &storer) const { - using td::store; - bool has_layer = layer > static_cast(SecretChatLayer::Default); - bool has_initial_folder_id = initial_folder_id != FolderId(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(is_outbound); - STORE_FLAG(has_layer); - STORE_FLAG(has_initial_folder_id); - END_STORE_FLAGS(); - - store(access_hash, storer); - store(user_id, storer); - store(state, storer); - store(ttl, storer); - store(date, storer); - store(key_hash, storer); - if (has_layer) { - store(layer, storer); - } - if (has_initial_folder_id) { - store(initial_folder_id, storer); - } -} - -template -void ContactsManager::SecretChat::parse(ParserT &parser) { - using td::parse; - bool has_layer; - bool has_initial_folder_id; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(is_outbound); - PARSE_FLAG(has_layer); - PARSE_FLAG(has_initial_folder_id); - END_PARSE_FLAGS(); - - if (parser.version() >= static_cast(Version::AddAccessHashToSecretChat)) { - parse(access_hash, parser); - } - parse(user_id, parser); - parse(state, parser); - parse(ttl, parser); - parse(date, parser); - if (parser.version() >= static_cast(Version::AddKeyHashToSecretChat)) { - parse(key_hash, parser); - } - if (has_layer) { - parse(layer, parser); - } else { - layer = static_cast(SecretChatLayer::Default); - } - if (has_initial_folder_id) { - parse(initial_folder_id, parser); - } -} - -template -void ContactsManager::RecommendedDialogs::store(StorerT &storer) const { - bool has_dialog_ids = !dialog_ids_.empty(); - bool has_total_count = static_cast(total_count_) != dialog_ids_.size(); - BEGIN_STORE_FLAGS(); - STORE_FLAG(has_dialog_ids); - STORE_FLAG(has_total_count); - END_STORE_FLAGS(); - if (has_dialog_ids) { - td::store(dialog_ids_, storer); - } - store_time(next_reload_time_, storer); - if (has_total_count) { - td::store(total_count_, storer); - } -} - -template -void ContactsManager::RecommendedDialogs::parse(ParserT &parser) { - bool has_dialog_ids; - bool has_total_count; - BEGIN_PARSE_FLAGS(); - PARSE_FLAG(has_dialog_ids); - PARSE_FLAG(has_total_count); - END_PARSE_FLAGS(); - if (has_dialog_ids) { - td::parse(dialog_ids_, parser); - } - parse_time(next_reload_time_, parser); - if (has_total_count) { - td::parse(total_count_, parser); - } else { - total_count_ = static_cast(dialog_ids_.size()); - } -} - -Result> ContactsManager::get_input_user(UserId user_id) const { - if (user_id == get_my_id()) { - return make_tl_object(); - } - - const User *u = get_user(user_id); - if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { - if (td_->auth_manager_->is_bot() && user_id.is_valid()) { - return make_tl_object(user_id.get(), 0); - } - auto it = user_messages_.find(user_id); - if (it != user_messages_.end()) { - CHECK(!it->second.empty()); - auto message_full_id = *it->second.begin(); - return make_tl_object( - get_simple_input_peer(message_full_id.get_dialog_id()), - message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); - } - if (u == nullptr) { - return Status::Error(400, "User not found"); - } else { - return Status::Error(400, "Have no access to the user"); - } - } - - return make_tl_object(user_id.get(), u->access_hash); -} - -telegram_api::object_ptr ContactsManager::get_input_user_force(UserId user_id) const { - auto r_input_user = get_input_user(user_id); - if (r_input_user.is_error()) { - CHECK(user_id.is_valid()); - return make_tl_object(user_id.get(), 0); - } - return r_input_user.move_as_ok(); -} - -tl_object_ptr ContactsManager::get_input_channel(ChannelId channel_id) const { - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { - return make_tl_object(channel_id.get(), 0); - } - auto it = channel_messages_.find(channel_id); - if (it != channel_messages_.end()) { - CHECK(!it->second.empty()); - auto message_full_id = *it->second.begin(); - return make_tl_object( - get_simple_input_peer(message_full_id.get_dialog_id()), - message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); - } - return nullptr; - } - - return make_tl_object(channel_id.get(), c->access_hash); -} - -bool ContactsManager::have_input_peer_user(UserId user_id, AccessRights access_rights) const { - if (user_id == get_my_id()) { - return true; - } - return have_input_peer_user(get_user(user_id), user_id, access_rights); -} - -bool ContactsManager::have_input_peer_user(const User *u, UserId user_id, AccessRights access_rights) const { - if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { - if (u == nullptr) { - LOG(DEBUG) << "Have no user"; - } else { - LOG(DEBUG) << "Have user without access hash"; - } - if (td_->auth_manager_->is_bot() && user_id.is_valid()) { - return true; - } - if (user_messages_.count(user_id) != 0) { - return true; - } - return false; - } - if (access_rights == AccessRights::Know) { - return true; - } - if (access_rights == AccessRights::Read) { - return true; - } - if (u->is_deleted) { - LOG(DEBUG) << "Have a deleted user"; - return false; - } - return true; -} - -tl_object_ptr ContactsManager::get_input_peer_user(UserId user_id, - AccessRights access_rights) const { - if (user_id == get_my_id()) { - return make_tl_object(); - } - const User *u = get_user(user_id); - if (!have_input_peer_user(u, user_id, access_rights)) { - return nullptr; - } - if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { - if (td_->auth_manager_->is_bot() && user_id.is_valid()) { - return make_tl_object(user_id.get(), 0); - } - auto it = user_messages_.find(user_id); - CHECK(it != user_messages_.end()); - CHECK(!it->second.empty()); - auto message_full_id = *it->second.begin(); - return make_tl_object( - get_simple_input_peer(message_full_id.get_dialog_id()), - message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); - } - - return make_tl_object(user_id.get(), u->access_hash); -} - -bool ContactsManager::have_input_peer_chat(ChatId chat_id, AccessRights access_rights) const { - return have_input_peer_chat(get_chat(chat_id), access_rights); -} - -bool ContactsManager::have_input_peer_chat(const Chat *c, AccessRights access_rights) { - if (c == nullptr) { - LOG(DEBUG) << "Have no basic group"; - return false; - } - if (access_rights == AccessRights::Know) { - return true; - } - if (access_rights == AccessRights::Read) { - return true; - } - if (c->status.is_left()) { - LOG(DEBUG) << "Have left basic group"; - return false; - } - if (access_rights == AccessRights::Write && !c->is_active) { - LOG(DEBUG) << "Have inactive basic group"; - return false; - } - return true; -} - -tl_object_ptr ContactsManager::get_input_peer_chat(ChatId chat_id, - AccessRights access_rights) const { - auto c = get_chat(chat_id); - if (!have_input_peer_chat(c, access_rights)) { - return nullptr; - } - - return make_tl_object(chat_id.get()); -} - -bool ContactsManager::have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const { - const Channel *c = get_channel(channel_id); - return have_input_peer_channel(c, channel_id, access_rights); -} - -tl_object_ptr ContactsManager::get_input_peer_channel(ChannelId channel_id, - AccessRights access_rights) const { - const Channel *c = get_channel(channel_id); - if (!have_input_peer_channel(c, channel_id, access_rights)) { - return nullptr; - } - if (c == nullptr) { - if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { - return make_tl_object(channel_id.get(), 0); - } - auto it = channel_messages_.find(channel_id); - CHECK(it != channel_messages_.end()); - CHECK(!it->second.empty()); - auto message_full_id = *it->second.begin(); - return make_tl_object( - get_simple_input_peer(message_full_id.get_dialog_id()), - message_full_id.get_message_id().get_server_message_id().get(), channel_id.get()); - } - - return make_tl_object(channel_id.get(), c->access_hash); -} - -tl_object_ptr ContactsManager::get_simple_input_peer(DialogId dialog_id) const { - CHECK(dialog_id.get_type() == DialogType::Channel); - auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - // if (!have_input_peer_channel(c, channel_id, AccessRights::Read)) { - // return nullptr; - // } - return make_tl_object(channel_id.get(), c->access_hash); -} - -bool ContactsManager::have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights, - bool from_linked) const { - if (c == nullptr) { - LOG(DEBUG) << "Have no " << channel_id; - if (td_->auth_manager_->is_bot() && channel_id.is_valid()) { - return true; - } - if (channel_messages_.count(channel_id) != 0) { - return true; - } - return false; - } - if (access_rights == AccessRights::Know) { - return true; - } - if (c->status.is_administrator()) { - return true; - } - if (c->status.is_banned()) { - LOG(DEBUG) << "Was banned in " << channel_id; - return false; - } - if (c->status.is_member()) { - return true; - } - - bool is_public = is_channel_public(c); - if (access_rights == AccessRights::Read) { - if (is_public) { - return true; - } - if (!from_linked && c->has_linked_channel) { - auto linked_channel_id = get_linked_channel_id(channel_id); - if (linked_channel_id.is_valid() && have_channel(linked_channel_id)) { - if (have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, access_rights, true)) { - return true; - } - } else { - return true; - } - } - if (!from_linked && td_->dialog_invite_link_manager_->have_dialog_access_by_invite_link(DialogId(channel_id))) { - return true; - } - } else { - if (!from_linked && c->is_megagroup && !td_->auth_manager_->is_bot() && c->has_linked_channel) { - auto linked_channel_id = get_linked_channel_id(channel_id); - if (linked_channel_id.is_valid() && (is_public || have_channel(linked_channel_id))) { - return is_public || - have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, AccessRights::Read, true); - } else { - return true; - } - } - } - LOG(DEBUG) << "Have no access to " << channel_id; - return false; -} - -bool ContactsManager::have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const { - return have_input_encrypted_peer(get_secret_chat(secret_chat_id), access_rights); -} - -bool ContactsManager::have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights) { - if (secret_chat == nullptr) { - LOG(DEBUG) << "Have no secret chat"; - return false; - } - if (access_rights == AccessRights::Know) { - return true; - } - if (access_rights == AccessRights::Read) { - return true; - } - return secret_chat->state == SecretChatState::Active; -} - -tl_object_ptr ContactsManager::get_input_encrypted_chat( - SecretChatId secret_chat_id, AccessRights access_rights) const { - auto sc = get_secret_chat(secret_chat_id); - if (!have_input_encrypted_peer(sc, access_rights)) { - return nullptr; - } - - return make_tl_object(secret_chat_id.get(), sc->access_hash); -} - -void ContactsManager::apply_pending_user_photo(User *u, UserId user_id) { - if (u == nullptr || u->is_photo_inited) { - return; - } - - if (pending_user_photos_.count(user_id) > 0) { - do_update_user_photo(u, user_id, std::move(pending_user_photos_[user_id]), "apply_pending_user_photo"); - pending_user_photos_.erase(user_id); - update_user(u, user_id); - } -} - -bool ContactsManager::is_user_received_from_server(UserId user_id) const { - const auto *u = get_user(user_id); - return u != nullptr && u->is_received_from_server; -} - -bool ContactsManager::is_chat_received_from_server(ChatId chat_id) const { - const auto *c = get_chat(chat_id); - return c != nullptr && c->is_received_from_server; -} - -bool ContactsManager::is_channel_received_from_server(ChannelId channel_id) const { - const auto *c = get_channel(channel_id); - return c != nullptr && c->is_received_from_server; -} - -const DialogPhoto *ContactsManager::get_user_dialog_photo(UserId user_id) { - auto u = get_user(user_id); - if (u == nullptr) { - return nullptr; - } - - apply_pending_user_photo(u, user_id); - return &u->photo; -} - -const DialogPhoto *ContactsManager::get_chat_dialog_photo(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return nullptr; - } - return &c->photo; -} - -const DialogPhoto *ContactsManager::get_channel_dialog_photo(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - auto min_channel = get_min_channel(channel_id); - if (min_channel != nullptr) { - return &min_channel->photo_; - } - return nullptr; - } - return &c->photo; -} - -const DialogPhoto *ContactsManager::get_secret_chat_dialog_photo(SecretChatId secret_chat_id) { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return nullptr; - } - return get_user_dialog_photo(c->user_id); -} - -int32 ContactsManager::get_user_accent_color_id_object(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr || !u->accent_color_id.is_valid()) { - return td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)); - } - - return td_->theme_manager_->get_accent_color_id_object(u->accent_color_id, AccentColorId(user_id)); -} - -int32 ContactsManager::get_chat_accent_color_id_object(ChatId chat_id) const { - return td_->theme_manager_->get_accent_color_id_object(AccentColorId(chat_id)); -} - -AccentColorId ContactsManager::get_channel_accent_color_id(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - auto min_channel = get_min_channel(channel_id); - if (min_channel != nullptr && min_channel->accent_color_id_.is_valid()) { - return min_channel->accent_color_id_; - } - return AccentColorId(channel_id); - } - if (!c->accent_color_id.is_valid()) { - return AccentColorId(channel_id); - } - - return c->accent_color_id; -} - -int32 ContactsManager::get_channel_accent_color_id_object(ChannelId channel_id) const { - return td_->theme_manager_->get_accent_color_id_object(get_channel_accent_color_id(channel_id), - AccentColorId(channel_id)); -} - -int32 ContactsManager::get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return 5; - } - return get_user_accent_color_id_object(c->user_id); -} - -CustomEmojiId ContactsManager::get_user_background_custom_emoji_id(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return CustomEmojiId(); - } - - return u->background_custom_emoji_id; -} - -CustomEmojiId ContactsManager::get_chat_background_custom_emoji_id(ChatId chat_id) const { - return CustomEmojiId(); -} - -CustomEmojiId ContactsManager::get_channel_background_custom_emoji_id(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return CustomEmojiId(); - } - - return c->background_custom_emoji_id; -} - -CustomEmojiId ContactsManager::get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return CustomEmojiId(); - } - return get_user_background_custom_emoji_id(c->user_id); -} - -int32 ContactsManager::get_user_profile_accent_color_id_object(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return -1; - } - - return td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id); -} - -int32 ContactsManager::get_chat_profile_accent_color_id_object(ChatId chat_id) const { - return -1; -} - -int32 ContactsManager::get_channel_profile_accent_color_id_object(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return -1; - } - return td_->theme_manager_->get_profile_accent_color_id_object(c->profile_accent_color_id); -} - -int32 ContactsManager::get_secret_chat_profile_accent_color_id_object(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return -1; - } - return get_user_profile_accent_color_id_object(c->user_id); -} - -CustomEmojiId ContactsManager::get_user_profile_background_custom_emoji_id(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return CustomEmojiId(); - } - - return u->profile_background_custom_emoji_id; -} - -CustomEmojiId ContactsManager::get_chat_profile_background_custom_emoji_id(ChatId chat_id) const { - return CustomEmojiId(); -} - -CustomEmojiId ContactsManager::get_channel_profile_background_custom_emoji_id(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return CustomEmojiId(); - } - - return c->profile_background_custom_emoji_id; -} - -CustomEmojiId ContactsManager::get_secret_chat_profile_background_custom_emoji_id(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return CustomEmojiId(); - } - return get_user_profile_background_custom_emoji_id(c->user_id); -} - -string ContactsManager::get_user_title(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return string(); - } - if (u->last_name.empty()) { - return u->first_name; - } - if (u->first_name.empty()) { - return u->last_name; - } - return PSTRING() << u->first_name << ' ' << u->last_name; -} - -string ContactsManager::get_chat_title(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return string(); - } - return c->title; -} - -string ContactsManager::get_channel_title(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - auto min_channel = get_min_channel(channel_id); - if (min_channel != nullptr) { - return min_channel->title_; - } - return string(); - } - return c->title; -} - -string ContactsManager::get_secret_chat_title(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return string(); - } - return get_user_title(c->user_id); -} - -RestrictedRights ContactsManager::get_user_default_permissions(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr || user_id == get_replies_bot_user_id()) { - return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, u != nullptr, false, ChannelType::Unknown); - } - return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, - true, false, ChannelType::Unknown); -} - -RestrictedRights ContactsManager::get_chat_default_permissions(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, ChannelType::Unknown); - } - return c->default_permissions; -} - -RestrictedRights ContactsManager::get_channel_default_permissions(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, ChannelType::Unknown); - } - return c->default_permissions; -} - -RestrictedRights ContactsManager::get_secret_chat_default_permissions(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, ChannelType::Unknown); - } - return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, - false, false, ChannelType::Unknown); -} - -td_api::object_ptr ContactsManager::get_user_emoji_status_object(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return nullptr; - } - return u->last_sent_emoji_status.get_emoji_status_object(); -} - -td_api::object_ptr ContactsManager::get_chat_emoji_status_object(ChatId chat_id) const { - return nullptr; -} - -td_api::object_ptr ContactsManager::get_channel_emoji_status_object(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return nullptr; - } - return c->last_sent_emoji_status.get_emoji_status_object(); -} - -td_api::object_ptr ContactsManager::get_secret_chat_emoji_status_object( - SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return nullptr; - } - return get_user_emoji_status_object(c->user_id); -} - -bool ContactsManager::get_chat_has_protected_content(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return false; - } - return c->noforwards; -} - -bool ContactsManager::get_channel_has_protected_content(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->noforwards; -} - -bool ContactsManager::get_user_stories_hidden(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return false; - } - return u->stories_hidden; -} - -bool ContactsManager::get_channel_stories_hidden(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->stories_hidden; -} - -bool ContactsManager::can_poll_user_active_stories(UserId user_id) const { - const User *u = get_user(user_id); - return need_poll_user_active_stories(u, user_id) && Time::now() >= u->max_active_story_id_next_reload_time; -} - -bool ContactsManager::can_poll_channel_active_stories(ChannelId channel_id) const { - const Channel *c = get_channel(channel_id); - return need_poll_channel_active_stories(c, channel_id) && Time::now() >= c->max_active_story_id_next_reload_time; -} - -string ContactsManager::get_user_private_forward_name(UserId user_id) { - auto user_full = get_user_full_force(user_id, "get_user_private_forward_name"); - if (user_full != nullptr) { - return user_full->private_forward_name; - } - return string(); -} - -bool ContactsManager::get_user_voice_messages_forbidden(UserId user_id) const { - if (!is_user_premium(user_id)) { - return false; - } - auto user_full = get_user_full(user_id); - if (user_full != nullptr) { - return user_full->voice_messages_forbidden; - } - return false; -} - -bool ContactsManager::get_user_read_dates_private(UserId user_id) { - auto user_full = get_user_full_force(user_id, "get_user_read_dates_private"); - if (user_full != nullptr) { - return user_full->read_dates_private; - } - return false; -} - -string ContactsManager::get_user_about(UserId user_id) { - auto user_full = get_user_full_force(user_id, "get_user_about"); - if (user_full != nullptr) { - return user_full->about; - } - return string(); -} - -string ContactsManager::get_chat_about(ChatId chat_id) { - auto chat_full = get_chat_full_force(chat_id, "get_chat_about"); - if (chat_full != nullptr) { - return chat_full->description; - } - return string(); -} - -string ContactsManager::get_channel_about(ChannelId channel_id) { - auto channel_full = get_channel_full_force(channel_id, false, "get_channel_about"); - if (channel_full != nullptr) { - return channel_full->description; - } - return string(); -} - -string ContactsManager::get_secret_chat_about(SecretChatId secret_chat_id) { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return string(); - } - return get_user_about(c->user_id); -} - -string ContactsManager::get_user_search_text(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return string(); - } - return get_user_search_text(u); -} - -string ContactsManager::get_user_search_text(const User *u) { - CHECK(u != nullptr); - return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << implode(u->usernames.get_active_usernames()); -} - -string ContactsManager::get_channel_search_text(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return get_channel_title(channel_id); - } - return PSTRING() << c->title << ' ' << implode(c->usernames.get_active_usernames()); -} - -int32 ContactsManager::get_secret_chat_date(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return 0; - } - return c->date; -} - -int32 ContactsManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return 0; - } - return c->ttl; -} - -string ContactsManager::get_user_first_username(UserId user_id) const { - if (!user_id.is_valid()) { - return string(); - } - - auto u = get_user(user_id); - if (u == nullptr) { - return string(); - } - return u->usernames.get_first_username(); -} - -string ContactsManager::get_channel_first_username(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return string(); - } - return c->usernames.get_first_username(); -} - -string ContactsManager::get_channel_editable_username(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return string(); - } - return c->usernames.get_editable_username(); -} - -UserId ContactsManager::get_secret_chat_user_id(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return UserId(); - } - return c->user_id; -} - -bool ContactsManager::get_secret_chat_is_outbound(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return false; - } - return c->is_outbound; -} - -SecretChatState ContactsManager::get_secret_chat_state(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return SecretChatState::Unknown; - } - return c->state; -} - -int32 ContactsManager::get_secret_chat_layer(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return 0; - } - return c->layer; -} - -FolderId ContactsManager::get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const { - auto c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - return FolderId::main(); - } - return c->initial_folder_id; -} - -bool ContactsManager::can_use_premium_custom_emoji(DialogId dialog_id) const { - if (td_->option_manager_->get_option_boolean("is_premium")) { - return true; - } - if (dialog_id.get_type() == DialogType::Channel) { - auto channel_id = dialog_id.get_channel_id(); - if (!td_->auth_manager_->is_bot() && is_megagroup_channel(channel_id)) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr || channel_full->emoji_sticker_set_id.is_valid()) { - return true; - } - } - } - if (!td_->auth_manager_->is_bot()) { - return false; - } - const User *u = get_user(get_my_id()); - return u == nullptr || u->usernames.get_active_usernames().size() > (u->usernames.has_editable_username() ? 1u : 0u); -} - -UserId ContactsManager::get_my_id() const { - LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my ID returned"; - return my_id_; -} - -void ContactsManager::set_my_id(UserId my_id) { - UserId my_old_id = my_id_; - if (my_old_id.is_valid() && my_old_id != my_id) { - LOG(ERROR) << "Already know that me is " << my_old_id << " but received userSelf with " << my_id; - } - if (!my_id.is_valid()) { - LOG(ERROR) << "Receive invalid my ID " << my_id; - return; - } - if (my_old_id != my_id) { - my_id_ = my_id; - G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); - td_->option_manager_->set_option_integer("my_id", my_id_.get()); - if (!td_->auth_manager_->is_bot()) { - G()->td_db()->get_binlog_pmc()->force_sync(Promise(), "set_my_id"); - } - } -} - -void ContactsManager::set_my_online_status(bool is_online, bool send_update, bool is_local) { - if (td_->auth_manager_->is_bot()) { - return; // just in case - } - - auto my_id = get_my_id(); - User *u = get_user_force(my_id, "set_my_online_status"); - if (u != nullptr) { - int32 new_online; - int32 unix_time = G()->unix_time(); - if (is_online) { - new_online = unix_time + 300; - } else { - new_online = unix_time - 1; - } - - auto old_was_online = get_user_was_online(u, my_id, unix_time); - if (is_local) { - LOG(INFO) << "Update my local online from " << my_was_online_local_ << " to " << new_online; - if (!is_online) { - new_online = min(new_online, u->was_online); - } - if (new_online != my_was_online_local_) { - my_was_online_local_ = new_online; - } - } else { - if (my_was_online_local_ != 0 || new_online != u->was_online) { - LOG(INFO) << "Update my online from " << u->was_online << " to " << new_online; - my_was_online_local_ = 0; - u->was_online = new_online; - u->need_save_to_database = true; - } - } - if (old_was_online != get_user_was_online(u, my_id, unix_time)) { - u->is_status_changed = true; - u->is_online_status_changed = true; - } - - if (was_online_local_ != new_online) { - was_online_local_ = new_online; - VLOG(notifications) << "Set was_online_local to " << was_online_local_; - G()->td_db()->get_binlog_pmc()->set("my_was_online_local", to_string(was_online_local_)); - } - - if (send_update) { - update_user(u, my_id); - } - } -} - -ContactsManager::MyOnlineStatusInfo ContactsManager::get_my_online_status() const { - MyOnlineStatusInfo status_info; - status_info.is_online_local = td_->is_online(); - status_info.is_online_remote = was_online_remote_ > G()->unix_time(); - status_info.was_online_local = was_online_local_; - status_info.was_online_remote = was_online_remote_; - - return status_info; -} - -UserId ContactsManager::get_service_notifications_user_id() { - return UserId(static_cast(777000)); -} - -UserId ContactsManager::add_service_notifications_user() { - auto user_id = get_service_notifications_user_id(); - if (!have_user_force(user_id, "add_service_notifications_user")) { - LOG(FATAL) << "Failed to load service notification user"; - } - return user_id; -} - -UserId ContactsManager::get_replies_bot_user_id() { - return UserId(static_cast(G()->is_test_dc() ? 708513 : 1271266957)); -} - -UserId ContactsManager::get_anonymous_bot_user_id() { - return UserId(static_cast(G()->is_test_dc() ? 552888 : 1087968824)); -} - -UserId ContactsManager::get_channel_bot_user_id() { - return UserId(static_cast(G()->is_test_dc() ? 936174 : 136817688)); -} - -UserId ContactsManager::get_anti_spam_bot_user_id() { - return UserId(static_cast(G()->is_test_dc() ? 2200583762ll : 5434988373ll)); -} - -UserId ContactsManager::add_anonymous_bot_user() { - auto user_id = get_anonymous_bot_user_id(); - if (!have_user_force(user_id, "add_anonymous_bot_user")) { - LOG(FATAL) << "Failed to load anonymous bot user"; - } - return user_id; -} - -UserId ContactsManager::add_channel_bot_user() { - auto user_id = get_channel_bot_user_id(); - if (!have_user_force(user_id, "add_channel_bot_user")) { - LOG(FATAL) << "Failed to load channel bot user"; - } - return user_id; -} - -ChannelId ContactsManager::get_unsupported_channel_id() { - return ChannelId(static_cast(G()->is_test_dc() ? 10304875 : 1535424647)); -} - -int32 ContactsManager::get_user_was_online(const User *u, UserId user_id, int32 unix_time) const { - if (u == nullptr || u->is_deleted) { - return 0; - } - - int32 was_online = u->was_online; - if (user_id == get_my_id()) { - if (my_was_online_local_ != 0) { - was_online = my_was_online_local_; - } - } else { - if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > unix_time) { - was_online = u->local_was_online; - } - } - return was_online; -} - -void ContactsManager::can_send_message_to_user( - UserId user_id, bool force, Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - if (user_id == get_my_id()) { - return promise.set_value(td_api::make_object()); - } - const auto *u = get_user(user_id); - if (!have_input_peer_user(u, user_id, AccessRights::Write)) { - return promise.set_value(td_api::make_object()); - } - CHECK(user_id.is_valid()); - if ((u != nullptr && (!u->contact_require_premium || u->is_mutual_contact)) || - td_->option_manager_->get_option_boolean("is_premium")) { - return promise.set_value(td_api::make_object()); - } - - auto user_full = get_user_full_force(user_id, "can_send_message_to_user"); - if (user_full != nullptr) { - if (!user_full->contact_require_premium) { - return promise.set_value(td_api::make_object()); - } - return promise.set_value(td_api::make_object()); - } - - auto it = user_full_contact_require_premium_.find(user_id); - if (it != user_full_contact_require_premium_.end()) { - if (!it->second) { - return promise.set_value(td_api::make_object()); - } - return promise.set_value(td_api::make_object()); - } - - if (force) { - return promise.set_value(td_api::make_object()); - } - - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - return promise.set_error(result.move_as_error()); - } - send_closure(actor_id, &ContactsManager::can_send_message_to_user, user_id, true, std::move(promise)); - }); - get_is_premium_required_to_contact_queries_.add_query(user_id.get(), std::move(query_promise), - "can_send_message_to_user"); -} - -void ContactsManager::on_get_is_premium_required_to_contact_users(vector &&user_ids, - vector &&is_premium_required, - Promise &&promise) { - if (user_ids.size() != is_premium_required.size()) { - LOG(ERROR) << "Receive " << is_premium_required.size() << " flags instead of " << user_ids.size(); - return promise.set_error(Status::Error(500, "Receive invalid response")); - } - for (size_t i = 0; i < user_ids.size(); i++) { - auto user_id = user_ids[i]; - CHECK(user_id.is_valid()); - if (get_user_full(user_id) == nullptr) { - user_full_contact_require_premium_[user_id] = is_premium_required[i]; - } - } - promise.set_value(Unit()); -} - -void ContactsManager::allow_send_message_to_user(UserId user_id) { - if (get_user_full(user_id) == nullptr) { - CHECK(user_id.is_valid()); - user_full_contact_require_premium_[user_id] = true; - } -} - -void ContactsManager::load_contacts(Promise &&promise) { - if (td_->auth_manager_->is_bot()) { - are_contacts_loaded_ = true; - saved_contact_count_ = 0; - } - if (are_contacts_loaded_ && saved_contact_count_ != -1) { - LOG(INFO) << "Contacts are already loaded"; - promise.set_value(Unit()); - return; - } - load_contacts_queries_.push_back(std::move(promise)); - if (load_contacts_queries_.size() == 1u) { - if (G()->use_chat_info_database() && next_contacts_sync_date_ > 0 && saved_contact_count_ != -1) { - LOG(INFO) << "Load contacts from database"; - G()->td_db()->get_sqlite_pmc()->get( - "user_contacts", PromiseCreator::lambda([](string value) { - send_closure(G()->contacts_manager(), &ContactsManager::on_load_contacts_from_database, std::move(value)); - })); - } else { - LOG(INFO) << "Load contacts from server"; - reload_contacts(true); - } - } else { - LOG(INFO) << "Load contacts request has already been sent"; - } -} - -int64 ContactsManager::get_contacts_hash() { - if (!are_contacts_loaded_) { - return 0; - } - - vector user_ids = contacts_hints_.search_empty(100000).second; - CHECK(std::is_sorted(user_ids.begin(), user_ids.end())); - auto my_id = get_my_id(); - const User *u = get_user_force(my_id, "get_contacts_hash"); - if (u != nullptr && u->is_contact) { - user_ids.insert(std::upper_bound(user_ids.begin(), user_ids.end(), my_id.get()), my_id.get()); - } - - vector numbers; - numbers.reserve(user_ids.size() + 1); - numbers.push_back(saved_contact_count_); - for (auto user_id : user_ids) { - numbers.push_back(user_id); - } - return get_vector_hash(numbers); -} - -void ContactsManager::reload_contacts(bool force) { - if (!G()->close_flag() && !td_->auth_manager_->is_bot() && - next_contacts_sync_date_ != std::numeric_limits::max() && - (next_contacts_sync_date_ < G()->unix_time() || force)) { - next_contacts_sync_date_ = std::numeric_limits::max(); - td_->create_handler()->send(get_contacts_hash()); - } -} - -void ContactsManager::add_contact(Contact contact, bool share_phone_number, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (!are_contacts_loaded_) { - load_contacts(PromiseCreator::lambda([actor_id = actor_id(this), contact = std::move(contact), share_phone_number, - promise = std::move(promise)](Result &&) mutable { - send_closure(actor_id, &ContactsManager::add_contact, std::move(contact), share_phone_number, std::move(promise)); - })); - return; - } - - LOG(INFO) << "Add " << contact << " with share_phone_number = " << share_phone_number; - - auto user_id = contact.get_user_id(); - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - - td_->create_handler(std::move(promise)) - ->send(user_id, std::move(input_user), contact, share_phone_number); -} - -std::pair, vector> ContactsManager::import_contacts(const vector &contacts, - int64 &random_id, Promise &&promise) { - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return {}; - } - - LOG(INFO) << "Asked to import " << contacts.size() << " contacts with random_id = " << random_id; - if (random_id != 0) { - // request has already been sent before - auto it = imported_contacts_.find(random_id); - CHECK(it != imported_contacts_.end()); - auto result = std::move(it->second); - imported_contacts_.erase(it); - - promise.set_value(Unit()); - return result; - } - - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || random_id == 1 || imported_contacts_.count(random_id) > 0); - imported_contacts_[random_id]; // reserve place for result - - do_import_contacts(contacts, random_id, std::move(promise)); - return {}; -} - -void ContactsManager::do_import_contacts(vector contacts, int64 random_id, Promise &&promise) { - size_t size = contacts.size(); - if (size == 0) { - on_import_contacts_finished(random_id, {}, {}); - return promise.set_value(Unit()); - } - - vector> input_phone_contacts; - input_phone_contacts.reserve(size); - for (size_t i = 0; i < size; i++) { - input_phone_contacts.push_back(contacts[i].get_input_phone_contact(static_cast(i))); - } - - auto task = make_unique(); - task->promise_ = std::move(promise); - task->input_contacts_ = std::move(contacts); - task->imported_user_ids_.resize(size); - task->unimported_contact_invites_.resize(size); - - bool is_added = import_contact_tasks_.emplace(random_id, std::move(task)).second; - CHECK(is_added); - - td_->create_handler()->send(std::move(input_phone_contacts), random_id); -} - -void ContactsManager::on_imported_contacts(int64 random_id, - Result> result) { - auto it = import_contact_tasks_.find(random_id); - CHECK(it != import_contact_tasks_.end()); - CHECK(it->second != nullptr); - - auto task = it->second.get(); - if (result.is_error()) { - auto promise = std::move(task->promise_); - import_contact_tasks_.erase(it); - return promise.set_error(result.move_as_error()); - } - - auto imported_contacts = result.move_as_ok(); - on_get_users(std::move(imported_contacts->users_), "on_imported_contacts"); - - for (auto &imported_contact : imported_contacts->imported_) { - int64 client_id = imported_contact->client_id_; - if (client_id < 0 || client_id >= static_cast(task->imported_user_ids_.size())) { - LOG(ERROR) << "Wrong client_id " << client_id << " returned"; - continue; - } - - task->imported_user_ids_[static_cast(client_id)] = UserId(imported_contact->user_id_); - } - for (auto &popular_contact : imported_contacts->popular_invites_) { - int64 client_id = popular_contact->client_id_; - if (client_id < 0 || client_id >= static_cast(task->unimported_contact_invites_.size())) { - LOG(ERROR) << "Wrong client_id " << client_id << " returned"; - continue; - } - if (popular_contact->importers_ < 0) { - LOG(ERROR) << "Wrong number of importers " << popular_contact->importers_ << " returned"; - continue; - } - - task->unimported_contact_invites_[static_cast(client_id)] = popular_contact->importers_; - } - - if (!imported_contacts->retry_contacts_.empty()) { - auto total_size = static_cast(task->input_contacts_.size()); - vector> input_phone_contacts; - input_phone_contacts.reserve(imported_contacts->retry_contacts_.size()); - for (auto &client_id : imported_contacts->retry_contacts_) { - if (client_id < 0 || client_id >= total_size) { - LOG(ERROR) << "Wrong client_id " << client_id << " returned"; - continue; - } - auto i = static_cast(client_id); - input_phone_contacts.push_back(task->input_contacts_[i].get_input_phone_contact(client_id)); - } - td_->create_handler()->send(std::move(input_phone_contacts), random_id); - return; - } - - auto promise = std::move(task->promise_); - on_import_contacts_finished(random_id, std::move(task->imported_user_ids_), - std::move(task->unimported_contact_invites_)); - import_contact_tasks_.erase(it); - promise.set_value(Unit()); -} - -void ContactsManager::remove_contacts(const vector &user_ids, Promise &&promise) { - LOG(INFO) << "Delete contacts: " << format::as_array(user_ids); - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return; - } - - vector to_delete_user_ids; - vector> input_users; - for (auto &user_id : user_ids) { - const User *u = get_user(user_id); - if (u != nullptr && u->is_contact) { - auto r_input_user = get_input_user(user_id); - if (r_input_user.is_ok()) { - to_delete_user_ids.push_back(user_id); - input_users.push_back(r_input_user.move_as_ok()); - } - } - } - - if (input_users.empty()) { - return promise.set_value(Unit()); - } - - td_->create_handler(std::move(promise))->send(std::move(input_users)); -} - -void ContactsManager::remove_contacts_by_phone_number(vector user_phone_numbers, vector user_ids, - Promise &&promise) { - LOG(INFO) << "Delete contacts by phone number: " << format::as_array(user_phone_numbers); - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return; - } - - td_->create_handler(std::move(promise)) - ->send(std::move(user_phone_numbers), std::move(user_ids)); -} - -int32 ContactsManager::get_imported_contact_count(Promise &&promise) { - LOG(INFO) << "Get imported contact count"; - - if (!are_contacts_loaded_ || saved_contact_count_ == -1) { - load_contacts(std::move(promise)); - return 0; - } - reload_contacts(false); - - promise.set_value(Unit()); - return saved_contact_count_; -} - -void ContactsManager::load_imported_contacts(Promise &&promise) { - if (td_->auth_manager_->is_bot()) { - are_imported_contacts_loaded_ = true; - } - if (are_imported_contacts_loaded_) { - LOG(INFO) << "Imported contacts are already loaded"; - promise.set_value(Unit()); - return; - } - load_imported_contacts_queries_.push_back(std::move(promise)); - if (load_imported_contacts_queries_.size() == 1u) { - if (G()->use_chat_info_database()) { - LOG(INFO) << "Load imported contacts from database"; - G()->td_db()->get_sqlite_pmc()->get( - "user_imported_contacts", PromiseCreator::lambda([](string value) { - send_closure_later(G()->contacts_manager(), &ContactsManager::on_load_imported_contacts_from_database, - std::move(value)); - })); - } else { - LOG(INFO) << "Have no previously imported contacts"; - send_closure_later(G()->contacts_manager(), &ContactsManager::on_load_imported_contacts_from_database, string()); - } - } else { - LOG(INFO) << "Load imported contacts request has already been sent"; - } -} - -void ContactsManager::on_load_imported_contacts_from_database(string value) { - if (G()->close_flag()) { - return; - } - - CHECK(!are_imported_contacts_loaded_); - if (need_clear_imported_contacts_) { - need_clear_imported_contacts_ = false; - value.clear(); - } - if (value.empty()) { - CHECK(all_imported_contacts_.empty()); - } else { - if (log_event_parse(all_imported_contacts_, value).is_error()) { - LOG(ERROR) << "Failed to load all imported contacts from database"; - all_imported_contacts_.clear(); - } else { - LOG(INFO) << "Successfully loaded " << all_imported_contacts_.size() << " imported contacts from database"; - } - } - - load_imported_contact_users_multipromise_.add_promise( - PromiseCreator::lambda([actor_id = actor_id(this)](Result result) { - if (result.is_ok()) { - send_closure_later(actor_id, &ContactsManager::on_load_imported_contacts_finished); - } - })); - - auto lock_promise = load_imported_contact_users_multipromise_.get_promise(); - - for (const auto &contact : all_imported_contacts_) { - auto user_id = contact.get_user_id(); - if (user_id.is_valid()) { - get_user(user_id, 3, load_imported_contact_users_multipromise_.get_promise()); - } - } - - lock_promise.set_value(Unit()); -} - -void ContactsManager::on_load_imported_contacts_finished() { - LOG(INFO) << "Finished to load " << all_imported_contacts_.size() << " imported contacts"; - - for (const auto &contact : all_imported_contacts_) { - get_user_id_object(contact.get_user_id(), "on_load_imported_contacts_finished"); // to ensure updateUser - } - - if (need_clear_imported_contacts_) { - need_clear_imported_contacts_ = false; - all_imported_contacts_.clear(); - } - are_imported_contacts_loaded_ = true; - set_promises(load_imported_contacts_queries_); -} - -std::pair, vector> ContactsManager::change_imported_contacts(vector &contacts, - int64 &random_id, - Promise &&promise) { - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return {}; - } - if (!are_imported_contacts_loaded_) { - load_imported_contacts(std::move(promise)); - return {}; - } - - LOG(INFO) << "Asked to change imported contacts to a list of " << contacts.size() - << " contacts with random_id = " << random_id; - if (random_id != 0) { - // request has already been sent before - if (need_clear_imported_contacts_) { - need_clear_imported_contacts_ = false; - all_imported_contacts_.clear(); - if (G()->use_chat_info_database()) { - G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); - } - reload_contacts(true); - } - - CHECK(are_imported_contacts_changing_); - are_imported_contacts_changing_ = false; - - auto unimported_contact_invites = std::move(unimported_contact_invites_); - unimported_contact_invites_.clear(); - - auto imported_contact_user_ids = std::move(imported_contact_user_ids_); - imported_contact_user_ids_.clear(); - - promise.set_value(Unit()); - return {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; - } - - if (are_imported_contacts_changing_) { - promise.set_error(Status::Error(400, "ChangeImportedContacts can be called only once at the same time")); - return {}; - } - - vector new_contacts_unique_id(contacts.size()); - vector unique_new_contacts; - unique_new_contacts.reserve(contacts.size()); - std::unordered_map different_new_contacts; - std::unordered_set> different_new_phone_numbers; - size_t unique_size = 0; - for (size_t i = 0; i < contacts.size(); i++) { - auto it_success = different_new_contacts.emplace(std::move(contacts[i]), unique_size); - new_contacts_unique_id[i] = it_success.first->second; - if (it_success.second) { - unique_new_contacts.push_back(it_success.first->first); - different_new_phone_numbers.insert(unique_new_contacts.back().get_phone_number()); - unique_size++; - } - } - - vector to_delete; - vector to_delete_user_ids; - for (auto &old_contact : all_imported_contacts_) { - auto user_id = old_contact.get_user_id(); - auto it = different_new_contacts.find(old_contact); - if (it == different_new_contacts.end()) { - auto phone_number = old_contact.get_phone_number(); - if (different_new_phone_numbers.count(phone_number) == 0) { - to_delete.push_back(std::move(phone_number)); - if (user_id.is_valid()) { - to_delete_user_ids.push_back(user_id); - } - } - } else { - unique_new_contacts[it->second].set_user_id(user_id); - different_new_contacts.erase(it); - } - } - std::pair, vector> to_add; - for (auto &new_contact : different_new_contacts) { - to_add.first.push_back(new_contact.second); - to_add.second.push_back(new_contact.first); - } - - if (to_add.first.empty() && to_delete.empty()) { - for (size_t i = 0; i < contacts.size(); i++) { - auto unique_id = new_contacts_unique_id[i]; - contacts[i].set_user_id(unique_new_contacts[unique_id].get_user_id()); - } - - promise.set_value(Unit()); - return {transform(contacts, [&](const Contact &contact) { return contact.get_user_id(); }), - vector(contacts.size())}; - } - - are_imported_contacts_changing_ = true; - random_id = 1; - - remove_contacts_by_phone_number( - std::move(to_delete), std::move(to_delete_user_ids), - PromiseCreator::lambda([new_contacts = std::move(unique_new_contacts), - new_contacts_unique_id = std::move(new_contacts_unique_id), to_add = std::move(to_add), - promise = std::move(promise)](Result<> result) mutable { - if (result.is_ok()) { - send_closure_later(G()->contacts_manager(), &ContactsManager::on_clear_imported_contacts, - std::move(new_contacts), std::move(new_contacts_unique_id), std::move(to_add), - std::move(promise)); - } else { - promise.set_error(result.move_as_error()); - } - })); - return {}; -} - -void ContactsManager::on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, - std::pair, vector> &&to_add, - Promise &&promise) { - LOG(INFO) << "Add " << to_add.first.size() << " contacts"; - next_all_imported_contacts_ = std::move(contacts); - imported_contacts_unique_id_ = std::move(contacts_unique_id); - imported_contacts_pos_ = std::move(to_add.first); - - do_import_contacts(std::move(to_add.second), 1, std::move(promise)); -} - -void ContactsManager::clear_imported_contacts(Promise &&promise) { - LOG(INFO) << "Delete imported contacts"; - - if (saved_contact_count_ == 0) { - promise.set_value(Unit()); - return; - } - - td_->create_handler(std::move(promise))->send(); -} - -void ContactsManager::on_update_contacts_reset() { - /* - UserId my_id = get_my_id(); - users_.foreach([&](const UserId &user_id, unique_ptr &user) { - User *u = user.get(); - if (u->is_contact) { - LOG(INFO) << "Drop contact with " << user_id; - if (user_id != my_id) { - CHECK(contacts_hints_.has_key(user_id.get())); - } - on_update_user_is_contact(u, user_id, false, false, false); - CHECK(u->is_is_contact_changed); - u->cache_version = 0; - u->is_repaired = false; - update_user(u, user_id); - CHECK(!u->is_contact); - if (user_id != my_id) { - CHECK(!contacts_hints_.has_key(user_id.get())); - } - } - }); - */ - - saved_contact_count_ = 0; - if (G()->use_chat_info_database()) { - G()->td_db()->get_binlog_pmc()->set("saved_contact_count", "0"); - G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); - } - if (!are_imported_contacts_loaded_) { - if (load_imported_contacts_queries_.empty()) { - CHECK(all_imported_contacts_.empty()); - LOG(INFO) << "Imported contacts was never loaded, just clear them"; - } else { - LOG(INFO) << "Imported contacts are being loaded, clear them after they will be loaded"; - need_clear_imported_contacts_ = true; - } - } else { - if (!are_imported_contacts_changing_) { - LOG(INFO) << "Imported contacts was loaded, but aren't changing now, just clear them"; - all_imported_contacts_.clear(); - } else { - LOG(INFO) << "Imported contacts are changing now, clear them after they will be changed"; - need_clear_imported_contacts_ = true; - } - } - reload_contacts(true); -} - -std::pair> ContactsManager::search_contacts(const string &query, int32 limit, - Promise &&promise) { - LOG(INFO) << "Search contacts with query = \"" << query << "\" and limit = " << limit; - - if (limit < 0) { - promise.set_error(Status::Error(400, "Limit must be non-negative")); - return {}; - } - - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return {}; - } - reload_contacts(false); - - std::pair> result; - if (query.empty()) { - result = contacts_hints_.search_empty(limit); - } else { - result = contacts_hints_.search(query, limit); - } - - vector user_ids; - user_ids.reserve(result.second.size()); - for (auto key : result.second) { - user_ids.emplace_back(key); - } - - promise.set_value(Unit()); - return {narrow_cast(result.first), std::move(user_ids)}; -} - -vector ContactsManager::get_close_friends(Promise &&promise) { - if (!are_contacts_loaded_) { - load_contacts(std::move(promise)); - return {}; - } - reload_contacts(false); - - auto result = contacts_hints_.search_empty(10000); - - vector user_ids; - for (auto key : result.second) { - UserId user_id(key); - const User *u = get_user(user_id); - if (u != nullptr && u->is_close_friend) { - user_ids.push_back(user_id); - } - } - - promise.set_value(Unit()); - return user_ids; -} - -void ContactsManager::set_close_friends(vector user_ids, Promise &&promise) { - for (auto &user_id : user_ids) { - if (!have_user(user_id)) { - return promise.set_error(Status::Error(400, "User not found")); - } - } - - td_->create_handler(std::move(promise))->send(std::move(user_ids)); -} - -void ContactsManager::on_set_close_friends(const vector &user_ids, Promise &&promise) { - FlatHashSet close_friend_user_ids; - for (auto &user_id : user_ids) { - CHECK(user_id.is_valid()); - close_friend_user_ids.insert(user_id); - } - users_.foreach([&](const UserId &user_id, unique_ptr &user) { - User *u = user.get(); - if (u->is_contact && u->is_close_friend != (close_friend_user_ids.count(user_id) > 0)) { - on_update_user_is_contact(u, user_id, u->is_contact, u->is_mutual_contact, !u->is_close_friend); - update_user(u, user_id); - } - }); - promise.set_value(Unit()); -} - -UserId ContactsManager::search_user_by_phone_number(string phone_number, Promise &&promise) { - clean_phone_number(phone_number); - if (phone_number.empty()) { - promise.set_error(Status::Error(200, "Phone number is invalid")); - return UserId(); - } - - auto it = resolved_phone_numbers_.find(phone_number); - if (it != resolved_phone_numbers_.end()) { - promise.set_value(Unit()); - return it->second; - } - - td_->create_handler(std::move(promise))->send(phone_number); - return UserId(); -} - -void ContactsManager::on_resolved_phone_number(const string &phone_number, UserId user_id) { - if (!user_id.is_valid()) { - resolved_phone_numbers_.emplace(phone_number, UserId()); // negative cache - return; - } - - auto it = resolved_phone_numbers_.find(phone_number); - if (it != resolved_phone_numbers_.end()) { - if (it->second != user_id) { - LOG(WARNING) << "Resolve phone number \"" << phone_number << "\" to " << user_id << ", but have it in " - << it->second; - it->second = user_id; - } - return; - } - - auto *u = get_user(user_id); - if (u == nullptr) { - LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to unknown " << user_id; - } else if (!u->phone_number.empty()) { - LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to " << user_id << " with phone number " - << u->phone_number; - } else { - // the user's phone number can be hidden by privacy settings, despite the user can be found by the phone number - } - resolved_phone_numbers_[phone_number] = user_id; // always update cached value -} - -void ContactsManager::share_phone_number(UserId user_id, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (!are_contacts_loaded_) { - load_contacts(PromiseCreator::lambda( - [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&) mutable { - send_closure(actor_id, &ContactsManager::share_phone_number, user_id, std::move(promise)); - })); - return; - } - - LOG(INFO) << "Share phone number with " << user_id; - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - - td_->messages_manager_->hide_dialog_action_bar(DialogId(user_id)); - - td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); -} - -void ContactsManager::search_dialogs_nearby(const Location &location, - Promise> &&promise) { - if (location.empty()) { - return promise.set_error(Status::Error(400, "Invalid location specified")); - } - last_user_location_ = location; - try_send_set_location_visibility_query(); - - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( - Result> result) mutable { - send_closure(actor_id, &ContactsManager::on_get_dialogs_nearby, std::move(result), std::move(promise)); - }); - td_->create_handler(std::move(query_promise))->send(location, false, -1); -} - -vector> ContactsManager::get_chats_nearby_object( - const vector &dialogs_nearby) const { - return transform(dialogs_nearby, [td = td_](const DialogNearby &dialog_nearby) { - return td_api::make_object( - td->dialog_manager_->get_chat_id_object(dialog_nearby.dialog_id, "chatNearby"), dialog_nearby.distance); - }); -} - -void ContactsManager::send_update_users_nearby() const { - send_closure(G()->td(), &Td::send_update, - td_api::make_object(get_chats_nearby_object(users_nearby_))); -} - -void ContactsManager::on_get_dialogs_nearby(Result> result, - Promise> &&promise) { - if (result.is_error()) { - return promise.set_error(result.move_as_error()); - } - - auto updates_ptr = result.move_as_ok(); - if (updates_ptr->get_id() != telegram_api::updates::ID) { - LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates"; - return promise.set_error(Status::Error(500, "Receive unsupported response from the server")); - } - - auto update = telegram_api::move_object_as(updates_ptr); - LOG(INFO) << "Receive chats nearby in " << to_string(update); - - on_get_users(std::move(update->users_), "on_get_dialogs_nearby"); - on_get_chats(std::move(update->chats_), "on_get_dialogs_nearby"); - - for (auto &dialog_nearby : users_nearby_) { - user_nearby_timeout_.cancel_timeout(dialog_nearby.dialog_id.get_user_id().get()); - } - auto old_users_nearby = std::move(users_nearby_); - users_nearby_.clear(); - channels_nearby_.clear(); - int32 location_visibility_expire_date = 0; - for (auto &update_ptr : update->updates_) { - if (update_ptr->get_id() != telegram_api::updatePeerLocated::ID) { - LOG(ERROR) << "Receive unexpected " << to_string(update); - continue; - } - - auto expire_date = on_update_peer_located( - std::move(static_cast(update_ptr.get())->peers_), false); - if (expire_date != -1) { - location_visibility_expire_date = expire_date; - } - } - if (location_visibility_expire_date != location_visibility_expire_date_) { - set_location_visibility_expire_date(location_visibility_expire_date); - update_is_location_visible(); - } - - std::sort(users_nearby_.begin(), users_nearby_.end()); - if (old_users_nearby != users_nearby_) { - send_update_users_nearby(); // for other clients connected to the same TDLib instance - } - promise.set_value(td_api::make_object(get_chats_nearby_object(users_nearby_), - get_chats_nearby_object(channels_nearby_))); -} - -void ContactsManager::set_location(const Location &location, Promise &&promise) { - if (location.empty()) { - return promise.set_error(Status::Error(400, "Invalid location specified")); - } - last_user_location_ = location; - try_send_set_location_visibility_query(); - - auto query_promise = PromiseCreator::lambda( - [promise = std::move(promise)](Result> result) mutable { - promise.set_value(Unit()); - }); - td_->create_handler(std::move(query_promise))->send(location, true, -1); -} - -void ContactsManager::set_location_visibility(Td *td) { - bool is_location_visible = td->option_manager_->get_option_boolean("is_location_visible"); - auto pending_location_visibility_expire_date = is_location_visible ? std::numeric_limits::max() : 0; - if (td->contacts_manager_ == nullptr) { - G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date", - to_string(pending_location_visibility_expire_date)); - return; - } - if (td->contacts_manager_->pending_location_visibility_expire_date_ == -1 && - pending_location_visibility_expire_date == td->contacts_manager_->location_visibility_expire_date_) { - return; - } - if (td->contacts_manager_->pending_location_visibility_expire_date_ != pending_location_visibility_expire_date) { - td->contacts_manager_->pending_location_visibility_expire_date_ = pending_location_visibility_expire_date; - G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date", - to_string(pending_location_visibility_expire_date)); - } - td->contacts_manager_->try_send_set_location_visibility_query(); -} - -void ContactsManager::try_send_set_location_visibility_query() { - if (G()->close_flag()) { - return; - } - if (pending_location_visibility_expire_date_ == -1) { - return; - } - - LOG(INFO) << "Trying to send set location visibility query"; - if (is_set_location_visibility_request_sent_) { - return; - } - if (pending_location_visibility_expire_date_ != 0 && last_user_location_.empty()) { - return; - } - - is_set_location_visibility_request_sent_ = true; - auto query_promise = - PromiseCreator::lambda([actor_id = actor_id(this), set_expire_date = pending_location_visibility_expire_date_]( - Result> result) { - send_closure(actor_id, &ContactsManager::on_set_location_visibility_expire_date, set_expire_date, - result.is_ok() ? 0 : result.error().code()); - }); - td_->create_handler(std::move(query_promise)) - ->send(last_user_location_, true, pending_location_visibility_expire_date_); -} - -void ContactsManager::on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code) { - bool success = error_code == 0; - is_set_location_visibility_request_sent_ = false; - - if (set_expire_date != pending_location_visibility_expire_date_) { - try_send_set_location_visibility_query(); - return; - } - - if (success) { - set_location_visibility_expire_date(pending_location_visibility_expire_date_); - } else { - if (G()->close_flag()) { - // request will be re-sent after restart - return; - } - if (error_code != 406) { - LOG(ERROR) << "Failed to set location visibility expire date to " << pending_location_visibility_expire_date_; - } - } - G()->td_db()->get_binlog_pmc()->erase("pending_location_visibility_expire_date"); - pending_location_visibility_expire_date_ = -1; - update_is_location_visible(); -} - -void ContactsManager::get_is_location_visible(Promise &&promise) { - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( - Result> result) mutable { - send_closure(actor_id, &ContactsManager::on_get_is_location_visible, std::move(result), std::move(promise)); - }); - td_->create_handler(std::move(query_promise))->send(Location(), true, -1); -} - -void ContactsManager::on_get_is_location_visible(Result> &&result, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - if (result.is_error()) { - if (result.error().message() == "GEO_POINT_INVALID" && pending_location_visibility_expire_date_ == -1 && - location_visibility_expire_date_ > 0) { - set_location_visibility_expire_date(0); - update_is_location_visible(); - } - return promise.set_value(Unit()); - } - - auto updates_ptr = result.move_as_ok(); - if (updates_ptr->get_id() != telegram_api::updates::ID) { - LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates"; - return promise.set_value(Unit()); - } - - auto updates = std::move(telegram_api::move_object_as(updates_ptr)->updates_); - if (updates.size() != 1 || updates[0]->get_id() != telegram_api::updatePeerLocated::ID) { - LOG(ERROR) << "Receive unexpected " << to_string(updates); - return promise.set_value(Unit()); - } - - auto peers = std::move(static_cast(updates[0].get())->peers_); - if (peers.size() != 1 || peers[0]->get_id() != telegram_api::peerSelfLocated::ID) { - LOG(ERROR) << "Receive unexpected " << to_string(peers); - return promise.set_value(Unit()); - } - - auto location_visibility_expire_date = static_cast(peers[0].get())->expires_; - if (location_visibility_expire_date != location_visibility_expire_date_) { - set_location_visibility_expire_date(location_visibility_expire_date); - update_is_location_visible(); - } - - promise.set_value(Unit()); -} - -int32 ContactsManager::on_update_peer_located(vector> &&peers, - bool from_update) { - auto now = G()->unix_time(); - bool need_update = false; - int32 location_visibility_expire_date = -1; - for (auto &peer_located_ptr : peers) { - if (peer_located_ptr->get_id() == telegram_api::peerSelfLocated::ID) { - auto peer_self_located = telegram_api::move_object_as(peer_located_ptr); - if (peer_self_located->expires_ == 0 || peer_self_located->expires_ > G()->unix_time()) { - location_visibility_expire_date = peer_self_located->expires_; - } - continue; - } - - CHECK(peer_located_ptr->get_id() == telegram_api::peerLocated::ID); - auto peer_located = telegram_api::move_object_as(peer_located_ptr); - DialogId dialog_id(peer_located->peer_); - int32 expires_at = peer_located->expires_; - int32 distance = peer_located->distance_; - if (distance < 0 || distance > 50000000) { - LOG(ERROR) << "Receive wrong distance to " << to_string(peer_located); - continue; - } - if (expires_at <= now) { - LOG(INFO) << "Skip expired result " << to_string(peer_located); - continue; - } - - auto dialog_type = dialog_id.get_type(); - if (dialog_type == DialogType::User) { - auto user_id = dialog_id.get_user_id(); - if (!have_user(user_id)) { - LOG(ERROR) << "Can't find " << user_id; - continue; - } - if (expires_at < now + 86400) { - user_nearby_timeout_.set_timeout_in(user_id.get(), expires_at - now + 1); - } - } else if (dialog_type == DialogType::Channel) { - auto channel_id = dialog_id.get_channel_id(); - if (!have_channel(channel_id)) { - LOG(ERROR) << "Can't find " << channel_id; - continue; - } - if (expires_at != std::numeric_limits::max()) { - LOG(ERROR) << "Receive expiring at " << expires_at << " group location in " << to_string(peer_located); - } - if (from_update) { - LOG(ERROR) << "Receive nearby " << channel_id << " from update"; - continue; - } - } else { - LOG(ERROR) << "Receive chat of wrong type in " << to_string(peer_located); - continue; - } - - td_->dialog_manager_->force_create_dialog(dialog_id, "on_update_peer_located"); - - if (from_update) { - CHECK(dialog_type == DialogType::User); - bool is_found = false; - for (auto &dialog_nearby : users_nearby_) { - if (dialog_nearby.dialog_id == dialog_id) { - if (dialog_nearby.distance != distance) { - dialog_nearby.distance = distance; - need_update = true; - } - is_found = true; - break; - } - } - if (!is_found) { - users_nearby_.emplace_back(dialog_id, distance); - all_users_nearby_.insert(dialog_id.get_user_id()); - need_update = true; - } - } else { - if (dialog_type == DialogType::User) { - users_nearby_.emplace_back(dialog_id, distance); - all_users_nearby_.insert(dialog_id.get_user_id()); - } else { - channels_nearby_.emplace_back(dialog_id, distance); - } - } - } - if (need_update) { - std::sort(users_nearby_.begin(), users_nearby_.end()); - send_update_users_nearby(); - } - return location_visibility_expire_date; -} - -void ContactsManager::set_location_visibility_expire_date(int32 expire_date) { - if (location_visibility_expire_date_ == expire_date) { - return; - } - - LOG(INFO) << "Set set_location_visibility_expire_date to " << expire_date; - location_visibility_expire_date_ = expire_date; - if (expire_date == 0) { - G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date"); - } else { - G()->td_db()->get_binlog_pmc()->set("location_visibility_expire_date", to_string(expire_date)); - } - // the caller must call update_is_location_visible() itself -} - -void ContactsManager::update_is_location_visible() { - auto expire_date = pending_location_visibility_expire_date_ != -1 ? pending_location_visibility_expire_date_ - : location_visibility_expire_date_; - td_->option_manager_->set_option_boolean("is_location_visible", expire_date != 0); -} - -void ContactsManager::on_update_bot_commands(DialogId dialog_id, UserId bot_user_id, - vector> &&bot_commands) { - if (!bot_user_id.is_valid()) { - LOG(ERROR) << "Receive updateBotCOmmands about invalid " << bot_user_id; - return; - } - if (!have_user(bot_user_id) || !is_user_bot(bot_user_id)) { - return; - } - if (td_->auth_manager_->is_bot()) { - return; - } - - switch (dialog_id.get_type()) { - case DialogType::User: { - UserId user_id(dialog_id.get_user_id()); - auto user_full = get_user_full(user_id); - if (user_full != nullptr) { - on_update_user_full_commands(user_full, user_id, std::move(bot_commands)); - update_user_full(user_full, user_id, "on_update_bot_commands"); - } - break; - } - case DialogType::Chat: { - ChatId chat_id(dialog_id.get_chat_id()); - auto chat_full = get_chat_full(chat_id); - if (chat_full != nullptr && BotCommands::update_all_bot_commands( - chat_full->bot_commands, BotCommands(bot_user_id, std::move(bot_commands)))) { - chat_full->is_changed = true; - update_chat_full(chat_full, chat_id, "on_update_bot_commands"); - } - break; - } - case DialogType::Channel: { - ChannelId channel_id(dialog_id.get_channel_id()); - auto channel_full = get_channel_full(channel_id, true, "on_update_bot_commands"); - if (channel_full != nullptr && - BotCommands::update_all_bot_commands(channel_full->bot_commands, - BotCommands(bot_user_id, std::move(bot_commands)))) { - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_bot_commands"); - } - break; - } - case DialogType::SecretChat: - default: - LOG(ERROR) << "Receive updateBotCommands in " << dialog_id; - break; - } -} - -void ContactsManager::on_update_bot_menu_button(UserId bot_user_id, - tl_object_ptr &&bot_menu_button) { - if (!bot_user_id.is_valid()) { - LOG(ERROR) << "Receive updateBotCOmmands about invalid " << bot_user_id; - return; - } - if (!have_user_force(bot_user_id, "on_update_bot_menu_button") || !is_user_bot(bot_user_id)) { - return; - } - if (td_->auth_manager_->is_bot()) { - return; - } - - auto user_full = get_user_full_force(bot_user_id, "on_update_bot_menu_button"); - if (user_full != nullptr) { - on_update_user_full_menu_button(user_full, bot_user_id, std::move(bot_menu_button)); - update_user_full(user_full, bot_user_id, "on_update_bot_menu_button"); - } -} - -FileId ContactsManager::get_profile_photo_file_id(int64 photo_id) const { - auto it = my_photo_file_id_.find(photo_id); - if (it == my_photo_file_id_.end()) { - return FileId(); - } - return it->second; -} - -void ContactsManager::set_bot_profile_photo(UserId bot_user_id, - const td_api::object_ptr &input_photo, - Promise &&promise) { - if (td_->auth_manager_->is_bot()) { - if (bot_user_id != UserId() && bot_user_id != get_my_id()) { - return promise.set_error(Status::Error(400, "Invalid bot user identifier specified")); - } - bot_user_id = get_my_id(); - } else { - TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); - if (!bot_data.can_be_edited) { - return promise.set_error(Status::Error(400, "The bot can't be edited")); - } - } - if (input_photo == nullptr) { - td_->create_handler(std::move(promise)) - ->send(bot_user_id, FileId(), 0, false, make_tl_object()); - return; - } - set_profile_photo_impl(bot_user_id, input_photo, false, false, std::move(promise)); -} - -void ContactsManager::set_profile_photo(const td_api::object_ptr &input_photo, bool is_fallback, - Promise &&promise) { - set_profile_photo_impl(get_my_id(), input_photo, is_fallback, false, std::move(promise)); -} - -void ContactsManager::set_profile_photo_impl(UserId user_id, - const td_api::object_ptr &input_photo, - bool is_fallback, bool only_suggest, Promise &&promise) { - if (input_photo == nullptr) { - return promise.set_error(Status::Error(400, "New profile photo must be non-empty")); - } - - const td_api::object_ptr *input_file = nullptr; - double main_frame_timestamp = 0.0; - bool is_animation = false; - switch (input_photo->get_id()) { - case td_api::inputChatPhotoPrevious::ID: { - if (user_id != get_my_id() || td_->auth_manager_->is_bot()) { - return promise.set_error(Status::Error(400, "Can't use inputChatPhotoPrevious")); - } - auto photo = static_cast(input_photo.get()); - auto photo_id = photo->chat_photo_id_; - auto *u = get_user(user_id); - if (u != nullptr && u->photo.id > 0 && photo_id == u->photo.id) { - // it is possible that u->photo.is_fallback != is_fallback, so we need to set the photo anyway - // return promise.set_value(Unit()); - } - - auto file_id = get_profile_photo_file_id(photo_id); - if (!file_id.is_valid()) { - return promise.set_error(Status::Error(400, "Unknown profile photo ID specified")); - } - return send_update_profile_photo_query(user_id, - td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), - photo_id, is_fallback, std::move(promise)); - } - case td_api::inputChatPhotoStatic::ID: { - auto photo = static_cast(input_photo.get()); - input_file = &photo->photo_; - break; - } - case td_api::inputChatPhotoAnimation::ID: { - auto photo = static_cast(input_photo.get()); - input_file = &photo->animation_; - main_frame_timestamp = photo->main_frame_timestamp_; - is_animation = true; - break; - } - case td_api::inputChatPhotoSticker::ID: { - auto photo = static_cast(input_photo.get()); - TRY_RESULT_PROMISE(promise, sticker_photo_size, StickerPhotoSize::get_sticker_photo_size(td_, photo->sticker_)); - - td_->create_handler(std::move(promise)) - ->send(user_id, std::move(sticker_photo_size), is_fallback, only_suggest); - return; - } - default: - UNREACHABLE(); - break; - } - - const double MAX_ANIMATION_DURATION = 10.0; - if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) { - return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified")); - } - - auto file_type = is_animation ? FileType::Animation : FileType::Photo; - TRY_RESULT_PROMISE(promise, file_id, - td_->file_manager_->get_input_file_id(file_type, *input_file, DialogId(user_id), false, false)); - CHECK(file_id.is_valid()); - - upload_profile_photo(user_id, td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), is_fallback, - only_suggest, is_animation, main_frame_timestamp, std::move(promise)); -} - -void ContactsManager::set_user_profile_photo(UserId user_id, - const td_api::object_ptr &input_photo, - bool only_suggest, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - if (!only_suggest && !is_user_contact(user_id)) { - return promise.set_error(Status::Error(400, "User isn't a contact")); - } - if (user_id == get_my_id()) { - return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to self")); - } - if (is_user_bot(user_id)) { - return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to bots")); - } - if (input_photo == nullptr) { - td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); - return; - } - - set_profile_photo_impl(user_id, input_photo, false, only_suggest, std::move(promise)); -} - -void ContactsManager::send_update_profile_photo_query(UserId user_id, FileId file_id, int64 old_photo_id, - bool is_fallback, Promise &&promise) { - FileView file_view = td_->file_manager_->get_file_view(file_id); - td_->create_handler(std::move(promise)) - ->send(user_id, file_id, old_photo_id, is_fallback, file_view.main_remote_location().as_input_photo()); -} - -void ContactsManager::upload_profile_photo(UserId user_id, FileId file_id, bool is_fallback, bool only_suggest, - bool is_animation, double main_frame_timestamp, Promise &&promise, - int reupload_count, vector bad_parts) { - CHECK(file_id.is_valid()); - bool is_inserted = - uploaded_profile_photos_ - .emplace(file_id, UploadedProfilePhoto{user_id, is_fallback, only_suggest, main_frame_timestamp, is_animation, - reupload_count, std::move(promise)}) - .second; - CHECK(is_inserted); - LOG(INFO) << "Ask to upload " << (is_animation ? "animated" : "static") << " profile photo " << file_id - << " for user " << user_id << " with bad parts " << bad_parts; - // TODO use force_reupload if reupload_count >= 1, replace reupload_count with is_reupload - td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_profile_photo_callback_, 32, 0); -} - -void ContactsManager::delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - const UserFull *user_full = get_user_full_force(get_my_id(), "delete_profile_photo"); - if (user_full == nullptr) { - // must load UserFull first, because fallback photo can't be deleted via DeleteProfilePhotoQuery - if (is_recursive) { - return promise.set_error(Status::Error(500, "Failed to load UserFullInfo")); - } - auto reload_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), profile_photo_id, promise = std::move(promise)](Result result) mutable { - if (result.is_error()) { - return promise.set_error(result.move_as_error()); - } - send_closure(actor_id, &ContactsManager::delete_profile_photo, profile_photo_id, true, std::move(promise)); - }); - reload_user_full(get_my_id(), std::move(reload_promise), "delete_profile_photo"); - return; - } - if (user_full->photo.id.get() == profile_photo_id || user_full->fallback_photo.id.get() == profile_photo_id) { - td_->create_handler(std::move(promise)) - ->send(get_my_id(), FileId(), profile_photo_id, user_full->fallback_photo.id.get() == profile_photo_id, - make_tl_object()); - return; - } - - td_->create_handler(std::move(promise))->send(profile_photo_id); -} - -void ContactsManager::set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, - Promise &&promise) { - if (!accent_color_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); - } - if (accent_color_id == AccentColorId(get_my_id())) { - accent_color_id = AccentColorId(); - } - - td_->create_handler(std::move(promise))->send(false, accent_color_id, background_custom_emoji_id); -} - -void ContactsManager::set_profile_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, - Promise &&promise) { - td_->create_handler(std::move(promise))->send(true, accent_color_id, background_custom_emoji_id); -} - -void ContactsManager::set_name(const string &first_name, const string &last_name, Promise &&promise) { - auto new_first_name = clean_name(first_name, MAX_NAME_LENGTH); - auto new_last_name = clean_name(last_name, MAX_NAME_LENGTH); - if (new_first_name.empty()) { - return promise.set_error(Status::Error(400, "First name must be non-empty")); - } - - const User *u = get_user(get_my_id()); - int32 flags = 0; - // TODO we can already send request for changing first_name and last_name and wanting to set initial values - // TODO need to be rewritten using invoke after and cancelling previous request - if (u == nullptr || u->first_name != new_first_name) { - flags |= ACCOUNT_UPDATE_FIRST_NAME; - } - if (u == nullptr || u->last_name != new_last_name) { - flags |= ACCOUNT_UPDATE_LAST_NAME; - } - if (flags == 0) { - return promise.set_value(Unit()); - } - - td_->create_handler(std::move(promise))->send(flags, new_first_name, new_last_name, ""); -} - -void ContactsManager::set_bio(const string &bio, Promise &&promise) { - auto max_bio_length = static_cast(td_->option_manager_->get_option_integer("bio_length_max")); - auto new_bio = strip_empty_characters(bio, max_bio_length); - for (auto &c : new_bio) { - if (c == '\n') { - c = ' '; - } - } - - const UserFull *user_full = get_user_full(get_my_id()); - int32 flags = 0; - // TODO we can already send request for changing bio and wanting to set initial values - // TODO need to be rewritten using invoke after and cancelling previous request - if (user_full == nullptr || user_full->about != new_bio) { - flags |= ACCOUNT_UPDATE_ABOUT; - } - if (flags == 0) { - return promise.set_value(Unit()); - } - - td_->create_handler(std::move(promise))->send(flags, "", "", new_bio); -} - -void ContactsManager::on_update_accent_color_success(bool for_profile, AccentColorId accent_color_id, - CustomEmojiId background_custom_emoji_id) { - auto user_id = get_my_id(); - User *u = get_user_force(user_id, "on_update_accent_color_success"); - if (u == nullptr) { - return; - } - if (for_profile) { - on_update_user_profile_accent_color_id(u, user_id, accent_color_id); - on_update_user_profile_background_custom_emoji_id(u, user_id, background_custom_emoji_id); - } else { - on_update_user_accent_color_id(u, user_id, accent_color_id); - on_update_user_background_custom_emoji_id(u, user_id, background_custom_emoji_id); - } - update_user(u, user_id); -} - -void ContactsManager::on_update_profile_success(int32 flags, const string &first_name, const string &last_name, - const string &about) { - CHECK(flags != 0); - - auto my_user_id = get_my_id(); - const User *u = get_user(my_user_id); - if (u == nullptr) { - LOG(ERROR) << "Doesn't receive info about me during update profile"; - return; - } - LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_FIRST_NAME) != 0 && u->first_name != first_name) - << "Wrong first name \"" << u->first_name << "\", expected \"" << first_name << '"'; - LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_LAST_NAME) != 0 && u->last_name != last_name) - << "Wrong last name \"" << u->last_name << "\", expected \"" << last_name << '"'; - - if ((flags & ACCOUNT_UPDATE_ABOUT) != 0) { - UserFull *user_full = get_user_full_force(my_user_id, "on_update_profile_success"); - if (user_full != nullptr) { - user_full->about = about; - user_full->is_changed = true; - update_user_full(user_full, my_user_id, "on_update_profile_success"); - td_->group_call_manager_->on_update_dialog_about(DialogId(my_user_id), user_full->about, true); - } - } -} - -void ContactsManager::set_username(const string &username, Promise &&promise) { - if (!username.empty() && !is_allowed_username(username)) { - return promise.set_error(Status::Error(400, "Username is invalid")); - } - td_->create_handler(std::move(promise))->send(username); -} - -void ContactsManager::toggle_username_is_active(string &&username, bool is_active, Promise &&promise) { - get_me(PromiseCreator::lambda([actor_id = actor_id(this), username = std::move(username), is_active, - promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &ContactsManager::toggle_username_is_active_impl, std::move(username), is_active, - std::move(promise)); - } - })); -} - -void ContactsManager::toggle_username_is_active_impl(string &&username, bool is_active, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - const User *u = get_user(get_my_id()); - CHECK(u != nullptr); - if (!u->usernames.can_toggle(username)) { - return promise.set_error(Status::Error(400, "Wrong username specified")); - } - td_->create_handler(std::move(promise))->send(std::move(username), is_active); -} - -void ContactsManager::reorder_usernames(vector &&usernames, Promise &&promise) { - get_me(PromiseCreator::lambda([actor_id = actor_id(this), usernames = std::move(usernames), - promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &ContactsManager::reorder_usernames_impl, std::move(usernames), std::move(promise)); - } - })); -} - -void ContactsManager::reorder_usernames_impl(vector &&usernames, Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - const User *u = get_user(get_my_id()); - CHECK(u != nullptr); - if (!u->usernames.can_reorder_to(usernames)) { - return promise.set_error(Status::Error(400, "Invalid username order specified")); - } - if (usernames.size() <= 1) { - return promise.set_value(Unit()); - } - td_->create_handler(std::move(promise))->send(std::move(usernames)); -} - -void ContactsManager::on_update_username_is_active(UserId user_id, string &&username, bool is_active, - Promise &&promise) { - User *u = get_user(user_id); - CHECK(u != nullptr); - if (!u->usernames.can_toggle(username)) { - return reload_user(user_id, std::move(promise), "on_update_username_is_active"); - } - on_update_user_usernames(u, user_id, u->usernames.toggle(username, is_active)); - update_user(u, user_id); - promise.set_value(Unit()); -} - -void ContactsManager::on_update_active_usernames_order(UserId user_id, vector &&usernames, - Promise &&promise) { - User *u = get_user(user_id); - CHECK(u != nullptr); - if (!u->usernames.can_reorder_to(usernames)) { - return reload_user(user_id, std::move(promise), "on_update_active_usernames_order"); - } - on_update_user_usernames(u, user_id, u->usernames.reorder_to(std::move(usernames))); - update_user(u, user_id); - promise.set_value(Unit()); -} - -void ContactsManager::toggle_bot_username_is_active(UserId bot_user_id, string &&username, bool is_active, - Promise &&promise) { - TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); - if (!bot_data.can_be_edited) { - return promise.set_error(Status::Error(400, "The bot can't be edited")); - } - const User *u = get_user(bot_user_id); - CHECK(u != nullptr); - if (!u->usernames.can_toggle(username)) { - return promise.set_error(Status::Error(400, "Wrong username specified")); - } - td_->create_handler(std::move(promise))->send(bot_user_id, std::move(username), is_active); -} - -void ContactsManager::reorder_bot_usernames(UserId bot_user_id, vector &&usernames, Promise &&promise) { - TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); - if (!bot_data.can_be_edited) { - return promise.set_error(Status::Error(400, "The bot can't be edited")); - } - const User *u = get_user(bot_user_id); - CHECK(u != nullptr); - if (!u->usernames.can_reorder_to(usernames)) { - return promise.set_error(Status::Error(400, "Invalid username order specified")); - } - if (usernames.size() <= 1) { - return promise.set_value(Unit()); - } - td_->create_handler(std::move(promise))->send(bot_user_id, std::move(usernames)); -} - -void ContactsManager::set_emoji_status(const EmojiStatus &emoji_status, Promise &&promise) { - if (!td_->option_manager_->get_option_boolean("is_premium")) { - return promise.set_error(Status::Error(400, "The method is available only to Telegram Premium users")); - } - add_recent_emoji_status(td_, emoji_status); - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), emoji_status, promise = std::move(promise)](Result result) mutable { - if (result.is_ok()) { - send_closure(actor_id, &ContactsManager::on_set_emoji_status, emoji_status, std::move(promise)); - } else { - promise.set_error(result.move_as_error()); - } - }); - td_->create_handler(std::move(query_promise))->send(emoji_status); -} - -void ContactsManager::on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise) { - auto user_id = get_my_id(); - User *u = get_user(user_id); - if (u != nullptr) { - on_update_user_emoji_status(u, user_id, emoji_status); - update_user(u, user_id); - } - promise.set_value(Unit()); -} - -void ContactsManager::set_chat_description(ChatId chat_id, const string &description, Promise &&promise) { - auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); - auto c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!get_chat_permissions(c).can_change_info_and_settings()) { - return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); - } - - td_->create_handler(std::move(promise))->send(DialogId(chat_id), new_description); -} - -void ContactsManager::set_channel_username(ChannelId channel_id, const string &username, Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to change supergroup username")); - } - - if (!username.empty() && !is_allowed_username(username)) { - return promise.set_error(Status::Error(400, "Username is invalid")); - } - - td_->create_handler(std::move(promise))->send(channel_id, username); -} - -void ContactsManager::toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, - Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to change username")); - } - if (!c->usernames.can_toggle(username)) { - return promise.set_error(Status::Error(400, "Wrong username specified")); - } - td_->create_handler(std::move(promise))->send(channel_id, std::move(username), is_active); -} - -void ContactsManager::disable_all_channel_usernames(ChannelId channel_id, Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to disable usernames")); - } - td_->create_handler(std::move(promise))->send(channel_id); -} - -void ContactsManager::reorder_channel_usernames(ChannelId channel_id, vector &&usernames, - Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to reorder usernames")); - } - if (!c->usernames.can_reorder_to(usernames)) { - return promise.set_error(Status::Error(400, "Invalid username order specified")); - } - if (usernames.size() <= 1) { - return promise.set_value(Unit()); - } - td_->create_handler(std::move(promise))->send(channel_id, std::move(usernames)); -} - -void ContactsManager::on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, - Promise &&promise) { - auto *c = get_channel(channel_id); - CHECK(c != nullptr); - if (!c->usernames.can_toggle(username)) { - return reload_channel(channel_id, std::move(promise), "on_update_channel_username_is_active"); - } - on_update_channel_usernames(c, channel_id, c->usernames.toggle(username, is_active)); - update_channel(c, channel_id); - promise.set_value(Unit()); -} - -void ContactsManager::on_deactivate_channel_usernames(ChannelId channel_id, Promise &&promise) { - auto *c = get_channel(channel_id); - CHECK(c != nullptr); - on_update_channel_usernames(c, channel_id, c->usernames.deactivate_all()); - update_channel(c, channel_id); - promise.set_value(Unit()); -} - -void ContactsManager::on_update_channel_active_usernames_order(ChannelId channel_id, vector &&usernames, - Promise &&promise) { - auto *c = get_channel(channel_id); - CHECK(c != nullptr); - if (!c->usernames.can_reorder_to(usernames)) { - return reload_channel(channel_id, std::move(promise), "on_update_channel_active_usernames_order"); - } - on_update_channel_usernames(c, channel_id, c->usernames.reorder_to(std::move(usernames))); - update_channel(c, channel_id); - promise.set_value(Unit()); -} - -void ContactsManager::set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, - CustomEmojiId background_custom_emoji_id, Promise &&promise) { - if (!accent_color_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); - } - - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (c->is_megagroup) { - return promise.set_error(Status::Error(400, "Accent color can be changed only in channel chats")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { - return promise.set_error(Status::Error(400, "Not enough rights in the channel")); - } - - td_->create_handler(std::move(promise)) - ->send(channel_id, false, accent_color_id, background_custom_emoji_id); -} - -void ContactsManager::set_channel_profile_accent_color(ChannelId channel_id, AccentColorId profile_accent_color_id, - CustomEmojiId profile_background_custom_emoji_id, - Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { - return promise.set_error(Status::Error(400, "Not enough rights in the chat")); - } - - td_->create_handler(std::move(promise)) - ->send(channel_id, true, profile_accent_color_id, profile_background_custom_emoji_id); -} - -void ContactsManager::set_channel_emoji_status(ChannelId channel_id, const EmojiStatus &emoji_status, - Promise &&promise) { - const auto *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { - return promise.set_error(Status::Error(400, "Not enough rights in the chat")); - } - - add_recent_emoji_status(td_, emoji_status); - - td_->create_handler(std::move(promise))->send(channel_id, emoji_status); -} - -void ContactsManager::set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Chat sticker set can be set only for supergroups")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { - return promise.set_error(Status::Error(400, "Not enough rights to change supergroup sticker set")); - } - - telegram_api::object_ptr input_sticker_set; - if (!sticker_set_id.is_valid()) { - input_sticker_set = telegram_api::make_object(); - } else { - input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); - if (input_sticker_set == nullptr) { - return promise.set_error(Status::Error(400, "Sticker set not found")); - } - } - - auto channel_full = get_channel_full(channel_id, false, "set_channel_sticker_set"); - if (channel_full != nullptr && !channel_full->can_set_sticker_set) { - return promise.set_error(Status::Error(400, "Can't set supergroup sticker set")); - } - - td_->create_handler(std::move(promise)) - ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); -} - -void ContactsManager::set_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Cuctom emoji sticker set can be set only for supergroups")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings_as_administrator()) { - return promise.set_error( - Status::Error(400, "Not enough rights to change custom emoji sticker set in the supergroup")); - } - - telegram_api::object_ptr input_sticker_set; - if (!sticker_set_id.is_valid()) { - input_sticker_set = telegram_api::make_object(); - } else { - input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id); - if (input_sticker_set == nullptr) { - return promise.set_error(Status::Error(400, "Sticker set not found")); - } - } - - td_->create_handler(std::move(promise)) - ->send(channel_id, sticker_set_id, std::move(input_sticker_set)); -} - -void ContactsManager::set_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Unrestrict boost count can be set only for supergroups")); - } - if (!get_channel_status(c).can_restrict_members()) { - return promise.set_error( - Status::Error(400, "Not enough rights to change unrestrict boost count set in the supergroup")); - } - if (unrestrict_boost_count < 0 || unrestrict_boost_count > 8) { - return promise.set_error(Status::Error(400, "Invalid new value for the unrestrict boost count specified")); - } - - td_->create_handler(std::move(promise)) - ->send(channel_id, unrestrict_boost_count); -} - -void ContactsManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (get_channel_type(c) == ChannelType::Megagroup) { - return promise.set_error(Status::Error(400, "Message signatures can't be toggled in supergroups")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { - 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); -} - -void ContactsManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { - return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); - } - if (!get_channel_status(c).can_restrict_members()) { - return promise.set_error(Status::Error(400, "Not enough rights")); - } - - td_->create_handler(std::move(promise))->send(channel_id, join_to_send); -} - -void ContactsManager::toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) { - return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups")); - } - if (!get_channel_status(c).can_restrict_members()) { - return promise.set_error(Status::Error(400, "Not enough rights")); - } - - td_->create_handler(std::move(promise))->send(channel_id, join_request); -} - -void ContactsManager::toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { - return promise.set_error(Status::Error(400, "Not enough rights to toggle all supergroup history availability")); - } - if (get_channel_type(c) != ChannelType::Megagroup) { - return promise.set_error(Status::Error(400, "Message history can be hidden in supergroups only")); - } - if (c->is_forum && !is_all_history_available) { - return promise.set_error(Status::Error(400, "Message history can't be hidden in forum supergroups")); - } - if (c->has_linked_channel && !is_all_history_available) { - return promise.set_error(Status::Error(400, "Message history can't be hidden in discussion supergroups")); - } - // it can be toggled in public chats, but will not affect them - - td_->create_handler(std::move(promise))->send(channel_id, is_all_history_available); -} - -Status ContactsManager::can_hide_chat_participants(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return Status::Error(400, "Basic group not found"); - } - if (!get_chat_permissions(c).is_creator()) { - return Status::Error(400, "Not enough rights to hide group members"); - } - if (c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { - return Status::Error(400, "The basic group is too small"); - } - return Status::OK(); -} - -Status ContactsManager::can_hide_channel_participants(ChannelId channel_id, const ChannelFull *channel_full) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return Status::Error(400, "Supergroup not found"); - } - if (!get_channel_status(c).can_restrict_members()) { - return Status::Error(400, "Not enough rights to hide group members"); - } - if (get_channel_type(c) != ChannelType::Megagroup) { - return Status::Error(400, "Group members are hidden by default in channels"); - } - if (channel_full != nullptr && channel_full->has_hidden_participants) { - return Status::OK(); - } - if (c->participant_count > 0 && - c->participant_count < td_->option_manager_->get_option_integer("hidden_members_group_size_min")) { - return Status::Error(400, "The supergroup is too small"); - } - return Status::OK(); -} - -void ContactsManager::toggle_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, - Promise &&promise) { - auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_hidden_participants"); - TRY_STATUS_PROMISE(promise, can_hide_channel_participants(channel_id, channel_full)); - - td_->create_handler(std::move(promise))->send(channel_id, has_hidden_participants); -} - -Status ContactsManager::can_toggle_chat_aggressive_anti_spam(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return Status::Error(400, "Basic group not found"); - } - if (!get_chat_permissions(c).is_creator()) { - return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); - } - if (c->participant_count < - td_->option_manager_->get_option_integer("aggressive_anti_spam_supergroup_member_count_min")) { - return Status::Error(400, "The basic group is too small"); - } - return Status::OK(); -} - -Status ContactsManager::can_toggle_channel_aggressive_anti_spam(ChannelId channel_id, - const ChannelFull *channel_full) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return Status::Error(400, "Supergroup not found"); - } - if (!get_channel_status(c).can_delete_messages()) { - return Status::Error(400, "Not enough rights to enable aggressive anti-spam checks"); - } - if (get_channel_type(c) != ChannelType::Megagroup) { - return Status::Error(400, "Aggressive anti-spam checks can be enabled in supergroups only"); - } - if (c->is_gigagroup) { - return Status::Error(400, "Aggressive anti-spam checks can't be enabled in broadcast supergroups"); - } - if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled) { - return Status::OK(); - } - if (c->has_location || begins_with(c->usernames.get_editable_username(), "translation_")) { - return Status::OK(); - } - if (c->participant_count > 0 && c->participant_count < td_->option_manager_->get_option_integer( - "aggressive_anti_spam_supergroup_member_count_min")) { - return Status::Error(400, "The supergroup is too small"); - } - return Status::OK(); -} - -void ContactsManager::toggle_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, - bool has_aggressive_anti_spam_enabled, - Promise &&promise) { - auto channel_full = get_channel_full_force(channel_id, true, "toggle_channel_has_aggressive_anti_spam_enabled"); - TRY_STATUS_PROMISE(promise, can_toggle_channel_aggressive_anti_spam(channel_id, channel_full)); - - td_->create_handler(std::move(promise))->send(channel_id, has_aggressive_anti_spam_enabled); -} - -void ContactsManager::toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (c->is_forum == is_forum) { - return promise.set_value(Unit()); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to convert the group to a forum")); - } - if (get_channel_type(c) != ChannelType::Megagroup) { - return promise.set_error(Status::Error(400, "Forums can be enabled in supergroups only")); - } - - td_->create_handler(std::move(promise))->send(channel_id, is_forum); -} - -void ContactsManager::convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!get_channel_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to convert group to broadcast group")); - } - if (get_channel_type(c) != ChannelType::Megagroup) { - return promise.set_error(Status::Error(400, "Chat must be a supergroup")); - } - - remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); - - td_->create_handler(std::move(promise))->send(channel_id); -} - -void ContactsManager::set_channel_description(ChannelId channel_id, const string &description, - Promise &&promise) { - auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH); - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!get_channel_permissions(channel_id, c).can_change_info_and_settings()) { - return promise.set_error(Status::Error(400, "Not enough rights to set chat description")); - } - - td_->create_handler(std::move(promise))->send(DialogId(channel_id), new_description); -} - -void ContactsManager::set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, - Promise &&promise) { - if (!dialog_id.is_valid() && !discussion_dialog_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid chat identifiers specified")); - } - - ChannelId broadcast_channel_id; - telegram_api::object_ptr broadcast_input_channel; - if (dialog_id.is_valid()) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_discussion_group 1")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Chat is not a channel")); - } - - broadcast_channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(broadcast_channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - - if (c->is_megagroup) { - return promise.set_error(Status::Error(400, "Chat is not a channel")); - } - if (!c->status.can_change_info_and_settings_as_administrator()) { - return promise.set_error(Status::Error(400, "Not enough rights in the channel")); - } - - broadcast_input_channel = get_input_channel(broadcast_channel_id); - CHECK(broadcast_input_channel != nullptr); - } else { - broadcast_input_channel = telegram_api::make_object(); - } - - ChannelId group_channel_id; - telegram_api::object_ptr group_input_channel; - if (discussion_dialog_id.is_valid()) { - if (!td_->dialog_manager_->have_dialog_force(discussion_dialog_id, "set_channel_discussion_group 2")) { - return promise.set_error(Status::Error(400, "Discussion chat not found")); - } - if (discussion_dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); - } - - group_channel_id = discussion_dialog_id.get_channel_id(); - const Channel *c = get_channel(group_channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Discussion chat info not found")); - } - - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup")); - } - if (!c->status.is_administrator() || !c->status.can_pin_messages()) { - return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); - } - - group_input_channel = get_input_channel(group_channel_id); - CHECK(group_input_channel != nullptr); - } else { - group_input_channel = telegram_api::make_object(); - } - - td_->create_handler(std::move(promise)) - ->send(broadcast_channel_id, std::move(broadcast_input_channel), group_channel_id, - std::move(group_input_channel)); -} - -void ContactsManager::set_channel_location(ChannelId channel_id, const DialogLocation &location, - Promise &&promise) { - if (location.empty()) { - return promise.set_error(Status::Error(400, "Invalid chat location specified")); - } - - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Chat is not a supergroup")); - } - if (!c->status.is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); - } - - td_->create_handler(std::move(promise))->send(channel_id, location); -} - -void ContactsManager::set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise &&promise) { - vector allowed_slow_mode_delays{0, 10, 30, 60, 300, 900, 3600}; - if (!td::contains(allowed_slow_mode_delays, slow_mode_delay)) { - return promise.set_error(Status::Error(400, "Invalid new value for slow mode delay")); - } - - if (!dialog_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); - } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_channel_slow_mode_delay")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Chat is not a supergroup")); - } - - auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Chat is not a supergroup")); - } - if (!get_channel_status(c).can_restrict_members()) { - return promise.set_error(Status::Error(400, "Not enough rights in the supergroup")); - } - - td_->create_handler(std::move(promise))->send(channel_id, slow_mode_delay); -} - -void ContactsManager::get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, - Promise &&promise) { - if (!dialog_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); - } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_statistics_dc_id")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Chat is not a channel")); - } - - auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - - auto channel_full = get_channel_full_force(channel_id, false, "get_channel_statistics_dc_id"); - if (channel_full == nullptr || !channel_full->stats_dc_id.is_exact() || - (for_full_statistics && !channel_full->can_view_statistics)) { - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, for_full_statistics, - promise = std::move(promise)](Result result) mutable { - send_closure(actor_id, &ContactsManager::get_channel_statistics_dc_id_impl, channel_id, for_full_statistics, - std::move(promise)); - }); - send_get_channel_full_query(channel_full, channel_id, std::move(query_promise), "get_channel_statistics_dc_id"); - return; - } - - promise.set_value(DcId(channel_full->stats_dc_id)); -} - -void ContactsManager::get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - auto channel_full = get_channel_full(channel_id, false, "get_channel_statistics_dc_id_impl"); - if (channel_full == nullptr) { - return promise.set_error(Status::Error(400, "Chat full info not found")); - } - - if (!channel_full->stats_dc_id.is_exact() || (for_full_statistics && !channel_full->can_view_statistics)) { - return promise.set_error(Status::Error(400, "Chat statistics are not available")); - } - - promise.set_value(DcId(channel_full->stats_dc_id)); -} - -bool ContactsManager::can_get_channel_message_statistics(DialogId dialog_id) const { - CHECK(!td_->auth_manager_->is_bot()); - if (dialog_id.get_type() != DialogType::Channel) { - return false; - } - - auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); - if (c == nullptr || c->is_megagroup) { - return false; - } - - auto channel_full = get_channel_full(channel_id); - if (channel_full != nullptr) { - return channel_full->stats_dc_id.is_exact(); - } - - return c->status.can_post_messages(); -} - -bool ContactsManager::can_get_channel_story_statistics(DialogId dialog_id) const { - CHECK(!td_->auth_manager_->is_bot()); - if (dialog_id.get_type() != DialogType::Channel) { - return false; - } - - auto channel_id = dialog_id.get_channel_id(); - const Channel *c = get_channel(channel_id); - if (c == nullptr || c->is_megagroup) { - return false; - } - - auto channel_full = get_channel_full(channel_id); - if (channel_full != nullptr) { - return channel_full->stats_dc_id.is_exact(); - } - - return c->status.can_post_messages(); -} - -void ContactsManager::report_channel_spam(ChannelId channel_id, const vector &message_ids, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "Spam can be reported only in supergroups")); - } - if (!c->status.is_administrator()) { - return promise.set_error(Status::Error(400, "Spam can be reported only by chat administrators")); - } - - FlatHashMap, DialogIdHash> server_message_ids; - for (auto &message_id : message_ids) { - if (message_id.is_valid_scheduled()) { - return promise.set_error(Status::Error(400, "Can't report scheduled messages")); - } - - if (!message_id.is_valid()) { - return promise.set_error(Status::Error(400, "Message not found")); - } - - if (!message_id.is_server()) { - continue; - } - - auto sender_dialog_id = td_->messages_manager_->get_dialog_message_sender({DialogId(channel_id), message_id}); - CHECK(sender_dialog_id.get_type() != DialogType::SecretChat); - if (sender_dialog_id.is_valid() && sender_dialog_id != DialogId(get_my_id()) && - td_->dialog_manager_->have_input_peer(sender_dialog_id, AccessRights::Know)) { - server_message_ids[sender_dialog_id].push_back(message_id); - } - } - if (server_message_ids.empty()) { - return promise.set_value(Unit()); - } - - MultiPromiseActorSafe mpas{"ReportSupergroupSpamMultiPromiseActor"}; - mpas.add_promise(std::move(promise)); - auto lock_promise = mpas.get_promise(); - - for (auto &it : server_message_ids) { - td_->create_handler(mpas.get_promise())->send(channel_id, it.first, it.second); - } - - lock_promise.set_value(Unit()); -} - -void ContactsManager::report_channel_anti_spam_false_positive(ChannelId channel_id, MessageId message_id, - Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - if (!c->is_megagroup) { - return promise.set_error(Status::Error(400, "The chat is not a supergroup")); - } - if (!c->status.is_administrator()) { - return promise.set_error( - Status::Error(400, "Anti-spam checks false positives can be reported only by chat administrators")); - } - - if (!message_id.is_valid() || !message_id.is_server()) { - return promise.set_error(Status::Error(400, "Invalid message identifier specified")); - } - - td_->create_handler(std::move(promise))->send(channel_id, message_id); -} - -void ContactsManager::delete_chat(ChatId chat_id, Promise &&promise) { - auto c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!get_chat_status(c).is_creator()) { - return promise.set_error(Status::Error(400, "Not enough rights to delete the chat")); - } - if (!c->is_active) { - return promise.set_error(Status::Error(400, "Chat is already deactivated")); - } - - td_->create_handler(std::move(promise))->send(chat_id); -} - -void ContactsManager::delete_channel(ChannelId channel_id, Promise &&promise) { - auto c = get_channel(channel_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!get_channel_can_be_deleted(c)) { - return promise.set_error(Status::Error(400, "The chat can't be deleted")); - } - - td_->create_handler(std::move(promise))->send(channel_id); -} - -void ContactsManager::migrate_dialog_to_megagroup(DialogId dialog_id, - Promise> &&promise) { - LOG(INFO) << "Trying to upgrade " << dialog_id << " to a supergroup"; - - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "migrate_dialog_to_megagroup")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (dialog_id.get_type() != DialogType::Chat) { - return promise.set_error(Status::Error(400, "Only basic group chats can be converted to supergroup")); - } - - auto chat_id = dialog_id.get_chat_id(); - auto c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } - if (!c->status.is_creator()) { - return promise.set_error(Status::Error(400, "Need creator rights in the chat")); - } - if (c->migrated_to_channel_id.is_valid()) { - return on_migrate_chat_to_megagroup(chat_id, std::move(promise)); - } - - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), chat_id, promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - return promise.set_error(result.move_as_error()); - } - send_closure(actor_id, &ContactsManager::on_migrate_chat_to_megagroup, chat_id, std::move(promise)); - }); - td_->create_handler(std::move(query_promise))->send(chat_id); -} - -void ContactsManager::on_migrate_chat_to_megagroup(ChatId chat_id, - Promise> &&promise) { - auto c = get_chat(chat_id); - CHECK(c != nullptr); - if (!c->migrated_to_channel_id.is_valid()) { - LOG(ERROR) << "Can't find the supergroup to which the basic group has migrated"; - return promise.set_error(Status::Error(500, "Supergroup not found")); - } - auto channel_id = c->migrated_to_channel_id; - if (!have_channel(channel_id)) { - LOG(ERROR) << "Can't find info about the supergroup to which the basic group has migrated"; - return promise.set_error(Status::Error(500, "Supergroup info is not found")); - } - - auto dialog_id = DialogId(channel_id); - td_->dialog_manager_->force_create_dialog(dialog_id, "on_migrate_chat_to_megagroup"); - promise.set_value(td_->messages_manager_->get_chat_object(dialog_id)); -} - -vector ContactsManager::get_channel_ids(vector> &&chats, - const char *source) { - vector channel_ids; - for (auto &chat : chats) { - auto channel_id = get_channel_id(chat); - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << " in " << to_string(chat); - continue; - } - on_get_chat(std::move(chat), source); - if (have_channel(channel_id)) { - channel_ids.push_back(channel_id); - } - } - return channel_ids; -} - -vector ContactsManager::get_dialog_ids(vector> &&chats, - const char *source) { - vector dialog_ids; - for (auto &chat : chats) { - auto channel_id = get_channel_id(chat); - if (!channel_id.is_valid()) { - auto chat_id = get_chat_id(chat); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid chat from " << source << " in " << to_string(chat); - } else { - dialog_ids.push_back(DialogId(chat_id)); - } - } else { - dialog_ids.push_back(DialogId(channel_id)); - } - on_get_chat(std::move(chat), source); - } - return dialog_ids; -} - -bool ContactsManager::is_suitable_recommended_channel(DialogId dialog_id) const { - if (dialog_id.get_type() != DialogType::Channel) { - return false; - } - return is_suitable_recommended_channel(dialog_id.get_channel_id()); -} - -bool ContactsManager::is_suitable_recommended_channel(ChannelId channel_id) const { - const Channel *c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return have_input_peer_channel(c, channel_id, AccessRights::Read) && !get_channel_status(c).is_member(); -} - -bool ContactsManager::are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const { - for (auto recommended_dialog_id : recommended_dialogs.dialog_ids_) { - if (!is_suitable_recommended_channel(recommended_dialog_id)) { - return false; - } - } - auto is_premium = td_->option_manager_->get_option_boolean("is_premium"); - auto have_all = recommended_dialogs.dialog_ids_.size() == static_cast(recommended_dialogs.total_count_); - if (!have_all && is_premium) { - return false; - } - return true; -} - -void ContactsManager::get_channel_recommendations(DialogId dialog_id, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_channel_recommendations")) { - if (chats_promise) { - chats_promise.set_error(Status::Error(400, "Chat not found")); - } - if (count_promise) { - count_promise.set_error(Status::Error(400, "Chat not found")); - } - return; - } - if (dialog_id.get_type() != DialogType::Channel) { - if (chats_promise) { - chats_promise.set_value(td_api::make_object()); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(0)); - } - return; - } - auto channel_id = dialog_id.get_channel_id(); - if (!is_broadcast_channel(channel_id) || get_input_channel(channel_id) == nullptr) { - if (chats_promise) { - chats_promise.set_value(td_api::make_object()); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(0)); - } - return; - } - bool use_database = true; - auto it = channel_recommended_dialogs_.find(channel_id); - if (it != channel_recommended_dialogs_.end()) { - if (are_suitable_recommended_dialogs(it->second)) { - auto next_reload_time = it->second.next_reload_time_; - if (chats_promise) { - chats_promise.set_value(td_->dialog_manager_->get_chats_object(it->second.total_count_, it->second.dialog_ids_, - "get_channel_recommendations")); - } - if (count_promise) { - count_promise.set_value(td_api::make_object(it->second.total_count_)); - } - if (next_reload_time > Time::now()) { - return; - } - chats_promise = {}; - count_promise = {}; - } else { - LOG(INFO) << "Drop cache for similar chats of " << dialog_id; - channel_recommended_dialogs_.erase(it); - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - } - } - use_database = false; - } - load_channel_recommendations(channel_id, use_database, return_local, std::move(chats_promise), - std::move(count_promise)); -} - -string ContactsManager::get_channel_recommendations_database_key(ChannelId channel_id) { - return PSTRING() << "channel_recommendations" << channel_id.get(); -} - -void ContactsManager::load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise) { - if (count_promise) { - get_channel_recommendation_count_queries_[return_local][channel_id].push_back(std::move(count_promise)); - } - auto &queries = get_channel_recommendations_queries_[channel_id]; - queries.push_back(std::move(chats_promise)); - if (queries.size() == 1) { - if (G()->use_message_database() && use_database) { - G()->td_db()->get_sqlite_pmc()->get( - get_channel_recommendations_database_key(channel_id), - PromiseCreator::lambda([actor_id = actor_id(this), channel_id](string value) { - send_closure(actor_id, &ContactsManager::on_load_channel_recommendations_from_database, channel_id, - std::move(value)); - })); - } else { - reload_channel_recommendations(channel_id); - } - } -} - -void ContactsManager::fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error) { - for (int return_local = 0; return_local < 2; return_local++) { - auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); - if (it != get_channel_recommendation_count_queries_[return_local].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[return_local].erase(it); - fail_promises(promises, error.clone()); - } - } - auto it = get_channel_recommendations_queries_.find(channel_id); - CHECK(it != get_channel_recommendations_queries_.end()); - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendations_queries_.erase(it); - fail_promises(promises, std::move(error)); -} - -void ContactsManager::finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, - vector dialog_ids) { - for (int return_local = 0; return_local < 2; return_local++) { - auto it = get_channel_recommendation_count_queries_[return_local].find(channel_id); - if (it != get_channel_recommendation_count_queries_[return_local].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[return_local].erase(it); - for (auto &promise : promises) { - promise.set_value(td_api::make_object(total_count)); - } - } - } - auto it = get_channel_recommendations_queries_.find(channel_id); - CHECK(it != get_channel_recommendations_queries_.end()); - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendations_queries_.erase(it); - for (auto &promise : promises) { - if (promise) { - promise.set_value(td_->dialog_manager_->get_chats_object(total_count, dialog_ids, - "finish_load_channel_recommendations_queries")); - } - } -} - -void ContactsManager::on_load_channel_recommendations_from_database(ChannelId channel_id, string value) { - if (G()->close_flag()) { - return fail_load_channel_recommendations_queries(channel_id, G()->close_status()); - } - - if (value.empty()) { - return reload_channel_recommendations(channel_id); - } - auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; - if (log_event_parse(recommended_dialogs, value).is_error()) { - channel_recommended_dialogs_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - return reload_channel_recommendations(channel_id); - } - Dependencies dependencies; - for (auto dialog_id : recommended_dialogs.dialog_ids_) { - dependencies.add_dialog_and_dependencies(dialog_id); - } - if (!dependencies.resolve_force(td_, "on_load_channel_recommendations_from_database") || - !are_suitable_recommended_dialogs(recommended_dialogs)) { - channel_recommended_dialogs_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Auto()); - return reload_channel_recommendations(channel_id); - } - - auto next_reload_time = recommended_dialogs.next_reload_time_; - finish_load_channel_recommendations_queries(channel_id, recommended_dialogs.total_count_, - recommended_dialogs.dialog_ids_); - - if (next_reload_time <= Time::now()) { - load_channel_recommendations(channel_id, false, false, Auto(), Auto()); - } -} - -void ContactsManager::reload_channel_recommendations(ChannelId channel_id) { - auto it = get_channel_recommendation_count_queries_[1].find(channel_id); - if (it != get_channel_recommendation_count_queries_[1].end()) { - auto promises = std::move(it->second); - CHECK(!promises.empty()); - get_channel_recommendation_count_queries_[1].erase(it); - for (auto &promise : promises) { - promise.set_value(td_api::make_object(-1)); - } - } - auto query_promise = - PromiseCreator::lambda([actor_id = actor_id(this), channel_id]( - Result>>> &&result) { - send_closure(actor_id, &ContactsManager::on_get_channel_recommendations, channel_id, std::move(result)); - }); - td_->create_handler(std::move(query_promise))->send(channel_id); -} - -void ContactsManager::on_get_channel_recommendations( - ChannelId channel_id, Result>>> &&r_chats) { - G()->ignore_result_if_closing(r_chats); - - if (r_chats.is_error()) { - return fail_load_channel_recommendations_queries(channel_id, r_chats.move_as_error()); - } - - auto chats = r_chats.move_as_ok(); - auto total_count = chats.first; - auto channel_ids = get_channel_ids(std::move(chats.second), "on_get_channel_recommendations"); - vector dialog_ids; - if (total_count < static_cast(channel_ids.size())) { - LOG(ERROR) << "Receive total_count = " << total_count << " and " << channel_ids.size() << " similar chats for " - << channel_id; - total_count = static_cast(channel_ids.size()); - } - for (auto recommended_channel_id : channel_ids) { - auto recommended_dialog_id = DialogId(recommended_channel_id); - td_->dialog_manager_->force_create_dialog(recommended_dialog_id, "on_get_channel_recommendations"); - if (is_suitable_recommended_channel(recommended_channel_id)) { - dialog_ids.push_back(recommended_dialog_id); - } else { - total_count--; - } - } - auto &recommended_dialogs = channel_recommended_dialogs_[channel_id]; - recommended_dialogs.total_count_ = total_count; - recommended_dialogs.dialog_ids_ = dialog_ids; - recommended_dialogs.next_reload_time_ = Time::now() + CHANNEL_RECOMMENDATIONS_CACHE_TIME; - - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->set(get_channel_recommendations_database_key(channel_id), - log_event_store(recommended_dialogs).as_slice().str(), Promise()); - } - - finish_load_channel_recommendations_queries(channel_id, total_count, std::move(dialog_ids)); -} - -void ContactsManager::open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, - Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "open_channel_recommended_channel") || - !td_->dialog_manager_->have_dialog_force(opened_dialog_id, "open_channel_recommended_channel")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (dialog_id.get_type() != DialogType::Channel || opened_dialog_id.get_type() != DialogType::Channel) { - return promise.set_error(Status::Error(400, "Invalid chat specified")); - } - vector> data; - data.push_back(telegram_api::make_object( - "ref_channel_id", make_tl_object(to_string(dialog_id.get_channel_id().get())))); - data.push_back(telegram_api::make_object( - "open_channel_id", make_tl_object(to_string(opened_dialog_id.get_channel_id().get())))); - save_app_log(td_, "channels.open_recommended_channel", DialogId(), - telegram_api::make_object(std::move(data)), std::move(promise)); -} - -void ContactsManager::return_created_public_dialogs(Promise> &&promise, - const vector &channel_ids) { - if (!promise) { - return; - } - - auto total_count = narrow_cast(channel_ids.size()); - promise.set_value(td_api::make_object( - total_count, transform(channel_ids, [](ChannelId channel_id) { return DialogId(channel_id).get(); }))); -} - -bool ContactsManager::is_suitable_created_public_channel(PublicDialogType type, const Channel *c) { - if (c == nullptr || !c->status.is_creator()) { - return false; - } - - switch (type) { - case PublicDialogType::HasUsername: - return c->usernames.has_editable_username(); - case PublicDialogType::IsLocationBased: - return c->has_location; - default: - UNREACHABLE(); - return false; - } -} - -void ContactsManager::get_created_public_dialogs(PublicDialogType type, - Promise> &&promise, - bool from_binlog) { - auto index = static_cast(type); - if (created_public_channels_inited_[index]) { - return return_created_public_dialogs(std::move(promise), created_public_channels_[index]); - } - - if (get_created_public_channels_queries_[index].empty() && G()->use_message_database()) { - auto pmc_key = PSTRING() << "public_channels" << index; - auto str = G()->td_db()->get_binlog_pmc()->get(pmc_key); - if (!str.empty()) { - auto r_channel_ids = transform(full_split(Slice(str), ','), [](Slice str) -> Result { - TRY_RESULT(channel_id_int, to_integer_safe(str)); - ChannelId channel_id(channel_id_int); - if (!channel_id.is_valid()) { - return Status::Error("Have invalid channel ID"); - } - return channel_id; - }); - if (any_of(r_channel_ids, [](const auto &r_channel_id) { return r_channel_id.is_error(); })) { - LOG(ERROR) << "Can't parse " << str; - G()->td_db()->get_binlog_pmc()->erase(pmc_key); - } else { - Dependencies dependencies; - vector channel_ids; - for (auto &r_channel_id : r_channel_ids) { - auto channel_id = r_channel_id.move_as_ok(); - dependencies.add_dialog_and_dependencies(DialogId(channel_id)); - channel_ids.push_back(channel_id); - } - if (!dependencies.resolve_force(td_, "get_created_public_dialogs")) { - G()->td_db()->get_binlog_pmc()->erase(pmc_key); - } else { - for (auto channel_id : channel_ids) { - if (is_suitable_created_public_channel(type, get_channel(channel_id))) { - created_public_channels_[index].push_back(channel_id); - } - } - created_public_channels_inited_[index] = true; - - if (type == PublicDialogType::HasUsername) { - update_created_public_broadcasts(); - } - - if (from_binlog) { - return_created_public_dialogs(std::move(promise), created_public_channels_[index]); - promise = {}; - } - } - } - } - } - - reload_created_public_dialogs(type, std::move(promise)); -} - -void ContactsManager::reload_created_public_dialogs(PublicDialogType type, - Promise> &&promise) { - auto index = static_cast(type); - get_created_public_channels_queries_[index].push_back(std::move(promise)); - if (get_created_public_channels_queries_[index].size() == 1) { - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), type](Result &&result) { - send_closure(actor_id, &ContactsManager::finish_get_created_public_dialogs, type, std::move(result)); - }); - td_->create_handler(std::move(query_promise))->send(type, false); - } -} - -void ContactsManager::finish_get_created_public_dialogs(PublicDialogType type, Result &&result) { - G()->ignore_result_if_closing(result); - - auto index = static_cast(type); - auto promises = std::move(get_created_public_channels_queries_[index]); - reset_to_empty(get_created_public_channels_queries_[index]); - if (result.is_error()) { - return fail_promises(promises, result.move_as_error()); - } - - CHECK(created_public_channels_inited_[index]); - for (auto &promise : promises) { - return_created_public_dialogs(std::move(promise), created_public_channels_[index]); - } -} - -void ContactsManager::update_created_public_channels(Channel *c, ChannelId channel_id) { - for (auto type : {PublicDialogType::HasUsername, PublicDialogType::IsLocationBased}) { - auto index = static_cast(type); - if (!created_public_channels_inited_[index]) { - continue; - } - bool was_changed = false; - if (!is_suitable_created_public_channel(type, c)) { - was_changed = td::remove(created_public_channels_[index], channel_id); - } else { - if (!td::contains(created_public_channels_[index], channel_id)) { - created_public_channels_[index].push_back(channel_id); - was_changed = true; - } - } - if (was_changed) { - if (!c->is_megagroup && type == PublicDialogType::HasUsername) { - update_created_public_broadcasts(); - } - - save_created_public_channels(type); - - reload_created_public_dialogs(type, Promise>()); - } - } -} - -void ContactsManager::on_get_created_public_channels(PublicDialogType type, - vector> &&chats) { - auto index = static_cast(type); - auto channel_ids = get_channel_ids(std::move(chats), "on_get_created_public_channels"); - if (created_public_channels_inited_[index] && created_public_channels_[index] == channel_ids) { - return; - } - created_public_channels_[index].clear(); - for (auto channel_id : channel_ids) { - td_->dialog_manager_->force_create_dialog(DialogId(channel_id), "on_get_created_public_channels"); - if (is_suitable_created_public_channel(type, get_channel(channel_id))) { - created_public_channels_[index].push_back(channel_id); - } - } - created_public_channels_inited_[index] = true; - - if (type == PublicDialogType::HasUsername) { - update_created_public_broadcasts(); - } - - save_created_public_channels(type); -} - -void ContactsManager::save_created_public_channels(PublicDialogType type) { - auto index = static_cast(type); - CHECK(created_public_channels_inited_[index]); - if (G()->use_message_database()) { - G()->td_db()->get_binlog_pmc()->set( - PSTRING() << "public_channels" << index, - implode( - transform(created_public_channels_[index], [](auto channel_id) { return PSTRING() << channel_id.get(); }), - ',')); - } -} - -void ContactsManager::update_created_public_broadcasts() { - CHECK(created_public_channels_inited_[0]); - vector channel_ids; - for (auto &channel_id : created_public_channels_[0]) { - auto c = get_channel(channel_id); - if (!c->is_megagroup) { - channel_ids.push_back(channel_id); - } - } - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_created_public_broadcasts, - std::move(channel_ids)); -} - -void ContactsManager::check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise) { - td_->create_handler(std::move(promise))->send(type, true); -} - -vector ContactsManager::get_dialogs_for_discussion(Promise &&promise) { - if (dialogs_for_discussion_inited_) { - promise.set_value(Unit()); - return transform(dialogs_for_discussion_, [&](DialogId dialog_id) { - td_->dialog_manager_->force_create_dialog(dialog_id, "get_dialogs_for_discussion"); - return dialog_id; - }); - } - - td_->create_handler(std::move(promise))->send(); - return {}; -} - -void ContactsManager::on_get_dialogs_for_discussion(vector> &&chats) { - dialogs_for_discussion_inited_ = true; - dialogs_for_discussion_ = get_dialog_ids(std::move(chats), "on_get_dialogs_for_discussion"); -} - -void ContactsManager::update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable) { - if (!dialogs_for_discussion_inited_) { - return; - } - - if (is_suitable) { - if (!td::contains(dialogs_for_discussion_, dialog_id)) { - LOG(DEBUG) << "Add " << dialog_id << " to list of suitable discussion chats"; - dialogs_for_discussion_.insert(dialogs_for_discussion_.begin(), dialog_id); - } - } else { - if (td::remove(dialogs_for_discussion_, dialog_id)) { - LOG(DEBUG) << "Remove " << dialog_id << " from list of suitable discussion chats"; - } - } -} - -vector ContactsManager::get_inactive_channels(Promise &&promise) { - if (inactive_channel_ids_inited_) { - promise.set_value(Unit()); - return transform(inactive_channel_ids_, [&](ChannelId channel_id) { return DialogId(channel_id); }); - } - - td_->create_handler(std::move(promise))->send(); - return {}; -} - -void ContactsManager::on_get_inactive_channels(vector> &&chats, - Promise &&promise) { - auto channel_ids = get_channel_ids(std::move(chats), "on_get_inactive_channels"); - - MultiPromiseActorSafe mpas{"GetInactiveChannelsMultiPromiseActor"}; - mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), channel_ids, - promise = std::move(promise)](Unit) mutable { - send_closure(actor_id, &ContactsManager::on_create_inactive_channels, std::move(channel_ids), std::move(promise)); - })); - mpas.set_ignore_errors(true); - auto lock_promise = mpas.get_promise(); - - for (auto channel_id : channel_ids) { - td_->messages_manager_->create_dialog(DialogId(channel_id), false, mpas.get_promise()); - } - - lock_promise.set_value(Unit()); -} - -void ContactsManager::on_create_inactive_channels(vector &&channel_ids, Promise &&promise) { - inactive_channel_ids_inited_ = true; - inactive_channel_ids_ = std::move(channel_ids); - promise.set_value(Unit()); -} - -void ContactsManager::remove_inactive_channel(ChannelId channel_id) { - if (inactive_channel_ids_inited_ && td::remove(inactive_channel_ids_, channel_id)) { - LOG(DEBUG) << "Remove " << channel_id << " from list of inactive channels"; - } -} - -void ContactsManager::register_message_users(MessageFullId message_full_id, vector user_ids) { - auto dialog_id = message_full_id.get_dialog_id(); - CHECK(dialog_id.get_type() == DialogType::Channel); - if (!have_channel(dialog_id.get_channel_id())) { - return; - } - for (auto user_id : user_ids) { - CHECK(user_id.is_valid()); - const User *u = get_user(user_id); - if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { - auto &user_messages = user_messages_[user_id]; - auto need_update = user_messages.empty(); - user_messages.insert(message_full_id); - if (need_update) { - send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); - } - } - } -} - -void ContactsManager::register_message_channels(MessageFullId message_full_id, vector channel_ids) { - auto dialog_id = message_full_id.get_dialog_id(); - CHECK(dialog_id.get_type() == DialogType::Channel); - if (!have_channel(dialog_id.get_channel_id())) { - return; - } - for (auto channel_id : channel_ids) { - CHECK(channel_id.is_valid()); - if (!have_channel(channel_id)) { - channel_messages_[channel_id].insert(message_full_id); - - // get info about the channel - get_channel_queries_.add_query(channel_id.get(), Promise(), "register_message_channels"); - } - } -} - -void ContactsManager::unregister_message_users(MessageFullId message_full_id, vector user_ids) { - if (user_messages_.empty()) { - // fast path - return; - } - for (auto user_id : user_ids) { - auto it = user_messages_.find(user_id); - if (it != user_messages_.end()) { - it->second.erase(message_full_id); - if (it->second.empty()) { - user_messages_.erase(it); - - const User *u = get_user(user_id); - if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { - send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); - } - } - } - } -} - -void ContactsManager::unregister_message_channels(MessageFullId message_full_id, vector channel_ids) { - if (channel_messages_.empty()) { - // fast path - return; - } - for (auto channel_id : channel_ids) { - auto it = channel_messages_.find(channel_id); - if (it != channel_messages_.end()) { - it->second.erase(message_full_id); - if (it->second.empty()) { - channel_messages_.erase(it); - } - } - } -} - -void ContactsManager::remove_dialog_suggested_action(SuggestedAction action) { - auto it = dialog_suggested_actions_.find(action.dialog_id_); - if (it == dialog_suggested_actions_.end()) { - return; - } - remove_suggested_action(it->second, action); - if (it->second.empty()) { - dialog_suggested_actions_.erase(it); - } -} - -void ContactsManager::dismiss_dialog_suggested_action(SuggestedAction action, Promise &&promise) { - auto dialog_id = action.dialog_id_; - if (!td_->messages_manager_->have_dialog(dialog_id)) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - - auto it = dialog_suggested_actions_.find(dialog_id); - if (it == dialog_suggested_actions_.end() || !td::contains(it->second, action)) { - return promise.set_value(Unit()); - } - - auto action_str = action.get_suggested_action_str(); - if (action_str.empty()) { - return promise.set_value(Unit()); - } - - auto &queries = dismiss_suggested_action_queries_[dialog_id]; - queries.push_back(std::move(promise)); - if (queries.size() == 1) { - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), action](Result &&result) { - send_closure(actor_id, &ContactsManager::on_dismiss_suggested_action, action, std::move(result)); - }); - td_->create_handler(std::move(query_promise))->send(std::move(action)); - } -} - -void ContactsManager::on_dismiss_suggested_action(SuggestedAction action, Result &&result) { - auto it = dismiss_suggested_action_queries_.find(action.dialog_id_); - CHECK(it != dismiss_suggested_action_queries_.end()); - auto promises = std::move(it->second); - dismiss_suggested_action_queries_.erase(it); - - if (result.is_error()) { - fail_promises(promises, result.move_as_error()); - return; - } - - remove_dialog_suggested_action(action); - - set_promises(promises); -} - -void ContactsManager::on_import_contacts_finished(int64 random_id, vector imported_contact_user_ids, - vector unimported_contact_invites) { - LOG(INFO) << "Contacts import with random_id " << random_id - << " has finished: " << format::as_array(imported_contact_user_ids); - if (random_id == 1) { - // import from change_imported_contacts - all_imported_contacts_ = std::move(next_all_imported_contacts_); - next_all_imported_contacts_.clear(); - - auto result_size = imported_contacts_unique_id_.size(); - auto unique_size = all_imported_contacts_.size(); - auto add_size = imported_contacts_pos_.size(); - - imported_contact_user_ids_.resize(result_size); - unimported_contact_invites_.resize(result_size); - - CHECK(imported_contact_user_ids.size() == add_size); - CHECK(unimported_contact_invites.size() == add_size); - CHECK(imported_contacts_unique_id_.size() == result_size); - - std::unordered_map> unique_id_to_unimported_contact_invites; - for (size_t i = 0; i < add_size; i++) { - auto unique_id = imported_contacts_pos_[i]; - get_user_id_object(imported_contact_user_ids[i], "on_import_contacts_finished"); // to ensure updateUser - all_imported_contacts_[unique_id].set_user_id(imported_contact_user_ids[i]); - unique_id_to_unimported_contact_invites[narrow_cast(unique_id)] = unimported_contact_invites[i]; - } - - if (G()->use_chat_info_database()) { - G()->td_db()->get_binlog()->force_sync( - PromiseCreator::lambda( - [log_event = log_event_store(all_imported_contacts_).as_slice().str()](Result<> result) mutable { - if (result.is_ok()) { - LOG(INFO) << "Save imported contacts to database"; - G()->td_db()->get_sqlite_pmc()->set("user_imported_contacts", std::move(log_event), Auto()); - } - }), - "on_import_contacts_finished"); - } - - for (size_t i = 0; i < result_size; i++) { - auto unique_id = imported_contacts_unique_id_[i]; - CHECK(unique_id < unique_size); - imported_contact_user_ids_[i] = all_imported_contacts_[unique_id].get_user_id(); - auto it = unique_id_to_unimported_contact_invites.find(narrow_cast(unique_id)); - if (it == unique_id_to_unimported_contact_invites.end()) { - unimported_contact_invites_[i] = 0; - } else { - unimported_contact_invites_[i] = it->second; - } - } - return; - } - - auto it = imported_contacts_.find(random_id); - CHECK(it != imported_contacts_.end()); - CHECK(it->second.first.empty()); - CHECK(it->second.second.empty()); - imported_contacts_[random_id] = {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; -} - -void ContactsManager::on_deleted_contacts(const vector &deleted_contact_user_ids) { - LOG(INFO) << "Contacts deletion has finished for " << deleted_contact_user_ids; - - for (auto user_id : deleted_contact_user_ids) { - auto u = get_user(user_id); - CHECK(u != nullptr); - if (!u->is_contact) { - continue; - } - - LOG(INFO) << "Drop contact with " << user_id; - on_update_user_is_contact(u, user_id, false, false, false); - CHECK(u->is_is_contact_changed); - u->cache_version = 0; - u->is_repaired = false; - update_user(u, user_id); - CHECK(!u->is_contact); - CHECK(!contacts_hints_.has_key(user_id.get())); - } -} - -void ContactsManager::save_next_contacts_sync_date() { - if (G()->close_flag()) { - return; - } - if (!G()->use_chat_info_database()) { - return; - } - G()->td_db()->get_binlog_pmc()->set("next_contacts_sync_date", to_string(next_contacts_sync_date_)); -} - -void ContactsManager::on_get_contacts(tl_object_ptr &&new_contacts) { - next_contacts_sync_date_ = G()->unix_time() + Random::fast(70000, 100000); - - CHECK(new_contacts != nullptr); - if (new_contacts->get_id() == telegram_api::contacts_contactsNotModified::ID) { - if (saved_contact_count_ == -1) { - saved_contact_count_ = 0; - } - on_get_contacts_finished(contacts_hints_.size()); - td_->create_handler()->send(); - return; - } - - auto contacts = move_tl_object_as(new_contacts); - FlatHashSet contact_user_ids; - for (auto &user : contacts->users_) { - auto user_id = get_user_id(user); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - continue; - } - contact_user_ids.insert(user_id); - } - on_get_users(std::move(contacts->users_), "on_get_contacts"); - - UserId my_id = get_my_id(); - users_.foreach([&](const UserId &user_id, unique_ptr &user) { - User *u = user.get(); - bool should_be_contact = contact_user_ids.count(user_id) == 1; - if (u->is_contact != should_be_contact) { - if (u->is_contact) { - LOG(INFO) << "Drop contact with " << user_id; - if (user_id != my_id) { - LOG_CHECK(contacts_hints_.has_key(user_id.get())) - << my_id << " " << user_id << " " << to_string(get_user_object(user_id, u)); - } - on_update_user_is_contact(u, user_id, false, false, false); - CHECK(u->is_is_contact_changed); - u->cache_version = 0; - u->is_repaired = false; - update_user(u, user_id); - CHECK(!u->is_contact); - if (user_id != my_id) { - CHECK(!contacts_hints_.has_key(user_id.get())); - } - } else { - LOG(ERROR) << "Receive non-contact " << user_id << " in the list of contacts"; - } - } - }); - - saved_contact_count_ = contacts->saved_count_; - on_get_contacts_finished(std::numeric_limits::max()); -} - -void ContactsManager::save_contacts_to_database() { - if (!G()->use_chat_info_database() || !are_contacts_loaded_) { - return; - } - - LOG(INFO) << "Schedule save contacts to database"; - vector user_ids = - transform(contacts_hints_.search_empty(100000).second, [](int64 key) { return UserId(key); }); - - G()->td_db()->get_binlog_pmc()->set("saved_contact_count", to_string(saved_contact_count_)); - G()->td_db()->get_binlog()->force_sync( - PromiseCreator::lambda([user_ids = std::move(user_ids)](Result<> result) { - if (result.is_ok()) { - LOG(INFO) << "Saved contacts to database"; - G()->td_db()->get_sqlite_pmc()->set( - "user_contacts", log_event_store(user_ids).as_slice().str(), PromiseCreator::lambda([](Result<> result) { - if (result.is_ok()) { - send_closure(G()->contacts_manager(), &ContactsManager::save_next_contacts_sync_date); - } - })); - } - }), - "save_contacts_to_database"); -} - -void ContactsManager::on_get_contacts_failed(Status error) { - CHECK(error.is_error()); - next_contacts_sync_date_ = G()->unix_time() + Random::fast(5, 10); - fail_promises(load_contacts_queries_, std::move(error)); -} - -void ContactsManager::on_load_contacts_from_database(string value) { - if (G()->close_flag()) { - return; - } - if (value.empty()) { - reload_contacts(true); - return; - } - - vector user_ids; - if (log_event_parse(user_ids, value).is_error()) { - LOG(ERROR) << "Failed to load contacts from database"; - reload_contacts(true); - return; - } - - if (log_event_get_version(value) < static_cast(Version::AddUserFlags2)) { - next_contacts_sync_date_ = 0; - save_next_contacts_sync_date(); - reload_contacts(true); - } - - LOG(INFO) << "Successfully loaded " << user_ids.size() << " contacts from database"; - - load_contact_users_multipromise_.add_promise(PromiseCreator::lambda( - [actor_id = actor_id(this), expected_contact_count = user_ids.size()](Result result) { - if (result.is_ok()) { - send_closure(actor_id, &ContactsManager::on_get_contacts_finished, expected_contact_count); - } else { - LOG(INFO) << "Failed to load contact users from database: " << result.error(); - send_closure(actor_id, &ContactsManager::reload_contacts, true); - } - })); - - auto lock_promise = load_contact_users_multipromise_.get_promise(); - - for (auto user_id : user_ids) { - get_user(user_id, 3, load_contact_users_multipromise_.get_promise()); - } - - lock_promise.set_value(Unit()); -} - -void ContactsManager::on_get_contacts_finished(size_t expected_contact_count) { - LOG(INFO) << "Finished to get " << contacts_hints_.size() << " contacts out of expected " << expected_contact_count; - are_contacts_loaded_ = true; - set_promises(load_contacts_queries_); - if (expected_contact_count != contacts_hints_.size()) { - save_contacts_to_database(); - } -} - -void ContactsManager::on_get_contacts_statuses(vector> &&statuses) { - auto my_user_id = get_my_id(); - for (auto &status : statuses) { - UserId user_id(status->user_id_); - if (user_id != my_user_id) { - on_update_user_online(user_id, std::move(status->status_)); - } - } - save_next_contacts_sync_date(); -} - -void ContactsManager::on_update_online_status_privacy() { - td_->create_handler()->send(); -} - -void ContactsManager::on_update_phone_number_privacy() { - // all UserFull.need_phone_number_privacy_exception can be outdated now, - // so mark all of them as expired - users_full_.foreach([&](const UserId &user_id, unique_ptr &user_full) { user_full->expires_at = 0.0; }); -} - -void ContactsManager::invalidate_user_full(UserId user_id) { - auto user_full = get_user_full_force(user_id, "invalidate_user_full"); - if (user_full != nullptr) { - td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(user_id)); - - if (!user_full->is_expired()) { - user_full->expires_at = 0.0; - user_full->need_save_to_database = true; - - update_user_full(user_full, user_id, "invalidate_user_full"); - } - } -} - -UserId ContactsManager::get_user_id(const tl_object_ptr &user) { - CHECK(user != nullptr); - switch (user->get_id()) { - case telegram_api::userEmpty::ID: - return UserId(static_cast(user.get())->id_); - case telegram_api::user::ID: - return UserId(static_cast(user.get())->id_); - default: - UNREACHABLE(); - return UserId(); - } -} - -ChatId ContactsManager::get_chat_id(const tl_object_ptr &chat) { - CHECK(chat != nullptr); - switch (chat->get_id()) { - case telegram_api::chatEmpty::ID: - return ChatId(static_cast(chat.get())->id_); - case telegram_api::chat::ID: - return ChatId(static_cast(chat.get())->id_); - case telegram_api::chatForbidden::ID: - return ChatId(static_cast(chat.get())->id_); - default: - return ChatId(); - } -} - -ChannelId ContactsManager::get_channel_id(const tl_object_ptr &chat) { - CHECK(chat != nullptr); - switch (chat->get_id()) { - case telegram_api::channel::ID: - return ChannelId(static_cast(chat.get())->id_); - case telegram_api::channelForbidden::ID: - return ChannelId(static_cast(chat.get())->id_); - default: - return ChannelId(); - } -} - -DialogId ContactsManager::get_dialog_id(const tl_object_ptr &chat) { - auto channel_id = get_channel_id(chat); - if (channel_id.is_valid()) { - return DialogId(channel_id); - } - return DialogId(get_chat_id(chat)); -} - -void ContactsManager::on_get_user(tl_object_ptr &&user_ptr, const char *source) { - LOG(DEBUG) << "Receive from " << source << ' ' << to_string(user_ptr); - int32 constructor_id = user_ptr->get_id(); - if (constructor_id == telegram_api::userEmpty::ID) { - auto user = move_tl_object_as(user_ptr); - UserId user_id(user->id_); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id << " from " << source; - return; - } - LOG(INFO) << "Receive empty " << user_id << " from " << source; - - User *u = get_user_force(user_id, source); - if (u == nullptr && Slice(source) != Slice("GetUsersQuery")) { - // userEmpty should be received only through getUsers for nonexistent users - LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty from " << source; - } - return; - } - - CHECK(constructor_id == telegram_api::user::ID); - auto user = move_tl_object_as(user_ptr); - UserId user_id(user->id_); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - int32 flags = user->flags_; - int32 flags2 = user->flags2_; - LOG(INFO) << "Receive " << user_id << " with flags " << flags << ' ' << flags2 << " from " << source; - - // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used - bool is_bot = (flags & USER_FLAG_IS_BOT) != 0; - if (flags & USER_FLAG_IS_ME) { - set_my_id(user_id); - if (!is_bot) { - td_->option_manager_->set_option_string("my_phone_number", user->phone_); - } - } - - bool have_access_hash = (flags & USER_FLAG_HAS_ACCESS_HASH) != 0; - bool is_received = (flags & USER_FLAG_IS_INACCESSIBLE) == 0; - bool is_contact = (flags & USER_FLAG_IS_CONTACT) != 0; - - User *u = get_user(user_id); - if (u == nullptr) { - if (!is_received) { - // we must preload received inaccessible users from database in order to not save - // the min-user to the database and to not override access_hash and another info - u = get_user_force(user_id, "on_get_user 2"); - if (u == nullptr) { - LOG(INFO) << "Receive inaccessible " << user_id; - u = add_user(user_id); - } - } else if (is_contact && !are_contacts_loaded_) { - // preload contact users from database to know that is_contact didn't changed - // and the list of contacts doesn't need to be saved to the database - u = get_user_force(user_id, "on_get_user 3"); - if (u == nullptr) { - LOG(INFO) << "Receive contact " << user_id << " for the first time"; - u = add_user(user_id); - } - } else { - u = add_user(user_id); - } - CHECK(u != nullptr); - } - - if (have_access_hash) { // access_hash must be updated before photo - auto access_hash = user->access_hash_; - bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty()); - if (u->access_hash != access_hash && (!is_min_access_hash || u->is_min_access_hash || u->access_hash == -1)) { - LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << "/" - << u->is_min_access_hash << " to " << access_hash << "/" << is_min_access_hash; - u->access_hash = access_hash; - u->is_min_access_hash = is_min_access_hash; - u->need_save_to_database = true; - } - } - - bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; - bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; - bool is_support = (flags & USER_FLAG_IS_SUPPORT) != 0; - bool is_deleted = (flags & USER_FLAG_IS_DELETED) != 0; - bool can_join_groups = (flags & USER_FLAG_IS_PRIVATE_BOT) == 0; - bool can_read_all_group_messages = (flags & USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED) != 0; - bool can_be_added_to_attach_menu = (flags & USER_FLAG_IS_ATTACH_MENU_BOT) != 0; - bool attach_menu_enabled = (flags & USER_FLAG_ATTACH_MENU_ENABLED) != 0; - bool is_scam = (flags & USER_FLAG_IS_SCAM) != 0; - bool can_be_edited_bot = (flags2 & USER_FLAG_CAN_BE_EDITED_BOT) != 0; - bool is_inline_bot = (flags & USER_FLAG_IS_INLINE_BOT) != 0; - string inline_query_placeholder = std::move(user->bot_inline_placeholder_); - bool need_location_bot = (flags & USER_FLAG_NEED_LOCATION_BOT) != 0; - bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0; - bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0; - bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0; - bool stories_available = user->stories_max_id_ > 0; - bool stories_unavailable = user->stories_unavailable_; - bool stories_hidden = user->stories_hidden_; - bool contact_require_premium = user->contact_require_premium_; - - LOG_IF(ERROR, !can_join_groups && !is_bot) - << "Receive not bot " << user_id << " which can't join groups from " << source; - LOG_IF(ERROR, can_read_all_group_messages && !is_bot) - << "Receive not bot " << user_id << " which can read all group messages from " << source; - LOG_IF(ERROR, can_be_added_to_attach_menu && !is_bot) - << "Receive not bot " << user_id << " which can be added to attachment menu from " << source; - LOG_IF(ERROR, can_be_edited_bot && !is_bot) - << "Receive not bot " << user_id << " which is inline bot from " << source; - LOG_IF(ERROR, is_inline_bot && !is_bot) << "Receive not bot " << user_id << " which is inline bot from " << source; - LOG_IF(ERROR, need_location_bot && !is_inline_bot) - << "Receive not inline bot " << user_id << " which needs user location from " << source; - - if (is_deleted) { - // just in case - is_verified = false; - is_premium = false; - is_support = false; - is_bot = false; - can_join_groups = false; - can_read_all_group_messages = false; - can_be_added_to_attach_menu = false; - can_be_edited_bot = false; - is_inline_bot = false; - inline_query_placeholder = string(); - need_location_bot = false; - has_bot_info_version = false; - need_apply_min_photo = false; - } - - LOG_IF(ERROR, has_bot_info_version && !is_bot) - << "Receive not bot " << user_id << " which has bot info version from " << source; - - int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; - if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || - can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || - is_scam != u->is_scam || is_fake != u->is_fake || is_inline_bot != u->is_inline_bot || - inline_query_placeholder != u->inline_query_placeholder || need_location_bot != u->need_location_bot || - can_be_added_to_attach_menu != u->can_be_added_to_attach_menu) { - if (is_bot != u->is_bot) { - LOG_IF(ERROR, !is_deleted && !u->is_deleted && u->is_received) - << "User.is_bot has changed for " << user_id << "/" << u->usernames << " from " << source << " from " - << u->is_bot << " to " << is_bot; - u->is_full_info_changed = true; - } - u->is_verified = is_verified; - u->is_support = is_support; - u->is_bot = is_bot; - u->can_join_groups = can_join_groups; - u->can_read_all_group_messages = can_read_all_group_messages; - u->is_scam = is_scam; - u->is_fake = is_fake; - u->is_inline_bot = is_inline_bot; - u->inline_query_placeholder = std::move(inline_query_placeholder); - u->need_location_bot = need_location_bot; - u->can_be_added_to_attach_menu = can_be_added_to_attach_menu; - - LOG(DEBUG) << "Info has changed for " << user_id; - u->is_changed = true; - } - if (u->contact_require_premium != contact_require_premium) { - u->contact_require_premium = contact_require_premium; - u->is_changed = true; - user_full_contact_require_premium_.erase(user_id); - } - if (is_received && attach_menu_enabled != u->attach_menu_enabled) { - u->attach_menu_enabled = attach_menu_enabled; - u->is_changed = true; - } - if (is_premium != u->is_premium) { - u->is_premium = is_premium; - u->is_is_premium_changed = true; - u->is_changed = true; - u->is_full_info_changed = true; - } - if (is_received && can_be_edited_bot != u->can_be_edited_bot) { - u->can_be_edited_bot = can_be_edited_bot; - u->is_changed = true; - u->is_full_info_changed = true; - } - - if (u->bot_info_version != bot_info_version) { - u->bot_info_version = bot_info_version; - LOG(DEBUG) << "Bot info version has changed for " << user_id; - u->need_save_to_database = true; - } - if (is_received && u->need_apply_min_photo != need_apply_min_photo) { - LOG(DEBUG) << "Need apply min photo has changed for " << user_id; - u->need_apply_min_photo = need_apply_min_photo; - u->need_save_to_database = true; - } - - if (is_received && !u->is_received) { - u->is_received = true; - - LOG(DEBUG) << "Receive " << user_id; - u->is_changed = true; - } - - if (is_deleted != u->is_deleted) { - u->is_deleted = is_deleted; - - LOG(DEBUG) << "User.is_deleted has changed for " << user_id << " to " << u->is_deleted; - u->is_is_deleted_changed = true; - u->is_changed = true; - } - - bool has_language_code = (flags & USER_FLAG_HAS_LANGUAGE_CODE) != 0; - LOG_IF(ERROR, has_language_code && !td_->auth_manager_->is_bot()) - << "Receive language code for " << user_id << " from " << source; - if (u->language_code != user->lang_code_ && !user->lang_code_.empty()) { - u->language_code = user->lang_code_; - - LOG(DEBUG) << "Language code has changed for " << user_id << " to " << u->language_code; - u->is_changed = true; - } - - bool is_me_regular_user = !td_->auth_manager_->is_bot(); - if (is_received || u->need_apply_min_photo || !u->is_received) { - on_update_user_photo(u, user_id, std::move(user->photo_), source); - } - if (is_me_regular_user) { - if (is_received || !u->is_received) { - on_update_user_phone_number(u, user_id, std::move(user->phone_)); - } - if (is_received || !u->is_received || u->was_online == 0) { - on_update_user_online(u, user_id, std::move(user->status_)); - } - if (is_received) { - auto is_mutual_contact = (flags & USER_FLAG_IS_MUTUAL_CONTACT) != 0; - auto is_close_friend = (flags2 & USER_FLAG_IS_CLOSE_FRIEND) != 0; - on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact, is_close_friend); - } - } - - if (is_received || !u->is_received) { - on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_)); - on_update_user_usernames(u, user_id, Usernames{std::move(user->username_), std::move(user->usernames_)}); - } - on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); - PeerColor peer_color(user->color_); - on_update_user_accent_color_id(u, user_id, peer_color.accent_color_id_); - on_update_user_background_custom_emoji_id(u, user_id, peer_color.background_custom_emoji_id_); - PeerColor profile_peer_color(user->profile_color_); - on_update_user_profile_accent_color_id(u, user_id, profile_peer_color.accent_color_id_); - on_update_user_profile_background_custom_emoji_id(u, user_id, profile_peer_color.background_custom_emoji_id_); - if (is_me_regular_user) { - if (is_received) { - on_update_user_stories_hidden(u, user_id, stories_hidden); - } - if (stories_available || stories_unavailable) { - // update at the end, because it calls need_poll_user_active_stories - on_update_user_story_ids_impl(u, user_id, StoryId(user->stories_max_id_), StoryId()); - } - auto restriction_reasons = get_restriction_reasons(std::move(user->restriction_reason_)); - if (restriction_reasons != u->restriction_reasons) { - u->restriction_reasons = std::move(restriction_reasons); - u->is_changed = true; - } - } - - if (u->cache_version != User::CACHE_VERSION && u->is_received) { - u->cache_version = User::CACHE_VERSION; - u->need_save_to_database = true; - } - u->is_received_from_server = true; - update_user(u, user_id); -} - -class ContactsManager::UserLogEvent { - public: - UserId user_id; - const User *u_in = nullptr; - unique_ptr u_out; - - UserLogEvent() = default; - - UserLogEvent(UserId user_id, const User *u) : user_id(user_id), u_in(u) { - } - - template - void store(StorerT &storer) const { - td::store(user_id, storer); - td::store(*u_in, storer); - } - - template - void parse(ParserT &parser) { - td::parse(user_id, parser); - td::parse(u_out, parser); - } -}; - -void ContactsManager::save_user(User *u, UserId user_id, bool from_binlog) { - if (!G()->use_chat_info_database()) { - return; - } - CHECK(u != nullptr); - if (!u->is_saved || !u->is_status_saved) { // TODO more effective handling of !u->is_status_saved - if (!from_binlog) { - auto log_event = UserLogEvent(user_id, u); - auto storer = get_log_event_storer(log_event); - if (u->log_event_id == 0) { - u->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Users, storer); - } else { - binlog_rewrite(G()->td_db()->get_binlog(), u->log_event_id, LogEvent::HandlerType::Users, storer); - } - } - - save_user_to_database(u, user_id); - } -} - -void ContactsManager::on_binlog_user_event(BinlogEvent &&event) { - if (!G()->use_chat_info_database()) { - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - UserLogEvent log_event; - if (log_event_parse(log_event, event.get_data()).is_error()) { - LOG(ERROR) << "Failed to load a user from binlog"; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - auto user_id = log_event.user_id; - if (have_min_user(user_id) || !user_id.is_valid()) { - LOG(ERROR) << "Skip adding already added " << user_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - LOG(INFO) << "Add " << user_id << " from binlog"; - users_.set(user_id, std::move(log_event.u_out)); - - User *u = get_user(user_id); - CHECK(u != nullptr); - u->log_event_id = event.id_; - - update_user(u, user_id, true, false); -} - -string ContactsManager::get_user_database_key(UserId user_id) { - return PSTRING() << "us" << user_id.get(); -} - -string ContactsManager::get_user_database_value(const User *u) { - return log_event_store(*u).as_slice().str(); -} - -void ContactsManager::save_user_to_database(User *u, UserId user_id) { - CHECK(u != nullptr); - if (u->is_being_saved) { - return; - } - if (loaded_from_database_users_.count(user_id)) { - save_user_to_database_impl(u, user_id, get_user_database_value(u)); - return; - } - if (load_user_from_database_queries_.count(user_id) != 0) { - return; - } - - load_user_from_database_impl(user_id, Auto()); -} - -void ContactsManager::save_user_to_database_impl(User *u, UserId user_id, string value) { - CHECK(u != nullptr); - CHECK(load_user_from_database_queries_.count(user_id) == 0); - CHECK(!u->is_being_saved); - u->is_being_saved = true; - u->is_saved = true; - u->is_status_saved = true; - LOG(INFO) << "Trying to save to database " << user_id; - G()->td_db()->get_sqlite_pmc()->set( - get_user_database_key(user_id), std::move(value), PromiseCreator::lambda([user_id](Result<> result) { - send_closure(G()->contacts_manager(), &ContactsManager::on_save_user_to_database, user_id, result.is_ok()); - })); -} - -void ContactsManager::on_save_user_to_database(UserId user_id, bool success) { - if (G()->close_flag()) { - return; - } - - User *u = get_user(user_id); - CHECK(u != nullptr); - LOG_CHECK(u->is_being_saved) << success << ' ' << user_id << ' ' << u->is_saved << ' ' << u->is_status_saved << ' ' - << load_user_from_database_queries_.count(user_id) << ' ' << u->is_received << ' ' - << u->is_deleted << ' ' << u->is_bot << ' ' << u->need_save_to_database << ' ' - << u->is_changed << ' ' << u->is_status_changed << ' ' << u->is_name_changed << ' ' - << u->is_username_changed << ' ' << u->is_photo_changed << ' ' - << u->is_is_contact_changed << ' ' << u->is_is_deleted_changed << ' ' - << u->is_stories_hidden_changed << ' ' << u->log_event_id; - CHECK(load_user_from_database_queries_.count(user_id) == 0); - u->is_being_saved = false; - - if (!success) { - LOG(ERROR) << "Failed to save " << user_id << " to database"; - u->is_saved = false; - u->is_status_saved = false; - } else { - LOG(INFO) << "Successfully saved " << user_id << " to database"; - } - if (u->is_saved && u->is_status_saved) { - if (u->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); - u->log_event_id = 0; - } - } else { - save_user(u, user_id, u->log_event_id != 0); - } -} - -void ContactsManager::load_user_from_database(User *u, UserId user_id, Promise promise) { - if (loaded_from_database_users_.count(user_id)) { - promise.set_value(Unit()); - return; - } - - CHECK(u == nullptr || !u->is_being_saved); - load_user_from_database_impl(user_id, std::move(promise)); -} - -void ContactsManager::load_user_from_database_impl(UserId user_id, Promise promise) { - LOG(INFO) << "Load " << user_id << " from database"; - auto &load_user_queries = load_user_from_database_queries_[user_id]; - load_user_queries.push_back(std::move(promise)); - if (load_user_queries.size() == 1u) { - G()->td_db()->get_sqlite_pmc()->get(get_user_database_key(user_id), PromiseCreator::lambda([user_id](string value) { - send_closure(G()->contacts_manager(), - &ContactsManager::on_load_user_from_database, user_id, - std::move(value), false); - })); - } -} - -void ContactsManager::on_load_user_from_database(UserId user_id, string value, bool force) { - if (G()->close_flag() && !force) { - // the user is in Binlog and will be saved after restart - return; - } - - CHECK(user_id.is_valid()); - if (!loaded_from_database_users_.insert(user_id).second) { - return; - } - - auto it = load_user_from_database_queries_.find(user_id); - vector> promises; - if (it != load_user_from_database_queries_.end()) { - promises = std::move(it->second); - CHECK(!promises.empty()); - load_user_from_database_queries_.erase(it); - } - - LOG(INFO) << "Successfully loaded " << user_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_user_database_key(user_id), Auto()); - // return; - - User *u = get_user(user_id); - if (u == nullptr) { - if (!value.empty()) { - u = add_user(user_id); - - if (log_event_parse(*u, value).is_error()) { - LOG(ERROR) << "Failed to load " << user_id << " from database"; - users_.erase(user_id); - } else { - u->is_saved = true; - u->is_status_saved = true; - update_user(u, user_id, true, true); - } - } - } else { - CHECK(!u->is_saved); // user can't be saved before load completes - CHECK(!u->is_being_saved); - auto new_value = get_user_database_value(u); - if (value != new_value) { - save_user_to_database_impl(u, user_id, std::move(new_value)); - } else if (u->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); - u->log_event_id = 0; - } - } - - set_promises(promises); -} - -bool ContactsManager::have_user_force(UserId user_id, const char *source) { - return get_user_force(user_id, source) != nullptr; -} - -ContactsManager::User *ContactsManager::get_user_force(UserId user_id, const char *source) { - auto u = get_user_force_impl(user_id, source); - if ((u == nullptr || !u->is_received) && - (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || - user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || - user_id == get_anti_spam_bot_user_id())) { - int32 flags = USER_FLAG_HAS_ACCESS_HASH | USER_FLAG_HAS_FIRST_NAME | USER_FLAG_NEED_APPLY_MIN_PHOTO; - int64 profile_photo_id = 0; - int32 profile_photo_dc_id = 1; - string first_name; - string last_name; - string username; - string phone_number; - int32 bot_info_version = 0; - - if (user_id == get_service_notifications_user_id()) { - flags |= USER_FLAG_HAS_PHONE_NUMBER | USER_FLAG_IS_VERIFIED | USER_FLAG_IS_SUPPORT; - first_name = "Telegram"; - if (G()->is_test_dc()) { - flags |= USER_FLAG_HAS_LAST_NAME; - last_name = "Notifications"; - } - phone_number = "42777"; - profile_photo_id = 3337190045231023; - } else if (user_id == get_replies_bot_user_id()) { - flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; - if (!G()->is_test_dc()) { - flags |= USER_FLAG_IS_PRIVATE_BOT; - } - first_name = "Replies"; - username = "replies"; - bot_info_version = G()->is_test_dc() ? 1 : 3; - } else if (user_id == get_anonymous_bot_user_id()) { - flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; - if (!G()->is_test_dc()) { - flags |= USER_FLAG_IS_PRIVATE_BOT; - } - first_name = "Group"; - username = G()->is_test_dc() ? "izgroupbot" : "GroupAnonymousBot"; - bot_info_version = G()->is_test_dc() ? 1 : 3; - profile_photo_id = 5159307831025969322; - } else if (user_id == get_channel_bot_user_id()) { - flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; - if (!G()->is_test_dc()) { - flags |= USER_FLAG_IS_PRIVATE_BOT; - } - first_name = G()->is_test_dc() ? "Channels" : "Channel"; - username = G()->is_test_dc() ? "channelsbot" : "Channel_Bot"; - bot_info_version = G()->is_test_dc() ? 1 : 4; - profile_photo_id = 587627495930570665; - } else if (user_id == get_service_notifications_user_id()) { - flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; - if (G()->is_test_dc()) { - first_name = "antispambot"; - username = "tantispambot"; - } else { - flags |= USER_FLAG_IS_VERIFIED; - first_name = "Telegram Anti-Spam"; - username = "tgsantispambot"; - profile_photo_id = 5170408289966598902; - } - } - - telegram_api::object_ptr profile_photo; - if (!G()->is_test_dc() && profile_photo_id != 0) { - profile_photo = telegram_api::make_object(0, false, false, profile_photo_id, - BufferSlice(), profile_photo_dc_id); - } - - auto user = telegram_api::make_object( - flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, string(), username, - phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string(), nullptr, - vector>(), 0, nullptr, nullptr); - on_get_user(std::move(user), "get_user_force"); - u = get_user(user_id); - CHECK(u != nullptr && u->is_received); - - reload_user(user_id, Promise(), "get_user_force"); - } - return u; -} - -ContactsManager::User *ContactsManager::get_user_force_impl(UserId user_id, const char *source) { - if (!user_id.is_valid()) { - return nullptr; - } - - User *u = get_user(user_id); - if (u != nullptr) { - return u; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (loaded_from_database_users_.count(user_id)) { - return nullptr; - } - - LOG(INFO) << "Trying to load " << user_id << " from database from " << source; - on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)), true); - return get_user(user_id); -} - -class ContactsManager::ChatLogEvent { - public: - ChatId chat_id; - const Chat *c_in = nullptr; - unique_ptr c_out; - - ChatLogEvent() = default; - - ChatLogEvent(ChatId chat_id, const Chat *c) : chat_id(chat_id), c_in(c) { - } - - template - void store(StorerT &storer) const { - td::store(chat_id, storer); - td::store(*c_in, storer); - } - - template - void parse(ParserT &parser) { - td::parse(chat_id, parser); - td::parse(c_out, parser); - } -}; - -void ContactsManager::save_chat(Chat *c, ChatId chat_id, bool from_binlog) { - if (!G()->use_chat_info_database()) { - return; - } - CHECK(c != nullptr); - if (!c->is_saved) { - if (!from_binlog) { - auto log_event = ChatLogEvent(chat_id, c); - auto storer = get_log_event_storer(log_event); - if (c->log_event_id == 0) { - c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Chats, storer); - } else { - binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Chats, storer); - } - } - - save_chat_to_database(c, chat_id); - return; - } -} - -void ContactsManager::on_binlog_chat_event(BinlogEvent &&event) { - if (!G()->use_chat_info_database()) { - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - ChatLogEvent log_event; - if (log_event_parse(log_event, event.get_data()).is_error()) { - LOG(ERROR) << "Failed to load a basic group from binlog"; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - auto chat_id = log_event.chat_id; - if (have_chat(chat_id) || !chat_id.is_valid()) { - LOG(ERROR) << "Skip adding already added " << chat_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - LOG(INFO) << "Add " << chat_id << " from binlog"; - chats_.set(chat_id, std::move(log_event.c_out)); - - Chat *c = get_chat(chat_id); - CHECK(c != nullptr); - c->log_event_id = event.id_; - - update_chat(c, chat_id, true, false); -} - -string ContactsManager::get_chat_database_key(ChatId chat_id) { - return PSTRING() << "gr" << chat_id.get(); -} - -string ContactsManager::get_chat_database_value(const Chat *c) { - return log_event_store(*c).as_slice().str(); -} - -void ContactsManager::save_chat_to_database(Chat *c, ChatId chat_id) { - CHECK(c != nullptr); - if (c->is_being_saved) { - return; - } - if (loaded_from_database_chats_.count(chat_id)) { - save_chat_to_database_impl(c, chat_id, get_chat_database_value(c)); - return; - } - if (load_chat_from_database_queries_.count(chat_id) != 0) { - return; - } - - load_chat_from_database_impl(chat_id, Auto()); -} - -void ContactsManager::save_chat_to_database_impl(Chat *c, ChatId chat_id, string value) { - CHECK(c != nullptr); - CHECK(load_chat_from_database_queries_.count(chat_id) == 0); - CHECK(!c->is_being_saved); - c->is_being_saved = true; - c->is_saved = true; - LOG(INFO) << "Trying to save to database " << chat_id; - G()->td_db()->get_sqlite_pmc()->set( - get_chat_database_key(chat_id), std::move(value), PromiseCreator::lambda([chat_id](Result<> result) { - send_closure(G()->contacts_manager(), &ContactsManager::on_save_chat_to_database, chat_id, result.is_ok()); - })); -} - -void ContactsManager::on_save_chat_to_database(ChatId chat_id, bool success) { - if (G()->close_flag()) { - return; - } - - Chat *c = get_chat(chat_id); - CHECK(c != nullptr); - CHECK(c->is_being_saved); - CHECK(load_chat_from_database_queries_.count(chat_id) == 0); - c->is_being_saved = false; - - if (!success) { - LOG(ERROR) << "Failed to save " << chat_id << " to database"; - c->is_saved = false; - } else { - LOG(INFO) << "Successfully saved " << chat_id << " to database"; - } - if (c->is_saved) { - if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } else { - save_chat(c, chat_id, c->log_event_id != 0); - } -} - -void ContactsManager::load_chat_from_database(Chat *c, ChatId chat_id, Promise promise) { - if (loaded_from_database_chats_.count(chat_id)) { - promise.set_value(Unit()); - return; - } - - CHECK(c == nullptr || !c->is_being_saved); - load_chat_from_database_impl(chat_id, std::move(promise)); -} - -void ContactsManager::load_chat_from_database_impl(ChatId chat_id, Promise promise) { - LOG(INFO) << "Load " << chat_id << " from database"; - auto &load_chat_queries = load_chat_from_database_queries_[chat_id]; - load_chat_queries.push_back(std::move(promise)); - if (load_chat_queries.size() == 1u) { - G()->td_db()->get_sqlite_pmc()->get(get_chat_database_key(chat_id), PromiseCreator::lambda([chat_id](string value) { - send_closure(G()->contacts_manager(), - &ContactsManager::on_load_chat_from_database, chat_id, - std::move(value), false); - })); - } -} - -void ContactsManager::on_load_chat_from_database(ChatId chat_id, string value, bool force) { - if (G()->close_flag() && !force) { - // the chat is in Binlog and will be saved after restart - return; - } - - CHECK(chat_id.is_valid()); - if (!loaded_from_database_chats_.insert(chat_id).second) { - return; - } - - auto it = load_chat_from_database_queries_.find(chat_id); - vector> promises; - if (it != load_chat_from_database_queries_.end()) { - promises = std::move(it->second); - CHECK(!promises.empty()); - load_chat_from_database_queries_.erase(it); - } - - LOG(INFO) << "Successfully loaded " << chat_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_chat_database_key(chat_id), Auto()); - // return; - - Chat *c = get_chat(chat_id); - if (c == nullptr) { - if (!value.empty()) { - c = add_chat(chat_id); - - if (log_event_parse(*c, value).is_error()) { - LOG(ERROR) << "Failed to load " << chat_id << " from database"; - chats_.erase(chat_id); - } else { - c->is_saved = true; - update_chat(c, chat_id, true, true); - } - } - } else { - CHECK(!c->is_saved); // chat can't be saved before load completes - CHECK(!c->is_being_saved); - auto new_value = get_chat_database_value(c); - if (value != new_value) { - save_chat_to_database_impl(c, chat_id, std::move(new_value)); - } else if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } - - if (c != nullptr && c->migrated_to_channel_id.is_valid() && - !have_channel_force(c->migrated_to_channel_id, "on_load_chat_from_database")) { - LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id; - } - - set_promises(promises); -} - -bool ContactsManager::have_chat_force(ChatId chat_id, const char *source) { - return get_chat_force(chat_id, source) != nullptr; -} - -ContactsManager::Chat *ContactsManager::get_chat_force(ChatId chat_id, const char *source) { - if (!chat_id.is_valid()) { - return nullptr; - } - - Chat *c = get_chat(chat_id); - if (c != nullptr) { - if (c->migrated_to_channel_id.is_valid() && !have_channel_force(c->migrated_to_channel_id, source)) { - LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id << " from " << source; - } - - return c; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (loaded_from_database_chats_.count(chat_id)) { - return nullptr; - } - - LOG(INFO) << "Trying to load " << chat_id << " from database from " << source; - on_load_chat_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_database_key(chat_id)), true); - return get_chat(chat_id); -} - -class ContactsManager::ChannelLogEvent { - public: - ChannelId channel_id; - const Channel *c_in = nullptr; - unique_ptr c_out; - - ChannelLogEvent() = default; - - ChannelLogEvent(ChannelId channel_id, const Channel *c) : channel_id(channel_id), c_in(c) { - } - - template - void store(StorerT &storer) const { - td::store(channel_id, storer); - td::store(*c_in, storer); - } - - template - void parse(ParserT &parser) { - td::parse(channel_id, parser); - td::parse(c_out, parser); - } -}; - -void ContactsManager::save_channel(Channel *c, ChannelId channel_id, bool from_binlog) { - if (!G()->use_chat_info_database()) { - return; - } - CHECK(c != nullptr); - if (!c->is_saved) { - if (!from_binlog) { - auto log_event = ChannelLogEvent(channel_id, c); - auto storer = get_log_event_storer(log_event); - if (c->log_event_id == 0) { - c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Channels, storer); - } else { - binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Channels, storer); - } - } - - save_channel_to_database(c, channel_id); - return; - } -} - -void ContactsManager::on_binlog_channel_event(BinlogEvent &&event) { - if (!G()->use_chat_info_database()) { - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - ChannelLogEvent log_event; - if (log_event_parse(log_event, event.get_data()).is_error()) { - LOG(ERROR) << "Failed to load a supergroup from binlog"; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - auto channel_id = log_event.channel_id; - if (have_channel(channel_id) || !channel_id.is_valid()) { - LOG(ERROR) << "Skip adding already added " << channel_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - LOG(INFO) << "Add " << channel_id << " from binlog"; - channels_.set(channel_id, std::move(log_event.c_out)); - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - c->log_event_id = event.id_; - - update_channel(c, channel_id, true, false); -} - -string ContactsManager::get_channel_database_key(ChannelId channel_id) { - return PSTRING() << "ch" << channel_id.get(); -} - -string ContactsManager::get_channel_database_value(const Channel *c) { - return log_event_store(*c).as_slice().str(); -} - -void ContactsManager::save_channel_to_database(Channel *c, ChannelId channel_id) { - CHECK(c != nullptr); - if (c->is_being_saved) { - return; - } - if (loaded_from_database_channels_.count(channel_id)) { - save_channel_to_database_impl(c, channel_id, get_channel_database_value(c)); - return; - } - if (load_channel_from_database_queries_.count(channel_id) != 0) { - return; - } - - load_channel_from_database_impl(channel_id, Auto()); -} - -void ContactsManager::save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value) { - CHECK(c != nullptr); - CHECK(load_channel_from_database_queries_.count(channel_id) == 0); - CHECK(!c->is_being_saved); - c->is_being_saved = true; - c->is_saved = true; - LOG(INFO) << "Trying to save to database " << channel_id; - G()->td_db()->get_sqlite_pmc()->set( - get_channel_database_key(channel_id), std::move(value), PromiseCreator::lambda([channel_id](Result<> result) { - send_closure(G()->contacts_manager(), &ContactsManager::on_save_channel_to_database, channel_id, - result.is_ok()); - })); -} - -void ContactsManager::on_save_channel_to_database(ChannelId channel_id, bool success) { - if (G()->close_flag()) { - return; - } - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - CHECK(c->is_being_saved); - CHECK(load_channel_from_database_queries_.count(channel_id) == 0); - c->is_being_saved = false; - - if (!success) { - LOG(ERROR) << "Failed to save " << channel_id << " to database"; - c->is_saved = false; - } else { - LOG(INFO) << "Successfully saved " << channel_id << " to database"; - } - if (c->is_saved) { - if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } else { - save_channel(c, channel_id, c->log_event_id != 0); - } -} - -void ContactsManager::load_channel_from_database(Channel *c, ChannelId channel_id, Promise promise) { - if (loaded_from_database_channels_.count(channel_id)) { - promise.set_value(Unit()); - return; - } - - CHECK(c == nullptr || !c->is_being_saved); - load_channel_from_database_impl(channel_id, std::move(promise)); -} - -void ContactsManager::load_channel_from_database_impl(ChannelId channel_id, Promise promise) { - LOG(INFO) << "Load " << channel_id << " from database"; - auto &load_channel_queries = load_channel_from_database_queries_[channel_id]; - load_channel_queries.push_back(std::move(promise)); - if (load_channel_queries.size() == 1u) { - G()->td_db()->get_sqlite_pmc()->get( - get_channel_database_key(channel_id), PromiseCreator::lambda([channel_id](string value) { - send_closure(G()->contacts_manager(), &ContactsManager::on_load_channel_from_database, channel_id, - std::move(value), false); - })); - } -} - -void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string value, bool force) { - if (G()->close_flag() && !force) { - // the channel is in Binlog and will be saved after restart - return; - } - - CHECK(channel_id.is_valid()); - if (!loaded_from_database_channels_.insert(channel_id).second) { - return; - } - - auto it = load_channel_from_database_queries_.find(channel_id); - vector> promises; - if (it != load_channel_from_database_queries_.end()) { - promises = std::move(it->second); - CHECK(!promises.empty()); - load_channel_from_database_queries_.erase(it); - } - - LOG(INFO) << "Successfully loaded " << channel_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_channel_database_key(channel_id), Auto()); - // return; - - Channel *c = get_channel(channel_id); - if (c == nullptr) { - if (!value.empty()) { - c = add_channel(channel_id, "on_load_channel_from_database"); - - if (log_event_parse(*c, value).is_error()) { - LOG(ERROR) << "Failed to load " << channel_id << " from database"; - channels_.erase(channel_id); - } else { - c->is_saved = true; - update_channel(c, channel_id, true, true); - } - } - } else { - CHECK(!c->is_saved); // channel can't be saved before load completes - CHECK(!c->is_being_saved); - if (!value.empty()) { - Channel temp_c; - if (log_event_parse(temp_c, value).is_ok()) { - if (c->participant_count == 0 && temp_c.participant_count != 0) { - c->participant_count = temp_c.participant_count; - CHECK(c->is_update_supergroup_sent); - send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); - } - - c->status.update_restrictions(); - temp_c.status.update_restrictions(); - if (temp_c.status != c->status) { - on_channel_status_changed(c, channel_id, temp_c.status, c->status); - CHECK(!c->is_being_saved); - } - - if (temp_c.usernames != c->usernames) { - on_channel_usernames_changed(c, channel_id, temp_c.usernames, c->usernames); - CHECK(!c->is_being_saved); - } - } - } - auto new_value = get_channel_database_value(c); - if (value != new_value) { - save_channel_to_database_impl(c, channel_id, std::move(new_value)); - } else if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } - - set_promises(promises); -} - -bool ContactsManager::have_channel_force(ChannelId channel_id, const char *source) { - return get_channel_force(channel_id, source) != nullptr; -} - -ContactsManager::Channel *ContactsManager::get_channel_force(ChannelId channel_id, const char *source) { - if (!channel_id.is_valid()) { - return nullptr; - } - - Channel *c = get_channel(channel_id); - if (c != nullptr) { - return c; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (loaded_from_database_channels_.count(channel_id)) { - return nullptr; - } - - LOG(INFO) << "Trying to load " << channel_id << " from database from " << source; - on_load_channel_from_database(channel_id, - G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_database_key(channel_id)), true); - return get_channel(channel_id); -} - -class ContactsManager::SecretChatLogEvent { - public: - SecretChatId secret_chat_id; - const SecretChat *c_in = nullptr; - unique_ptr c_out; - - SecretChatLogEvent() = default; - - SecretChatLogEvent(SecretChatId secret_chat_id, const SecretChat *c) : secret_chat_id(secret_chat_id), c_in(c) { - } - - template - void store(StorerT &storer) const { - td::store(secret_chat_id, storer); - td::store(*c_in, storer); - } - - template - void parse(ParserT &parser) { - td::parse(secret_chat_id, parser); - td::parse(c_out, parser); - } -}; - -void ContactsManager::save_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog) { - if (!G()->use_chat_info_database()) { - return; - } - CHECK(c != nullptr); - if (!c->is_saved) { - if (!from_binlog) { - auto log_event = SecretChatLogEvent(secret_chat_id, c); - auto storer = get_log_event_storer(log_event); - if (c->log_event_id == 0) { - c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SecretChatInfos, storer); - } else { - binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::SecretChatInfos, storer); - } - } - - save_secret_chat_to_database(c, secret_chat_id); - return; - } -} - -void ContactsManager::on_binlog_secret_chat_event(BinlogEvent &&event) { - if (!G()->use_chat_info_database()) { - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - SecretChatLogEvent log_event; - if (log_event_parse(log_event, event.get_data()).is_error()) { - LOG(ERROR) << "Failed to load a secret chat from binlog"; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - auto secret_chat_id = log_event.secret_chat_id; - if (have_secret_chat(secret_chat_id) || !secret_chat_id.is_valid()) { - LOG(ERROR) << "Skip adding already added " << secret_chat_id; - binlog_erase(G()->td_db()->get_binlog(), event.id_); - return; - } - - LOG(INFO) << "Add " << secret_chat_id << " from binlog"; - secret_chats_.set(secret_chat_id, std::move(log_event.c_out)); - - SecretChat *c = get_secret_chat(secret_chat_id); - CHECK(c != nullptr); - c->log_event_id = event.id_; - - update_secret_chat(c, secret_chat_id, true, false); -} - -string ContactsManager::get_secret_chat_database_key(SecretChatId secret_chat_id) { - return PSTRING() << "sc" << secret_chat_id.get(); -} - -string ContactsManager::get_secret_chat_database_value(const SecretChat *c) { - return log_event_store(*c).as_slice().str(); -} - -void ContactsManager::save_secret_chat_to_database(SecretChat *c, SecretChatId secret_chat_id) { - CHECK(c != nullptr); - if (c->is_being_saved) { - return; - } - if (loaded_from_database_secret_chats_.count(secret_chat_id)) { - save_secret_chat_to_database_impl(c, secret_chat_id, get_secret_chat_database_value(c)); - return; - } - if (load_secret_chat_from_database_queries_.count(secret_chat_id) != 0) { - return; - } - - load_secret_chat_from_database_impl(secret_chat_id, Auto()); -} - -void ContactsManager::save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value) { - CHECK(c != nullptr); - CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); - CHECK(!c->is_being_saved); - c->is_being_saved = true; - c->is_saved = true; - LOG(INFO) << "Trying to save to database " << secret_chat_id; - G()->td_db()->get_sqlite_pmc()->set(get_secret_chat_database_key(secret_chat_id), std::move(value), - PromiseCreator::lambda([secret_chat_id](Result<> result) { - send_closure(G()->contacts_manager(), - &ContactsManager::on_save_secret_chat_to_database, secret_chat_id, - result.is_ok()); - })); -} - -void ContactsManager::on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success) { - if (G()->close_flag()) { - return; - } - - SecretChat *c = get_secret_chat(secret_chat_id); - CHECK(c != nullptr); - CHECK(c->is_being_saved); - CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); - c->is_being_saved = false; - - if (!success) { - LOG(ERROR) << "Failed to save " << secret_chat_id << " to database"; - c->is_saved = false; - } else { - LOG(INFO) << "Successfully saved " << secret_chat_id << " to database"; - } - if (c->is_saved) { - if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } else { - save_secret_chat(c, secret_chat_id, c->log_event_id != 0); - } -} - -void ContactsManager::load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, - Promise promise) { - if (loaded_from_database_secret_chats_.count(secret_chat_id)) { - promise.set_value(Unit()); - return; - } - - CHECK(c == nullptr || !c->is_being_saved); - load_secret_chat_from_database_impl(secret_chat_id, std::move(promise)); -} - -void ContactsManager::load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise promise) { - LOG(INFO) << "Load " << secret_chat_id << " from database"; - auto &load_secret_chat_queries = load_secret_chat_from_database_queries_[secret_chat_id]; - load_secret_chat_queries.push_back(std::move(promise)); - if (load_secret_chat_queries.size() == 1u) { - G()->td_db()->get_sqlite_pmc()->get( - get_secret_chat_database_key(secret_chat_id), PromiseCreator::lambda([secret_chat_id](string value) { - send_closure(G()->contacts_manager(), &ContactsManager::on_load_secret_chat_from_database, secret_chat_id, - std::move(value), false); - })); - } -} - -void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force) { - if (G()->close_flag() && !force) { - // the secret chat is in Binlog and will be saved after restart - return; - } - - CHECK(secret_chat_id.is_valid()); - if (!loaded_from_database_secret_chats_.insert(secret_chat_id).second) { - return; - } - - auto it = load_secret_chat_from_database_queries_.find(secret_chat_id); - vector> promises; - if (it != load_secret_chat_from_database_queries_.end()) { - promises = std::move(it->second); - CHECK(!promises.empty()); - load_secret_chat_from_database_queries_.erase(it); - } - - LOG(INFO) << "Successfully loaded " << secret_chat_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_secret_chat_database_key(secret_chat_id), Auto()); - // return; - - SecretChat *c = get_secret_chat(secret_chat_id); - if (c == nullptr) { - if (!value.empty()) { - c = add_secret_chat(secret_chat_id); - - if (log_event_parse(*c, value).is_error()) { - LOG(ERROR) << "Failed to load " << secret_chat_id << " from database"; - secret_chats_.erase(secret_chat_id); - } else { - c->is_saved = true; - update_secret_chat(c, secret_chat_id, true, true); - } - } - } else { - CHECK(!c->is_saved); // secret chat can't be saved before load completes - CHECK(!c->is_being_saved); - auto new_value = get_secret_chat_database_value(c); - if (value != new_value) { - save_secret_chat_to_database_impl(c, secret_chat_id, std::move(new_value)); - } else if (c->log_event_id != 0) { - binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); - c->log_event_id = 0; - } - } - - // TODO load users asynchronously - if (c != nullptr && !have_user_force(c->user_id, "on_load_secret_chat_from_database")) { - LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id; - } - - set_promises(promises); -} - -bool ContactsManager::have_secret_chat_force(SecretChatId secret_chat_id, const char *source) { - return get_secret_chat_force(secret_chat_id, source) != nullptr; -} - -ContactsManager::SecretChat *ContactsManager::get_secret_chat_force(SecretChatId secret_chat_id, const char *source) { - if (!secret_chat_id.is_valid()) { - return nullptr; - } - - SecretChat *c = get_secret_chat(secret_chat_id); - if (c != nullptr) { - if (!have_user_force(c->user_id, source)) { - LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id << " from " << source; - } - return c; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (loaded_from_database_secret_chats_.count(secret_chat_id)) { - return nullptr; - } - - LOG(INFO) << "Trying to load " << secret_chat_id << " from database from " << source; - on_load_secret_chat_from_database( - secret_chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_secret_chat_database_key(secret_chat_id)), true); - return get_secret_chat(secret_chat_id); -} - -void ContactsManager::save_user_full(const UserFull *user_full, UserId user_id) { - if (!G()->use_chat_info_database()) { - return; - } - - LOG(INFO) << "Trying to save to database full " << user_id; - CHECK(user_full != nullptr); - G()->td_db()->get_sqlite_pmc()->set(get_user_full_database_key(user_id), get_user_full_database_value(user_full), - Auto()); -} - -string ContactsManager::get_user_full_database_key(UserId user_id) { - return PSTRING() << "usf" << user_id.get(); -} - -string ContactsManager::get_user_full_database_value(const UserFull *user_full) { - return log_event_store(*user_full).as_slice().str(); -} - -void ContactsManager::on_load_user_full_from_database(UserId user_id, string value) { - LOG(INFO) << "Successfully loaded full " << user_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); - // return; - - if (get_user_full(user_id) != nullptr || value.empty()) { - return; - } - - UserFull *user_full = add_user_full(user_id); - auto status = log_event_parse(*user_full, value); - if (status.is_error()) { - // can't happen unless database is broken - LOG(ERROR) << "Repair broken full " << user_id << ' ' << format::as_hex_dump<4>(Slice(value)); - - // just clean all known data about the user and pretend that there was nothing in the database - users_full_.erase(user_id); - G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); - return; - } - - Dependencies dependencies; - dependencies.add(user_id); - if (!dependencies.resolve_force(td_, "on_load_user_full_from_database")) { - users_full_.erase(user_id); - G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); - return; - } - - if (user_full->need_phone_number_privacy_exception && is_user_contact(user_id)) { - user_full->need_phone_number_privacy_exception = false; - } - - User *u = get_user(user_id); - CHECK(u != nullptr); - drop_user_full_photos(user_full, user_id, u->photo.id, "on_load_user_full_from_database"); - if (!user_full->photo.is_empty()) { - register_user_photo(u, user_id, user_full->photo); - } - if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { - register_suggested_profile_photo(user_full->fallback_photo); - } - - td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, false); - - user_full->is_update_user_full_sent = true; - update_user_full(user_full, user_id, "on_load_user_full_from_database", true); - - if (is_user_deleted(u)) { - drop_user_full(user_id); - } else if (user_full->expires_at == 0.0) { - reload_user_full(user_id, Auto(), "on_load_user_full_from_database"); - } -} - -ContactsManager::UserFull *ContactsManager::get_user_full_force(UserId user_id, const char *source) { - if (!have_user_force(user_id, source)) { - return nullptr; - } - - UserFull *user_full = get_user_full(user_id); - if (user_full != nullptr) { - return user_full; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (!unavailable_user_fulls_.insert(user_id).second) { - return nullptr; - } - - LOG(INFO) << "Trying to load full " << user_id << " from database from " << source; - on_load_user_full_from_database(user_id, - G()->td_db()->get_sqlite_sync_pmc()->get(get_user_full_database_key(user_id))); - return get_user_full(user_id); -} - -void ContactsManager::save_chat_full(const ChatFull *chat_full, ChatId chat_id) { - if (!G()->use_chat_info_database()) { - return; - } - - LOG(INFO) << "Trying to save to database full " << chat_id; - CHECK(chat_full != nullptr); - G()->td_db()->get_sqlite_pmc()->set(get_chat_full_database_key(chat_id), get_chat_full_database_value(chat_full), - Auto()); -} - -string ContactsManager::get_chat_full_database_key(ChatId chat_id) { - return PSTRING() << "grf" << chat_id.get(); -} - -string ContactsManager::get_chat_full_database_value(const ChatFull *chat_full) { - return log_event_store(*chat_full).as_slice().str(); -} - -void ContactsManager::on_load_chat_full_from_database(ChatId chat_id, string value) { - LOG(INFO) << "Successfully loaded full " << chat_id << " of size " << value.size() << " from database"; - // G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); - // return; - - if (get_chat_full(chat_id) != nullptr || value.empty()) { - return; - } - - ChatFull *chat_full = add_chat_full(chat_id); - auto status = log_event_parse(*chat_full, value); - if (status.is_error()) { - // can't happen unless database is broken - LOG(ERROR) << "Repair broken full " << chat_id << ' ' << format::as_hex_dump<4>(Slice(value)); - - // just clean all known data about the chat and pretend that there was nothing in the database - chats_full_.erase(chat_id); - G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); - return; - } - - Dependencies dependencies; - dependencies.add(chat_id); - dependencies.add(chat_full->creator_user_id); - for (auto &participant : chat_full->participants) { - dependencies.add_message_sender_dependencies(participant.dialog_id_); - dependencies.add(participant.inviter_user_id_); - } - dependencies.add(chat_full->invite_link.get_creator_user_id()); - if (!dependencies.resolve_force(td_, "on_load_chat_full_from_database")) { - chats_full_.erase(chat_id); - G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto()); - return; - } - - Chat *c = get_chat(chat_id); - CHECK(c != nullptr); - - bool need_invite_link = c->is_active && c->status.can_manage_invite_links(); - bool have_invite_link = chat_full->invite_link.is_valid(); - if (need_invite_link != have_invite_link) { - if (need_invite_link) { - // ignore ChatFull without invite link - chats_full_.erase(chat_id); - return; - } else { - chat_full->invite_link = DialogInviteLink(); - } - } - - if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { - chat_full->photo = Photo(); - if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto(), "on_load_chat_full_from_database"); - } - } - - auto photo = std::move(chat_full->photo); - chat_full->photo = Photo(); - on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); - - td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, false); - - chat_full->is_update_chat_full_sent = true; - update_chat_full(chat_full, chat_id, "on_load_chat_full_from_database", true); -} - -ContactsManager::ChatFull *ContactsManager::get_chat_full_force(ChatId chat_id, const char *source) { - if (!have_chat_force(chat_id, source)) { - return nullptr; - } - - ChatFull *chat_full = get_chat_full(chat_id); - if (chat_full != nullptr) { - return chat_full; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (!unavailable_chat_fulls_.insert(chat_id).second) { - return nullptr; - } - - LOG(INFO) << "Trying to load full " << chat_id << " from database from " << source; - on_load_chat_full_from_database(chat_id, - G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_full_database_key(chat_id))); - return get_chat_full(chat_id); -} - -void ContactsManager::save_channel_full(const ChannelFull *channel_full, ChannelId channel_id) { - if (!G()->use_chat_info_database()) { - return; - } - - LOG(INFO) << "Trying to save to database full " << channel_id; - CHECK(channel_full != nullptr); - G()->td_db()->get_sqlite_pmc()->set(get_channel_full_database_key(channel_id), - get_channel_full_database_value(channel_full), Auto()); -} - -string ContactsManager::get_channel_full_database_key(ChannelId channel_id) { - return PSTRING() << "chf" << channel_id.get(); -} - -string ContactsManager::get_channel_full_database_value(const ChannelFull *channel_full) { - return log_event_store(*channel_full).as_slice().str(); -} - -void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source) { - LOG(INFO) << "Successfully loaded full " << channel_id << " of size " << value.size() << " from database from " - << source; - // G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); - // return; - - if (get_channel_full(channel_id, true, "on_load_channel_full_from_database") != nullptr || value.empty()) { - return; - } - - ChannelFull *channel_full = add_channel_full(channel_id); - auto status = log_event_parse(*channel_full, value); - if (status.is_error()) { - // can't happen unless database is broken - LOG(ERROR) << "Repair broken full " << channel_id << ' ' << format::as_hex_dump<4>(Slice(value)); - - // just clean all known data about the channel and pretend that there was nothing in the database - channels_full_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); - return; - } - - Dependencies dependencies; - dependencies.add(channel_id); - // must not depend on the linked_dialog_id itself, because message database can be disabled - // the Dialog will be forcely created in update_channel_full - dependencies.add_dialog_dependencies(DialogId(channel_full->linked_channel_id)); - dependencies.add(channel_full->migrated_from_chat_id); - for (auto bot_user_id : channel_full->bot_user_ids) { - dependencies.add(bot_user_id); - } - dependencies.add(channel_full->invite_link.get_creator_user_id()); - if (!dependencies.resolve_force(td_, source)) { - channels_full_.erase(channel_id); - G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto()); - return; - } - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - - bool need_invite_link = c->status.can_manage_invite_links(); - bool have_invite_link = channel_full->invite_link.is_valid(); - if (need_invite_link != have_invite_link) { - if (need_invite_link) { - // ignore ChannelFull without invite link - channels_full_.erase(channel_id); - return; - } else { - channel_full->invite_link = DialogInviteLink(); - } - } - - if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { - channel_full->photo = Photo(); - if (c->photo.small_file_id.is_valid()) { - channel_full->expires_at = 0.0; - } - } - auto photo = std::move(channel_full->photo); - channel_full->photo = Photo(); - on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); - - if (channel_full->participant_count < channel_full->administrator_count) { - channel_full->participant_count = channel_full->administrator_count; - } - if (c->participant_count != 0 && c->participant_count != channel_full->participant_count) { - channel_full->participant_count = c->participant_count; - - if (channel_full->participant_count < channel_full->administrator_count) { - channel_full->participant_count = channel_full->administrator_count; - channel_full->expires_at = 0.0; - - c->participant_count = channel_full->participant_count; - c->is_changed = true; - } - } - if (c->can_be_deleted != channel_full->can_be_deleted) { - c->can_be_deleted = channel_full->can_be_deleted; - c->need_save_to_database = true; - } - - if (invalidated_channels_full_.erase(channel_id) > 0 || - (!c->is_slow_mode_enabled && channel_full->slow_mode_delay != 0)) { - do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); - } - - td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, false); - - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - channel_full->bot_user_ids, true); - - update_channel(c, channel_id); - - channel_full->is_update_channel_full_sent = true; - update_channel_full(channel_full, channel_id, "on_load_channel_full_from_database", true); - - if (channel_full->expires_at == 0.0) { - load_channel_full(channel_id, true, Auto(), "on_load_channel_full_from_database"); - } -} - -ContactsManager::ChannelFull *ContactsManager::get_channel_full_force(ChannelId channel_id, bool only_local, - const char *source) { - if (!have_channel_force(channel_id, source)) { - return nullptr; - } - - ChannelFull *channel_full = get_channel_full(channel_id, only_local, source); - if (channel_full != nullptr) { - return channel_full; - } - if (!G()->use_chat_info_database()) { - return nullptr; - } - if (!unavailable_channel_fulls_.insert(channel_id).second) { - return nullptr; - } - - LOG(INFO) << "Trying to load full " << channel_id << " from database from " << source; - on_load_channel_full_from_database( - channel_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_full_database_key(channel_id)), source); - return get_channel_full(channel_id, only_local, source); -} - -void ContactsManager::for_each_secret_chat_with_user(UserId user_id, const std::function &f) { - auto it = secret_chats_with_user_.find(user_id); - if (it != secret_chats_with_user_.end()) { - for (auto secret_chat_id : it->second) { - f(secret_chat_id); - } - } -} - -void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) { - CHECK(u != nullptr); - - if (u->is_being_updated) { - LOG(ERROR) << "Detected recursive update of " << user_id; - } - u->is_being_updated = true; - SCOPE_EXIT { - u->is_being_updated = false; - }; - - if (user_id == get_my_id()) { - if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) { - td_->option_manager_->set_option_boolean("is_premium", u->is_premium); - send_closure(td_->config_manager_, &ConfigManager::request_config, true); - td_->reaction_manager_->reload_reaction_list(ReactionListType::Top); - td_->messages_manager_->update_is_translatable(u->is_premium); - } - } - if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) { - update_contacts_hints(u, user_id, from_database); - u->is_username_changed = false; - } - if (u->is_is_contact_changed) { - td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact); - send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, - DialogId(user_id), "is_contact"); - if (is_user_contact(u, user_id, false)) { - auto user_full = get_user_full(user_id); - if (user_full != nullptr && user_full->need_phone_number_privacy_exception) { - on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, false); - update_user_full(user_full, user_id, "update_user"); - } - } - u->is_is_contact_changed = false; - } - if (u->is_is_mutual_contact_changed) { - if (!from_database && u->is_update_user_sent) { - send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, DialogId(user_id)); - } - u->is_is_mutual_contact_changed = false; - } - if (u->is_is_deleted_changed) { - td_->messages_manager_->on_dialog_user_is_deleted_updated(DialogId(user_id), u->is_deleted); - if (u->is_deleted) { - auto user_full = get_user_full(user_id); // must not load user_full from database before sending updateUser - if (user_full != nullptr) { - u->is_full_info_changed = false; - drop_user_full(user_id); - } - } - u->is_is_deleted_changed = false; - } - if (u->is_is_premium_changed) { - send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, - DialogId(user_id), "is_premium"); - u->is_is_premium_changed = false; - } - if (u->is_name_changed) { - auto messages_manager = td_->messages_manager_.get(); - messages_manager->on_dialog_title_updated(DialogId(user_id)); - for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { - messages_manager->on_dialog_title_updated(DialogId(secret_chat_id)); - }); - u->is_name_changed = false; - } - if (u->is_photo_changed) { - auto messages_manager = td_->messages_manager_.get(); - messages_manager->on_dialog_photo_updated(DialogId(user_id)); - for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { - messages_manager->on_dialog_photo_updated(DialogId(secret_chat_id)); - }); - u->is_photo_changed = false; - } - if (u->is_accent_color_changed) { - auto messages_manager = td_->messages_manager_.get(); - messages_manager->on_dialog_accent_colors_updated(DialogId(user_id)); - for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { - messages_manager->on_dialog_accent_colors_updated(DialogId(secret_chat_id)); - }); - u->is_accent_color_changed = false; - } - if (u->is_phone_number_changed) { - if (!u->phone_number.empty() && !td_->auth_manager_->is_bot()) { - resolved_phone_numbers_[u->phone_number] = user_id; - } - u->is_phone_number_changed = false; - } - auto unix_time = G()->unix_time(); - if (u->is_status_changed && user_id != get_my_id()) { - auto left_time = get_user_was_online(u, user_id, unix_time) - G()->server_time(); - if (left_time >= 0 && left_time < 30 * 86400) { - left_time += 2.0; // to guarantee expiration - LOG(DEBUG) << "Set online timeout for " << user_id << " in " << left_time << " seconds"; - user_online_timeout_.set_timeout_in(user_id.get(), left_time); - } else { - LOG(DEBUG) << "Cancel online timeout for " << user_id; - user_online_timeout_.cancel_timeout(user_id.get()); - } - } - if (u->is_stories_hidden_changed) { - send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, - DialogId(user_id), "stories_hidden"); - u->is_stories_hidden_changed = false; - } - if (!td_->auth_manager_->is_bot()) { - if (u->restriction_reasons.empty()) { - restricted_user_ids_.erase(user_id); - } else { - restricted_user_ids_.insert(user_id); - } - } - - auto effective_emoji_status = u->emoji_status.get_effective_emoji_status(u->is_premium, unix_time); - if (effective_emoji_status != u->last_sent_emoji_status) { - if (!u->last_sent_emoji_status.is_empty()) { - user_emoji_status_timeout_.cancel_timeout(user_id.get()); - } - u->last_sent_emoji_status = effective_emoji_status; - if (!u->last_sent_emoji_status.is_empty()) { - auto until_date = u->last_sent_emoji_status.get_until_date(); - auto left_time = until_date - unix_time; - if (left_time >= 0 && left_time < 30 * 86400) { - user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time); - } - } - u->is_changed = true; - - auto messages_manager = td_->messages_manager_.get(); - messages_manager->on_dialog_emoji_status_updated(DialogId(user_id)); - for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { - messages_manager->on_dialog_emoji_status_updated(DialogId(secret_chat_id)); - }); - u->is_emoji_status_changed = false; - } else if (u->is_emoji_status_changed) { - LOG(DEBUG) << "Emoji status for " << user_id << " has changed"; - u->need_save_to_database = true; - u->is_emoji_status_changed = false; - } - - if (u->is_deleted) { - td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>()); - } - if (from_binlog || from_database) { - td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), u->usernames, true); - } - - LOG(DEBUG) << "Update " << user_id << ": need_save_to_database = " << u->need_save_to_database - << ", is_changed = " << u->is_changed << ", is_status_changed = " << u->is_status_changed - << ", from_binlog = " << from_binlog << ", from_database = " << from_database; - u->need_save_to_database |= u->is_changed; - if (u->need_save_to_database) { - if (!from_database) { - u->is_saved = false; - } - u->need_save_to_database = false; - } - if (u->is_changed) { - send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); - u->is_changed = false; - u->is_status_changed = false; - u->is_update_user_sent = true; - } - if (u->is_status_changed) { - if (!from_database) { - u->is_status_saved = false; - } - CHECK(u->is_update_user_sent); - send_closure( - G()->td(), &Td::send_update, - make_tl_object(user_id.get(), get_user_status_object(user_id, u, unix_time))); - u->is_status_changed = false; - } - if (u->is_online_status_changed) { - td_->dialog_participant_manager_->update_user_online_member_count(user_id); - u->is_online_status_changed = false; - } - - if (!from_database) { - save_user(u, user_id, from_binlog); - } - - if (u->cache_version != User::CACHE_VERSION && !u->is_repaired && - have_input_peer_user(u, user_id, AccessRights::Read) && !G()->close_flag()) { - u->is_repaired = true; - - LOG(INFO) << "Repairing cache of " << user_id; - reload_user(user_id, Promise(), "update_user"); - } - - if (u->is_full_info_changed) { - u->is_full_info_changed = false; - auto user_full = get_user_full(user_id); - if (user_full != nullptr) { - user_full->need_send_update = true; - update_user_full(user_full, user_id, "update_user is_full_info_changed"); - reload_user_full(user_id, Promise(), "update_user"); - } - } -} - -void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, bool from_database) { - CHECK(c != nullptr); - - if (c->is_being_updated) { - LOG(ERROR) << "Detected recursive update of " << chat_id; - } - c->is_being_updated = true; - SCOPE_EXIT { - c->is_being_updated = false; - }; - - bool need_update_chat_full = false; - if (c->is_photo_changed) { - td_->messages_manager_->on_dialog_photo_updated(DialogId(chat_id)); - c->is_photo_changed = false; - - auto chat_full = get_chat_full(chat_id); // must not load ChatFull - if (chat_full != nullptr && - !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { - on_update_chat_full_photo(chat_full, chat_id, Photo()); - if (chat_full->is_update_chat_full_sent) { - need_update_chat_full = true; - } - if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto(), "update_chat"); - } - } - } - if (c->is_title_changed) { - td_->messages_manager_->on_dialog_title_updated(DialogId(chat_id)); - c->is_title_changed = false; - } - if (c->is_default_permissions_changed) { - td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(chat_id)); - c->is_default_permissions_changed = false; - } - if (c->is_is_active_changed) { - update_dialogs_for_discussion(DialogId(chat_id), c->is_active && c->status.is_creator()); - c->is_is_active_changed = false; - } - if (c->is_status_changed) { - if (!c->status.can_manage_invite_links()) { - td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(chat_id)); - } - if (!from_database) { - // if the chat is empty, this can add it to a chat list or remove it from a chat list - send_closure_later(G()->messages_manager(), &MessagesManager::try_update_dialog_pos, DialogId(chat_id)); - - if (c->is_update_basic_group_sent) { - // reload the chat to repair its status if it is changed back after receiving of outdated data - create_actor( - "ReloadChatSleepActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), chat_id](Unit) { - send_closure(actor_id, &ContactsManager::reload_chat, chat_id, Promise(), "ReloadChatSleepActor"); - })) - .release(); - } - } - c->is_status_changed = false; - } - if (c->is_noforwards_changed) { - td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(chat_id)); - c->is_noforwards_changed = false; - } - - if (need_update_chat_full) { - auto chat_full = get_chat_full(chat_id); - CHECK(chat_full != nullptr); - update_chat_full(chat_full, chat_id, "update_chat"); - } - - LOG(DEBUG) << "Update " << chat_id << ": need_save_to_database = " << c->need_save_to_database - << ", is_changed = " << c->is_changed; - c->need_save_to_database |= c->is_changed; - if (c->need_save_to_database) { - if (!from_database) { - c->is_saved = false; - } - c->need_save_to_database = false; - } - if (c->is_changed) { - send_closure(G()->td(), &Td::send_update, get_update_basic_group_object(chat_id, c)); - c->is_changed = false; - c->is_update_basic_group_sent = true; - } - - if (!from_database) { - save_chat(c, chat_id, from_binlog); - } - - if (c->cache_version != Chat::CACHE_VERSION && !c->is_repaired && have_input_peer_chat(c, AccessRights::Read) && - !G()->close_flag()) { - c->is_repaired = true; - - LOG(INFO) << "Repairing cache of " << chat_id; - reload_chat(chat_id, Promise(), "update_chat"); - } -} - -void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from_binlog, bool from_database) { - CHECK(c != nullptr); - - if (c->is_being_updated) { - LOG(ERROR) << "Detected recursive update of " << channel_id; - } - c->is_being_updated = true; - SCOPE_EXIT { - c->is_being_updated = false; - }; - - bool need_update_channel_full = false; - if (c->is_photo_changed) { - td_->messages_manager_->on_dialog_photo_updated(DialogId(channel_id)); - c->is_photo_changed = false; - - auto channel_full = get_channel_full(channel_id, true, "update_channel"); - if (channel_full != nullptr && - !is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo, false)) { - on_update_channel_full_photo(channel_full, channel_id, Photo()); - if (channel_full->is_update_channel_full_sent) { - need_update_channel_full = true; - } - if (c->photo.small_file_id.is_valid()) { - if (channel_full->expires_at > 0.0) { - channel_full->expires_at = 0.0; - channel_full->need_save_to_database = true; - } - send_get_channel_full_query(channel_full, channel_id, Auto(), "update_channel"); - } - } - } - if (c->is_accent_color_changed) { - td_->messages_manager_->on_dialog_accent_colors_updated(DialogId(channel_id)); - c->is_accent_color_changed = false; - } - if (c->is_title_changed) { - td_->messages_manager_->on_dialog_title_updated(DialogId(channel_id)); - c->is_title_changed = false; - } - if (c->is_status_changed) { - c->status.update_restrictions(); - auto until_date = c->status.get_until_date(); - double left_time = 0; - if (until_date > 0) { - left_time = until_date - G()->server_time() + 2; - if (left_time <= 0) { - c->status.update_restrictions(); - CHECK(c->status.get_until_date() == 0); - } - } - if (left_time > 0 && left_time < 366 * 86400) { - channel_unban_timeout_.set_timeout_in(channel_id.get(), left_time); - } else { - channel_unban_timeout_.cancel_timeout(channel_id.get()); - } - - if (c->is_megagroup) { - update_dialogs_for_discussion(DialogId(channel_id), c->status.is_administrator() && c->status.can_pin_messages()); - } - if (!c->status.is_member()) { - remove_inactive_channel(channel_id); - } - if (!c->status.can_manage_invite_links()) { - td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(channel_id)); - } - if (!from_database && c->is_update_supergroup_sent) { - // reload the channel to repair its status if it is changed back after receiving of outdated data - create_actor("ReloadChannelSleepActor", 1.0, - PromiseCreator::lambda([actor_id = actor_id(this), channel_id](Unit) { - send_closure(actor_id, &ContactsManager::reload_channel, channel_id, Promise(), - "ReloadChannelSleepActor"); - })) - .release(); - } - c->is_status_changed = false; - } - if (c->is_username_changed) { - if (c->status.is_creator()) { - update_created_public_channels(c, channel_id); - } - c->is_username_changed = false; - } - if (c->is_default_permissions_changed) { - td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(channel_id)); - if (c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, - ChannelType::Unknown)) { - remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); - } - c->is_default_permissions_changed = false; - } - if (c->is_has_location_changed) { - if (c->status.is_creator()) { - update_created_public_channels(c, channel_id); - } - c->is_has_location_changed = false; - } - if (c->is_creator_changed) { - update_created_public_channels(c, channel_id); - c->is_creator_changed = false; - } - if (c->is_noforwards_changed) { - td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(channel_id)); - c->is_noforwards_changed = false; - } - if (c->is_stories_hidden_changed) { - send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, - DialogId(channel_id), "stories_hidden"); - c->is_stories_hidden_changed = false; - } - auto unix_time = G()->unix_time(); - auto effective_emoji_status = c->emoji_status.get_effective_emoji_status(true, unix_time); - if (effective_emoji_status != c->last_sent_emoji_status) { - if (!c->last_sent_emoji_status.is_empty()) { - channel_emoji_status_timeout_.cancel_timeout(channel_id.get()); - } - c->last_sent_emoji_status = effective_emoji_status; - if (!c->last_sent_emoji_status.is_empty()) { - auto until_date = c->last_sent_emoji_status.get_until_date(); - auto left_time = until_date - unix_time; - if (left_time >= 0 && left_time < 30 * 86400) { - channel_emoji_status_timeout_.set_timeout_in(channel_id.get(), left_time); - } - } - - td_->messages_manager_->on_dialog_emoji_status_updated(DialogId(channel_id)); - } - c->is_emoji_status_changed = false; - - if (!td_->auth_manager_->is_bot()) { - if (c->restriction_reasons.empty()) { - restricted_channel_ids_.erase(channel_id); - } else { - restricted_channel_ids_.insert(channel_id); - } - } - - if (from_binlog || from_database) { - td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), c->usernames, true); - } - - if (!is_channel_public(c) && !c->has_linked_channel) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, - DialogId(channel_id), DialogId(), false); - } - - if (need_update_channel_full) { - auto channel_full = get_channel_full(channel_id, true, "update_channel"); - CHECK(channel_full != nullptr); - update_channel_full(channel_full, channel_id, "update_channel"); - } - - LOG(DEBUG) << "Update " << channel_id << ": need_save_to_database = " << c->need_save_to_database - << ", is_changed = " << c->is_changed; - c->need_save_to_database |= c->is_changed; - if (c->need_save_to_database) { - if (!from_database) { - c->is_saved = false; - } - c->need_save_to_database = false; - } - if (c->is_changed) { - send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, c)); - c->is_changed = false; - c->is_update_supergroup_sent = true; - } - - if (!from_database) { - save_channel(c, channel_id, from_binlog); - } - - bool have_read_access = have_input_peer_channel(c, channel_id, AccessRights::Read); - bool is_member = c->status.is_member(); - if (c->had_read_access && !have_read_access) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), - Promise()); - if (G()->use_message_database()) { - G()->td_db()->get_sqlite_pmc()->erase(get_channel_recommendations_database_key(channel_id), Promise()); - } - } else if (!from_database && c->was_member != is_member) { - DialogId dialog_id(channel_id); - send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel", - true, true); - } - c->had_read_access = have_read_access; - c->was_member = is_member; - - if (c->cache_version != Channel::CACHE_VERSION && !c->is_repaired && - have_input_peer_channel(c, channel_id, AccessRights::Read) && !G()->close_flag()) { - c->is_repaired = true; - - LOG(INFO) << "Repairing cache of " << channel_id; - reload_channel(channel_id, Promise(), "update_channel"); - } -} - -void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog, - bool from_database) { - CHECK(c != nullptr); - - if (c->is_being_updated) { - LOG(ERROR) << "Detected recursive update of " << secret_chat_id; - } - c->is_being_updated = true; - SCOPE_EXIT { - c->is_being_updated = false; - }; - - LOG(DEBUG) << "Update " << secret_chat_id << ": need_save_to_database = " << c->need_save_to_database - << ", is_changed = " << c->is_changed; - c->need_save_to_database |= c->is_changed; - if (c->need_save_to_database) { - if (!from_database) { - c->is_saved = false; - } - c->need_save_to_database = false; - - DialogId dialog_id(secret_chat_id); - send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update secret chat", - true, true); - if (c->is_state_changed) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_secret_chat_state, secret_chat_id, - c->state); - c->is_state_changed = false; - } - if (c->is_ttl_changed) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_message_ttl, - DialogId(secret_chat_id), MessageTtl(c->ttl)); - c->is_ttl_changed = false; - } - } - if (c->is_changed) { - send_closure(G()->td(), &Td::send_update, get_update_secret_chat_object(secret_chat_id, c)); - c->is_changed = false; - } - - if (!from_database) { - save_secret_chat(c, secret_chat_id, from_binlog); - } -} - -void ContactsManager::update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database) { - CHECK(user_full != nullptr); - - if (user_full->is_being_updated) { - LOG(ERROR) << "Detected recursive update of full " << user_id << " from " << source; - } - user_full->is_being_updated = true; - SCOPE_EXIT { - user_full->is_being_updated = false; - }; - - unavailable_user_fulls_.erase(user_id); // don't needed anymore - if (user_full->is_common_chat_count_changed) { - td_->common_dialog_manager_->drop_common_dialogs_cache(user_id); - user_full->is_common_chat_count_changed = false; - } - if (true) { - vector file_ids; - if (!user_full->personal_photo.is_empty()) { - append(file_ids, photo_get_file_ids(user_full->personal_photo)); - } - if (!user_full->fallback_photo.is_empty()) { - append(file_ids, photo_get_file_ids(user_full->fallback_photo)); - } - if (!user_full->description_photo.is_empty()) { - append(file_ids, photo_get_file_ids(user_full->description_photo)); - } - if (user_full->description_animation_file_id.is_valid()) { - file_ids.push_back(user_full->description_animation_file_id); - } - if (user_full->registered_file_ids != file_ids) { - auto &file_source_id = user_full->file_source_id; - if (!file_source_id.is_valid()) { - file_source_id = user_full_file_source_ids_.get(user_id); - if (file_source_id.is_valid()) { - VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; - user_full_file_source_ids_.erase(user_id); - } else { - VLOG(file_references) << "Need to create new file source for full " << user_id; - file_source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); - } - } - - td_->file_manager_->change_files_source(file_source_id, user_full->registered_file_ids, file_ids); - user_full->registered_file_ids = std::move(file_ids); - } - } - - user_full->need_send_update |= user_full->is_changed; - user_full->need_save_to_database |= user_full->is_changed; - user_full->is_changed = false; - if (user_full->need_send_update || user_full->need_save_to_database) { - LOG(INFO) << "Update full " << user_id << " from " << source; - } - if (user_full->need_send_update) { - { - auto u = get_user(user_id); - CHECK(u == nullptr || u->is_update_user_sent); - } - if (!user_full->is_update_user_full_sent) { - LOG(ERROR) << "Send partial updateUserFullInfo for " << user_id << " from " << source; - user_full->is_update_user_full_sent = true; - } - send_closure(G()->td(), &Td::send_update, - make_tl_object(get_user_id_object(user_id, "updateUserFullInfo"), - get_user_full_info_object(user_id, user_full))); - user_full->need_send_update = false; - } - if (user_full->need_save_to_database) { - if (!from_database) { - save_user_full(user_full, user_id); - } - user_full->need_save_to_database = false; - } -} - -void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database) { - CHECK(chat_full != nullptr); - - if (chat_full->is_being_updated) { - LOG(ERROR) << "Detected recursive update of full " << chat_id << " from " << source; - } - chat_full->is_being_updated = true; - SCOPE_EXIT { - chat_full->is_being_updated = false; - }; - - unavailable_chat_fulls_.erase(chat_id); // don't needed anymore - - chat_full->need_send_update |= chat_full->is_changed; - chat_full->need_save_to_database |= chat_full->is_changed; - chat_full->is_changed = false; - if (chat_full->need_send_update || chat_full->need_save_to_database) { - LOG(INFO) << "Update full " << chat_id << " from " << source; - } - if (chat_full->need_send_update) { - vector administrators; - vector bot_user_ids; - for (const auto &participant : chat_full->participants) { - if (participant.status_.is_administrator() && participant.dialog_id_.get_type() == DialogType::User) { - administrators.emplace_back(participant.dialog_id_.get_user_id(), participant.status_.get_rank(), - participant.status_.is_creator()); - } - if (participant.dialog_id_.get_type() == DialogType::User) { - auto user_id = participant.dialog_id_.get_user_id(); - if (is_user_bot(user_id)) { - bot_user_ids.push_back(user_id); - } - } - } - td::remove_if(chat_full->bot_commands, [&bot_user_ids](const BotCommands &commands) { - return !td::contains(bot_user_ids, commands.get_bot_user_id()); - }); - - td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), - chat_full->version != -1, from_database); - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(chat_id), - std::move(bot_user_ids), from_database); - - { - Chat *c = get_chat(chat_id); - CHECK(c == nullptr || c->is_update_basic_group_sent); - } - if (!chat_full->is_update_chat_full_sent) { - LOG(ERROR) << "Send partial updateBasicGroupFullInfo for " << chat_id << " from " << source; - chat_full->is_update_chat_full_sent = true; - } - send_closure( - G()->td(), &Td::send_update, - make_tl_object(get_basic_group_id_object(chat_id, "update_chat_full"), - get_basic_group_full_info_object(chat_id, chat_full))); - chat_full->need_send_update = false; - } - if (chat_full->need_save_to_database) { - if (!from_database) { - save_chat_full(chat_full, chat_id); - } - chat_full->need_save_to_database = false; - } -} - -void ContactsManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, - bool from_database) { - CHECK(channel_full != nullptr); - - if (channel_full->is_being_updated) { - LOG(ERROR) << "Detected recursive update of full " << channel_id << " from " << source; - } - channel_full->is_being_updated = true; - SCOPE_EXIT { - channel_full->is_being_updated = false; - }; - - unavailable_channel_fulls_.erase(channel_id); // don't needed anymore - - CHECK(channel_full->participant_count >= channel_full->administrator_count); - - if (channel_full->is_slow_mode_next_send_date_changed) { - auto now = G()->server_time(); - if (channel_full->slow_mode_next_send_date > now + 3601) { - channel_full->slow_mode_next_send_date = static_cast(now) + 3601; - } - if (channel_full->slow_mode_next_send_date <= now) { - channel_full->slow_mode_next_send_date = 0; - } - if (channel_full->slow_mode_next_send_date == 0) { - slow_mode_delay_timeout_.cancel_timeout(channel_id.get()); - } else { - slow_mode_delay_timeout_.set_timeout_in(channel_id.get(), channel_full->slow_mode_next_send_date - now + 0.002); - } - channel_full->is_slow_mode_next_send_date_changed = false; - } - - if (channel_full->need_save_to_database) { - channel_full->is_changed |= td::remove_if( - channel_full->bot_commands, [bot_user_ids = &channel_full->bot_user_ids](const BotCommands &commands) { - return !td::contains(*bot_user_ids, commands.get_bot_user_id()); - }); - } - - channel_full->need_send_update |= channel_full->is_changed; - channel_full->need_save_to_database |= channel_full->is_changed; - channel_full->is_changed = false; - if (channel_full->need_send_update || channel_full->need_save_to_database) { - LOG(INFO) << "Update full " << channel_id << " from " << source; - } - if (channel_full->need_send_update) { - if (channel_full->linked_channel_id.is_valid()) { - td_->dialog_manager_->force_create_dialog(DialogId(channel_full->linked_channel_id), "update_channel_full", true); - } - - { - Channel *c = get_channel(channel_id); - CHECK(c == nullptr || c->is_update_supergroup_sent); - } - if (!channel_full->is_update_channel_full_sent) { - LOG(ERROR) << "Send partial updateSupergroupFullInfo for " << channel_id << " from " << source; - channel_full->is_update_channel_full_sent = true; - } - send_closure( - G()->td(), &Td::send_update, - make_tl_object(get_supergroup_id_object(channel_id, "update_channel_full"), - get_supergroup_full_info_object(channel_id, channel_full))); - channel_full->need_send_update = false; - } - if (channel_full->need_save_to_database) { - if (!from_database) { - save_channel_full(channel_full, channel_id); - } - channel_full->need_save_to_database = false; - } -} - -void ContactsManager::on_get_users(vector> &&users, const char *source) { - for (auto &user : users) { - on_get_user(std::move(user), source); - } -} - -void ContactsManager::on_get_user_full(tl_object_ptr &&user) { - LOG(INFO) << "Receive " << to_string(user); - - UserId user_id(user->id_); - User *u = get_user(user_id); - if (u == nullptr) { - LOG(ERROR) << "Failed to find " << user_id; - return; - } - - apply_pending_user_photo(u, user_id); - - td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user->notify_settings_), - "on_get_user_full"); - - td_->messages_manager_->on_update_dialog_background(DialogId(user_id), std::move(user->wallpaper_)); - - td_->messages_manager_->on_update_dialog_theme_name(DialogId(user_id), std::move(user->theme_emoticon_)); - - td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(user_id), - MessageId(ServerMessageId(user->pinned_msg_id_))); - - td_->messages_manager_->on_update_dialog_folder_id(DialogId(user_id), FolderId(user->folder_id_)); - - td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(user_id), user->has_scheduled_); - - td_->messages_manager_->on_update_dialog_message_ttl(DialogId(user_id), MessageTtl(user->ttl_period_)); - - td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_, - user->blocked_my_stories_from_); - - td_->messages_manager_->on_update_dialog_is_translatable(DialogId(user_id), !user->translations_disabled_); - - send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(user_id), - std::move(user->stories_), Promise()); - - UserFull *user_full = add_user_full(user_id); - user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; - - on_update_user_full_is_blocked(user_full, user_id, user->blocked_, user->blocked_my_stories_from_); - on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_); - on_update_user_full_location(user_full, user_id, DialogLocation(td_, std::move(user->business_location_))); - on_update_user_full_work_hours(user_full, user_id, BusinessWorkHours(std::move(user->business_work_hours_))); - on_update_user_full_away_message(user_full, user_id, BusinessAwayMessage(std::move(user->business_away_message_))); - on_update_user_full_greeting_message(user_full, user_id, - BusinessGreetingMessage(std::move(user->business_greeting_message_))); - on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, - user->settings_->need_contacts_exception_); - on_update_user_full_wallpaper_overridden(user_full, user_id, user->wallpaper_overridden_); - - bool can_pin_messages = user->can_pin_message_; - bool can_be_called = user->phone_calls_available_ && !user->phone_calls_private_; - bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_; - bool has_private_calls = user->phone_calls_private_; - bool voice_messages_forbidden = u->is_premium ? user->voice_messages_forbidden_ : false; - auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_)); - AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup); - AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast); - bool has_pinned_stories = user->stories_pinned_available_; - if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || - user_full->has_private_calls != has_private_calls || - user_full->group_administrator_rights != group_administrator_rights || - user_full->broadcast_administrator_rights != broadcast_administrator_rights || - user_full->premium_gift_options != premium_gift_options || - user_full->voice_messages_forbidden != voice_messages_forbidden || - user_full->can_pin_messages != can_pin_messages || user_full->has_pinned_stories != has_pinned_stories) { - user_full->can_be_called = can_be_called; - user_full->supports_video_calls = supports_video_calls; - user_full->has_private_calls = has_private_calls; - user_full->group_administrator_rights = group_administrator_rights; - user_full->broadcast_administrator_rights = broadcast_administrator_rights; - user_full->premium_gift_options = std::move(premium_gift_options); - user_full->voice_messages_forbidden = voice_messages_forbidden; - user_full->can_pin_messages = can_pin_messages; - user_full->has_pinned_stories = has_pinned_stories; - - user_full->is_changed = true; - } - if (user_full->private_forward_name != user->private_forward_name_) { - if (user_full->private_forward_name.empty() != user->private_forward_name_.empty()) { - user_full->is_changed = true; - } - user_full->private_forward_name = std::move(user->private_forward_name_); - user_full->need_save_to_database = true; - } - if (user_full->read_dates_private != user->read_dates_private_ || - user_full->contact_require_premium != user->contact_require_premium_) { - user_full->read_dates_private = user->read_dates_private_; - user_full->contact_require_premium = user->contact_require_premium_; - user_full->need_save_to_database = true; - } - if (user_full->about != user->about_) { - user_full->about = std::move(user->about_); - user_full->is_changed = true; - td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); - } - string description; - Photo description_photo; - FileId description_animation_file_id; - if (user->bot_info_ != nullptr && !td_->auth_manager_->is_bot()) { - description = std::move(user->bot_info_->description_); - description_photo = get_photo(td_, std::move(user->bot_info_->description_photo_), DialogId(user_id)); - auto document = std::move(user->bot_info_->description_document_); - if (document != nullptr) { - int32 document_id = document->get_id(); - if (document_id == telegram_api::document::ID) { - auto parsed_document = td_->documents_manager_->on_get_document( - move_tl_object_as(document), DialogId(user_id)); - if (parsed_document.type == Document::Type::Animation) { - description_animation_file_id = parsed_document.file_id; - } else { - LOG(ERROR) << "Receive non-animation document in bot description"; - } - } - } - - on_update_user_full_commands(user_full, user_id, std::move(user->bot_info_->commands_)); - on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_)); - } - if (user_full->description != description) { - user_full->description = std::move(description); - user_full->is_changed = true; - } - if (user_full->description_photo != description_photo || - user_full->description_animation_file_id != description_animation_file_id) { - user_full->description_photo = std::move(description_photo); - user_full->description_animation_file_id = description_animation_file_id; - user_full->is_changed = true; - } - - auto photo = get_photo(td_, std::move(user->profile_photo_), DialogId(user_id)); - auto personal_photo = get_photo(td_, std::move(user->personal_photo_), DialogId(user_id)); - auto fallback_photo = get_photo(td_, std::move(user->fallback_photo_), DialogId(user_id)); - // do_update_user_photo should be a no-op if server sent consistent data - const Photo *photo_ptr = nullptr; - bool is_personal = false; - if (!personal_photo.is_empty()) { - photo_ptr = &personal_photo; - is_personal = true; - } else if (!photo.is_empty()) { - photo_ptr = &photo; - } else { - photo_ptr = &fallback_photo; - } - bool is_photo_empty = photo_ptr->is_empty(); - do_update_user_photo(u, user_id, - as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, *photo_ptr, is_personal), - false, "on_get_user_full"); - if (photo != user_full->photo) { - user_full->photo = std::move(photo); - user_full->is_changed = true; - } - if (personal_photo != user_full->personal_photo) { - user_full->personal_photo = std::move(personal_photo); - user_full->is_changed = true; - } - if (fallback_photo != user_full->fallback_photo) { - user_full->fallback_photo = std::move(fallback_photo); - user_full->is_changed = true; - } - if (!user_full->photo.is_empty()) { - register_user_photo(u, user_id, user_full->photo); - } - if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { - register_suggested_profile_photo(user_full->fallback_photo); - } - if (is_photo_empty) { - drop_user_photos(user_id, true, "on_get_user_full"); - } - - // User must be updated before UserFull - if (u->is_changed) { - LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << user_id; - update_user(u, user_id); - } - - user_full->is_update_user_full_sent = true; - update_user_full(user_full, user_id, "on_get_user_full"); - - // update peer settings after UserFull is created and updated to not update twice need_phone_number_privacy_exception - td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user->settings_)); -} - -ContactsManager::UserPhotos *ContactsManager::add_user_photos(UserId user_id) { - CHECK(user_id.is_valid()); - auto &user_photos_ptr = user_photos_[user_id]; - if (user_photos_ptr == nullptr) { - user_photos_ptr = make_unique(); - } - return user_photos_ptr.get(); -} - -void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, - vector> photos) { - auto photo_count = narrow_cast(photos.size()); - int32 min_total_count = (offset >= 0 && photo_count > 0 ? offset : 0) + photo_count; - if (total_count < min_total_count) { - LOG(ERROR) << "Receive wrong photos total_count " << total_count << " for user " << user_id << ": receive " - << photo_count << " photos with offset " << offset; - total_count = min_total_count; - } - LOG_IF(ERROR, limit < photo_count) << "Requested not more than " << limit << " photos, but " << photo_count - << " received"; - - User *u = get_user(user_id); - if (u == nullptr) { - LOG(ERROR) << "Can't find " << user_id; - return; - } - - if (offset == -1) { - // from reload_user_profile_photo - CHECK(limit == 1); - for (auto &photo_ptr : photos) { - if (photo_ptr->get_id() == telegram_api::photo::ID) { - auto server_photo = telegram_api::move_object_as(photo_ptr); - if (server_photo->id_ == u->photo.id) { - auto profile_photo = convert_photo_to_profile_photo(server_photo, u->photo.is_personal); - if (profile_photo) { - LOG_IF(ERROR, u->access_hash == -1) << "Receive profile photo of " << user_id << " without access hash"; - get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(profile_photo)); - } else { - LOG(ERROR) << "Failed to get profile photo from " << to_string(server_photo); - } - } - - auto photo = get_photo(td_, std::move(server_photo), DialogId(user_id)); - register_user_photo(u, user_id, photo); - } - } - return; - } - - LOG(INFO) << "Receive " << photo_count << " photos of " << user_id << " out of " << total_count << " with offset " - << offset << " and limit " << limit; - UserPhotos *user_photos = add_user_photos(user_id); - user_photos->count = total_count; - CHECK(!user_photos->pending_requests.empty()); - - if (user_photos->offset == -1) { - user_photos->offset = 0; - CHECK(user_photos->photos.empty()); - } - - if (offset != narrow_cast(user_photos->photos.size()) + user_photos->offset) { - LOG(INFO) << "Inappropriate offset to append " << user_id << " profile photos to cache: offset = " << offset - << ", current_offset = " << user_photos->offset << ", photo_count = " << user_photos->photos.size(); - user_photos->photos.clear(); - user_photos->offset = offset; - } - - for (auto &photo : photos) { - auto user_photo = get_photo(td_, std::move(photo), DialogId(user_id)); - if (user_photo.is_empty()) { - LOG(ERROR) << "Receive empty profile photo in getUserPhotos request for " << user_id << " with offset " << offset - << " and limit " << limit << ". Receive " << photo_count << " photos out of " << total_count - << " photos"; - user_photos->count--; - CHECK(user_photos->count >= 0); - continue; - } - - user_photos->photos.push_back(std::move(user_photo)); - register_user_photo(u, user_id, user_photos->photos.back()); - } - if (user_photos->offset > user_photos->count) { - user_photos->offset = user_photos->count; - user_photos->photos.clear(); - } - - auto known_photo_count = narrow_cast(user_photos->photos.size()); - if (user_photos->offset + known_photo_count > user_photos->count) { - user_photos->photos.resize(user_photos->count - user_photos->offset); - } -} - -void ContactsManager::on_get_chat(tl_object_ptr &&chat, const char *source) { - LOG(DEBUG) << "Receive from " << source << ' ' << to_string(chat); - switch (chat->get_id()) { - case telegram_api::chatEmpty::ID: - on_get_chat_empty(static_cast(*chat), source); - break; - case telegram_api::chat::ID: - on_get_chat(static_cast(*chat), source); - break; - case telegram_api::chatForbidden::ID: - on_get_chat_forbidden(static_cast(*chat), source); - break; - case telegram_api::channel::ID: - on_get_channel(static_cast(*chat), source); - break; - case telegram_api::channelForbidden::ID: - on_get_channel_forbidden(static_cast(*chat), source); - break; - default: - UNREACHABLE(); - } -} - -void ContactsManager::on_get_chats(vector> &&chats, const char *source) { - for (auto &chat : chats) { - auto constuctor_id = chat->get_id(); - if (constuctor_id == telegram_api::channel::ID || constuctor_id == telegram_api::channelForbidden::ID) { - // apply info about megagroups before corresponding chats - on_get_chat(std::move(chat), source); - chat = nullptr; - } - } - for (auto &chat : chats) { - if (chat != nullptr) { - on_get_chat(std::move(chat), source); - chat = nullptr; - } - } -} - -vector ContactsManager::get_bot_commands(vector> &&bot_infos, - const vector *participants) { - vector result; - if (td_->auth_manager_->is_bot()) { - return result; - } - for (auto &bot_info : bot_infos) { - if (bot_info->commands_.empty()) { - continue; - } - - auto user_id = UserId(bot_info->user_id_); - const User *u = get_user_force(user_id, "get_bot_commands"); - if (u == nullptr) { - LOG(ERROR) << "Receive unknown " << user_id; - continue; - } - if (!is_user_bot(u)) { - if (!is_user_deleted(u)) { - LOG(ERROR) << "Receive non-bot " << user_id; - } - continue; - } - if (participants != nullptr) { - bool is_participant = false; - for (auto &participant : *participants) { - if (participant.dialog_id_ == DialogId(user_id)) { - is_participant = true; - break; - } - } - if (!is_participant) { - LOG(ERROR) << "Skip commands of non-member bot " << user_id; - continue; - } - } - result.emplace_back(user_id, std::move(bot_info->commands_)); - } - return result; -} - -void ContactsManager::on_get_chat_full(tl_object_ptr &&chat_full_ptr, Promise &&promise) { - LOG(INFO) << "Receive " << to_string(chat_full_ptr); - if (chat_full_ptr->get_id() == telegram_api::chatFull::ID) { - auto chat = move_tl_object_as(chat_full_ptr); - ChatId chat_id(chat->id_); - Chat *c = get_chat(chat_id); - if (c == nullptr) { - LOG(ERROR) << "Can't find " << chat_id; - return promise.set_value(Unit()); - } - if (c->version >= c->pinned_message_version) { - auto pinned_message_id = MessageId(ServerMessageId(chat->pinned_msg_id_)); - LOG(INFO) << "Receive pinned " << pinned_message_id << " in " << chat_id << " with version " << c->version - << ". Current version is " << c->pinned_message_version; - td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); - if (c->version > c->pinned_message_version) { - c->pinned_message_version = c->version; - c->need_save_to_database = true; - update_chat(c, chat_id); - } - } - - td_->messages_manager_->on_update_dialog_folder_id(DialogId(chat_id), FolderId(chat->folder_id_)); - - td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(chat_id), chat->has_scheduled_); - - { - InputGroupCallId input_group_call_id; - if (chat->call_ != nullptr) { - input_group_call_id = InputGroupCallId(chat->call_); - } - td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id); - } - - { - DialogId default_join_group_call_as_dialog_id; - if (chat->groupcall_default_join_as_ != nullptr) { - default_join_group_call_as_dialog_id = DialogId(chat->groupcall_default_join_as_); - } - // use send closure later to not create synchronously default_join_group_call_as_dialog_id - send_closure_later(G()->messages_manager(), - &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(chat_id), - default_join_group_call_as_dialog_id, false); - } - - td_->messages_manager_->on_update_dialog_message_ttl(DialogId(chat_id), MessageTtl(chat->ttl_period_)); - - td_->messages_manager_->on_update_dialog_is_translatable(DialogId(chat_id), !chat->translations_disabled_); - - ChatFull *chat_full = add_chat_full(chat_id); - on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_)); - auto photo = get_photo(td_, std::move(chat->chat_photo_), DialogId(chat_id)); - // on_update_chat_photo should be a no-op if server sent consistent data - on_update_chat_photo(c, chat_id, as_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, photo, false), - false); - on_update_chat_full_photo(chat_full, chat_id, std::move(photo)); - if (chat_full->description != chat->about_) { - chat_full->description = std::move(chat->about_); - chat_full->is_changed = true; - td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); - } - if (chat_full->can_set_username != chat->can_set_username_) { - chat_full->can_set_username = chat->can_set_username_; - chat_full->need_save_to_database = true; - } - - on_get_chat_participants(std::move(chat->participants_), false); - td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_), - "on_get_chat_full"); - - td_->messages_manager_->on_update_dialog_available_reactions(DialogId(chat_id), - std::move(chat->available_reactions_)); - - td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_)); - - td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(chat_id), chat->requests_pending_, - std::move(chat->recent_requesters_)); - - auto bot_commands = get_bot_commands(std::move(chat->bot_info_), &chat_full->participants); - if (chat_full->bot_commands != bot_commands) { - chat_full->bot_commands = std::move(bot_commands); - chat_full->is_changed = true; - } - - if (c->is_changed) { - LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << chat_id; - update_chat(c, chat_id); - } - - chat_full->is_update_chat_full_sent = true; - update_chat_full(chat_full, chat_id, "on_get_chat_full"); - } else { - CHECK(chat_full_ptr->get_id() == telegram_api::channelFull::ID); - auto channel = move_tl_object_as(chat_full_ptr); - ChannelId channel_id(channel->id_); - auto c = get_channel(channel_id); - if (c == nullptr) { - LOG(ERROR) << "Can't find " << channel_id; - return promise.set_value(Unit()); - } - - invalidated_channels_full_.erase(channel_id); - - if (!G()->close_flag()) { - auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); - if (channel_full != nullptr) { - if (channel_full->repair_request_version != 0 && - channel_full->repair_request_version < channel_full->speculative_version) { - LOG(INFO) << "Receive ChannelFull with request version " << channel_full->repair_request_version - << ", but current speculative version is " << channel_full->speculative_version; - - channel_full->repair_request_version = channel_full->speculative_version; - - auto input_channel = get_input_channel(channel_id); - CHECK(input_channel != nullptr); - td_->create_handler(std::move(promise))->send(channel_id, std::move(input_channel)); - return; - } - channel_full->repair_request_version = 0; - } - } - - td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_), - "on_get_channel_full"); - - 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_)); - - td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_)); - - td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(channel_id), channel->requests_pending_, - std::move(channel->recent_requesters_)); - - td_->messages_manager_->on_update_dialog_message_ttl(DialogId(channel_id), MessageTtl(channel->ttl_period_)); - - td_->messages_manager_->on_update_dialog_view_as_messages(DialogId(channel_id), channel->view_forum_as_messages_); - - td_->messages_manager_->on_update_dialog_is_translatable(DialogId(channel_id), !channel->translations_disabled_); - - send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(channel_id), - std::move(channel->stories_), Promise()); - - ChannelFull *channel_full = add_channel_full(channel_id); - - bool have_participant_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT) != 0; - auto participant_count = have_participant_count ? channel->participants_count_ : channel_full->participant_count; - auto administrator_count = 0; - if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT) != 0) { - administrator_count = channel->admins_count_; - } else if (c->is_megagroup || c->status.is_administrator()) { - // in megagroups and administered channels don't drop known number of administrators - administrator_count = channel_full->administrator_count; - } - if (participant_count < administrator_count) { - participant_count = administrator_count; - } - auto restricted_count = channel->banned_count_; - auto banned_count = channel->kicked_count_; - auto can_get_participants = channel->can_view_participants_; - auto has_hidden_participants = channel->participants_hidden_; - auto can_set_username = channel->can_set_username_; - auto can_set_sticker_set = channel->can_set_stickers_; - auto can_set_location = channel->can_set_location_; - auto is_all_history_available = !channel->hidden_prehistory_; - auto has_aggressive_anti_spam_enabled = channel->antispam_; - auto can_view_statistics = channel->can_view_stats_; - bool has_pinned_stories = channel->stories_pinned_available_; - auto boost_count = channel->boosts_applied_; - auto unrestrict_boost_count = channel->boosts_unrestrict_; - StickerSetId sticker_set_id; - if (channel->stickerset_ != nullptr) { - sticker_set_id = - td_->stickers_manager_->on_get_sticker_set(std::move(channel->stickerset_), true, "on_get_channel_full"); - } - StickerSetId emoji_sticker_set_id; - if (channel->emojiset_ != nullptr) { - emoji_sticker_set_id = - td_->stickers_manager_->on_get_sticker_set(std::move(channel->emojiset_), true, "on_get_channel_full"); - } - DcId stats_dc_id; - if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID) != 0) { - stats_dc_id = DcId::create(channel->stats_dc_); - } - if (!stats_dc_id.is_exact() && can_view_statistics) { - LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id; - can_view_statistics = false; - } - - channel_full->repair_request_version = 0; - channel_full->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME; - if (channel_full->participant_count != participant_count || - channel_full->administrator_count != administrator_count || - channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count || - channel_full->can_get_participants != can_get_participants || - channel_full->can_set_sticker_set != can_set_sticker_set || - channel_full->can_set_location != can_set_location || - channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id || - channel_full->sticker_set_id != sticker_set_id || channel_full->emoji_sticker_set_id != emoji_sticker_set_id || - channel_full->is_all_history_available != is_all_history_available || - channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled || - channel_full->has_hidden_participants != has_hidden_participants || - channel_full->has_pinned_stories != has_pinned_stories || channel_full->boost_count != boost_count || - channel_full->unrestrict_boost_count != unrestrict_boost_count) { - channel_full->participant_count = participant_count; - channel_full->administrator_count = administrator_count; - channel_full->restricted_count = restricted_count; - channel_full->banned_count = banned_count; - channel_full->can_get_participants = can_get_participants; - channel_full->has_hidden_participants = has_hidden_participants; - channel_full->can_set_sticker_set = can_set_sticker_set; - channel_full->can_set_location = can_set_location; - channel_full->can_view_statistics = can_view_statistics; - channel_full->stats_dc_id = stats_dc_id; - channel_full->sticker_set_id = sticker_set_id; - channel_full->emoji_sticker_set_id = emoji_sticker_set_id; - channel_full->is_all_history_available = is_all_history_available; - channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; - channel_full->has_pinned_stories = has_pinned_stories; - channel_full->boost_count = boost_count; - channel_full->unrestrict_boost_count = unrestrict_boost_count; - - channel_full->is_changed = true; - } - if (channel_full->description != channel->about_) { - channel_full->description = std::move(channel->about_); - channel_full->is_changed = true; - td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); - } - - if (have_participant_count && c->participant_count != participant_count) { - c->participant_count = participant_count; - c->is_changed = true; - update_channel(c, channel_id); - } - if (!channel_full->is_can_view_statistics_inited) { - channel_full->is_can_view_statistics_inited = true; - channel_full->need_save_to_database = true; - } - if (channel_full->can_set_username != can_set_username) { - channel_full->can_set_username = can_set_username; - channel_full->need_save_to_database = true; - } - - auto photo = get_photo(td_, std::move(channel->chat_photo_), DialogId(channel_id)); - // on_update_channel_photo should be a no-op if server sent consistent data - on_update_channel_photo( - c, channel_id, as_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, photo, false), - false); - on_update_channel_full_photo(channel_full, channel_id, std::move(photo)); - - td_->messages_manager_->on_read_channel_outbox(channel_id, - MessageId(ServerMessageId(channel->read_outbox_max_id_))); - if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) { - td_->messages_manager_->on_update_channel_max_unavailable_message_id( - channel_id, MessageId(ServerMessageId(channel->available_min_id_)), "ChannelFull"); - } - td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)), - channel->unread_count_, channel->pts_, "ChannelFull"); - - on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_)); - - td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), channel->blocked_, false); - - td_->messages_manager_->on_update_dialog_last_pinned_message_id( - DialogId(channel_id), MessageId(ServerMessageId(channel->pinned_msg_id_))); - - td_->messages_manager_->on_update_dialog_folder_id(DialogId(channel_id), FolderId(channel->folder_id_)); - - td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(channel_id), - channel->has_scheduled_); - { - InputGroupCallId input_group_call_id; - if (channel->call_ != nullptr) { - input_group_call_id = InputGroupCallId(channel->call_); - } - td_->messages_manager_->on_update_dialog_group_call_id(DialogId(channel_id), input_group_call_id); - } - { - DialogId default_join_group_call_as_dialog_id; - if (channel->groupcall_default_join_as_ != nullptr) { - default_join_group_call_as_dialog_id = DialogId(channel->groupcall_default_join_as_); - } - // use send closure later to not create synchronously default_join_group_call_as_dialog_id - send_closure_later(G()->messages_manager(), - &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(channel_id), - default_join_group_call_as_dialog_id, false); - } - { - DialogId default_send_message_as_dialog_id; - if (channel->default_send_as_ != nullptr) { - default_send_message_as_dialog_id = DialogId(channel->default_send_as_); - } - // use send closure later to not create synchronously default_send_message_as_dialog_id - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id, - DialogId(channel_id), default_send_message_as_dialog_id, false); - } - - if (participant_count >= 190 || !can_get_participants || has_hidden_participants) { - td_->dialog_participant_manager_->on_update_dialog_online_member_count(DialogId(channel_id), - channel->online_count_, true); - } - - vector bot_user_ids; - for (const auto &bot_info : channel->bot_info_) { - UserId user_id(bot_info->user_id_); - if (!is_user_bot(user_id)) { - continue; - } - - bot_user_ids.push_back(user_id); - } - on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); - - auto bot_commands = get_bot_commands(std::move(channel->bot_info_), nullptr); - if (channel_full->bot_commands != bot_commands) { - channel_full->bot_commands = std::move(bot_commands); - channel_full->is_changed = true; - } - - ChannelId linked_channel_id; - if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID) != 0) { - linked_channel_id = ChannelId(channel->linked_chat_id_); - auto linked_channel = get_channel_force(linked_channel_id, "ChannelFull"); - if (linked_channel == nullptr || c->is_megagroup == linked_channel->is_megagroup || - channel_id == linked_channel_id) { - LOG(ERROR) << "Failed to add a link between " << channel_id << " and " << linked_channel_id; - linked_channel_id = ChannelId(); - } - } - on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id); - - on_update_channel_full_location(channel_full, channel_id, DialogLocation(td_, std::move(channel->location_))); - - if (c->is_megagroup) { - on_update_channel_full_slow_mode_delay(channel_full, channel_id, channel->slowmode_seconds_, - channel->slowmode_next_send_date_); - } - if (channel_full->can_be_deleted != channel->can_delete_channel_) { - channel_full->can_be_deleted = channel->can_delete_channel_; - channel_full->need_save_to_database = true; - } - if (c->can_be_deleted != channel_full->can_be_deleted) { - c->can_be_deleted = channel_full->can_be_deleted; - c->need_save_to_database = true; - } - - auto migrated_from_chat_id = ChatId(channel->migrated_from_chat_id_); - auto migrated_from_max_message_id = MessageId(ServerMessageId(channel->migrated_from_max_id_)); - if (channel_full->migrated_from_chat_id != migrated_from_chat_id || - channel_full->migrated_from_max_message_id != migrated_from_max_message_id) { - channel_full->migrated_from_chat_id = migrated_from_chat_id; - channel_full->migrated_from_max_message_id = migrated_from_max_message_id; - channel_full->is_changed = true; - } - - if (c->is_changed) { - LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << channel_id; - update_channel(c, channel_id); - } - - channel_full->is_update_channel_full_sent = true; - update_channel_full(channel_full, channel_id, "on_get_channel_full"); - - if (linked_channel_id.is_valid()) { - auto linked_channel_full = get_channel_full_force(linked_channel_id, true, "on_get_channel_full"); - on_update_channel_full_linked_channel_id(linked_channel_full, linked_channel_id, channel_id); - if (linked_channel_full != nullptr) { - update_channel_full(linked_channel_full, linked_channel_id, "on_get_channel_full 2"); - } - } - - if (dismiss_suggested_action_queries_.count(DialogId(channel_id)) == 0) { - auto it = dialog_suggested_actions_.find(DialogId(channel_id)); - if (it != dialog_suggested_actions_.end() || !channel->pending_suggestions_.empty()) { - vector suggested_actions; - for (auto &action_str : channel->pending_suggestions_) { - SuggestedAction suggested_action(action_str, DialogId(channel_id)); - if (!suggested_action.is_empty()) { - if (suggested_action == SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)} && - (c->is_gigagroup || - c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, - false, ChannelType::Unknown))) { - LOG(INFO) << "Skip ConvertToGigagroup suggested action"; - } else { - suggested_actions.push_back(suggested_action); - } - } - } - if (it == dialog_suggested_actions_.end()) { - it = dialog_suggested_actions_.emplace(DialogId(channel_id), vector()).first; - } - update_suggested_actions(it->second, std::move(suggested_actions)); - if (it->second.empty()) { - dialog_suggested_actions_.erase(it); - } - } - } - } - promise.set_value(Unit()); -} - -void ContactsManager::on_get_chat_full_failed(ChatId chat_id) { - if (G()->close_flag()) { - return; - } - - LOG(INFO) << "Failed to get full " << chat_id; -} - -void ContactsManager::on_get_channel_full_failed(ChannelId channel_id) { - if (G()->close_flag()) { - return; - } - - LOG(INFO) << "Failed to get full " << channel_id; - auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full"); - if (channel_full != nullptr) { - channel_full->repair_request_version = 0; - } -} - -void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name, - Usernames &&usernames) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_name"); - if (u != nullptr) { - on_update_user_name(u, user_id, std::move(first_name), std::move(last_name)); - on_update_user_usernames(u, user_id, std::move(usernames)); - update_user(u, user_id); - } else { - LOG(INFO) << "Ignore update user name about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name) { - if (first_name.empty() && last_name.empty()) { - first_name = u->phone_number; - } - if (u->first_name != first_name || u->last_name != last_name) { - u->first_name = std::move(first_name); - u->last_name = std::move(last_name); - u->is_name_changed = true; - LOG(DEBUG) << "Name has changed for " << user_id; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames) { - if (u->usernames != usernames) { - td_->dialog_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); - td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); - if (u->can_be_edited_bot && u->usernames.get_editable_username() != usernames.get_editable_username()) { - u->is_full_info_changed = true; - } - u->usernames = std::move(usernames); - u->is_username_changed = true; - LOG(DEBUG) << "Usernames have changed for " << user_id; - u->is_changed = true; - } else if (u->is_bot || !td_->auth_manager_->is_bot()) { - td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), usernames, false); - } -} - -void ContactsManager::on_update_user_phone_number(UserId user_id, string &&phone_number) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_phone_number"); - if (u != nullptr) { - on_update_user_phone_number(u, user_id, std::move(phone_number)); - update_user(u, user_id); - } else { - LOG(INFO) << "Ignore update user phone number about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_phone_number(User *u, UserId user_id, string &&phone_number) { - if (td_->auth_manager_->is_bot()) { - return; - } - - clean_phone_number(phone_number); - if (u->phone_number != phone_number) { - if (!u->phone_number.empty()) { - auto it = resolved_phone_numbers_.find(u->phone_number); - if (it != resolved_phone_numbers_.end() && it->second == user_id) { - resolved_phone_numbers_.erase(it); - } - } - - u->phone_number = std::move(phone_number); - u->is_phone_number_changed = true; - LOG(DEBUG) << "Phone number has changed for " << user_id; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_photo(User *u, UserId user_id, - tl_object_ptr &&photo, const char *source) { - if (td_->auth_manager_->is_bot() && !G()->use_chat_info_database()) { - if (!u->is_photo_inited) { - auto new_photo_id = get_profile_photo_id(photo); - auto &old_photo = pending_user_photos_[user_id]; - if (new_photo_id == get_profile_photo_id(old_photo)) { - return; - } - if (photo != nullptr && photo->get_id() == telegram_api::userProfilePhoto::ID) { - auto *profile_photo = static_cast(photo.get()); - if ((profile_photo->flags_ & telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK) != 0) { - profile_photo->flags_ -= telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK; - profile_photo->stripped_thumb_ = BufferSlice(); - } - } - - old_photo = std::move(photo); - - drop_user_photos(user_id, new_photo_id == 0, "on_update_user_photo"); - auto user_full = get_user_full(user_id); // must not load UserFull - if (user_full != nullptr && new_photo_id != get_user_full_profile_photo_id(user_full)) { - // we didn't sent updateUser yet, so we must not sent updateUserFull with new_photo_id yet - drop_user_full_photos(user_full, user_id, 0, "on_update_user_photo"); - } - return; - } - if (u->is_received) { - auto new_photo_id = get_profile_photo_id(photo); - if (new_photo_id == u->photo.id) { - return; - } - } - } - - do_update_user_photo(u, user_id, std::move(photo), source); -} - -void ContactsManager::do_update_user_photo(User *u, UserId user_id, - tl_object_ptr &&photo, const char *source) { - ProfilePhoto new_photo = get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(photo)); - if (td_->auth_manager_->is_bot()) { - new_photo.minithumbnail.clear(); - } - do_update_user_photo(u, user_id, std::move(new_photo), true, source); -} - -void ContactsManager::do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, - bool invalidate_photo_cache, const char *source) { - u->is_photo_inited = true; - if (need_update_profile_photo(u->photo, new_photo)) { - LOG_IF(ERROR, u->access_hash == -1 && new_photo.small_file_id.is_valid()) - << "Update profile photo of " << user_id << " without access hash from " << source; - u->photo = new_photo; - u->is_photo_changed = true; - LOG(DEBUG) << "Photo has changed for " << user_id << " to " << u->photo - << ", invalidate_photo_cache = " << invalidate_photo_cache << " from " << source; - u->is_changed = true; - - if (invalidate_photo_cache) { - drop_user_photos(user_id, u->photo.id == 0, source); - } - auto user_full = get_user_full(user_id); // must not load UserFull - if (user_full != nullptr && u->photo.id != get_user_full_profile_photo_id(user_full)) { - // we didn't sent updateUser yet, so we must not sent updateUserFull with u->photo.id yet - drop_user_full_photos(user_full, user_id, 0, "do_update_user_photo"); - } - } else if (need_update_dialog_photo_minithumbnail(u->photo.minithumbnail, new_photo.minithumbnail)) { - LOG(DEBUG) << "Photo minithumbnail has changed for " << user_id << " from " << source; - u->photo.minithumbnail = std::move(new_photo.minithumbnail); - u->is_photo_changed = true; - u->is_changed = true; - } -} - -void ContactsManager::register_suggested_profile_photo(const Photo &photo) { - auto photo_file_ids = photo_get_file_ids(photo); - if (photo.is_empty() || photo_file_ids.empty()) { - return; - } - auto first_file_id = photo_file_ids[0]; - auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); - if (file_type == FileType::ProfilePhoto) { - return; - } - CHECK(file_type == FileType::Photo); - auto photo_id = photo.id.get(); - if (photo_id != 0) { - my_photo_file_id_[photo_id] = first_file_id; - } -} - -void ContactsManager::register_user_photo(User *u, UserId user_id, const Photo &photo) { - auto photo_file_ids = photo_get_file_ids(photo); - if (photo.is_empty() || photo_file_ids.empty()) { - return; - } - auto first_file_id = photo_file_ids[0]; - auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); - if (file_type == FileType::ProfilePhoto) { - return; - } - CHECK(file_type == FileType::Photo); - CHECK(u != nullptr); - auto photo_id = photo.id.get(); - if (photo_id != 0 && u->photo_ids.emplace(photo_id).second) { - VLOG(file_references) << "Register photo " << photo_id << " of " << user_id; - if (user_id == get_my_id()) { - my_photo_file_id_[photo_id] = first_file_id; - } - auto file_source_id = user_profile_photo_file_source_ids_.get(std::make_pair(user_id, photo_id)); - if (file_source_id.is_valid()) { - VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; - user_profile_photo_file_source_ids_.erase(std::make_pair(user_id, photo_id)); - } else { - VLOG(file_references) << "Need to create new file source for photo " << photo_id << " of " << user_id; - file_source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); - } - for (auto &file_id : photo_file_ids) { - td_->file_manager_->add_file_source(file_id, file_source_id); - } - } -} - -void ContactsManager::on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { - if (accent_color_id == AccentColorId(user_id) || !accent_color_id.is_valid()) { - accent_color_id = AccentColorId(); - } - if (u->accent_color_id != accent_color_id) { - u->accent_color_id = accent_color_id; - u->is_accent_color_changed = true; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_background_custom_emoji_id(User *u, UserId user_id, - CustomEmojiId background_custom_emoji_id) { - if (u->background_custom_emoji_id != background_custom_emoji_id) { - u->background_custom_emoji_id = background_custom_emoji_id; - u->is_accent_color_changed = true; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_profile_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { - if (!accent_color_id.is_valid()) { - accent_color_id = AccentColorId(); - } - if (u->profile_accent_color_id != accent_color_id) { - u->profile_accent_color_id = accent_color_id; - u->is_accent_color_changed = true; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_profile_background_custom_emoji_id(User *u, UserId user_id, - CustomEmojiId background_custom_emoji_id) { - if (u->profile_background_custom_emoji_id != background_custom_emoji_id) { - u->profile_background_custom_emoji_id = background_custom_emoji_id; - u->is_accent_color_changed = true; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_emoji_status(UserId user_id, - tl_object_ptr &&emoji_status) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_emoji_status"); - if (u != nullptr) { - on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status))); - update_user(u, user_id); - } else { - LOG(INFO) << "Ignore update user emoji status about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status) { - if (u->emoji_status != emoji_status) { - LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status; - u->emoji_status = std::move(emoji_status); - u->is_emoji_status_changed = true; - // effective emoji status might not be changed; checked in update_user - // u->is_changed = true; - } -} - -void ContactsManager::on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_story_ids"); - if (u != nullptr) { - on_update_user_story_ids_impl(u, user_id, max_active_story_id, max_read_story_id); - update_user(u, user_id); - } else { - LOG(INFO) << "Ignore update user story identifiers about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, - StoryId max_read_story_id) { - if (td_->auth_manager_->is_bot()) { - return; - } - if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { - LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << user_id; - return; - } - if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { - LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << user_id; - return; - } - - auto has_unread_stories = get_user_has_unread_stories(u); - if (u->max_active_story_id != max_active_story_id) { - LOG(DEBUG) << "Change last active story of " << user_id << " from " << u->max_active_story_id << " to " - << max_active_story_id; - u->max_active_story_id = max_active_story_id; - u->need_save_to_database = true; - } - if (need_poll_user_active_stories(u, user_id)) { - auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; - if (max_active_story_id_next_reload_time > - u->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { - LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << user_id; - u->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; - u->need_save_to_database = true; - } - } - if (!max_active_story_id.is_valid()) { - CHECK(max_read_story_id == StoryId()); - if (u->max_read_story_id != StoryId()) { - LOG(DEBUG) << "Drop last read " << u->max_read_story_id << " of " << user_id; - u->max_read_story_id = StoryId(); - u->need_save_to_database = true; - } - } else if (max_read_story_id.get() > u->max_read_story_id.get()) { - LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " - << max_read_story_id; - u->max_read_story_id = max_read_story_id; - u->need_save_to_database = true; - } - if (has_unread_stories != get_user_has_unread_stories(u)) { - LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id) { - CHECK(user_id.is_valid()); - - User *u = get_user(user_id); - if (u != nullptr) { - on_update_user_max_read_story_id(u, user_id, max_read_story_id); - update_user(u, user_id); - } -} - -void ContactsManager::on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id) { - if (td_->auth_manager_->is_bot() || !u->is_received) { - return; - } - - auto has_unread_stories = get_user_has_unread_stories(u); - if (max_read_story_id.get() > u->max_read_story_id.get()) { - LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " - << max_read_story_id; - u->max_read_story_id = max_read_story_id; - u->need_save_to_database = true; - } - if (has_unread_stories != get_user_has_unread_stories(u)) { - LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_stories_hidden(UserId user_id, bool stories_hidden) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_stories_hidden"); - if (u != nullptr) { - on_update_user_stories_hidden(u, user_id, stories_hidden); - update_user(u, user_id); - } else { - LOG(INFO) << "Ignore update user stories are archived about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden) { - if (td_->auth_manager_->is_bot()) { - return; - } - - if (u->stories_hidden != stories_hidden) { - LOG(DEBUG) << "Change stories are archived of " << user_id << " to " << stories_hidden; - u->stories_hidden = stories_hidden; - u->is_stories_hidden_changed = true; - u->need_save_to_database = true; - } -} - -void ContactsManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, - bool is_close_friend) { - if (td_->auth_manager_->is_bot()) { - return; - } - - UserId my_id = get_my_id(); - if (user_id == my_id) { - is_mutual_contact = is_contact; - is_close_friend = false; - } - if (!is_contact && (is_mutual_contact || is_close_friend)) { - LOG(ERROR) << "Receive is_mutual_contact = " << is_mutual_contact << ", and is_close_friend = " << is_close_friend - << " for non-contact " << user_id; - is_mutual_contact = false; - is_close_friend = false; - } - - if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact || - u->is_close_friend != is_close_friend) { - LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact << ", " - << u->is_close_friend << ") to (" << is_contact << ", " << is_mutual_contact << ", " << is_close_friend - << ")"; - if (u->is_contact != is_contact) { - u->is_contact = is_contact; - u->is_is_contact_changed = true; - } - if (u->is_mutual_contact != is_mutual_contact) { - u->is_mutual_contact = is_mutual_contact; - u->is_is_mutual_contact_changed = true; - } - u->is_close_friend = is_close_friend; - u->is_changed = true; - } -} - -void ContactsManager::on_update_user_online(UserId user_id, tl_object_ptr &&status) { - if (td_->auth_manager_->is_bot()) { - return; - } - - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - User *u = get_user_force(user_id, "on_update_user_online"); - if (u != nullptr) { - if (u->is_bot) { - LOG(ERROR) << "Receive updateUserStatus about bot " << user_id; - return; - } - on_update_user_online(u, user_id, std::move(status)); - update_user(u, user_id); - - if (user_id == get_my_id() && - was_online_remote_ != u->was_online) { // only update was_online_remote_ from updateUserStatus - was_online_remote_ = u->was_online; - VLOG(notifications) << "Set was_online_remote to " << was_online_remote_; - G()->td_db()->get_binlog_pmc()->set("my_was_online_remote", to_string(was_online_remote_)); - } - } else { - LOG(INFO) << "Ignore update user online about unknown " << user_id; - } -} - -void ContactsManager::on_update_user_online(User *u, UserId user_id, tl_object_ptr &&status) { - if (td_->auth_manager_->is_bot()) { - return; - } - - int32 id = status == nullptr ? telegram_api::userStatusEmpty::ID : status->get_id(); - int32 new_online; - bool is_offline = false; - if (id == telegram_api::userStatusOnline::ID) { - int32 now = G()->unix_time(); - - auto st = move_tl_object_as(status); - new_online = st->expires_; - LOG_IF(ERROR, new_online < now - 86400) - << "Receive userStatusOnline expired more than one day in past " << new_online; - } else if (id == telegram_api::userStatusOffline::ID) { - int32 now = G()->unix_time(); - - auto st = move_tl_object_as(status); - new_online = st->was_online_; - if (new_online >= now) { - LOG_IF(ERROR, new_online > now + 10) - << "Receive userStatusOffline but was online points to future time " << new_online << ", now is " << now; - new_online = now - 1; - } - is_offline = true; - } else if (id == telegram_api::userStatusRecently::ID) { - auto st = telegram_api::move_object_as(status); - new_online = st->by_me_ ? -4 : -1; - } else if (id == telegram_api::userStatusLastWeek::ID) { - auto st = telegram_api::move_object_as(status); - new_online = st->by_me_ ? -5 : -2; - } else if (id == telegram_api::userStatusLastMonth::ID) { - auto st = telegram_api::move_object_as(status); - new_online = st->by_me_ ? -6 : -3; - } else { - CHECK(id == telegram_api::userStatusEmpty::ID); - new_online = 0; - } - - if (new_online != u->was_online && !(new_online < 0 && user_id == get_my_id())) { - LOG(DEBUG) << "Update " << user_id << " online from " << u->was_online << " to " << new_online; - auto unix_time = G()->unix_time(); - bool old_is_online = u->was_online > unix_time; - bool new_is_online = new_online > unix_time; - u->was_online = new_online; - u->is_status_changed = true; - if (u->was_online > 0) { - u->local_was_online = 0; - } - - if (user_id == get_my_id()) { - if (my_was_online_local_ != 0 || old_is_online != new_is_online) { - my_was_online_local_ = 0; - u->is_online_status_changed = true; - } - if (is_offline) { - td_->on_online_updated(false, false); - } - } else if (old_is_online != new_is_online) { - u->is_online_status_changed = true; - } - } -} - -void ContactsManager::on_update_user_local_was_online(UserId user_id, int32 local_was_online) { - CHECK(user_id.is_valid()); - if (td_->auth_manager_->is_bot()) { - return; - } - - User *u = get_user_force(user_id, "on_update_user_local_was_online"); - if (u == nullptr) { - return; - } - - on_update_user_local_was_online(u, user_id, local_was_online); - update_user(u, user_id); -} - -void ContactsManager::on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online) { - CHECK(u != nullptr); - if (u->is_deleted || u->is_bot || u->is_support || user_id == get_my_id()) { - return; - } - int32 unix_time = G()->unix_time(); - if (u->was_online > unix_time) { - // if user is currently online, ignore local online - return; - } - - // bring users online for 30 seconds - local_was_online += 30; - if (local_was_online < unix_time + 2 || local_was_online <= u->local_was_online || - local_was_online <= u->was_online) { - return; - } - - LOG(DEBUG) << "Update " << user_id << " local online from " << u->local_was_online << " to " << local_was_online; - bool old_is_online = u->local_was_online > unix_time; - u->local_was_online = local_was_online; - u->is_status_changed = true; - - if (!old_is_online) { - u->is_online_status_changed = true; - } -} - -void ContactsManager::on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories) { - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_is_blocked"); - if (user_full == nullptr) { - return; - } - on_update_user_full_is_blocked(user_full, user_id, is_blocked, is_blocked_for_stories); - update_user_full(user_full, user_id, "on_update_user_is_blocked"); -} - -void ContactsManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, - bool is_blocked_for_stories) { - CHECK(user_full != nullptr); - if (user_full->is_blocked != is_blocked || user_full->is_blocked_for_stories != is_blocked_for_stories) { - LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked << '/' - << is_blocked_for_stories; - user_full->is_blocked = is_blocked; - user_full->is_blocked_for_stories = is_blocked_for_stories; - user_full->is_changed = true; - } -} - -void ContactsManager::on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories) { - if (td_->auth_manager_->is_bot()) { - return; - } - - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_has_pinned_stories"); - if (user_full == nullptr || user_full->has_pinned_stories == has_pinned_stories) { - return; - } - user_full->has_pinned_stories = has_pinned_stories; - user_full->is_changed = true; - update_user_full(user_full, user_id, "on_update_user_has_pinned_stories"); -} - -void ContactsManager::on_update_user_common_chat_count(UserId user_id, int32 common_chat_count) { - LOG(INFO) << "Receive " << common_chat_count << " common chat count with " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_common_chat_count"); - if (user_full == nullptr) { - return; - } - on_update_user_full_common_chat_count(user_full, user_id, common_chat_count); - update_user_full(user_full, user_id, "on_update_user_common_chat_count"); -} - -void ContactsManager::on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, - int32 common_chat_count) { - CHECK(user_full != nullptr); - if (common_chat_count < 0) { - LOG(ERROR) << "Receive " << common_chat_count << " as common group count with " << user_id; - common_chat_count = 0; - } - if (user_full->common_chat_count != common_chat_count) { - user_full->common_chat_count = common_chat_count; - user_full->is_common_chat_count_changed = true; - user_full->is_changed = true; - } -} - -void ContactsManager::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; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_location"); - if (user_full == nullptr) { - return; - } - on_update_user_full_location(user_full, user_id, std::move(location)); - update_user_full(user_full, user_id, "on_update_user_location"); -} - -void ContactsManager::on_update_user_full_location(UserFull *user_full, UserId user_id, DialogLocation &&location) { - CHECK(user_full != nullptr); - if (BusinessInfo::set_location(user_full->business_info, std::move(location))) { - user_full->is_changed = true; - } -} - -void ContactsManager::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; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_work_hours"); - if (user_full == nullptr) { - return; - } - on_update_user_full_work_hours(user_full, user_id, std::move(work_hours)); - update_user_full(user_full, user_id, "on_update_user_work_hours"); -} - -void ContactsManager::on_update_user_full_work_hours(UserFull *user_full, UserId user_id, - BusinessWorkHours &&work_hours) { - CHECK(user_full != nullptr); - if (BusinessInfo::set_work_hours(user_full->business_info, std::move(work_hours))) { - user_full->is_changed = true; - } -} - -void ContactsManager::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; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_away_message"); - if (user_full == nullptr) { - return; - } - on_update_user_full_away_message(user_full, user_id, std::move(away_message)); - update_user_full(user_full, user_id, "on_update_user_away_message"); -} - -void ContactsManager::on_update_user_full_away_message(UserFull *user_full, UserId user_id, - BusinessAwayMessage &&away_message) const { - CHECK(user_full != nullptr); - if (away_message.is_valid() && user_id != get_my_id()) { - LOG(ERROR) << "Receive " << away_message << " for " << user_id; - return; - } - if (BusinessInfo::set_away_message(user_full->business_info, std::move(away_message))) { - user_full->is_changed = true; - } -} - -void ContactsManager::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; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_greeting_message"); - if (user_full == nullptr) { - return; - } - on_update_user_full_greeting_message(user_full, user_id, std::move(greeting_message)); - update_user_full(user_full, user_id, "on_update_user_greeting_message"); -} - -void ContactsManager::on_update_user_full_greeting_message(UserFull *user_full, UserId user_id, - BusinessGreetingMessage &&greeting_message) const { - CHECK(user_full != nullptr); - if (greeting_message.is_valid() && user_id != get_my_id()) { - LOG(ERROR) << "Receive " << greeting_message << " for " << user_id; - return; - } - if (BusinessInfo::set_greeting_message(user_full->business_info, std::move(greeting_message))) { - user_full->is_changed = true; - } -} - -void ContactsManager::on_update_user_full_commands(UserFull *user_full, UserId user_id, - vector> &&bot_commands) { - CHECK(user_full != nullptr); - auto commands = transform(std::move(bot_commands), [](tl_object_ptr &&bot_command) { - return BotCommand(std::move(bot_command)); - }); - if (user_full->commands != commands) { - user_full->commands = std::move(commands); - user_full->is_changed = true; - } -} - -void ContactsManager::on_update_user_full_menu_button(UserFull *user_full, UserId user_id, - tl_object_ptr &&bot_menu_button) { - CHECK(user_full != nullptr); - auto new_button = get_bot_menu_button(std::move(bot_menu_button)); - bool is_changed; - if (user_full->menu_button == nullptr) { - is_changed = (new_button != nullptr); - } else { - is_changed = (new_button == nullptr || *user_full->menu_button != *new_button); - } - if (is_changed) { - user_full->menu_button = std::move(new_button); - user_full->is_changed = true; - } -} - -void ContactsManager::on_update_user_need_phone_number_privacy_exception(UserId user_id, - bool need_phone_number_privacy_exception) { - LOG(INFO) << "Receive " << need_phone_number_privacy_exception << " need phone number privacy exception with " - << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_need_phone_number_privacy_exception"); - if (user_full == nullptr) { - return; - } - on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, need_phone_number_privacy_exception); - update_user_full(user_full, user_id, "on_update_user_need_phone_number_privacy_exception"); -} - -void ContactsManager::on_update_user_full_need_phone_number_privacy_exception( - UserFull *user_full, UserId user_id, bool need_phone_number_privacy_exception) const { - CHECK(user_full != nullptr); - if (need_phone_number_privacy_exception) { - const User *u = get_user(user_id); - if (u == nullptr || u->is_contact || user_id == get_my_id()) { - need_phone_number_privacy_exception = false; - } - } - if (user_full->need_phone_number_privacy_exception != need_phone_number_privacy_exception) { - user_full->need_phone_number_privacy_exception = need_phone_number_privacy_exception; - user_full->is_changed = true; - } -} - -void ContactsManager::on_update_user_wallpaper_overridden(UserId user_id, bool wallpaper_overridden) { - LOG(INFO) << "Receive " << wallpaper_overridden << " set chat background for " << user_id; - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << user_id; - return; - } - - UserFull *user_full = get_user_full_force(user_id, "on_update_user_wallpaper_overridden"); - if (user_full == nullptr) { - return; - } - on_update_user_full_wallpaper_overridden(user_full, user_id, wallpaper_overridden); - update_user_full(user_full, user_id, "on_update_user_wallpaper_overridden"); -} - -void ContactsManager::on_update_user_full_wallpaper_overridden(UserFull *user_full, UserId user_id, - bool wallpaper_overridden) const { - CHECK(user_full != nullptr); - if (user_full->wallpaper_overridden != wallpaper_overridden) { - user_full->wallpaper_overridden = wallpaper_overridden; - user_full->is_changed = true; - } -} - -void ContactsManager::on_ignored_restriction_reasons_changed() { - restricted_user_ids_.foreach([&](const UserId &user_id) { - send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, get_user(user_id))); - }); - restricted_channel_ids_.foreach([&](const ChannelId &channel_id) { - send_closure(G()->td(), &Td::send_update, get_update_supergroup_object(channel_id, get_channel(channel_id))); - }); -} - -void ContactsManager::on_set_profile_photo(UserId user_id, tl_object_ptr &&photo, - bool is_fallback, int64 old_photo_id, Promise &&promise) { - LOG(INFO) << "Changed profile photo to " << to_string(photo); - - bool is_bot = is_user_bot(user_id); - bool is_my = (user_id == get_my_id()); - if (is_my && !is_fallback) { - delete_my_profile_photo_from_cache(old_photo_id); - } - bool have_user = false; - for (const auto &user : photo->users_) { - if (get_user_id(user) == user_id) { - have_user = true; - } - } - on_get_users(std::move(photo->users_), "on_set_profile_photo"); - if (!is_bot) { - add_set_profile_photo_to_cache(user_id, get_photo(td_, std::move(photo->photo_), DialogId(user_id)), is_fallback); - } - if (have_user) { - promise.set_value(Unit()); - } else { - reload_user(user_id, std::move(promise), "on_set_profile_photo"); - } -} - -void ContactsManager::on_delete_profile_photo(int64 profile_photo_id, Promise promise) { - bool need_reget_user = delete_my_profile_photo_from_cache(profile_photo_id); - if (need_reget_user && !G()->close_flag()) { - return reload_user(get_my_id(), std::move(promise), "on_delete_profile_photo"); - } - - promise.set_value(Unit()); -} - -int64 ContactsManager::get_user_full_profile_photo_id(const UserFull *user_full) { - if (!user_full->personal_photo.is_empty()) { - return user_full->personal_photo.id.get(); - } - if (!user_full->photo.is_empty()) { - return user_full->photo.id.get(); - } - return user_full->fallback_photo.id.get(); -} - -void ContactsManager::add_set_profile_photo_to_cache(UserId user_id, Photo &&photo, bool is_fallback) { - // we have subsequence of user photos in user_photos_ - // ProfilePhoto in User and Photo in UserFull - - User *u = get_user_force(user_id, "add_set_profile_photo_to_cache"); - if (u == nullptr) { - return; - } - - LOG(INFO) << "Add profile photo " << photo.id.get() << " to cache"; - - bool is_me = user_id == get_my_id(); - - // update photo list - auto user_photos = user_photos_.get_pointer(user_id); - if (is_me && !is_fallback && user_photos != nullptr && user_photos->count != -1 && !photo.is_empty()) { - if (user_photos->offset == 0) { - if (user_photos->photos.empty() || user_photos->photos[0].id.get() != photo.id.get()) { - user_photos->photos.insert(user_photos->photos.begin(), photo); - user_photos->count++; - register_user_photo(u, user_id, user_photos->photos[0]); - } - } else { - user_photos->count++; - user_photos->offset++; - } - } - - // update ProfilePhoto in User - if ((!is_fallback || u->photo.id == 0) && !photo.is_empty()) { - do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, photo, !is_me), - false, "add_set_profile_photo_to_cache"); - update_user(u, user_id); - } - - // update Photo in UserFull - auto user_full = get_user_full_force(user_id, "add_set_profile_photo_to_cache"); - if (user_full != nullptr) { - Photo *current_photo = nullptr; - // don't update the changed photo if other photos aren't known to avoid having only some photos known - bool need_apply = get_user_full_profile_photo_id(user_full) > 0; - if (!is_me) { - current_photo = &user_full->personal_photo; - if (photo.is_empty()) { - // always can apply empty personal photo - need_apply = true; - } - } else if (!is_fallback) { - current_photo = &user_full->photo; - if (photo.is_empty()) { - // never can apply empty photo - need_apply = false; - } - } else { - current_photo = &user_full->fallback_photo; - if (photo.is_empty()) { - // always can apply empty fallback photo - need_apply = true; - } - } - if (*current_photo != photo && need_apply) { - LOG(INFO) << "Update full photo of " << user_id << " to " << photo; - *current_photo = photo; - user_full->is_changed = true; - if (is_me && !photo.is_empty()) { - if (!is_fallback) { - register_user_photo(u, user_id, photo); - } else { - register_suggested_profile_photo(photo); - } - } - drop_user_full_photos(user_full, user_id, u->photo.id, "add_set_profile_photo_to_cache"); // just in case - } - if (user_full->expires_at > 0.0) { - user_full->expires_at = 0.0; - user_full->need_save_to_database = true; - } - update_user_full(user_full, user_id, "add_set_profile_photo_to_cache"); - reload_user_full(user_id, Auto(), "add_set_profile_photo_to_cache"); - } -} - -bool ContactsManager::delete_my_profile_photo_from_cache(int64 profile_photo_id) { - if (profile_photo_id == 0 || profile_photo_id == -2) { - return false; - } - - // we have subsequence of user photos in user_photos_ - // ProfilePhoto in User and Photo in UserFull - - LOG(INFO) << "Delete profile photo " << profile_photo_id << " from cache"; - - auto user_id = get_my_id(); - User *u = get_user_force(user_id, "delete_my_profile_photo_from_cache"); - bool is_main_photo_deleted = u != nullptr && u->photo.id == profile_photo_id; - - // update photo list - auto user_photos = user_photos_.get_pointer(user_id); - if (user_photos != nullptr && user_photos->count > 0) { - auto old_size = user_photos->photos.size(); - if (td::remove_if(user_photos->photos, - [profile_photo_id](const auto &photo) { return photo.id.get() == profile_photo_id; })) { - auto removed_photos = old_size - user_photos->photos.size(); - CHECK(removed_photos > 0); - LOG_IF(ERROR, removed_photos != 1) << "Had " << removed_photos << " photos with ID " << profile_photo_id; - user_photos->count -= narrow_cast(removed_photos); - // offset was not changed - CHECK(user_photos->count >= 0); - } else { - // failed to find photo to remove from cache - // don't know how to adjust user_photos->offset, so drop photos cache - LOG(INFO) << "Drop photos of " << user_id; - user_photos->photos.clear(); - user_photos->count = -1; - user_photos->offset = -1; - } - } - bool have_new_photo = - user_photos != nullptr && user_photos->count != -1 && user_photos->offset == 0 && !user_photos->photos.empty(); - - auto user_full = get_user_full_force(user_id, "delete_my_profile_photo_from_cache"); - - // update ProfilePhoto in User - bool need_reget_user = false; - if (is_main_photo_deleted) { - if (have_new_photo) { - do_update_user_photo( - u, user_id, - as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, user_photos->photos[0], false), false, - "delete_my_profile_photo_from_cache"); - } else { - do_update_user_photo(u, user_id, ProfilePhoto(), false, "delete_my_profile_photo_from_cache 2"); - need_reget_user = user_photos == nullptr || user_photos->count != 0; - } - update_user(u, user_id); - - // update Photo in UserFull - if (user_full != nullptr) { - if (user_full->fallback_photo.id.get() == profile_photo_id) { - LOG(INFO) << "Drop full public photo of " << user_id; - user_full->photo = Photo(); - user_full->is_changed = true; - } else if (have_new_photo) { - if (user_full->photo.id.get() == profile_photo_id && user_photos->photos[0] != user_full->photo) { - LOG(INFO) << "Update full photo of " << user_id << " to " << user_photos->photos[0]; - user_full->photo = user_photos->photos[0]; - user_full->is_changed = true; - } - } else { - // repair UserFull photo - if (!user_full->photo.is_empty()) { - user_full->photo = Photo(); - user_full->is_changed = true; - } - if (!user_full->fallback_photo.is_empty()) { - user_full->fallback_photo = Photo(); - user_full->is_changed = true; - } - } - if (user_full->expires_at > 0.0) { - user_full->expires_at = 0.0; - user_full->need_save_to_database = true; - } - reload_user_full(user_id, Auto(), "delete_my_profile_photo_from_cache"); - update_user_full(user_full, user_id, "delete_my_profile_photo_from_cache"); - } - } - - return need_reget_user; -} - -void ContactsManager::drop_user_full_photos(UserFull *user_full, UserId user_id, int64 expected_photo_id, - const char *source) { - if (user_full == nullptr) { - return; - } - LOG(INFO) << "Expect full photo " << expected_photo_id << " from " << source; - for (auto photo_ptr : {&user_full->personal_photo, &user_full->photo, &user_full->fallback_photo}) { - if (photo_ptr->is_empty()) { - continue; - } - if (expected_photo_id == 0) { - // if profile photo is empty, we must drop the full photo - *photo_ptr = Photo(); - user_full->is_changed = true; - } else if (expected_photo_id != photo_ptr->id.get()) { - LOG(INFO) << "Drop full photo " << photo_ptr->id.get(); - // if full profile photo is unknown, we must drop the full photo - *photo_ptr = Photo(); - user_full->is_changed = true; - } else { - // nothing to drop - break; - } - } - if (expected_photo_id != get_user_full_profile_photo_id(user_full)) { - user_full->expires_at = 0.0; - } - if (user_full->is_update_user_full_sent) { - update_user_full(user_full, user_id, "drop_user_full_photos"); - } -} - -void ContactsManager::drop_user_photos(UserId user_id, bool is_empty, const char *source) { - LOG(INFO) << "Drop user photos to " << (is_empty ? "empty" : "unknown") << " from " << source; - auto user_photos = user_photos_.get_pointer(user_id); - if (user_photos != nullptr) { - int32 new_count = is_empty ? 0 : -1; - if (user_photos->count == new_count) { - CHECK(user_photos->photos.empty()); - CHECK(user_photos->offset == user_photos->count); - } else { - LOG(INFO) << "Drop photos of " << user_id << " to " << (is_empty ? "empty" : "unknown") << " from " << source; - user_photos->photos.clear(); - user_photos->count = new_count; - user_photos->offset = user_photos->count; - } - } -} - -void ContactsManager::drop_user_full(UserId user_id) { - auto user_full = get_user_full_force(user_id, "drop_user_full"); - - drop_user_photos(user_id, false, "drop_user_full"); - - if (user_full == nullptr) { - return; - } - - user_full->expires_at = 0.0; - - user_full->photo = Photo(); - user_full->personal_photo = Photo(); - user_full->fallback_photo = Photo(); - // user_full->is_blocked = false; - // user_full->is_blocked_for_stories = false; - user_full->can_be_called = false; - user_full->supports_video_calls = false; - user_full->has_private_calls = false; - user_full->need_phone_number_privacy_exception = false; - user_full->wallpaper_overridden = false; - user_full->about = string(); - user_full->description = string(); - user_full->description_photo = Photo(); - user_full->description_animation_file_id = FileId(); - user_full->menu_button = nullptr; - user_full->commands.clear(); - user_full->common_chat_count = 0; - user_full->business_info = nullptr; - user_full->private_forward_name.clear(); - user_full->group_administrator_rights = {}; - user_full->broadcast_administrator_rights = {}; - user_full->premium_gift_options.clear(); - user_full->voice_messages_forbidden = false; - user_full->has_pinned_stories = false; - user_full->read_dates_private = false; - user_full->contact_require_premium = false; - user_full->is_changed = true; - - update_user_full(user_full, user_id, "drop_user_full"); - td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); -} - -void ContactsManager::update_chat_online_member_count(ChatId chat_id, bool is_from_server) { - auto chat_full = get_chat_full(chat_id); - if (chat_full != nullptr) { - update_chat_online_member_count(chat_full, chat_id, false); - } -} - -void ContactsManager::update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server) { - td_->dialog_participant_manager_->update_dialog_online_member_count(chat_full->participants, DialogId(chat_id), - is_from_server); -} - -void ContactsManager::on_get_chat_participants(tl_object_ptr &&participants_ptr, - bool from_update) { - switch (participants_ptr->get_id()) { - case telegram_api::chatParticipantsForbidden::ID: { - auto participants = move_tl_object_as(participants_ptr); - ChatId chat_id(participants->chat_id_); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - - if (!have_chat_force(chat_id, "on_get_chat_participants")) { - LOG(ERROR) << chat_id << " not found"; - return; - } - - if (from_update) { - drop_chat_full(chat_id); - } - break; - } - case telegram_api::chatParticipants::ID: { - auto participants = move_tl_object_as(participants_ptr); - ChatId chat_id(participants->chat_id_); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - - const Chat *c = get_chat_force(chat_id, "chatParticipants"); - if (c == nullptr) { - LOG(ERROR) << chat_id << " not found"; - return; - } - - ChatFull *chat_full = get_chat_full_force(chat_id, "telegram_api::chatParticipants"); - if (chat_full == nullptr) { - LOG(INFO) << "Ignore update of members for unknown full " << chat_id; - return; - } - - UserId new_creator_user_id; - vector new_participants; - new_participants.reserve(participants->participants_.size()); - - for (auto &participant_ptr : participants->participants_) { - DialogParticipant dialog_participant(std::move(participant_ptr), c->date, c->status.is_creator()); - if (!dialog_participant.is_valid()) { - LOG(ERROR) << "Receive invalid " << dialog_participant; - continue; - } - - LOG_IF(ERROR, !td_->dialog_manager_->have_dialog_info(dialog_participant.dialog_id_)) - << "Have no information about " << dialog_participant.dialog_id_ << " as a member of " << chat_id; - LOG_IF(ERROR, !have_user(dialog_participant.inviter_user_id_)) - << "Have no information about " << dialog_participant.inviter_user_id_ << " as a member of " << chat_id; - if (dialog_participant.joined_date_ < c->date) { - LOG_IF(ERROR, dialog_participant.joined_date_ < c->date - 30 && c->date >= 1486000000) - << "Wrong join date = " << dialog_participant.joined_date_ << " for " << dialog_participant.dialog_id_ - << ", " << chat_id << " was created at " << c->date; - dialog_participant.joined_date_ = c->date; - } - if (dialog_participant.status_.is_creator() && dialog_participant.dialog_id_.get_type() == DialogType::User) { - new_creator_user_id = dialog_participant.dialog_id_.get_user_id(); - } - new_participants.push_back(std::move(dialog_participant)); - } - - if (chat_full->creator_user_id != new_creator_user_id) { - if (new_creator_user_id.is_valid() && chat_full->creator_user_id.is_valid()) { - LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to " << new_creator_user_id - << " in " << chat_id; - } - chat_full->creator_user_id = new_creator_user_id; - chat_full->is_changed = true; - } - - on_update_chat_full_participants(chat_full, chat_id, std::move(new_participants), participants->version_, - from_update); - if (from_update) { - update_chat_full(chat_full, chat_id, "on_get_chat_participants"); - } - break; - } - default: - UNREACHABLE(); - } -} - -const DialogParticipant *ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id) const { - auto chat_full = get_chat_full(chat_id); - if (chat_full == nullptr) { - return nullptr; - } - return get_chat_full_participant(chat_full, DialogId(user_id)); -} - -const DialogParticipant *ContactsManager::get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id) { - for (const auto &dialog_participant : chat_full->participants) { - if (dialog_participant.dialog_id_ == dialog_id) { - return &dialog_participant; - } - } - return nullptr; -} - -const vector *ContactsManager::get_chat_participants(ChatId chat_id) const { - auto chat_full = get_chat_full(chat_id); - if (chat_full == nullptr) { - return nullptr; - } - return &chat_full->participants; -} - -tl_object_ptr ContactsManager::get_chat_member_object(const DialogParticipant &dialog_participant, - const char *source) const { - return td_api::make_object( - get_message_sender_object(td_, dialog_participant.dialog_id_, source), - get_user_id_object(dialog_participant.inviter_user_id_, "chatMember.inviter_user_id"), - dialog_participant.joined_date_, dialog_participant.status_.get_chat_member_status_object()); -} - -bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &status, const char *source) { - LOG(INFO) << "Receive " << status << " in " << channel_id << " from " << source; - if (status.message() == CSlice("BOT_METHOD_INVALID")) { - LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source; - return true; - } - if (G()->is_expected_error(status)) { - return true; - } - if (status.message() == "CHANNEL_PRIVATE" || status.message() == "CHANNEL_PUBLIC_GROUP_NA") { - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive " << status.message() << " in invalid " << channel_id << " from " << source; - return false; - } - - auto c = get_channel(channel_id); - if (c == nullptr) { - if (Slice(source) == Slice("GetChannelDifferenceQuery") || Slice(source) == Slice("GetChannelsQuery")) { - // get channel difference after restart - // get channel from server by its identifier - return true; - } - LOG(ERROR) << "Receive " << status.message() << " in not found " << channel_id << " from " << source; - return false; - } - - auto debug_channel_object = oneline(to_string(get_supergroup_object(channel_id, c))); - if (c->status.is_member()) { - LOG(INFO) << "Emulate leaving " << channel_id; - // TODO we also may try to write to a public channel - int32 flags = 0; - if (c->is_megagroup) { - flags |= CHANNEL_FLAG_IS_MEGAGROUP; - } else { - flags |= CHANNEL_FLAG_IS_BROADCAST; - } - telegram_api::channelForbidden channel_forbidden(flags, false /*ignored*/, false /*ignored*/, channel_id.get(), - c->access_hash, c->title, 0); - on_get_channel_forbidden(channel_forbidden, "CHANNEL_PRIVATE"); - } else if (!c->status.is_banned()) { - if (!c->usernames.is_empty()) { - LOG(INFO) << "Drop usernames of " << channel_id; - on_update_channel_usernames(c, channel_id, Usernames()); - } - - on_update_channel_has_location(c, channel_id, false); - - on_update_channel_linked_channel_id(channel_id, ChannelId()); - - update_channel(c, channel_id); - - td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); - } - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, source); - LOG_IF(ERROR, have_input_peer_channel(c, channel_id, AccessRights::Read)) - << "Have read access to channel after receiving CHANNEL_PRIVATE. Channel state: " - << oneline(to_string(get_supergroup_object(channel_id, c))) - << ". Previous channel state: " << debug_channel_object; - - return true; - } - return false; -} - -bool ContactsManager::is_user_contact(UserId user_id, bool is_mutual) const { - return is_user_contact(get_user(user_id), user_id, is_mutual); -} - -bool ContactsManager::is_user_contact(const User *u, UserId user_id, bool is_mutual) const { - return u != nullptr && (is_mutual ? u->is_mutual_contact : u->is_contact) && user_id != get_my_id(); -} - -bool ContactsManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) { - auto new_count = count + delta_count; - if (new_count < min_count) { - new_count = min_count; - } - if (new_count == count) { - return false; - } - - count = new_count; - return true; -} - -void ContactsManager::speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, - UserId inviter_user_id, int32 date, bool by_me) { - td_->dialog_participant_manager_->add_cached_channel_participants(channel_id, added_user_ids, inviter_user_id, date); - auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participants"); - - int32 delta_participant_count = 0; - for (auto user_id : added_user_ids) { - if (!user_id.is_valid()) { - continue; - } - - delta_participant_count++; - if (channel_full != nullptr && is_user_bot(user_id) && !td::contains(channel_full->bot_user_ids, user_id)) { - channel_full->bot_user_ids.push_back(user_id); - channel_full->need_save_to_database = true; - reload_channel_full(channel_id, Promise(), "speculative_add_channel_participants"); - - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - channel_full->bot_user_ids, false); - } - } - if (channel_full != nullptr) { - if (channel_full->is_changed) { - channel_full->speculative_version++; - } - update_channel_full(channel_full, channel_id, "speculative_add_channel_participants"); - } - if (delta_participant_count == 0) { - return; - } - - speculative_add_channel_participant_count(channel_id, delta_participant_count, by_me); -} - -void ContactsManager::speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me) { - if (!deleted_user_id.is_valid()) { - return; - } - - td_->dialog_participant_manager_->delete_cached_channel_participant(channel_id, deleted_user_id); - - if (is_user_bot(deleted_user_id)) { - auto channel_full = get_channel_full_force(channel_id, true, "speculative_delete_channel_participant"); - if (channel_full != nullptr && td::remove(channel_full->bot_user_ids, deleted_user_id)) { - channel_full->need_save_to_database = true; - update_channel_full(channel_full, channel_id, "speculative_delete_channel_participant"); - - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - channel_full->bot_user_ids, false); - } - } - - speculative_add_channel_participant_count(channel_id, -1, by_me); -} - -void ContactsManager::speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, - bool by_me) { - if (by_me) { - // Currently, ignore all changes made by the current user, because they may have been already counted - invalidate_channel_full(channel_id, false, "speculative_add_channel_participant_count"); // just in case - return; - } - - auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participant_count"); - auto min_count = channel_full == nullptr ? 0 : channel_full->administrator_count; - - auto c = get_channel_force(channel_id, "speculative_add_channel_participant_count"); - if (c != nullptr && c->participant_count != 0 && - speculative_add_count(c->participant_count, delta_participant_count, min_count)) { - c->is_changed = true; - update_channel(c, channel_id); - } - - if (channel_full == nullptr) { - return; - } - - channel_full->is_changed |= - speculative_add_count(channel_full->participant_count, delta_participant_count, min_count); - - if (channel_full->is_changed) { - channel_full->speculative_version++; - } - - update_channel_full(channel_full, channel_id, "speculative_add_channel_participant_count"); -} - -void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId user_id, - const DialogParticipantStatus &new_status, - const DialogParticipantStatus &old_status) { - auto c = get_channel_force(channel_id, "speculative_add_channel_user"); - // channel full must be loaded before c->participant_count is updated, because on_load_channel_full_from_database - // must copy the initial c->participant_count before it is speculatibely updated - auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_user"); - int32 min_count = 0; - LOG(INFO) << "Speculatively change status of " << user_id << " in " << channel_id << " from " << old_status << " to " - << new_status; - if (channel_full != nullptr) { - channel_full->is_changed |= speculative_add_count( - channel_full->administrator_count, new_status.is_administrator_member() - old_status.is_administrator_member()); - min_count = channel_full->administrator_count; - } - - if (c != nullptr && c->participant_count != 0 && - speculative_add_count(c->participant_count, new_status.is_member() - old_status.is_member(), min_count)) { - c->is_changed = true; - update_channel(c, channel_id); - } - - td_->dialog_participant_manager_->update_cached_channel_participant_status(channel_id, user_id, new_status); - - if (channel_full == nullptr) { - return; - } - - channel_full->is_changed |= speculative_add_count(channel_full->participant_count, - new_status.is_member() - old_status.is_member(), min_count); - channel_full->is_changed |= - speculative_add_count(channel_full->restricted_count, new_status.is_restricted() - old_status.is_restricted()); - channel_full->is_changed |= - speculative_add_count(channel_full->banned_count, new_status.is_banned() - old_status.is_banned()); - - if (channel_full->is_changed) { - channel_full->speculative_version++; - } - - if (new_status.is_member() != old_status.is_member() && is_user_bot(user_id)) { - if (new_status.is_member()) { - if (!td::contains(channel_full->bot_user_ids, user_id)) { - channel_full->bot_user_ids.push_back(user_id); - channel_full->need_save_to_database = true; - reload_channel_full(channel_id, Promise(), "speculative_add_channel_user"); - - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - channel_full->bot_user_ids, false); - } - } else { - if (td::remove(channel_full->bot_user_ids, user_id)) { - channel_full->need_save_to_database = true; - - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - channel_full->bot_user_ids, false); - } - } - } - - update_channel_full(channel_full, channel_id, "speculative_add_channel_user"); -} - -void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, - const char *source) { - LOG(INFO) << "Invalidate supergroup full for " << channel_id << " from " << source; - auto channel_full = get_channel_full(channel_id, true, "invalidate_channel_full"); // must not load ChannelFull - if (channel_full != nullptr) { - do_invalidate_channel_full(channel_full, channel_id, need_drop_slow_mode_delay); - update_channel_full(channel_full, channel_id, source); - } else if (channel_id.is_valid()) { - invalidated_channels_full_.insert(channel_id); - } -} - -void ContactsManager::do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, - bool need_drop_slow_mode_delay) { - CHECK(channel_full != nullptr); - td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(channel_id)); - if (channel_full->expires_at >= Time::now()) { - channel_full->expires_at = 0.0; - channel_full->need_save_to_database = true; - } - if (need_drop_slow_mode_delay && channel_full->slow_mode_delay != 0) { - channel_full->slow_mode_delay = 0; - channel_full->slow_mode_next_send_date = 0; - channel_full->is_slow_mode_next_send_date_changed = true; - channel_full->is_changed = true; - } -} - -void ContactsManager::on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo) { - CHECK(chat_full != nullptr); - if (photo != chat_full->photo) { - chat_full->photo = std::move(photo); - chat_full->is_changed = true; - } - - auto photo_file_ids = photo_get_file_ids(chat_full->photo); - if (chat_full->registered_photo_file_ids == photo_file_ids) { - return; - } - - auto &file_source_id = chat_full->file_source_id; - if (!file_source_id.is_valid()) { - file_source_id = chat_full_file_source_ids_.get(chat_id); - if (file_source_id.is_valid()) { - VLOG(file_references) << "Move " << file_source_id << " inside of " << chat_id; - chat_full_file_source_ids_.erase(chat_id); - } else { - VLOG(file_references) << "Need to create new file source for full " << chat_id; - file_source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); - } - } - - td_->file_manager_->change_files_source(file_source_id, chat_full->registered_photo_file_ids, photo_file_ids); - chat_full->registered_photo_file_ids = std::move(photo_file_ids); -} - -void ContactsManager::on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo) { - CHECK(channel_full != nullptr); - if (photo != channel_full->photo) { - channel_full->photo = std::move(photo); - channel_full->is_changed = true; - } - - auto photo_file_ids = photo_get_file_ids(channel_full->photo); - if (channel_full->registered_photo_file_ids == photo_file_ids) { - return; - } - - auto &file_source_id = channel_full->file_source_id; - if (!file_source_id.is_valid()) { - file_source_id = channel_full_file_source_ids_.get(channel_id); - if (file_source_id.is_valid()) { - VLOG(file_references) << "Move " << file_source_id << " inside of " << channel_id; - channel_full_file_source_ids_.erase(channel_id); - } else { - VLOG(file_references) << "Need to create new file source for full " << channel_id; - file_source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); - } - } - - td_->file_manager_->change_files_source(file_source_id, channel_full->registered_photo_file_ids, photo_file_ids); - channel_full->registered_photo_file_ids = std::move(photo_file_ids); -} - -void ContactsManager::on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link) { - switch (dialog_id.get_type()) { - case DialogType::Chat: { - auto chat_id = dialog_id.get_chat_id(); - auto chat_full = get_chat_full_force(chat_id, "on_get_permanent_dialog_invite_link"); - if (chat_full != nullptr && update_permanent_invite_link(chat_full->invite_link, invite_link)) { - chat_full->is_changed = true; - update_chat_full(chat_full, chat_id, "on_get_permanent_dialog_invite_link"); - } - break; - } - case DialogType::Channel: { - auto channel_id = dialog_id.get_channel_id(); - auto channel_full = get_channel_full_force(channel_id, true, "on_get_permanent_dialog_invite_link"); - if (channel_full != nullptr && update_permanent_invite_link(channel_full->invite_link, invite_link)) { - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_get_permanent_dialog_invite_link"); - } - break; - } - case DialogType::User: - case DialogType::SecretChat: - case DialogType::None: - default: - UNREACHABLE(); - } -} - -void ContactsManager::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"))) { - chat_full->is_changed = true; - } -} - -void ContactsManager::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"))) { - channel_full->is_changed = true; - } -} - -void ContactsManager::remove_linked_channel_id(ChannelId channel_id) { - if (!channel_id.is_valid()) { - return; - } - - auto linked_channel_id = linked_channel_ids_.get(channel_id); - if (linked_channel_id.is_valid()) { - linked_channel_ids_.erase(channel_id); - linked_channel_ids_.erase(linked_channel_id); - } -} - -ChannelId ContactsManager::get_linked_channel_id(ChannelId channel_id) const { - auto channel_full = get_channel_full(channel_id); - if (channel_full != nullptr) { - return channel_full->linked_channel_id; - } - - return linked_channel_ids_.get(channel_id); -} - -void ContactsManager::on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, - ChannelId linked_channel_id) { - auto old_linked_channel_id = get_linked_channel_id(channel_id); - LOG(INFO) << "Uplate linked channel in " << channel_id << " from " << old_linked_channel_id << " to " - << linked_channel_id; - - if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id && - channel_full->linked_channel_id.is_valid()) { - get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 10"); - get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 0"); - } - auto old_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); - - remove_linked_channel_id(channel_id); - remove_linked_channel_id(linked_channel_id); - if (channel_id.is_valid() && linked_channel_id.is_valid()) { - linked_channel_ids_.set(channel_id, linked_channel_id); - linked_channel_ids_.set(linked_channel_id, channel_id); - } - - if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id) { - if (channel_full->linked_channel_id.is_valid()) { - // remove link from a previously linked channel_full - auto linked_channel = - get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 11"); - if (linked_channel != nullptr && linked_channel->has_linked_channel) { - linked_channel->has_linked_channel = false; - linked_channel->is_changed = true; - update_channel(linked_channel, channel_full->linked_channel_id); - reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 21"); - } - auto linked_channel_full = - get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 1"); - if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id == channel_id) { - linked_channel_full->linked_channel_id = ChannelId(); - linked_channel_full->is_changed = true; - update_channel_full(linked_channel_full, channel_full->linked_channel_id, - "on_update_channel_full_linked_channel_id 3"); - } - } - - channel_full->linked_channel_id = linked_channel_id; - channel_full->is_changed = true; - - if (channel_full->linked_channel_id.is_valid()) { - // add link from a newly linked channel_full - auto linked_channel = - get_channel_force(channel_full->linked_channel_id, "on_update_channel_full_linked_channel_id 12"); - if (linked_channel != nullptr && !linked_channel->has_linked_channel) { - linked_channel->has_linked_channel = true; - linked_channel->is_changed = true; - update_channel(linked_channel, channel_full->linked_channel_id); - reload_channel(channel_full->linked_channel_id, Auto(), "on_update_channel_full_linked_channel_id 22"); - } - auto linked_channel_full = - get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 2"); - if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id != channel_id) { - linked_channel_full->linked_channel_id = channel_id; - linked_channel_full->is_changed = true; - update_channel_full(linked_channel_full, channel_full->linked_channel_id, - "on_update_channel_full_linked_channel_id 4"); - } - } - } - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - if (linked_channel_id.is_valid() != c->has_linked_channel) { - c->has_linked_channel = linked_channel_id.is_valid(); - c->is_changed = true; - update_channel(c, channel_id); - } - - if (old_linked_channel_id != linked_channel_id) { - // must be called after the linked channel is changed - td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(channel_id), old_linked_channel_id, - linked_channel_id); - } - - if (linked_channel_id.is_valid()) { - auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id); - LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to " - << new_linked_linked_channel_id; - if (old_linked_linked_channel_id != new_linked_linked_channel_id) { - // must be called after the linked channel is changed - td_->messages_manager_->on_dialog_linked_channel_updated( - DialogId(linked_channel_id), old_linked_linked_channel_id, new_linked_linked_channel_id); - } - } -} - -void ContactsManager::on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, - const DialogLocation &location) { - if (channel_full->location != location) { - channel_full->location = location; - channel_full->is_changed = true; - } - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - on_update_channel_has_location(c, channel_id, !location.empty()); - update_channel(c, channel_id); -} - -void ContactsManager::on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, - int32 slow_mode_delay, int32 slow_mode_next_send_date) { - if (slow_mode_delay < 0) { - LOG(ERROR) << "Receive slow mode delay " << slow_mode_delay << " in " << channel_id; - slow_mode_delay = 0; - } - - if (channel_full->slow_mode_delay != slow_mode_delay) { - channel_full->slow_mode_delay = slow_mode_delay; - channel_full->is_changed = true; - } - on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); - - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - bool is_slow_mode_enabled = slow_mode_delay != 0; - if (is_slow_mode_enabled != c->is_slow_mode_enabled) { - c->is_slow_mode_enabled = is_slow_mode_enabled; - c->is_changed = true; - update_channel(c, channel_id); - } -} - -void ContactsManager::on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full, - int32 slow_mode_next_send_date) { - if (slow_mode_next_send_date < 0) { - LOG(ERROR) << "Receive slow mode next send date " << slow_mode_next_send_date; - slow_mode_next_send_date = 0; - } - if (channel_full->slow_mode_delay == 0 && slow_mode_next_send_date > 0) { - LOG(ERROR) << "Slow mode is disabled, but next send date is " << slow_mode_next_send_date; - slow_mode_next_send_date = 0; - } - - if (slow_mode_next_send_date != 0) { - auto now = G()->unix_time(); - if (slow_mode_next_send_date <= now) { - slow_mode_next_send_date = 0; - } - if (slow_mode_next_send_date > now + 3601) { - slow_mode_next_send_date = now + 3601; - } - } - if (channel_full->slow_mode_next_send_date != slow_mode_next_send_date) { - channel_full->slow_mode_next_send_date = slow_mode_next_send_date; - channel_full->is_slow_mode_next_send_date_changed = true; - if (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count) { - channel_full->is_changed = true; - } else { - channel_full->need_save_to_database = true; - } - } -} - -bool ContactsManager::update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link) { - if (new_invite_link != invite_link) { - if (invite_link.is_valid() && invite_link.get_invite_link() != new_invite_link.get_invite_link()) { - // old link was invalidated - td_->dialog_invite_link_manager_->invalidate_invite_link_info(invite_link.get_invite_link()); - } - - invite_link = std::move(new_invite_link); - return true; - } - return false; -} - -bool ContactsManager::need_poll_user_active_stories(const User *u, UserId user_id) const { - return u != nullptr && user_id != get_my_id() && !is_user_contact(u, user_id, false) && !is_user_bot(u) && - !is_user_support(u) && !is_user_deleted(u) && u->was_online != 0; -} - -void ContactsManager::repair_chat_participants(ChatId chat_id) { - send_get_chat_full_query(chat_id, Auto(), "repair_chat_participants"); -} - -void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, - int32 version) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - if (!have_user(user_id)) { - LOG(ERROR) << "Can't find " << user_id; - return; - } - if (!have_user(inviter_user_id)) { - LOG(ERROR) << "Can't find " << inviter_user_id; - return; - } - LOG(INFO) << "Receive updateChatParticipantAdd to " << chat_id << " with " << user_id << " invited by " - << inviter_user_id << " at " << date << " with version " << version; - - ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_add_user"); - if (chat_full == nullptr) { - LOG(INFO) << "Ignoring update about members of " << chat_id; - return; - } - auto c = get_chat(chat_id); - if (c == nullptr) { - LOG(ERROR) << "Receive updateChatParticipantAdd for unknown " << chat_id << ". Couldn't apply it"; - repair_chat_participants(chat_id); - return; - } - if (c->status.is_left()) { - // possible if updates come out of order - LOG(WARNING) << "Receive updateChatParticipantAdd for left " << chat_id << ". Couldn't apply it"; - - repair_chat_participants(chat_id); // just in case - return; - } - if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { - for (auto &participant : chat_full->participants) { - if (participant.dialog_id_ == DialogId(user_id)) { - if (participant.inviter_user_id_ != inviter_user_id) { - LOG(ERROR) << user_id << " was readded to " << chat_id << " by " << inviter_user_id - << ", previously invited by " << participant.inviter_user_id_; - participant.inviter_user_id_ = inviter_user_id; - participant.joined_date_ = date; - repair_chat_participants(chat_id); - } else { - // Possible if update comes twice - LOG(INFO) << user_id << " was readded to " << chat_id; - } - return; - } - } - 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()}); - 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"); - - // Chat is already updated - if (chat_full->version == c->version && - narrow_cast(chat_full->participants.size()) != c->participant_count) { - LOG(ERROR) << "Number of members in " << chat_id << " with version " << c->version << " is " - << c->participant_count << " but there are " << chat_full->participants.size() - << " members in the ChatFull"; - repair_chat_participants(chat_id); - } - } -} - -void ContactsManager::on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, - int32 version) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - if (!have_user(user_id)) { - LOG(ERROR) << "Can't find " << user_id; - return; - } - LOG(INFO) << "Receive updateChatParticipantAdmin in " << chat_id << " with " << user_id << ", administrator rights " - << (is_administrator ? "enabled" : "disabled") << " with version " << version; - - auto c = get_chat_force(chat_id, "on_update_chat_edit_administrator"); - if (c == nullptr) { - LOG(INFO) << "Ignoring update about members of unknown " << chat_id; - return; - } - - if (c->status.is_left()) { - // possible if updates come out of order - LOG(WARNING) << "Receive updateChatParticipantAdmin for left " << chat_id << ". Couldn't apply it"; - - repair_chat_participants(chat_id); // just in case - return; - } - if (version <= -1) { - LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; - return; - } - CHECK(c->version >= 0); - - auto status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->status.is_creator()) - : DialogParticipantStatus::Member(); - if (version > c->version) { - if (version != c->version + 1) { - LOG(INFO) << "Administrators of " << chat_id << " with version " << c->version - << " has changed, but new version is " << version; - repair_chat_participants(chat_id); - return; - } - - c->version = version; - c->need_save_to_database = true; - if (user_id == get_my_id() && !c->status.is_creator()) { - // if chat with version was already received, then the update is already processed - // so we need to call on_update_chat_status only if version > c->version - on_update_chat_status(c, chat_id, status); - } - update_chat(c, chat_id); - } - - ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_edit_administrator"); - if (chat_full != nullptr) { - if (chat_full->version + 1 == version) { - for (auto &participant : chat_full->participants) { - if (participant.dialog_id_ == DialogId(user_id)) { - participant.status_ = std::move(status); - chat_full->is_changed = true; - update_chat_full(chat_full, chat_id, "on_update_chat_edit_administrator"); - return; - } - } - } - - // can't find chat member or version have increased too much - repair_chat_participants(chat_id); - } -} - -void ContactsManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - if (!have_user(user_id)) { - LOG(ERROR) << "Can't find " << user_id; - return; - } - LOG(INFO) << "Receive updateChatParticipantDelete from " << chat_id << " with " << user_id << " and version " - << version; - - ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_delete_user"); - if (chat_full == nullptr) { - LOG(INFO) << "Ignoring update about members of " << chat_id; - return; - } - const Chat *c = get_chat_force(chat_id, "on_update_chat_delete_user"); - if (c == nullptr) { - LOG(ERROR) << "Receive updateChatParticipantDelete for unknown " << chat_id; - repair_chat_participants(chat_id); - return; - } - if (user_id == get_my_id()) { - LOG_IF(WARNING, c->status.is_member()) << "User was removed from " << chat_id - << " but it is not left the group. Possible if updates comes out of order"; - return; - } - if (c->status.is_left()) { - // possible if updates come out of order - LOG(INFO) << "Receive updateChatParticipantDelete for left " << chat_id; - - repair_chat_participants(chat_id); - return; - } - if (on_update_chat_full_participants_short(chat_full, chat_id, version)) { - for (size_t i = 0; i < chat_full->participants.size(); i++) { - if (chat_full->participants[i].dialog_id_ == DialogId(user_id)) { - chat_full->participants[i] = chat_full->participants.back(); - chat_full->participants.resize(chat_full->participants.size() - 1); - chat_full->is_changed = true; - update_chat_online_member_count(chat_full, chat_id, false); - update_chat_full(chat_full, chat_id, "on_update_chat_delete_user"); - - if (static_cast(chat_full->participants.size()) != c->participant_count) { - repair_chat_participants(chat_id); - } - return; - } - } - LOG(ERROR) << "Can't find basic group member " << user_id << " in " << chat_id << " to be removed"; - repair_chat_participants(chat_id); - } -} - -void ContactsManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status) { - if (c->status != status) { - LOG(INFO) << "Update " << chat_id << " status from " << c->status << " to " << status; - bool need_reload_group_call = c->status.can_manage_calls() != status.can_manage_calls(); - bool need_drop_invite_link = c->status.can_manage_invite_links() && !status.can_manage_invite_links(); - - c->status = std::move(status); - c->is_status_changed = true; - - if (c->status.is_left()) { - c->participant_count = 0; - c->version = -1; - c->default_permissions_version = -1; - c->pinned_message_version = -1; - - drop_chat_full(chat_id); - } else if (need_drop_invite_link) { - ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_status"); - if (chat_full != nullptr) { - on_update_chat_full_invite_link(chat_full, nullptr); - update_chat_full(chat_full, chat_id, "on_update_chat_status"); - } - } - if (need_reload_group_call) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, - DialogId(chat_id)); - } - - c->is_changed = true; - } -} - -void ContactsManager::on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, - int32 version) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - auto c = get_chat_force(chat_id, "on_update_chat_default_permissions"); - if (c == nullptr) { - LOG(INFO) << "Ignoring update about unknown " << chat_id; - return; - } - - LOG(INFO) << "Receive updateChatDefaultBannedRights in " << chat_id << " with " << default_permissions - << " and version " << version << ". Current version is " << c->version; - - if (c->status.is_left()) { - // possible if updates come out of order - LOG(WARNING) << "Receive updateChatDefaultBannedRights for left " << chat_id << ". Couldn't apply it"; - - repair_chat_participants(chat_id); // just in case - return; - } - if (version <= -1) { - LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; - return; - } - CHECK(c->version >= 0); - - if (version > c->version) { - // this should be unreachable, because version and default permissions must be already updated from - // the chat object in on_get_chat - if (version != c->version + 1) { - LOG(INFO) << "Default permissions of " << chat_id << " with version " << c->version - << " has changed, but new version is " << version; - repair_chat_participants(chat_id); - return; - } - - LOG_IF(ERROR, default_permissions == c->default_permissions) - << "Receive updateChatDefaultBannedRights in " << chat_id << " with version " << version - << " and default_permissions = " << default_permissions - << ", but default_permissions are not changed. Current version is " << c->version; - c->version = version; - c->need_save_to_database = true; - on_update_chat_default_permissions(c, chat_id, default_permissions, version); - update_chat(c, chat_id); - } -} - -void ContactsManager::on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions, - int32 version) { - if (c->default_permissions != default_permissions && version >= c->default_permissions_version) { - LOG(INFO) << "Update " << chat_id << " default permissions from " << c->default_permissions << " to " - << default_permissions << " and version from " << c->default_permissions_version << " to " << version; - c->default_permissions = default_permissions; - c->default_permissions_version = version; - c->is_default_permissions_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards) { - if (c->noforwards != noforwards) { - LOG(INFO) << "Update " << chat_id << " has_protected_content from " << c->noforwards << " to " << noforwards; - c->noforwards = noforwards; - c->is_noforwards_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - auto c = get_chat_force(chat_id, "on_update_chat_pinned_message"); - if (c == nullptr) { - LOG(INFO) << "Ignoring update about unknown " << chat_id; - return; - } - - LOG(INFO) << "Receive updateChatPinnedMessage in " << chat_id << " with " << pinned_message_id << " and version " - << version << ". Current version is " << c->version << "/" << c->pinned_message_version; - - if (c->status.is_left()) { - // possible if updates come out of order - repair_chat_participants(chat_id); // just in case - return; - } - if (version <= -1) { - LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; - return; - } - CHECK(c->version >= 0); - - if (version >= c->pinned_message_version) { - if (version != c->version + 1 && version != c->version) { - LOG(INFO) << "Pinned message of " << chat_id << " with version " << c->version - << " has changed, but new version is " << version; - repair_chat_participants(chat_id); - } else if (version == c->version + 1) { - c->version = version; - c->need_save_to_database = true; - } - td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id); - if (version > c->pinned_message_version) { - LOG(INFO) << "Change pinned message version of " << chat_id << " from " << c->pinned_message_version << " to " - << version; - c->pinned_message_version = version; - c->need_save_to_database = true; - } - update_chat(c, chat_id); - } -} - -void ContactsManager::on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version, - const string &debug_str) { - if (version <= -1) { - LOG(ERROR) << "Receive wrong version " << version << " in " << chat_id << debug_str; - return; - } - - if (version < c->version) { - // some outdated data - LOG(INFO) << "Receive number of members in " << chat_id << " with version " << version << debug_str - << ", but current version is " << c->version; - return; - } - - if (c->participant_count != participant_count) { - if (version == c->version && participant_count != 0) { - // version is not changed when deleted user is removed from the chat - LOG_IF(ERROR, c->participant_count != participant_count + 1) - << "Number of members in " << chat_id << " has changed from " << c->participant_count << " to " - << participant_count << ", but version " << c->version << " remains unchanged" << debug_str; - repair_chat_participants(chat_id); - } - - c->participant_count = participant_count; - c->version = version; - c->is_changed = true; - return; - } - - if (version > c->version) { - c->version = version; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_chat_photo(Chat *c, ChatId chat_id, - tl_object_ptr &&chat_photo_ptr) { - on_update_chat_photo( - c, chat_id, get_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, std::move(chat_photo_ptr)), true); -} - -void ContactsManager::on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache) { - if (td_->auth_manager_->is_bot()) { - photo.minithumbnail.clear(); - } - - if (need_update_dialog_photo(c->photo, photo)) { - c->photo = std::move(photo); - c->is_photo_changed = true; - c->need_save_to_database = true; - - if (invalidate_photo_cache) { - auto chat_full = get_chat_full(chat_id); // must not load ChatFull - if (chat_full != nullptr) { - if (!chat_full->photo.is_empty()) { - chat_full->photo = Photo(); - chat_full->is_changed = true; - } - if (c->photo.small_file_id.is_valid()) { - reload_chat_full(chat_id, Auto(), "on_update_chat_photo"); - } - update_chat_full(chat_full, chat_id, "on_update_chat_photo"); - } - } - } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { - c->photo.minithumbnail = std::move(photo.minithumbnail); - c->is_photo_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_chat_title(Chat *c, ChatId chat_id, string &&title) { - if (c->title != title) { - c->title = std::move(title); - c->is_title_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_chat_active(Chat *c, ChatId chat_id, bool is_active) { - if (c->is_active != is_active) { - c->is_active = is_active; - c->is_is_active_changed = true; - c->is_changed = true; - } -} - -void ContactsManager::on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id) { - if (c->migrated_to_channel_id != migrated_to_channel_id && migrated_to_channel_id.is_valid()) { - LOG_IF(ERROR, c->migrated_to_channel_id.is_valid()) - << "Upgraded supergroup ID for " << chat_id << " has changed from " << c->migrated_to_channel_id << " to " - << migrated_to_channel_id; - c->migrated_to_channel_id = migrated_to_channel_id; - c->is_changed = true; - } -} - -void ContactsManager::on_update_chat_description(ChatId chat_id, string &&description) { - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id; - return; - } - - auto chat_full = get_chat_full_force(chat_id, "on_update_chat_description"); - if (chat_full == nullptr) { - return; - } - if (chat_full->description != description) { - chat_full->description = std::move(description); - chat_full->is_changed = true; - update_chat_full(chat_full, chat_id, "on_update_chat_description"); - td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true); - } -} - -bool ContactsManager::on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version) { - if (version <= -1) { - LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id; - return false; - } - if (chat_full->version == -1) { - // chat members are unknown, nothing to update - return false; - } - - if (chat_full->version + 1 == version) { - chat_full->version = version; - return true; - } - - LOG(INFO) << "Number of members in " << chat_id << " with version " << chat_full->version - << " has changed, but new version is " << version; - repair_chat_participants(chat_id); - return false; -} - -void ContactsManager::on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, - vector participants, int32 version, - bool from_update) { - if (version <= -1) { - LOG(ERROR) << "Receive members with wrong version " << version << " in " << chat_id; - return; - } - - if (version < chat_full->version) { - // some outdated data - LOG(WARNING) << "Receive members of " << chat_id << " with version " << version << " but current version is " - << chat_full->version; - return; - } - - if ((chat_full->participants.size() != participants.size() && version == chat_full->version) || - (from_update && version != chat_full->version + 1)) { - LOG(INFO) << "Members of " << chat_id << " has changed"; - // this is possible in very rare situations - repair_chat_participants(chat_id); - } - - chat_full->participants = std::move(participants); - chat_full->version = version; - chat_full->is_changed = true; - update_chat_online_member_count(chat_full, chat_id, true); -} - -void ContactsManager::drop_chat_full(ChatId chat_id) { - ChatFull *chat_full = get_chat_full_force(chat_id, "drop_chat_full"); - if (chat_full == nullptr) { - return; - } - - LOG(INFO) << "Drop basicGroupFullInfo of " << chat_id; - on_update_chat_full_photo(chat_full, chat_id, Photo()); - // chat_full->creator_user_id = UserId(); - chat_full->participants.clear(); - chat_full->bot_commands.clear(); - chat_full->version = -1; - on_update_chat_full_invite_link(chat_full, nullptr); - update_chat_online_member_count(chat_full, chat_id, true); - chat_full->is_changed = true; - update_chat_full(chat_full, chat_id, "drop_chat_full"); -} - -void ContactsManager::on_update_channel_photo(Channel *c, ChannelId channel_id, - tl_object_ptr &&chat_photo_ptr) { - on_update_channel_photo( - c, channel_id, - get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, std::move(chat_photo_ptr)), - true); -} - -void ContactsManager::on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, - bool invalidate_photo_cache) { - if (td_->auth_manager_->is_bot()) { - photo.minithumbnail.clear(); - } - - if (need_update_dialog_photo(c->photo, photo)) { - c->photo = std::move(photo); - c->is_photo_changed = true; - c->need_save_to_database = true; - - if (invalidate_photo_cache) { - auto channel_full = get_channel_full(channel_id, true, "on_update_channel_photo"); // must not load ChannelFull - if (channel_full != nullptr) { - if (!channel_full->photo.is_empty()) { - channel_full->photo = Photo(); - channel_full->is_changed = true; - } - if (c->photo.small_file_id.is_valid()) { - if (channel_full->expires_at > 0.0) { - channel_full->expires_at = 0.0; - channel_full->need_save_to_database = true; - } - reload_channel_full(channel_id, Auto(), "on_update_channel_photo"); - } - update_channel_full(channel_full, channel_id, "on_update_channel_photo"); - } - } - } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) { - c->photo.minithumbnail = std::move(photo.minithumbnail); - c->is_photo_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_emoji_status(Channel *c, ChannelId channel_id, EmojiStatus emoji_status) { - if (c->emoji_status != emoji_status) { - LOG(DEBUG) << "Change emoji status of " << channel_id << " from " << c->emoji_status << " to " << emoji_status; - c->emoji_status = std::move(emoji_status); - c->is_emoji_status_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, - AccentColorId accent_color_id) { - if (accent_color_id == AccentColorId(channel_id) || !accent_color_id.is_valid()) { - accent_color_id = AccentColorId(); - } - if (c->accent_color_id != accent_color_id) { - c->accent_color_id = accent_color_id; - c->is_accent_color_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, - CustomEmojiId background_custom_emoji_id) { - if (c->background_custom_emoji_id != background_custom_emoji_id) { - c->background_custom_emoji_id = background_custom_emoji_id; - c->is_accent_color_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_profile_accent_color_id(Channel *c, ChannelId channel_id, - AccentColorId profile_accent_color_id) { - if (!profile_accent_color_id.is_valid()) { - profile_accent_color_id = AccentColorId(); - } - if (c->profile_accent_color_id != profile_accent_color_id) { - c->profile_accent_color_id = profile_accent_color_id; - c->is_accent_color_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_profile_background_custom_emoji_id( - Channel *c, ChannelId channel_id, CustomEmojiId profile_background_custom_emoji_id) { - if (c->profile_background_custom_emoji_id != profile_background_custom_emoji_id) { - c->profile_background_custom_emoji_id = profile_background_custom_emoji_id; - c->is_accent_color_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_title(Channel *c, ChannelId channel_id, string &&title) { - if (c->title != title) { - c->title = std::move(title); - c->is_title_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status) { - if (c->status != status) { - LOG(INFO) << "Update " << channel_id << " status from " << c->status << " to " << status; - if (c->is_update_supergroup_sent) { - on_channel_status_changed(c, channel_id, c->status, status); - } - c->status = status; - c->is_status_changed = true; - c->is_changed = true; - } -} - -void ContactsManager::on_channel_status_changed(Channel *c, ChannelId channel_id, - const DialogParticipantStatus &old_status, - const DialogParticipantStatus &new_status) { - CHECK(c->is_update_supergroup_sent); - bool have_channel_full = get_channel_full(channel_id) != nullptr; - - if (old_status.can_post_stories() != new_status.can_post_stories()) { - td_->story_manager_->update_dialogs_to_send_stories(channel_id, new_status.can_post_stories()); - } - - bool need_reload_group_call = old_status.can_manage_calls() != new_status.can_manage_calls(); - if (old_status.can_manage_invite_links() && !new_status.can_manage_invite_links()) { - auto channel_full = get_channel_full(channel_id, true, "on_channel_status_changed"); - if (channel_full != nullptr) { // otherwise invite_link will be dropped when the channel is loaded - on_update_channel_full_invite_link(channel_full, nullptr); - do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled); - update_channel_full(channel_full, channel_id, "on_channel_status_changed"); - } - } else { - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_status_changed"); - } - - if (old_status.is_creator() != new_status.is_creator()) { - c->is_creator_changed = true; - - send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner"); - td_->dialog_participant_manager_->reload_dialog_administrators(DialogId(channel_id), {}, Auto()); - remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)}); - } - - if (old_status.is_member() != new_status.is_member() || new_status.is_banned()) { - td_->dialog_invite_link_manager_->remove_dialog_access_by_invite_link(DialogId(channel_id)); - - if (new_status.is_member() || new_status.is_creator()) { - reload_channel_full(channel_id, - PromiseCreator::lambda([channel_id](Unit) { LOG(INFO) << "Reloaded full " << channel_id; }), - "on_channel_status_changed"); - } - } - if (need_reload_group_call) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights, - DialogId(channel_id)); - } - bool is_bot = td_->auth_manager_->is_bot(); - if (is_bot && old_status.is_administrator() && !new_status.is_administrator()) { - td_->dialog_participant_manager_->drop_channel_participant_cache(channel_id); - } - if (is_bot && old_status.is_member() && !new_status.is_member() && !G()->use_message_database()) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id), - Promise()); - } - if (!is_bot && old_status.is_member() != new_status.is_member()) { - if (new_status.is_member()) { - send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, - DialogId(channel_id)); - } else { - send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, - DialogId(channel_id), "on_channel_status_changed"); - } - } - - // must not load ChannelFull, because must not change the Channel - CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); -} - -void ContactsManager::on_update_channel_default_permissions(Channel *c, ChannelId channel_id, - RestrictedRights default_permissions) { - if (c->is_megagroup && c->default_permissions != default_permissions) { - LOG(INFO) << "Update " << channel_id << " default permissions from " << c->default_permissions << " to " - << default_permissions; - c->default_permissions = default_permissions; - c->is_default_permissions_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location) { - if (c->has_location != has_location) { - LOG(INFO) << "Update " << channel_id << " has_location from " << c->has_location << " to " << has_location; - c->has_location = has_location; - c->is_has_location_changed = true; - c->is_changed = true; - } -} - -void ContactsManager::on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards) { - if (c->noforwards != noforwards) { - LOG(INFO) << "Update " << channel_id << " has_protected_content from " << c->noforwards << " to " << noforwards; - c->noforwards = noforwards; - c->is_noforwards_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_story_ids(ChannelId channel_id, StoryId max_active_story_id, - StoryId max_read_story_id) { - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id; - return; - } - - Channel *c = get_channel_force(channel_id, "on_update_channel_story_ids"); - if (c != nullptr) { - on_update_channel_story_ids_impl(c, channel_id, max_active_story_id, max_read_story_id); - update_channel(c, channel_id); - } else { - LOG(INFO) << "Ignore update channel story identifiers about unknown " << channel_id; - } -} - -void ContactsManager::on_update_channel_story_ids_impl(Channel *c, ChannelId channel_id, StoryId max_active_story_id, - StoryId max_read_story_id) { - if (td_->auth_manager_->is_bot()) { - return; - } - if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { - LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << channel_id; - return; - } - if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { - LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << channel_id; - return; - } - - auto has_unread_stories = get_channel_has_unread_stories(c); - if (c->max_active_story_id != max_active_story_id) { - LOG(DEBUG) << "Change last active story of " << channel_id << " from " << c->max_active_story_id << " to " - << max_active_story_id; - c->max_active_story_id = max_active_story_id; - c->need_save_to_database = true; - } - if (need_poll_channel_active_stories(c, channel_id)) { - auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; - if (max_active_story_id_next_reload_time > - c->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { - LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << channel_id; - c->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; - c->need_save_to_database = true; - } - } - if (!max_active_story_id.is_valid()) { - CHECK(max_read_story_id == StoryId()); - if (c->max_read_story_id != StoryId()) { - LOG(DEBUG) << "Drop last read " << c->max_read_story_id << " of " << channel_id; - c->max_read_story_id = StoryId(); - c->need_save_to_database = true; - } - } else if (max_read_story_id.get() > c->max_read_story_id.get()) { - LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " - << max_read_story_id; - c->max_read_story_id = max_read_story_id; - c->need_save_to_database = true; - } - if (has_unread_stories != get_channel_has_unread_stories(c)) { - LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; - c->is_changed = true; - } -} - -void ContactsManager::on_update_channel_max_read_story_id(ChannelId channel_id, StoryId max_read_story_id) { - CHECK(channel_id.is_valid()); - - Channel *c = get_channel(channel_id); - if (c != nullptr) { - on_update_channel_max_read_story_id(c, channel_id, max_read_story_id); - update_channel(c, channel_id); - } -} - -void ContactsManager::on_update_channel_max_read_story_id(Channel *c, ChannelId channel_id, StoryId max_read_story_id) { - if (td_->auth_manager_->is_bot()) { - return; - } - - auto has_unread_stories = get_channel_has_unread_stories(c); - if (max_read_story_id.get() > c->max_read_story_id.get()) { - LOG(DEBUG) << "Change last read story of " << channel_id << " from " << c->max_read_story_id << " to " - << max_read_story_id; - c->max_read_story_id = max_read_story_id; - c->need_save_to_database = true; - } - if (has_unread_stories != get_channel_has_unread_stories(c)) { - LOG(DEBUG) << "Change has_unread_stories of " << channel_id << " to " << !has_unread_stories; - c->is_changed = true; - } -} - -void ContactsManager::on_update_channel_stories_hidden(ChannelId channel_id, bool stories_hidden) { - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id; - return; - } - - Channel *c = get_channel_force(channel_id, "on_update_channel_stories_hidden"); - if (c != nullptr) { - on_update_channel_stories_hidden(c, channel_id, stories_hidden); - update_channel(c, channel_id); - } else { - LOG(INFO) << "Ignore update channel stories are archived about unknown " << channel_id; - } -} - -void ContactsManager::on_update_channel_stories_hidden(Channel *c, ChannelId channel_id, bool stories_hidden) { - if (td_->auth_manager_->is_bot()) { - return; - } - - if (c->stories_hidden != stories_hidden) { - LOG(DEBUG) << "Change stories are archived of " << channel_id << " to " << stories_hidden; - c->stories_hidden = stories_hidden; - c->is_stories_hidden_changed = true; - c->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_participant_count(ChannelId channel_id, int32 participant_count) { - Channel *c = get_channel(channel_id); - if (c == nullptr || c->participant_count == participant_count) { - return; - } - - c->participant_count = participant_count; - c->is_changed = true; - update_channel(c, channel_id); - - auto channel_full = get_channel_full(channel_id, true, "on_update_channel_participant_count"); - if (channel_full != nullptr && channel_full->participant_count != participant_count) { - if (channel_full->administrator_count > participant_count) { - channel_full->administrator_count = participant_count; - } - channel_full->participant_count = participant_count; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_participant_count"); - } -} - -void ContactsManager::on_update_channel_editable_username(ChannelId channel_id, string &&username) { - Channel *c = get_channel(channel_id); - CHECK(c != nullptr); - on_update_channel_usernames(c, channel_id, c->usernames.change_editable_username(std::move(username))); - update_channel(c, channel_id); -} - -void ContactsManager::on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames) { - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id; - return; - } - - Channel *c = get_channel_force(channel_id, "on_update_channel_usernames"); - if (c != nullptr) { - on_update_channel_usernames(c, channel_id, std::move(usernames)); - update_channel(c, channel_id); - } else { - LOG(INFO) << "Ignore update channel usernames about unknown " << channel_id; - } -} - -void ContactsManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) { - if (c->usernames != usernames) { - td_->dialog_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); - td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames); - if (c->is_update_supergroup_sent) { - on_channel_usernames_changed(c, channel_id, c->usernames, usernames); - } - - c->usernames = std::move(usernames); - c->is_username_changed = true; - c->is_changed = true; - } else { - td_->dialog_manager_->on_dialog_usernames_received(DialogId(channel_id), usernames, false); - } -} - -void ContactsManager::on_channel_usernames_changed(const Channel *c, ChannelId channel_id, - const Usernames &old_usernames, const Usernames &new_usernames) { - bool have_channel_full = get_channel_full(channel_id) != nullptr; - if (!old_usernames.has_first_username() || !new_usernames.has_first_username()) { - // moving channel from private to public can change availability of chat members - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_usernames_changed"); - } - - // must not load ChannelFull, because must not change the Channel - CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr)); -} - -void ContactsManager::on_update_channel_description(ChannelId channel_id, string &&description) { - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_description"); - if (channel_full == nullptr) { - return; - } - if (channel_full->description != description) { - channel_full->description = std::move(description); - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_description"); - td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true); - } -} - -void ContactsManager::on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_sticker_set"); - if (channel_full == nullptr) { - return; - } - if (channel_full->sticker_set_id != sticker_set_id) { - channel_full->sticker_set_id = sticker_set_id; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_sticker_set"); - } -} - -void ContactsManager::on_update_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) { - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_emoji_sticker_set"); - if (channel_full == nullptr) { - return; - } - if (channel_full->emoji_sticker_set_id != sticker_set_id) { - channel_full->emoji_sticker_set_id = sticker_set_id; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_emoji_sticker_set"); - } -} - -void ContactsManager::on_update_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count) { - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_unrestrict_boost_count"); - if (channel_full == nullptr) { - return; - } - if (channel_full->unrestrict_boost_count != unrestrict_boost_count) { - channel_full->unrestrict_boost_count = unrestrict_boost_count; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_unrestrict_boost_count"); - } -} - -void ContactsManager::on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id) { - if (channel_id.is_valid()) { - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_linked_channel_id 1"); - on_update_channel_full_linked_channel_id(channel_full, channel_id, group_channel_id); - if (channel_full != nullptr) { - update_channel_full(channel_full, channel_id, "on_update_channel_linked_channel_id 3"); - } - } - if (group_channel_id.is_valid()) { - auto channel_full = get_channel_full_force(group_channel_id, true, "on_update_channel_linked_channel_id 2"); - on_update_channel_full_linked_channel_id(channel_full, group_channel_id, channel_id); - if (channel_full != nullptr) { - update_channel_full(channel_full, group_channel_id, "on_update_channel_linked_channel_id 4"); - } - } -} - -void ContactsManager::on_update_channel_location(ChannelId channel_id, const DialogLocation &location) { - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_location"); - if (channel_full != nullptr) { - on_update_channel_full_location(channel_full, channel_id, location); - update_channel_full(channel_full, channel_id, "on_update_channel_location"); - } -} - -void ContactsManager::on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_delay"); - if (channel_full != nullptr) { - on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, 0); - update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_delay"); - } - promise.set_value(Unit()); -} - -void ContactsManager::on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date) { - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_next_send_date"); - if (channel_full != nullptr) { - on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date); - update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_next_send_date"); - } -} - -void ContactsManager::on_update_channel_bot_user_ids(ChannelId channel_id, vector &&bot_user_ids) { - CHECK(channel_id.is_valid()); - if (!have_channel(channel_id)) { - LOG(ERROR) << channel_id << " not found"; - return; - } - - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_user_ids"); - if (channel_full == nullptr) { - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - std::move(bot_user_ids), false); - return; - } - on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids)); - update_channel_full(channel_full, channel_id, "on_update_channel_bot_user_ids"); -} - -void ContactsManager::on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id, - vector &&bot_user_ids) { - CHECK(channel_full != nullptr); - send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id), - bot_user_ids, false); - if (channel_full->bot_user_ids != bot_user_ids) { - channel_full->bot_user_ids = std::move(bot_user_ids); - channel_full->need_save_to_database = true; - } -} - -void ContactsManager::on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_is_all_history_available"); - if (channel_full != nullptr && channel_full->is_all_history_available != is_all_history_available) { - channel_full->is_all_history_available = is_all_history_available; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_is_all_history_available"); - } - promise.set_value(Unit()); -} - -void ContactsManager::on_update_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_hidden_participants"); - if (channel_full != nullptr && channel_full->has_hidden_participants != has_hidden_participants) { - channel_full->has_hidden_participants = has_hidden_participants; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_has_hidden_participants"); - } - promise.set_value(Unit()); -} - -void ContactsManager::on_update_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, - bool has_aggressive_anti_spam_enabled, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(channel_id.is_valid()); - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_aggressive_anti_spam_enabled"); - if (channel_full != nullptr && channel_full->has_aggressive_anti_spam_enabled != has_aggressive_anti_spam_enabled) { - channel_full->has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_has_aggressive_anti_spam_enabled"); - } - promise.set_value(Unit()); -} - -void ContactsManager::on_update_channel_has_pinned_stories(ChannelId channel_id, bool has_pinned_stories) { - if (td_->auth_manager_->is_bot()) { - return; - } - - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id; - return; - } - - ChannelFull *channel_full = get_channel_full_force(channel_id, true, "on_update_channel_has_pinned_stories"); - if (channel_full == nullptr || channel_full->has_pinned_stories == has_pinned_stories) { - return; - } - channel_full->has_pinned_stories = has_pinned_stories; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_update_channel_has_pinned_stories"); -} - -void ContactsManager::on_update_channel_default_permissions(ChannelId channel_id, - RestrictedRights default_permissions) { - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id; - return; - } - - Channel *c = get_channel_force(channel_id, "on_update_channel_default_permissions"); - if (c != nullptr) { - on_update_channel_default_permissions(c, channel_id, std::move(default_permissions)); - update_channel(c, channel_id); - } else { - LOG(INFO) << "Ignore update channel default permissions about unknown " << channel_id; - } -} - -void ContactsManager::update_contacts_hints(const User *u, UserId user_id, bool from_database) { - bool is_contact = is_user_contact(u, user_id, false); - if (td_->auth_manager_->is_bot()) { - LOG_IF(ERROR, is_contact) << "Bot has " << user_id << " in the contacts list"; - return; - } - - int64 key = user_id.get(); - string old_value = contacts_hints_.key_to_string(key); - string new_value = is_contact ? get_user_search_text(u) : string(); - - if (new_value != old_value) { - if (is_contact) { - contacts_hints_.add(key, new_value); - } else { - contacts_hints_.remove(key); - } - } - - if (G()->use_chat_info_database()) { - // update contacts database - if (!are_contacts_loaded_) { - if (!from_database && load_contacts_queries_.empty() && is_contact && u->is_is_contact_changed) { - search_contacts("", std::numeric_limits::max(), Auto()); - } - } else { - if (old_value.empty() == is_contact) { - save_contacts_to_database(); - } - } - } -} - -bool ContactsManager::have_user(UserId user_id) const { - auto u = get_user(user_id); - return u != nullptr && u->is_received; -} - -bool ContactsManager::have_min_user(UserId user_id) const { - return users_.count(user_id) > 0; -} - -bool ContactsManager::is_user_premium(UserId user_id) const { - return is_user_premium(get_user(user_id)); -} - -bool ContactsManager::is_user_premium(const User *u) { - return u != nullptr && u->is_premium; -} - -bool ContactsManager::is_user_deleted(UserId user_id) const { - return is_user_deleted(get_user(user_id)); -} - -bool ContactsManager::is_user_deleted(const User *u) { - return u == nullptr || u->is_deleted; -} - -bool ContactsManager::is_user_support(UserId user_id) const { - return is_user_support(get_user(user_id)); -} - -bool ContactsManager::is_user_support(const User *u) { - return u != nullptr && !u->is_deleted && u->is_support; -} - -bool ContactsManager::is_user_bot(UserId user_id) const { - return is_user_bot(get_user(user_id)); -} - -bool ContactsManager::is_user_bot(const User *u) { - return u != nullptr && !u->is_deleted && u->is_bot; -} - -Result ContactsManager::get_bot_data(UserId user_id) const { - auto u = get_user(user_id); - if (u == nullptr) { - return Status::Error(400, "Bot not found"); - } - if (!u->is_bot) { - return Status::Error(400, "User is not a bot"); - } - if (u->is_deleted) { - return Status::Error(400, "Bot is deleted"); - } - if (!u->is_received) { - return Status::Error(400, "Bot is inaccessible"); - } - - BotData bot_data; - bot_data.username = u->usernames.get_first_username(); - bot_data.can_be_edited = u->can_be_edited_bot; - bot_data.can_join_groups = u->can_join_groups; - bot_data.can_read_all_group_messages = u->can_read_all_group_messages; - bot_data.is_inline = u->is_inline_bot; - bot_data.need_location = u->need_location_bot; - bot_data.can_be_added_to_attach_menu = u->can_be_added_to_attach_menu; - return bot_data; -} - -bool ContactsManager::is_user_online(UserId user_id, int32 tolerance, int32 unix_time) const { - if (unix_time <= 0) { - unix_time = G()->unix_time(); - } - int32 was_online = get_user_was_online(get_user(user_id), user_id, unix_time); - return was_online > unix_time - tolerance; -} - -int32 ContactsManager::get_user_was_online(UserId user_id, int32 unix_time) const { - if (unix_time <= 0) { - unix_time = G()->unix_time(); - } - return get_user_was_online(get_user(user_id), user_id, unix_time); -} - -bool ContactsManager::is_user_status_exact(UserId user_id) const { - auto u = get_user(user_id); - return u != nullptr && !u->is_deleted && !u->is_bot && u->was_online > 0; -} - -bool ContactsManager::can_report_user(UserId user_id) const { - auto u = get_user(user_id); - return u != nullptr && !u->is_deleted && !u->is_support && (u->is_bot || all_users_nearby_.count(user_id) != 0); -} - -const ContactsManager::User *ContactsManager::get_user(UserId user_id) const { - return users_.get_pointer(user_id); -} - -ContactsManager::User *ContactsManager::get_user(UserId user_id) { - return users_.get_pointer(user_id); -} - -void ContactsManager::send_get_me_query(Td *td, Promise &&promise) { - vector> users; - users.push_back(make_tl_object()); - td->create_handler(std::move(promise))->send(std::move(users)); -} - -UserId ContactsManager::get_me(Promise &&promise) { - auto my_id = get_my_id(); - if (!have_user_force(my_id, "get_me")) { - get_user_queries_.add_query(my_id.get(), std::move(promise), "get_me"); - return UserId(); - } - - promise.set_value(Unit()); - return my_id; -} - -bool ContactsManager::get_user(UserId user_id, int left_tries, Promise &&promise) { - if (!user_id.is_valid()) { - promise.set_error(Status::Error(400, "Invalid user identifier")); - return false; - } - - if (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || - user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || - user_id == get_anti_spam_bot_user_id()) { - get_user_force(user_id, "get_user"); - } - - if (td_->auth_manager_->is_bot() ? !have_user(user_id) : !have_min_user(user_id)) { - if (left_tries > 2 && G()->use_chat_info_database()) { - send_closure_later(actor_id(this), &ContactsManager::load_user_from_database, nullptr, user_id, - std::move(promise)); - return false; - } - auto r_input_user = get_input_user(user_id); - if (left_tries == 1 || r_input_user.is_error()) { - if (r_input_user.is_error()) { - promise.set_error(r_input_user.move_as_error()); - } else { - promise.set_error(Status::Error(400, "User not found")); - } - return false; - } - - get_user_queries_.add_query(user_id.get(), std::move(promise), "get_user"); - return false; - } - - promise.set_value(Unit()); - return true; -} - -ContactsManager::User *ContactsManager::add_user(UserId user_id) { - CHECK(user_id.is_valid()); - auto &user_ptr = users_[user_id]; - if (user_ptr == nullptr) { - user_ptr = make_unique(); - } - return user_ptr.get(); -} - -const ContactsManager::UserFull *ContactsManager::get_user_full(UserId user_id) const { - return users_full_.get_pointer(user_id); -} - -ContactsManager::UserFull *ContactsManager::get_user_full(UserId user_id) { - return users_full_.get_pointer(user_id); -} - -ContactsManager::UserFull *ContactsManager::add_user_full(UserId user_id) { - CHECK(user_id.is_valid()); - auto &user_full_ptr = users_full_[user_id]; - if (user_full_ptr == nullptr) { - user_full_ptr = make_unique(); - user_full_contact_require_premium_.erase(user_id); - } - return user_full_ptr.get(); -} - -void ContactsManager::reload_user(UserId user_id, Promise &&promise, const char *source) { - if (!user_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid user identifier")); - } - - have_user_force(user_id, source); - - TRY_STATUS_PROMISE(promise, get_input_user(user_id)); - - get_user_queries_.add_query(user_id.get(), std::move(promise), source); -} - -void ContactsManager::load_user_full(UserId user_id, bool force, Promise &&promise, const char *source) { - auto u = get_user(user_id); - if (u == nullptr) { - return promise.set_error(Status::Error(400, "User not found")); - } - - auto user_full = get_user_full_force(user_id, source); - if (user_full == nullptr) { - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); - } - if (user_full->is_expired()) { - auto input_user = get_input_user_force(user_id); - if (td_->auth_manager_->is_bot() && !force) { - return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), "load expired user_full"); - } - - send_get_user_full_query(user_id, std::move(input_user), Auto(), "load expired user_full"); - } - - td_->story_manager_->on_view_dialog_active_stories({DialogId(user_id)}); - promise.set_value(Unit()); -} - -void ContactsManager::reload_user_full(UserId user_id, Promise &&promise, const char *source) { - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); -} - -void ContactsManager::send_get_user_full_query(UserId user_id, tl_object_ptr &&input_user, - Promise &&promise, const char *source) { - LOG(INFO) << "Get full " << user_id << " from " << source; - if (!user_id.is_valid()) { - return promise.set_error(Status::Error(500, "Invalid user_id")); - } - auto send_query = - PromiseCreator::lambda([td = td_, input_user = std::move(input_user)](Result> &&promise) mutable { - if (promise.is_ok() && !G()->close_flag()) { - td->create_handler(promise.move_as_ok())->send(std::move(input_user)); - } - }); - get_user_full_queries_.add_query(user_id.get(), std::move(send_query), std::move(promise)); -} - -void ContactsManager::get_user_profile_photos(UserId user_id, int32 offset, int32 limit, - Promise> &&promise) { - if (offset < 0) { - return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); - } - if (limit <= 0) { - return promise.set_error(Status::Error(400, "Parameter limit must be positive")); - } - if (limit > MAX_GET_PROFILE_PHOTOS) { - limit = MAX_GET_PROFILE_PHOTOS; - } - - TRY_STATUS_PROMISE(promise, get_input_user(user_id)); - - auto *u = get_user(user_id); - if (u == nullptr) { - return promise.set_error(Status::Error(400, "User not found")); - } - - apply_pending_user_photo(u, user_id); - - auto user_photos = add_user_photos(user_id); - if (user_photos->count != -1) { // know photo count - CHECK(user_photos->offset != -1); - LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; - vector> photo_objects; - - if (offset >= user_photos->count) { - // offset if too big - return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); - } - - if (limit > user_photos->count - offset) { - limit = user_photos->count - offset; - } - - int32 cache_begin = user_photos->offset; - int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); - if (cache_begin <= offset && offset + limit <= cache_end) { - // answer query from cache - for (int i = 0; i < limit; i++) { - photo_objects.push_back( - get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + offset - cache_begin])); - } - return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); - } - } - - PendingGetPhotoRequest pending_request; - pending_request.offset = offset; - pending_request.limit = limit; - pending_request.promise = std::move(promise); - user_photos->pending_requests.push_back(std::move(pending_request)); - if (user_photos->pending_requests.size() != 1u) { - return; - } - - send_get_user_photos_query(user_id, user_photos); -} - -void ContactsManager::send_get_user_photos_query(UserId user_id, const UserPhotos *user_photos) { - CHECK(!user_photos->pending_requests.empty()); - auto offset = user_photos->pending_requests[0].offset; - auto limit = user_photos->pending_requests[0].limit; - - if (user_photos->count != -1 && offset >= user_photos->offset) { - int32 cache_end = user_photos->offset + narrow_cast(user_photos->photos.size()); - if (offset < cache_end) { - // adjust offset to the end of cache - CHECK(offset + limit > cache_end); // otherwise the request has already been answered - limit = offset + limit - cache_end; - offset = cache_end; - } - } - - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), user_id](Result &&result) { - send_closure(actor_id, &ContactsManager::on_get_user_profile_photos, user_id, std::move(result)); - }); - - td_->create_handler(std::move(query_promise)) - ->send(user_id, get_input_user_force(user_id), offset, max(limit, MAX_GET_PROFILE_PHOTOS / 5), 0); -} - -void ContactsManager::on_get_user_profile_photos(UserId user_id, Result &&result) { - G()->ignore_result_if_closing(result); - auto user_photos = add_user_photos(user_id); - auto pending_requests = std::move(user_photos->pending_requests); - CHECK(!pending_requests.empty()); - if (result.is_error()) { - for (auto &request : pending_requests) { - request.promise.set_error(result.error().clone()); - } - return; - } - if (user_photos->count == -1) { - CHECK(have_user(user_id)); - // received result has just been dropped; resend request - if (++pending_requests[0].retry_count >= 3) { - pending_requests[0].promise.set_error(Status::Error(500, "Failed to return profile photos")); - pending_requests.erase(pending_requests.begin()); - if (pending_requests.empty()) { - return; - } - } - user_photos->pending_requests = std::move(pending_requests); - return send_get_user_photos_query(user_id, user_photos); - } - - CHECK(user_photos->offset != -1); - LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; - vector left_requests; - for (size_t request_index = 0; request_index < pending_requests.size(); request_index++) { - auto &request = pending_requests[request_index]; - vector> photo_objects; - - if (request.offset >= user_photos->count) { - // offset if too big - request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); - continue; - } - - if (request.limit > user_photos->count - request.offset) { - request.limit = user_photos->count - request.offset; - } - - int32 cache_begin = user_photos->offset; - int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); - if (cache_begin <= request.offset && request.offset + request.limit <= cache_end) { - // answer query from cache - for (int i = 0; i < request.limit; i++) { - photo_objects.push_back( - get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + request.offset - cache_begin])); - } - request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); - continue; - } - - if (request_index == 0 && ++request.retry_count >= 3) { - request.promise.set_error(Status::Error(500, "Failed to get profile photos")); - continue; - } - - left_requests.push_back(std::move(request)); - } - - if (!left_requests.empty()) { - bool need_send = user_photos->pending_requests.empty(); - append(user_photos->pending_requests, std::move(left_requests)); - if (need_send) { - send_get_user_photos_query(user_id, user_photos); - } - } -} - -void ContactsManager::reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise) { - get_user_force(user_id, "reload_user_profile_photo"); - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - - // this request will be needed only to download the photo, - // so there is no reason to combine different requests for a photo into one request - td_->create_handler(std::move(promise))->send(user_id, std::move(input_user), -1, 1, photo_id); -} - -FileSourceId ContactsManager::get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id) { - if (!user_id.is_valid()) { - return FileSourceId(); - } - - auto u = get_user(user_id); - if (u != nullptr && u->photo_ids.count(photo_id) != 0) { - VLOG(file_references) << "Don't need to create file source for photo " << photo_id << " of " << user_id; - // photo was already added, source ID was registered and shouldn't be needed - return FileSourceId(); - } - - auto &source_id = user_profile_photo_file_source_ids_[std::make_pair(user_id, photo_id)]; - if (!source_id.is_valid()) { - source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); - } - VLOG(file_references) << "Return " << source_id << " for photo " << photo_id << " of " << user_id; - return source_id; -} - -FileSourceId ContactsManager::get_user_full_file_source_id(UserId user_id) { - if (!user_id.is_valid()) { - return FileSourceId(); - } - - auto user_full = get_user_full(user_id); - if (user_full != nullptr) { - VLOG(file_references) << "Don't need to create file source for full " << user_id; - // user full was already added, source ID was registered and shouldn't be needed - return user_full->is_update_user_full_sent ? FileSourceId() : user_full->file_source_id; - } - - auto &source_id = user_full_file_source_ids_[user_id]; - if (!source_id.is_valid()) { - source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); - } - VLOG(file_references) << "Return " << source_id << " for full " << user_id; - return source_id; -} - -FileSourceId ContactsManager::get_chat_full_file_source_id(ChatId chat_id) { - if (!chat_id.is_valid()) { - return FileSourceId(); - } - - auto chat_full = get_chat_full(chat_id); - if (chat_full != nullptr) { - VLOG(file_references) << "Don't need to create file source for full " << chat_id; - // chat full was already added, source ID was registered and shouldn't be needed - return chat_full->is_update_chat_full_sent ? FileSourceId() : chat_full->file_source_id; - } - - auto &source_id = chat_full_file_source_ids_[chat_id]; - if (!source_id.is_valid()) { - source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id); - } - VLOG(file_references) << "Return " << source_id << " for full " << chat_id; - return source_id; -} - -FileSourceId ContactsManager::get_channel_full_file_source_id(ChannelId channel_id) { - if (!channel_id.is_valid()) { - return FileSourceId(); - } - - auto channel_full = get_channel_full(channel_id); - if (channel_full != nullptr) { - VLOG(file_references) << "Don't need to create file source for full " << channel_id; - // channel full was already added, source ID was registered and shouldn't be needed - return channel_full->is_update_channel_full_sent ? FileSourceId() : channel_full->file_source_id; - } - - auto &source_id = channel_full_file_source_ids_[channel_id]; - if (!source_id.is_valid()) { - source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id); - } - VLOG(file_references) << "Return " << source_id << " for full " << channel_id; - return source_id; -} - -void ContactsManager::create_new_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, - Promise> &&promise) { - auto new_title = clean_name(title, MAX_TITLE_LENGTH); - if (new_title.empty()) { - return promise.set_error(Status::Error(400, "Title must be non-empty")); - } - - vector> input_users; - for (auto user_id : user_ids) { - auto r_input_user = get_input_user(user_id); - if (r_input_user.is_error()) { - return promise.set_error(r_input_user.move_as_error()); - } - input_users.push_back(r_input_user.move_as_ok()); - } - - td_->create_handler(std::move(promise))->send(std::move(input_users), new_title, message_ttl); -} - -void ContactsManager::create_new_channel(const string &title, bool is_forum, bool is_megagroup, - const string &description, const DialogLocation &location, bool for_import, - MessageTtl message_ttl, Promise> &&promise) { - auto new_title = clean_name(title, MAX_TITLE_LENGTH); - if (new_title.empty()) { - return promise.set_error(Status::Error(400, "Title must be non-empty")); - } - - td_->create_handler(std::move(promise)) - ->send(new_title, is_forum, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, - for_import, message_ttl); -} - -bool ContactsManager::have_chat(ChatId chat_id) const { - return chats_.count(chat_id) > 0; -} - -const ContactsManager::Chat *ContactsManager::get_chat(ChatId chat_id) const { - return chats_.get_pointer(chat_id); -} - -ContactsManager::Chat *ContactsManager::get_chat(ChatId chat_id) { - return chats_.get_pointer(chat_id); -} - -ContactsManager::Chat *ContactsManager::add_chat(ChatId chat_id) { - CHECK(chat_id.is_valid()); - auto &chat_ptr = chats_[chat_id]; - if (chat_ptr == nullptr) { - chat_ptr = make_unique(); - } - return chat_ptr.get(); -} - -bool ContactsManager::get_chat(ChatId chat_id, int left_tries, Promise &&promise) { - if (!chat_id.is_valid()) { - promise.set_error(Status::Error(400, "Invalid basic group identifier")); - return false; - } - - if (!have_chat(chat_id)) { - if (left_tries > 2 && G()->use_chat_info_database()) { - send_closure_later(actor_id(this), &ContactsManager::load_chat_from_database, nullptr, chat_id, - std::move(promise)); - return false; - } - - if (left_tries > 1) { - get_chat_queries_.add_query(chat_id.get(), std::move(promise), "get_chat"); - return false; - } - - promise.set_error(Status::Error(400, "Group not found")); - return false; - } - - promise.set_value(Unit()); - return true; -} - -void ContactsManager::reload_chat(ChatId chat_id, Promise &&promise, const char *source) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (!chat_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid basic group identifier")); - } - - get_chat_queries_.add_query(chat_id.get(), std::move(promise), source); -} - -const ContactsManager::ChatFull *ContactsManager::get_chat_full(ChatId chat_id) const { - return chats_full_.get_pointer(chat_id); -} - -ContactsManager::ChatFull *ContactsManager::get_chat_full(ChatId chat_id) { - return chats_full_.get_pointer(chat_id); -} - -ContactsManager::ChatFull *ContactsManager::add_chat_full(ChatId chat_id) { - CHECK(chat_id.is_valid()); - auto &chat_full_ptr = chats_full_[chat_id]; - if (chat_full_ptr == nullptr) { - chat_full_ptr = make_unique(); - } - return chat_full_ptr.get(); -} - -bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, - bool only_participants) const { - CHECK(c != nullptr); - CHECK(chat_full != nullptr); - if (!c->is_active && chat_full->version == -1) { - return false; - } - - if (chat_full->version != c->version) { - LOG(INFO) << "Have outdated ChatFull " << chat_id << " with current version " << chat_full->version - << " and chat version " << c->version; - return true; - } - - if (!only_participants && c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) { - LOG(INFO) << "Have outdated invite link in " << chat_id; - return true; - } - - if (!only_participants && - !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo, false)) { - LOG(INFO) << "Have outdated chat photo in " << chat_id; - return true; - } - - LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version << " and photos " << c->photo - << '/' << chat_full->photo; - return false; -} - -void ContactsManager::load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source) { - auto c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Group not found")); - } - - auto chat_full = get_chat_full_force(chat_id, source); - if (chat_full == nullptr) { - LOG(INFO) << "Full " << chat_id << " not found"; - return send_get_chat_full_query(chat_id, std::move(promise), source); - } - - if (is_chat_full_outdated(chat_full, c, chat_id, false)) { - LOG(INFO) << "Have outdated full " << chat_id; - if (td_->auth_manager_->is_bot() && !force) { - return send_get_chat_full_query(chat_id, std::move(promise), source); - } - - send_get_chat_full_query(chat_id, Auto(), source); - } - - vector participant_dialog_ids; - for (const auto &dialog_participant : chat_full->participants) { - participant_dialog_ids.push_back(dialog_participant.dialog_id_); - } - td_->story_manager_->on_view_dialog_active_stories(std::move(participant_dialog_ids)); - - promise.set_value(Unit()); -} - -void ContactsManager::reload_chat_full(ChatId chat_id, Promise &&promise, const char *source) { - send_get_chat_full_query(chat_id, std::move(promise), source); -} - -void ContactsManager::send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source) { - LOG(INFO) << "Get full " << chat_id << " from " << source; - if (!chat_id.is_valid()) { - return promise.set_error(Status::Error(500, "Invalid chat_id")); - } - auto send_query = PromiseCreator::lambda([td = td_, chat_id](Result> &&promise) { - if (promise.is_ok() && !G()->close_flag()) { - td->create_handler(promise.move_as_ok())->send(chat_id); - } - }); - - get_chat_full_queries_.add_query(DialogId(chat_id).get(), std::move(send_query), std::move(promise)); -} - -int32 ContactsManager::get_chat_date(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return 0; - } - return c->date; -} - -int32 ContactsManager::get_chat_participant_count(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return 0; - } - return c->participant_count; -} - -bool ContactsManager::get_chat_is_active(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return false; - } - return c->is_active; -} - -ChannelId ContactsManager::get_chat_migrated_to_channel_id(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return ChannelId(); - } - return c->migrated_to_channel_id; -} - -DialogParticipantStatus ContactsManager::get_chat_status(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return DialogParticipantStatus::Banned(0); - } - return get_chat_status(c); -} - -DialogParticipantStatus ContactsManager::get_chat_status(const Chat *c) { - if (!c->is_active) { - return DialogParticipantStatus::Banned(0); - } - return c->status; -} - -DialogParticipantStatus ContactsManager::get_chat_permissions(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return DialogParticipantStatus::Banned(0); - } - return get_chat_permissions(c); -} - -DialogParticipantStatus ContactsManager::get_chat_permissions(const Chat *c) const { - if (!c->is_active) { - return DialogParticipantStatus::Banned(0); - } - return c->status.apply_restrictions(c->default_permissions, false, td_->auth_manager_->is_bot()); -} - -bool ContactsManager::is_appointed_chat_administrator(ChatId chat_id) const { - auto c = get_chat(chat_id); - if (c == nullptr) { - return false; - } - return c->status.is_administrator(); -} - -bool ContactsManager::is_channel_public(ChannelId channel_id) const { - return is_channel_public(get_channel(channel_id)); -} - -bool ContactsManager::is_channel_public(const Channel *c) { - return c != nullptr && (c->usernames.has_first_username() || c->has_location); -} - -ChannelType ContactsManager::get_channel_type(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - auto min_channel = get_min_channel(channel_id); - if (min_channel != nullptr) { - return min_channel->is_megagroup_ ? ChannelType::Megagroup : ChannelType::Broadcast; - } - return ChannelType::Unknown; - } - return get_channel_type(c); -} - -ChannelType ContactsManager::get_channel_type(const Channel *c) { - if (c->is_megagroup) { - return ChannelType::Megagroup; - } - return ChannelType::Broadcast; -} - -bool ContactsManager::is_broadcast_channel(ChannelId channel_id) const { - return get_channel_type(channel_id) == ChannelType::Broadcast; -} - -bool ContactsManager::is_megagroup_channel(ChannelId channel_id) const { - return get_channel_type(channel_id) == ChannelType::Megagroup; -} - -bool ContactsManager::is_forum_channel(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->is_forum; -} - -int32 ContactsManager::get_channel_date(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return 0; - } - return c->date; -} - -DialogParticipantStatus ContactsManager::get_channel_status(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return DialogParticipantStatus::Banned(0); - } - return get_channel_status(c); -} - -DialogParticipantStatus ContactsManager::get_channel_status(const Channel *c) { - c->status.update_restrictions(); - return c->status; -} - -DialogParticipantStatus ContactsManager::get_channel_permissions(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return DialogParticipantStatus::Banned(0); - } - return get_channel_permissions(channel_id, c); -} - -DialogParticipantStatus ContactsManager::get_channel_permissions(ChannelId channel_id, const Channel *c) const { - c->status.update_restrictions(); - bool is_booster = false; - if (!td_->auth_manager_->is_bot() && c->is_megagroup) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr || (channel_full->unrestrict_boost_count > 0 && - channel_full->boost_count >= channel_full->unrestrict_boost_count)) { - is_booster = true; - } - } - return c->status.apply_restrictions(c->default_permissions, is_booster, td_->auth_manager_->is_bot()); -} - -int32 ContactsManager::get_channel_participant_count(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return 0; - } - return c->participant_count; -} - -bool ContactsManager::get_channel_is_verified(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->is_verified; -} - -bool ContactsManager::get_channel_is_scam(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->is_scam; -} - -bool ContactsManager::get_channel_is_fake(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return c->is_fake; -} - -bool ContactsManager::get_channel_sign_messages(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return get_channel_sign_messages(c); -} - -bool ContactsManager::get_channel_sign_messages(const Channel *c) { - return c->sign_messages; -} - -bool ContactsManager::get_channel_has_linked_channel(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return get_channel_has_linked_channel(c); -} - -bool ContactsManager::get_channel_has_linked_channel(const Channel *c) { - return c->has_linked_channel; -} - -bool ContactsManager::get_channel_can_be_deleted(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return get_channel_can_be_deleted(c); -} - -bool ContactsManager::get_channel_can_be_deleted(const Channel *c) { - return c->can_be_deleted; -} - -bool ContactsManager::get_channel_join_to_send(const Channel *c) { - return c->join_to_send || !c->is_megagroup || !c->has_linked_channel; -} - -bool ContactsManager::get_channel_join_request(ChannelId channel_id) const { - auto c = get_channel(channel_id); - if (c == nullptr) { - return false; - } - return get_channel_join_request(c); -} - -bool ContactsManager::get_channel_join_request(const Channel *c) { - return c->join_request && c->is_megagroup && (is_channel_public(c) || c->has_linked_channel); -} - -ChannelId ContactsManager::get_channel_linked_channel_id(ChannelId channel_id, const char *source) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr) { - channel_full = get_channel_full_force(channel_id, true, source); - if (channel_full == nullptr) { - return ChannelId(); - } - } - return channel_full->linked_channel_id; -} - -int32 ContactsManager::get_channel_slow_mode_delay(ChannelId channel_id, const char *source) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr) { - channel_full = get_channel_full_force(channel_id, true, source); - if (channel_full == nullptr) { - return 0; - } - } - return channel_full->slow_mode_delay; -} - -bool ContactsManager::get_channel_effective_has_hidden_participants(ChannelId channel_id, const char *source) { - auto c = get_channel_force(channel_id, "get_channel_effective_has_hidden_participants"); - if (c == nullptr) { - return true; - } - if (get_channel_status(c).is_administrator()) { - return false; - } - - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr) { - channel_full = get_channel_full_force(channel_id, true, source); - if (channel_full == nullptr) { - return true; - } - } - return channel_full->has_hidden_participants || !channel_full->can_get_participants; -} - -int32 ContactsManager::get_channel_my_boost_count(ChannelId channel_id) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full == nullptr) { - channel_full = get_channel_full_force(channel_id, true, "get_channel_my_boost_count"); - if (channel_full == nullptr) { - return 0; - } - } - return channel_full->boost_count; -} - -bool ContactsManager::have_channel(ChannelId channel_id) const { - return channels_.count(channel_id) > 0; -} - -bool ContactsManager::have_min_channel(ChannelId channel_id) const { - return min_channels_.count(channel_id) > 0; -} - -const MinChannel *ContactsManager::get_min_channel(ChannelId channel_id) const { - return min_channels_.get_pointer(channel_id); -} - -void ContactsManager::add_min_channel(ChannelId channel_id, const MinChannel &min_channel) { - if (have_channel(channel_id) || have_min_channel(channel_id) || !channel_id.is_valid()) { - return; - } - min_channels_.set(channel_id, td::make_unique(min_channel)); -} - -const ContactsManager::Channel *ContactsManager::get_channel(ChannelId channel_id) const { - return channels_.get_pointer(channel_id); -} - -ContactsManager::Channel *ContactsManager::get_channel(ChannelId channel_id) { - return channels_.get_pointer(channel_id); -} - -ContactsManager::Channel *ContactsManager::add_channel(ChannelId channel_id, const char *source) { - CHECK(channel_id.is_valid()); - auto &channel_ptr = channels_[channel_id]; - if (channel_ptr == nullptr) { - channel_ptr = make_unique(); - min_channels_.erase(channel_id); - } - return channel_ptr.get(); -} - -bool ContactsManager::get_channel(ChannelId channel_id, int left_tries, Promise &&promise) { - if (!channel_id.is_valid()) { - promise.set_error(Status::Error(400, "Invalid supergroup identifier")); - return false; - } - - if (!have_channel(channel_id)) { - if (left_tries > 2 && G()->use_chat_info_database()) { - send_closure_later(actor_id(this), &ContactsManager::load_channel_from_database, nullptr, channel_id, - std::move(promise)); - return false; - } - - if (left_tries > 1 && td_->auth_manager_->is_bot()) { - get_channel_queries_.add_query(channel_id.get(), std::move(promise), "get_channel"); - return false; - } - - promise.set_error(Status::Error(400, "Supergroup not found")); - return false; - } - - promise.set_value(Unit()); - return true; -} - -void ContactsManager::reload_channel(ChannelId channel_id, Promise &&promise, const char *source) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - if (!channel_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid supergroup identifier")); - } - - have_channel_force(channel_id, source); - auto input_channel = get_input_channel(channel_id); - if (input_channel == nullptr) { - // requests with 0 access_hash must not be merged - td_->create_handler(std::move(promise)) - ->send(telegram_api::make_object(channel_id.get(), 0)); - return; - } - - get_channel_queries_.add_query(channel_id.get(), std::move(promise), source); -} - -const ContactsManager::ChannelFull *ContactsManager::get_channel_full_const(ChannelId channel_id) const { - return channels_full_.get_pointer(channel_id); -} - -const ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id) const { - return channels_full_.get_pointer(channel_id); -} - -ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id, bool only_local, - const char *source) { - auto channel_full = channels_full_.get_pointer(channel_id); - if (channel_full == nullptr) { - return nullptr; - } - - if (!only_local && channel_full->is_expired() && !td_->auth_manager_->is_bot()) { - send_get_channel_full_query(channel_full, channel_id, Auto(), source); - } - - return channel_full; -} - -ContactsManager::ChannelFull *ContactsManager::add_channel_full(ChannelId channel_id) { - CHECK(channel_id.is_valid()); - auto &channel_full_ptr = channels_full_[channel_id]; - if (channel_full_ptr == nullptr) { - channel_full_ptr = make_unique(); - } - return channel_full_ptr.get(); -} - -void ContactsManager::load_channel_full(ChannelId channel_id, bool force, Promise &&promise, const char *source) { - auto channel_full = get_channel_full_force(channel_id, true, source); - if (channel_full == nullptr) { - return send_get_channel_full_query(channel_full, channel_id, std::move(promise), source); - } - if (channel_full->is_expired()) { - if (td_->auth_manager_->is_bot() && !force) { - return send_get_channel_full_query(channel_full, channel_id, std::move(promise), "load expired channel_full"); - } - - Promise new_promise; - if (promise) { - new_promise = PromiseCreator::lambda([channel_id](Result result) { - if (result.is_error()) { - LOG(INFO) << "Failed to reload expired " << channel_id << ": " << result.error(); - } else { - LOG(INFO) << "Reloaded expired " << channel_id; - } - }); - } - send_get_channel_full_query(channel_full, channel_id, std::move(new_promise), "load expired channel_full"); - } - - promise.set_value(Unit()); -} - -void ContactsManager::reload_channel_full(ChannelId channel_id, Promise &&promise, const char *source) { - send_get_channel_full_query(get_channel_full(channel_id, true, "reload_channel_full"), channel_id, std::move(promise), - source); -} - -void ContactsManager::send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, - Promise &&promise, const char *source) { - auto input_channel = get_input_channel(channel_id); - if (input_channel == nullptr) { - return promise.set_error(Status::Error(400, "Supergroup not found")); - } - - if (!have_input_peer_channel(channel_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - - if (channel_full != nullptr) { - if (!promise) { - if (channel_full->repair_request_version != 0) { - LOG(INFO) << "Skip get full " << channel_id << " request from " << source; - return; - } - channel_full->repair_request_version = channel_full->speculative_version; - } else { - channel_full->repair_request_version = std::numeric_limits::max(); - } - } - - LOG(INFO) << "Get full " << channel_id << " from " << source; - auto send_query = PromiseCreator::lambda( - [td = td_, channel_id, input_channel = std::move(input_channel)](Result> &&promise) mutable { - if (promise.is_ok() && !G()->close_flag()) { - td->create_handler(promise.move_as_ok())->send(channel_id, std::move(input_channel)); - } - }); - get_chat_full_queries_.add_query(DialogId(channel_id).get(), std::move(send_query), std::move(promise)); -} - -void ContactsManager::create_new_secret_chat(UserId user_id, Promise> &&promise) { - TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); - if (input_user->get_id() != telegram_api::inputUser::ID) { - return promise.set_error(Status::Error(400, "Can't create secret chat with the user")); - } - auto user = static_cast(input_user.get()); - - send_closure( - G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_), user->access_hash_, - PromiseCreator::lambda([actor_id = actor_id(this), - promise = std::move(promise)](Result r_secret_chat_id) mutable { - if (r_secret_chat_id.is_error()) { - return promise.set_error(r_secret_chat_id.move_as_error()); - } - send_closure(actor_id, &ContactsManager::on_create_new_secret_chat, r_secret_chat_id.ok(), std::move(promise)); - })); -} - -void ContactsManager::on_create_new_secret_chat(SecretChatId secret_chat_id, - Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - CHECK(secret_chat_id.is_valid()); - DialogId dialog_id(secret_chat_id); - td_->dialog_manager_->force_create_dialog(dialog_id, "on_create_new_secret_chat"); - promise.set_value(td_->messages_manager_->get_chat_object(dialog_id)); -} - -bool ContactsManager::have_secret_chat(SecretChatId secret_chat_id) const { - return secret_chats_.count(secret_chat_id) > 0; -} - -ContactsManager::SecretChat *ContactsManager::add_secret_chat(SecretChatId secret_chat_id) { - CHECK(secret_chat_id.is_valid()); - auto &secret_chat_ptr = secret_chats_[secret_chat_id]; - if (secret_chat_ptr == nullptr) { - secret_chat_ptr = make_unique(); - } - return secret_chat_ptr.get(); -} - -const ContactsManager::SecretChat *ContactsManager::get_secret_chat(SecretChatId secret_chat_id) const { - return secret_chats_.get_pointer(secret_chat_id); -} - -ContactsManager::SecretChat *ContactsManager::get_secret_chat(SecretChatId secret_chat_id) { - return secret_chats_.get_pointer(secret_chat_id); -} - -bool ContactsManager::get_secret_chat(SecretChatId secret_chat_id, bool force, Promise &&promise) { - if (!secret_chat_id.is_valid()) { - promise.set_error(Status::Error(400, "Invalid secret chat identifier")); - return false; - } - - if (!have_secret_chat(secret_chat_id)) { - if (!force && G()->use_chat_info_database()) { - send_closure_later(actor_id(this), &ContactsManager::load_secret_chat_from_database, nullptr, secret_chat_id, - std::move(promise)); - return false; - } - - promise.set_error(Status::Error(400, "Secret chat not found")); - return false; - } - - promise.set_value(Unit()); - return true; -} - -void ContactsManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, - SecretChatState state, bool is_outbound, int32 ttl, int32 date, - string key_hash, int32 layer, FolderId initial_folder_id) { - LOG(INFO) << "Update " << secret_chat_id << " with " << user_id << " and access_hash " << access_hash; - auto *secret_chat = add_secret_chat(secret_chat_id); - if (access_hash != secret_chat->access_hash) { - secret_chat->access_hash = access_hash; - secret_chat->need_save_to_database = true; - } - if (user_id.is_valid() && user_id != secret_chat->user_id) { - if (secret_chat->user_id.is_valid()) { - LOG(ERROR) << "Secret chat user has changed from " << secret_chat->user_id << " to " << user_id; - auto &old_secret_chat_ids = secret_chats_with_user_[secret_chat->user_id]; - td::remove(old_secret_chat_ids, secret_chat_id); - } - secret_chat->user_id = user_id; - secret_chats_with_user_[secret_chat->user_id].push_back(secret_chat_id); - secret_chat->is_changed = true; - } - if (state != SecretChatState::Unknown && state != secret_chat->state) { - secret_chat->state = state; - secret_chat->is_changed = true; - secret_chat->is_state_changed = true; - } - if (is_outbound != secret_chat->is_outbound) { - secret_chat->is_outbound = is_outbound; - secret_chat->is_changed = true; - } - - if (ttl != -1 && ttl != secret_chat->ttl) { - secret_chat->ttl = ttl; - secret_chat->need_save_to_database = true; - secret_chat->is_ttl_changed = true; - } - if (date != 0 && date != secret_chat->date) { - secret_chat->date = date; - secret_chat->need_save_to_database = true; - } - if (!key_hash.empty() && key_hash != secret_chat->key_hash) { - secret_chat->key_hash = std::move(key_hash); - secret_chat->is_changed = true; - } - if (layer != 0 && layer != secret_chat->layer) { - secret_chat->layer = layer; - secret_chat->is_changed = true; - } - if (initial_folder_id != FolderId() && initial_folder_id != secret_chat->initial_folder_id) { - secret_chat->initial_folder_id = initial_folder_id; - secret_chat->is_changed = true; - } - - update_secret_chat(secret_chat, secret_chat_id); -} - -void ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise) { - LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id; - - auto c = get_chat(chat_id); - if (c == nullptr) { - return promise.set_error(Status::Error(400, "Group not found")); - } - - auto chat_full = get_chat_full_force(chat_id, "get_chat_participant"); - if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id, true))) { - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result &&result) mutable { - TRY_STATUS_PROMISE(promise, std::move(result)); - send_closure(actor_id, &ContactsManager::finish_get_chat_participant, chat_id, user_id, std::move(promise)); - }); - send_get_chat_full_query(chat_id, std::move(query_promise), "get_chat_participant"); - return; - } - - if (is_chat_full_outdated(chat_full, c, chat_id, true)) { - send_get_chat_full_query(chat_id, Auto(), "get_chat_participant lazy"); - } - - finish_get_chat_participant(chat_id, user_id, std::move(promise)); -} - -void ContactsManager::finish_get_chat_participant(ChatId chat_id, UserId user_id, - Promise &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - const auto *participant = get_chat_participant(chat_id, user_id); - if (participant == nullptr) { - return promise.set_value(DialogParticipant::left(DialogId(user_id))); - } - - promise.set_value(DialogParticipant(*participant)); -} - -void ContactsManager::on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count) { - auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_administrator_count"); - if (channel_full != nullptr && channel_full->administrator_count != administrator_count) { - channel_full->administrator_count = administrator_count; - channel_full->is_changed = true; - - if (channel_full->participant_count < channel_full->administrator_count) { - channel_full->participant_count = channel_full->administrator_count; - - auto c = get_channel(channel_id); - if (c != nullptr && c->participant_count != channel_full->participant_count) { - c->participant_count = channel_full->participant_count; - c->is_changed = true; - update_channel(c, channel_id); - } - } - - update_channel_full(channel_full, channel_id, "on_update_channel_administrator_count"); - } -} - -void ContactsManager::on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source) { - ChatId chat_id(chat.id_); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; - return; - } - - if (!have_chat(chat_id)) { - LOG(ERROR) << "Have no information about " << chat_id << " but received chatEmpty from " << source; - } -} - -void ContactsManager::on_get_chat(telegram_api::chat &chat, const char *source) { - auto debug_str = PSTRING() << " from " << source << " in " << oneline(to_string(chat)); - ChatId chat_id(chat.id_); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id << debug_str; - return; - } - - DialogParticipantStatus status = [&] { - bool is_creator = 0 != (chat.flags_ & CHAT_FLAG_USER_IS_CREATOR); - bool has_left = 0 != (chat.flags_ & CHAT_FLAG_USER_HAS_LEFT); - if (is_creator) { - return DialogParticipantStatus::Creator(!has_left, false, string()); - } else if (chat.admin_rights_ != nullptr) { - return DialogParticipantStatus(false, std::move(chat.admin_rights_), string(), ChannelType::Unknown); - } else if (has_left) { - return DialogParticipantStatus::Left(); - } else { - return DialogParticipantStatus::Member(); - } - }(); - - bool is_active = 0 == (chat.flags_ & CHAT_FLAG_IS_DEACTIVATED); - - ChannelId migrated_to_channel_id; - if (chat.flags_ & CHAT_FLAG_WAS_MIGRATED) { - switch (chat.migrated_to_->get_id()) { - case telegram_api::inputChannelFromMessage::ID: - case telegram_api::inputChannelEmpty::ID: - LOG(ERROR) << "Receive invalid information about upgraded supergroup for " << chat_id << debug_str; - break; - case telegram_api::inputChannel::ID: { - auto input_channel = move_tl_object_as(chat.migrated_to_); - migrated_to_channel_id = ChannelId(input_channel->channel_id_); - if (!have_channel_force(migrated_to_channel_id, source)) { - if (!migrated_to_channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << migrated_to_channel_id << debug_str; - } else { - // temporarily create the channel - Channel *c = add_channel(migrated_to_channel_id, "on_get_chat"); - c->access_hash = input_channel->access_hash_; - c->title = chat.title_; - c->status = DialogParticipantStatus::Left(); - c->is_megagroup = true; - - // we definitely need to call update_channel, because client should know about every added channel - update_channel(c, migrated_to_channel_id); - - // get info about the channel - get_channel_queries_.add_query(migrated_to_channel_id.get(), Promise(), "on_get_chat"); - } - } - break; - } - default: - UNREACHABLE(); - } - } - - Chat *c = get_chat_force(chat_id, source); // to load versions - if (c == nullptr) { - c = add_chat(chat_id); - } - on_update_chat_title(c, chat_id, std::move(chat.title_)); - if (!status.is_left()) { - on_update_chat_participant_count(c, chat_id, chat.participants_count_, chat.version_, debug_str); - } else { - chat.photo_ = nullptr; - } - if (c->date != chat.date_) { - LOG_IF(ERROR, c->date != 0) << "Chat creation date has changed from " << c->date << " to " << chat.date_ - << debug_str; - c->date = chat.date_; - c->need_save_to_database = true; - } - on_update_chat_status(c, chat_id, std::move(status)); - on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_, ChannelType::Unknown), - chat.version_); - on_update_chat_photo(c, chat_id, std::move(chat.photo_)); - on_update_chat_active(c, chat_id, is_active); - on_update_chat_noforwards(c, chat_id, chat.noforwards_); - on_update_chat_migrated_to_channel_id(c, chat_id, migrated_to_channel_id); - LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated" << debug_str; - if (c->cache_version != Chat::CACHE_VERSION) { - c->cache_version = Chat::CACHE_VERSION; - c->need_save_to_database = true; - } - c->is_received_from_server = true; - update_chat(c, chat_id); - - bool has_active_group_call = (chat.flags_ & CHAT_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; - bool is_group_call_empty = (chat.flags_ & CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; - td_->messages_manager_->on_update_dialog_group_call(DialogId(chat_id), has_active_group_call, is_group_call_empty, - "receive chat"); -} - -void ContactsManager::on_get_chat_forbidden(telegram_api::chatForbidden &chat, const char *source) { - ChatId chat_id(chat.id_); - if (!chat_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << chat_id << " from " << source; - return; - } - - bool is_uninited = get_chat_force(chat_id, source) == nullptr; - Chat *c = add_chat(chat_id); - on_update_chat_title(c, chat_id, std::move(chat.title_)); - // chat participant count will be updated in on_update_chat_status - on_update_chat_photo(c, chat_id, nullptr); - if (c->date != 0) { - c->date = 0; // removed in 38-th layer - c->need_save_to_database = true; - } - on_update_chat_status(c, chat_id, DialogParticipantStatus::Banned(0)); - if (is_uninited) { - on_update_chat_active(c, chat_id, true); - on_update_chat_migrated_to_channel_id(c, chat_id, ChannelId()); - } else { - // leave active and migrated to as is - } - if (c->cache_version != Chat::CACHE_VERSION) { - c->cache_version = Chat::CACHE_VERSION; - c->need_save_to_database = true; - } - c->is_received_from_server = true; - update_chat(c, chat_id); -} - -void ContactsManager::on_get_channel(telegram_api::channel &channel, const char *source) { - ChannelId channel_id(channel.id_); - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); - return; - } - - if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { - Channel *c = get_channel_force(channel_id, source); - LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " - << to_string(get_supergroup_object(channel_id, c)); - if (c == nullptr && !have_min_channel(channel_id)) { - min_channels_.set(channel_id, td::make_unique()); - } - return; - } - - bool is_min = (channel.flags_ & CHANNEL_FLAG_IS_MIN) != 0; - bool has_access_hash = (channel.flags_ & CHANNEL_FLAG_HAS_ACCESS_HASH) != 0; - auto access_hash = has_access_hash ? channel.access_hash_ : 0; - - bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0; - bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0; - bool join_to_send = (channel.flags_ & CHANNEL_FLAG_JOIN_TO_SEND) != 0; - bool join_request = (channel.flags_ & CHANNEL_FLAG_JOIN_REQUEST) != 0; - bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0; - bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; - bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0; - bool is_scam = (channel.flags_ & CHANNEL_FLAG_IS_SCAM) != 0; - bool is_fake = (channel.flags_ & CHANNEL_FLAG_IS_FAKE) != 0; - bool is_gigagroup = (channel.flags_ & CHANNEL_FLAG_IS_GIGAGROUP) != 0; - bool is_forum = (channel.flags_ & CHANNEL_FLAG_IS_FORUM) != 0; - bool have_participant_count = (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0; - int32 participant_count = have_participant_count ? channel.participants_count_ : 0; - bool stories_available = channel.stories_max_id_ > 0; - bool stories_unavailable = channel.stories_unavailable_; - auto boost_level = channel.level_; - - if (have_participant_count) { - auto channel_full = get_channel_full_const(channel_id); - if (channel_full != nullptr && channel_full->administrator_count > participant_count) { - participant_count = channel_full->administrator_count; - } - } - - { - bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; - LOG_IF(ERROR, is_broadcast == is_megagroup) - << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " - << oneline(to_string(channel)); - } - - if (is_megagroup) { - LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id << " from " << source; - sign_messages = true; - } else { - LOG_IF(ERROR, is_slow_mode_enabled) << "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; - is_gigagroup = false; - is_forum = false; - } - if (is_gigagroup) { - remove_dialog_suggested_action(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) { - LOG(DEBUG) << "Receive known min " << channel_id; - - auto old_join_to_send = get_channel_join_to_send(c); - auto old_join_request = get_channel_join_request(c); - on_update_channel_title(c, channel_id, std::move(channel.title_)); - on_update_channel_usernames(c, channel_id, - Usernames(std::move(channel.username_), std::move(channel.usernames_))); - if (!c->status.is_banned()) { - on_update_channel_photo(c, channel_id, std::move(channel.photo_)); - } - on_update_channel_has_location(c, channel_id, channel.has_geo_); - on_update_channel_noforwards(c, channel_id, channel.noforwards_); - on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); - - if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || - c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || - c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { - c->has_linked_channel = has_linked_channel; - c->is_slow_mode_enabled = is_slow_mode_enabled; - c->is_megagroup = is_megagroup; - c->is_scam = is_scam; - c->is_fake = is_fake; - c->is_gigagroup = is_gigagroup; - if (c->is_forum != is_forum) { - c->is_forum = is_forum; - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), - is_forum); - } - c->boost_level = boost_level; - - c->is_changed = true; - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_min_channel"); - } - if (!td_->auth_manager_->is_bot()) { - auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); - if (restriction_reasons != c->restriction_reasons) { - c->restriction_reasons = std::move(restriction_reasons); - c->is_changed = true; - } - } - if (c->join_to_send != join_to_send || c->join_request != join_request) { - c->join_to_send = join_to_send; - c->join_request = join_request; - - c->need_save_to_database = true; - } - // sign_messages isn't known for min-channels - if (c->is_verified != is_verified) { - c->is_verified = is_verified; - - c->is_changed = true; - } - if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { - c->is_changed = true; - } - - // must be after setting of c->is_megagroup - on_update_channel_default_permissions(c, channel_id, - RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); - - update_channel(c, channel_id); - } else { - auto min_channel = td::make_unique(); - min_channel->photo_ = - get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), access_hash, std::move(channel.photo_)); - if (td_->auth_manager_->is_bot()) { - min_channel->photo_.minithumbnail.clear(); - } - PeerColor peer_color(channel.color_); - min_channel->accent_color_id_ = peer_color.accent_color_id_; - min_channel->title_ = std::move(channel.title_); - min_channel->is_megagroup_ = is_megagroup; - - min_channels_.set(channel_id, std::move(min_channel)); - } - return; - } - if (!has_access_hash) { - LOG(ERROR) << "Receive non-min " << channel_id << " without access_hash from " << source; - return; - } - - if (status.is_creator()) { - // to correctly calculate is_ownership_transferred in on_update_channel_status - get_channel_force(channel_id, source); - } - - Channel *c = add_channel(channel_id, "on_get_channel"); - auto old_join_to_send = get_channel_join_to_send(c); - auto old_join_request = get_channel_join_request(c); - if (c->access_hash != access_hash) { - c->access_hash = access_hash; - c->need_save_to_database = true; - } - if (c->date != channel.date_) { - c->date = channel.date_; - c->is_changed = true; - } - - bool need_update_participant_count = have_participant_count && participant_count != c->participant_count; - if (need_update_participant_count) { - c->participant_count = participant_count; - c->is_changed = true; - } - - bool need_invalidate_channel_full = false; - if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled || - c->is_megagroup != is_megagroup || c->is_scam != is_scam || c->is_fake != is_fake || - c->is_gigagroup != is_gigagroup || c->is_forum != is_forum || c->boost_level != boost_level) { - c->has_linked_channel = has_linked_channel; - c->is_slow_mode_enabled = is_slow_mode_enabled; - c->is_megagroup = is_megagroup; - c->is_scam = is_scam; - c->is_fake = is_fake; - c->is_gigagroup = is_gigagroup; - if (c->is_forum != is_forum) { - c->is_forum = is_forum; - send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_is_forum, DialogId(channel_id), - is_forum); - } - c->boost_level = boost_level; - - c->is_changed = true; - need_invalidate_channel_full = true; - } - if (!td_->auth_manager_->is_bot()) { - auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_)); - if (restriction_reasons != c->restriction_reasons) { - c->restriction_reasons = std::move(restriction_reasons); - c->is_changed = true; - } - } - if (c->join_to_send != join_to_send || c->join_request != join_request) { - c->join_to_send = join_to_send; - c->join_request = join_request; - - c->need_save_to_database = true; - } - if (c->is_verified != is_verified || c->sign_messages != sign_messages) { - c->is_verified = is_verified; - c->sign_messages = sign_messages; - - c->is_changed = true; - } - if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { - c->is_changed = true; - } - - on_update_channel_title(c, channel_id, std::move(channel.title_)); - on_update_channel_photo(c, channel_id, std::move(channel.photo_)); - PeerColor peer_color(channel.color_); - on_update_channel_accent_color_id(c, channel_id, peer_color.accent_color_id_); - on_update_channel_background_custom_emoji_id(c, channel_id, peer_color.background_custom_emoji_id_); - PeerColor profile_peer_color(channel.profile_color_); - on_update_channel_profile_accent_color_id(c, channel_id, profile_peer_color.accent_color_id_); - on_update_channel_profile_background_custom_emoji_id(c, channel_id, profile_peer_color.background_custom_emoji_id_); - on_update_channel_status(c, channel_id, std::move(status)); - on_update_channel_usernames( - c, channel_id, - Usernames(std::move(channel.username_), - std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status - on_update_channel_has_location(c, channel_id, channel.has_geo_); - on_update_channel_noforwards(c, channel_id, channel.noforwards_); - on_update_channel_emoji_status(c, channel_id, EmojiStatus(std::move(channel.emoji_status_))); - if (!td_->auth_manager_->is_bot() && !channel.stories_hidden_min_) { - on_update_channel_stories_hidden(c, channel_id, channel.stories_hidden_); - } - // must be after setting of c->is_megagroup - on_update_channel_default_permissions(c, channel_id, - RestrictedRights(channel.default_banned_rights_, ChannelType::Megagroup)); - if (!td_->auth_manager_->is_bot() && (stories_available || stories_unavailable)) { - // update at the end, because it calls need_poll_channel_active_stories - on_update_channel_story_ids_impl(c, channel_id, StoryId(channel.stories_max_id_), StoryId()); - } - - if (c->cache_version != Channel::CACHE_VERSION) { - c->cache_version = Channel::CACHE_VERSION; - c->need_save_to_database = true; - } - c->is_received_from_server = true; - update_channel(c, channel_id); - - if (need_update_participant_count) { - auto channel_full = get_channel_full(channel_id, true, "on_get_channel"); - if (channel_full != nullptr && channel_full->participant_count != participant_count) { - channel_full->participant_count = participant_count; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_get_channel"); - } - } - - if (need_invalidate_channel_full) { - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel"); - } - - bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0; - bool is_group_call_empty = (channel.flags_ & CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0; - td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), has_active_group_call, is_group_call_empty, - "receive channel"); -} - -void ContactsManager::on_get_channel_forbidden(telegram_api::channelForbidden &channel, const char *source) { - ChannelId channel_id(channel.id_); - if (!channel_id.is_valid()) { - LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel); - return; - } - - if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) { - Channel *c = get_channel_force(channel_id, source); - LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have " - << to_string(get_supergroup_object(channel_id, c)); - if (c == nullptr && !have_min_channel(channel_id)) { - min_channels_.set(channel_id, td::make_unique()); - } - return; - } - - Channel *c = add_channel(channel_id, "on_get_channel_forbidden"); - auto old_join_to_send = get_channel_join_to_send(c); - auto old_join_request = get_channel_join_request(c); - if (c->access_hash != channel.access_hash_) { - c->access_hash = channel.access_hash_; - c->need_save_to_database = true; - } - if (c->date != 0) { - c->date = 0; - c->is_changed = true; - } - - bool sign_messages = false; - bool join_to_send = false; - bool join_request = false; - bool is_slow_mode_enabled = false; - bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0; - bool is_verified = false; - bool is_scam = false; - bool is_fake = false; - - { - bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0; - LOG_IF(ERROR, is_broadcast == is_megagroup) - << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": " - << oneline(to_string(channel)); - } - - if (is_megagroup) { - sign_messages = true; - } - - bool need_invalidate_channel_full = false; - if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup || - !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake || - c->join_to_send != join_to_send || c->join_request != join_request) { - // c->has_linked_channel = has_linked_channel; - c->is_slow_mode_enabled = is_slow_mode_enabled; - c->is_megagroup = is_megagroup; - c->restriction_reasons.clear(); - c->is_scam = is_scam; - c->is_fake = is_fake; - c->join_to_send = join_to_send; - c->join_request = join_request; - - c->is_changed = true; - need_invalidate_channel_full = true; - } - if (c->join_to_send != join_to_send || c->join_request != join_request) { - c->join_to_send = join_to_send; - c->join_request = join_request; - - c->need_save_to_database = true; - } - if (c->sign_messages != sign_messages || c->is_verified != is_verified) { - c->sign_messages = sign_messages; - c->is_verified = is_verified; - - c->is_changed = true; - } - if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) { - c->is_changed = true; - } - - on_update_channel_title(c, channel_id, std::move(channel.title_)); - on_update_channel_photo(c, channel_id, nullptr); - on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(channel.until_date_)); - // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it - // on_update_channel_has_location(c, channel_id, false); - on_update_channel_noforwards(c, channel_id, false); - on_update_channel_emoji_status(c, channel_id, EmojiStatus()); - td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "on_get_channel_forbidden"); - // must be after setting of c->is_megagroup - tl_object_ptr banned_rights; // == nullptr - on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights, ChannelType::Megagroup)); - - bool need_drop_participant_count = c->participant_count != 0; - if (need_drop_participant_count) { - c->participant_count = 0; - c->is_changed = true; - } - - if (c->cache_version != Channel::CACHE_VERSION) { - c->cache_version = Channel::CACHE_VERSION; - c->need_save_to_database = true; - } - c->is_received_from_server = true; - update_channel(c, channel_id); - - if (need_drop_participant_count) { - auto channel_full = get_channel_full(channel_id, true, "on_get_channel_forbidden"); - if (channel_full != nullptr && channel_full->participant_count != 0) { - channel_full->participant_count = 0; - channel_full->administrator_count = 0; - channel_full->is_changed = true; - update_channel_full(channel_full, channel_id, "on_get_channel_forbidden 2"); - } - } - if (need_invalidate_channel_full) { - invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_get_channel_forbidden 3"); - } -} - -void ContactsManager::on_upload_profile_photo(FileId file_id, tl_object_ptr input_file) { - auto it = uploaded_profile_photos_.find(file_id); - CHECK(it != uploaded_profile_photos_.end()); - - UserId user_id = it->second.user_id; - bool is_fallback = it->second.is_fallback; - bool only_suggest = it->second.only_suggest; - double main_frame_timestamp = it->second.main_frame_timestamp; - bool is_animation = it->second.is_animation; - int32 reupload_count = it->second.reupload_count; - auto promise = std::move(it->second.promise); - - uploaded_profile_photos_.erase(it); - - LOG(INFO) << "Uploaded " << (is_animation ? "animated" : "static") << " profile photo " << file_id << " for " - << user_id << " with reupload_count = " << reupload_count; - FileView file_view = td_->file_manager_->get_file_view(file_id); - if (file_view.has_remote_location() && input_file == nullptr) { - if (file_view.main_remote_location().is_web()) { - return promise.set_error(Status::Error(400, "Can't use web photo as profile photo")); - } - if (reupload_count == 3) { // upload, ForceReupload repair file reference, reupload - return promise.set_error(Status::Error(400, "Failed to reupload the file")); - } - - // delete file reference and forcely reupload the file - if (is_animation) { - CHECK(file_view.get_type() == FileType::Animation); - LOG_CHECK(file_view.main_remote_location().is_common()) << file_view.main_remote_location(); - } else { - CHECK(file_view.get_type() == FileType::Photo); - LOG_CHECK(file_view.main_remote_location().is_photo()) << file_view.main_remote_location(); - } - auto file_reference = - is_animation ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()) - : FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo()); - td_->file_manager_->delete_file_reference(file_id, file_reference); - upload_profile_photo(user_id, file_id, is_fallback, only_suggest, is_animation, main_frame_timestamp, - std::move(promise), reupload_count + 1, {-1}); - return; - } - CHECK(input_file != nullptr); - - td_->create_handler(std::move(promise)) - ->send(user_id, file_id, std::move(input_file), is_fallback, only_suggest, is_animation, main_frame_timestamp); -} - -void ContactsManager::on_upload_profile_photo_error(FileId file_id, Status status) { - LOG(INFO) << "File " << file_id << " has upload error " << status; - CHECK(status.is_error()); - - auto it = uploaded_profile_photos_.find(file_id); - CHECK(it != uploaded_profile_photos_.end()); - - auto promise = std::move(it->second.promise); - - uploaded_profile_photos_.erase(it); - - promise.set_error(std::move(status)); // TODO check that status has valid error code -} - -td_api::object_ptr ContactsManager::get_user_status_object(UserId user_id, const User *u, - int32 unix_time) const { - if (u->is_bot) { - return make_tl_object(std::numeric_limits::max()); - } - - int32 was_online = get_user_was_online(u, user_id, unix_time); - switch (was_online) { - case -6: - case -3: - return make_tl_object(was_online == -6); - case -5: - case -2: - return make_tl_object(was_online == -5); - case -4: - case -1: - return make_tl_object(was_online == -4); - case 0: - return make_tl_object(); - default: { - int32 time = G()->unix_time(); - if (was_online > time) { - return make_tl_object(was_online); - } else { - return make_tl_object(was_online); - } - } - } -} - -bool ContactsManager::get_user_has_unread_stories(const User *u) { - CHECK(u != nullptr); - return u->max_active_story_id.get() > u->max_read_story_id.get(); -} - -td_api::object_ptr ContactsManager::get_update_user_object(UserId user_id, const User *u) const { - if (u == nullptr) { - return get_update_unknown_user_object(user_id); - } - return td_api::make_object(get_user_object(user_id, u)); -} - -td_api::object_ptr ContactsManager::get_update_unknown_user_object(UserId user_id) const { - auto have_access = user_id == get_my_id() || user_messages_.count(user_id) != 0; - return td_api::make_object(td_api::make_object( - user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, - td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)), 0, -1, 0, nullptr, false, false, false, - false, false, false, "", false, false, false, false, false, have_access, - td_api::make_object(), "", false)); -} - -int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const { - if (user_id.is_valid() && get_user(user_id) == nullptr && unknown_users_.count(user_id) == 0) { - LOG(ERROR) << "Have no information about " << user_id << " from " << source; - unknown_users_.insert(user_id); - send_closure(G()->td(), &Td::send_update, get_update_unknown_user_object(user_id)); - } - return user_id.get(); -} - -tl_object_ptr ContactsManager::get_user_object(UserId user_id) const { - return get_user_object(user_id, get_user(user_id)); -} - -tl_object_ptr ContactsManager::get_user_object(UserId user_id, const User *u) const { - if (u == nullptr) { - return nullptr; - } - tl_object_ptr type; - if (u->is_deleted) { - type = make_tl_object(); - } else if (u->is_bot) { - type = make_tl_object(u->can_be_edited_bot, u->can_join_groups, u->can_read_all_group_messages, - u->is_inline_bot, u->inline_query_placeholder, u->need_location_bot, - u->can_be_added_to_attach_menu); - } else { - type = make_tl_object(); - } - - auto emoji_status = u->last_sent_emoji_status.get_emoji_status_object(); - auto have_access = user_id == get_my_id() || have_input_peer_user(u, user_id, AccessRights::Know); - auto accent_color_id = u->accent_color_id.is_valid() ? u->accent_color_id : AccentColorId(user_id); - auto restricts_new_chats = u->contact_require_premium && !u->is_mutual_contact; - return td_api::make_object( - user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number, - get_user_status_object(user_id, u, G()->unix_time()), - get_profile_photo_object(td_->file_manager_.get(), u->photo), - td_->theme_manager_->get_accent_color_id_object(accent_color_id, AccentColorId(user_id)), - u->background_custom_emoji_id.get(), - td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id), - u->profile_background_custom_emoji_id.get(), std::move(emoji_status), u->is_contact, u->is_mutual_contact, - u->is_close_friend, u->is_verified, u->is_premium, u->is_support, - get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, - u->max_active_story_id.is_valid(), get_user_has_unread_stories(u), restricts_new_chats, have_access, - std::move(type), u->language_code, u->attach_menu_enabled); -} - -vector ContactsManager::get_user_ids_object(const vector &user_ids, const char *source) const { - return transform(user_ids, [this, source](UserId user_id) { return get_user_id_object(user_id, source); }); -} - -tl_object_ptr ContactsManager::get_users_object(int32 total_count, - const vector &user_ids) const { - if (total_count == -1) { - total_count = narrow_cast(user_ids.size()); - } - return td_api::make_object(total_count, get_user_ids_object(user_ids, "get_users_object")); -} - -tl_object_ptr ContactsManager::get_user_full_info_object(UserId user_id) const { - return get_user_full_info_object(user_id, get_user_full(user_id)); -} - -tl_object_ptr ContactsManager::get_user_full_info_object(UserId user_id, - const UserFull *user_full) const { - CHECK(user_full != nullptr); - td_api::object_ptr bot_info; - const User *u = get_user(user_id); - bool is_bot = is_user_bot(u); - bool is_premium = is_user_premium(u); - td_api::object_ptr bio_object; - if (is_bot) { - auto menu_button = get_bot_menu_button_object(td_, user_full->menu_button.get()); - auto commands = - transform(user_full->commands, [](const auto &command) { return command.get_bot_command_object(); }); - bot_info = td_api::make_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), - user_full->group_administrator_rights == AdministratorRights() - ? nullptr - : user_full->group_administrator_rights.get_chat_administrator_rights_object(), - user_full->broadcast_administrator_rights == AdministratorRights() - ? nullptr - : user_full->broadcast_administrator_rights.get_chat_administrator_rights_object(), - nullptr, nullptr, nullptr, nullptr); - if (u != nullptr && u->can_be_edited_bot && u->usernames.has_editable_username()) { - auto bot_username = u->usernames.get_editable_username(); - bot_info->edit_commands_link_ = td_api::make_object( - "botfather", PSTRING() << bot_username << "-commands", true); - bot_info->edit_description_link_ = td_api::make_object( - "botfather", PSTRING() << bot_username << "-intro", true); - bot_info->edit_description_media_link_ = td_api::make_object( - "botfather", PSTRING() << bot_username << "-intropic", true); - bot_info->edit_settings_link_ = - td_api::make_object("botfather", bot_username, true); - } - } else { - FormattedText bio; - bio.text = user_full->about; - bio.entities = find_entities(bio.text, true, true); - if (!is_premium) { - td::remove_if(bio.entities, [&](const MessageEntity &entity) { - if (entity.type == MessageEntity::Type::EmailAddress) { - return true; - } - if (entity.type == MessageEntity::Type::Url && - !LinkManager::is_internal_link(utf8_utf16_substr(bio.text, entity.offset, entity.length))) { - return true; - } - return false; - }); - } - bio_object = get_formatted_text_object(bio, true, 0); - } - auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false; - auto block_list_id = BlockListId(user_full->is_blocked, user_full->is_blocked_for_stories); - auto business_info = is_premium && user_full->business_info != nullptr - ? user_full->business_info->get_business_info_object(td_) - : nullptr; - return td_api::make_object( - get_chat_photo_object(td_->file_manager_.get(), user_full->personal_photo), - get_chat_photo_object(td_->file_manager_.get(), user_full->photo), - get_chat_photo_object(td_->file_manager_.get(), user_full->fallback_photo), block_list_id.get_block_list_object(), - user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, - !user_full->private_forward_name.empty(), voice_messages_forbidden, user_full->has_pinned_stories, - user_full->need_phone_number_privacy_exception, user_full->wallpaper_overridden, std::move(bio_object), - get_premium_payment_options_object(user_full->premium_gift_options), user_full->common_chat_count, - std::move(business_info), std::move(bot_info)); -} - -td_api::object_ptr ContactsManager::get_update_basic_group_object(ChatId chat_id, - const Chat *c) { - if (c == nullptr) { - return get_update_unknown_basic_group_object(chat_id); - } - return td_api::make_object(get_basic_group_object(chat_id, c)); -} - -td_api::object_ptr ContactsManager::get_update_unknown_basic_group_object(ChatId chat_id) { - return td_api::make_object(td_api::make_object( - chat_id.get(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), true, 0)); -} - -int64 ContactsManager::get_basic_group_id_object(ChatId chat_id, const char *source) const { - if (chat_id.is_valid() && get_chat(chat_id) == nullptr && unknown_chats_.count(chat_id) == 0) { - LOG(ERROR) << "Have no information about " << chat_id << " from " << source; - unknown_chats_.insert(chat_id); - send_closure(G()->td(), &Td::send_update, get_update_unknown_basic_group_object(chat_id)); - } - return chat_id.get(); -} - -tl_object_ptr ContactsManager::get_basic_group_object(ChatId chat_id) { - return get_basic_group_object(chat_id, get_chat(chat_id)); -} - -tl_object_ptr ContactsManager::get_basic_group_object(ChatId chat_id, const Chat *c) { - if (c == nullptr) { - return nullptr; - } - if (c->migrated_to_channel_id.is_valid()) { - get_channel_force(c->migrated_to_channel_id, "get_basic_group_object"); - } - return get_basic_group_object_const(chat_id, c); -} - -tl_object_ptr ContactsManager::get_basic_group_object_const(ChatId chat_id, const Chat *c) const { - return make_tl_object( - chat_id.get(), c->participant_count, get_chat_status(c).get_chat_member_status_object(), c->is_active, - get_supergroup_id_object(c->migrated_to_channel_id, "get_basic_group_object")); -} - -tl_object_ptr ContactsManager::get_basic_group_full_info_object(ChatId chat_id) const { - return get_basic_group_full_info_object(chat_id, get_chat_full(chat_id)); -} - -tl_object_ptr ContactsManager::get_basic_group_full_info_object( - ChatId chat_id, const ChatFull *chat_full) const { - CHECK(chat_full != nullptr); - auto bot_commands = transform(chat_full->bot_commands, [td = td_](const BotCommands &commands) { - return commands.get_bot_commands_object(td); - }); - auto members = transform(chat_full->participants, [this](const DialogParticipant &dialog_participant) { - return get_chat_member_object(dialog_participant, "get_basic_group_full_info_object"); - }); - return make_tl_object( - get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description, - get_user_id_object(chat_full->creator_user_id, "basicGroupFullInfo"), std::move(members), - can_hide_chat_participants(chat_id).is_ok(), can_toggle_chat_aggressive_anti_spam(chat_id).is_ok(), - chat_full->invite_link.get_chat_invite_link_object(this), std::move(bot_commands)); -} - -td_api::object_ptr ContactsManager::get_update_supergroup_object(ChannelId channel_id, - const Channel *c) const { - if (c == nullptr) { - return get_update_unknown_supergroup_object(channel_id); - } - return td_api::make_object(get_supergroup_object(channel_id, c)); -} - -td_api::object_ptr ContactsManager::get_update_unknown_supergroup_object( - ChannelId channel_id) const { - auto min_channel = get_min_channel(channel_id); - 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)); -} - -int64 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const { - if (channel_id.is_valid() && get_channel(channel_id) == nullptr && unknown_channels_.count(channel_id) == 0) { - if (have_min_channel(channel_id)) { - LOG(INFO) << "Have only min " << channel_id << " received from " << source; - } else { - LOG(ERROR) << "Have no information about " << channel_id << " received from " << source; - } - unknown_channels_.insert(channel_id); - send_closure(G()->td(), &Td::send_update, get_update_unknown_supergroup_object(channel_id)); - } - return channel_id.get(); -} - -bool ContactsManager::need_poll_channel_active_stories(const Channel *c, ChannelId channel_id) const { - return c != nullptr && !get_channel_status(c).is_member() && - have_input_peer_channel(c, channel_id, AccessRights::Read); -} - -bool ContactsManager::get_channel_has_unread_stories(const Channel *c) { - CHECK(c != nullptr); - return c->max_active_story_id.get() > c->max_read_story_id.get(); -} - -tl_object_ptr ContactsManager::get_supergroup_object(ChannelId channel_id) const { - return get_supergroup_object(channel_id, get_channel(channel_id)); -} - -tl_object_ptr ContactsManager::get_supergroup_object(ChannelId channel_id, const Channel *c) { - if (c == nullptr) { - return nullptr; - } - 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), - 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->max_active_story_id.is_valid(), get_channel_has_unread_stories(c)); -} - -tl_object_ptr ContactsManager::get_supergroup_full_info_object(ChannelId channel_id) const { - return get_supergroup_full_info_object(channel_id, get_channel_full(channel_id)); -} - -tl_object_ptr ContactsManager::get_supergroup_full_info_object( - ChannelId channel_id, const ChannelFull *channel_full) const { - CHECK(channel_full != nullptr); - double slow_mode_delay_expires_in = 0; - if (channel_full->slow_mode_next_send_date != 0 && - (channel_full->unrestrict_boost_count == 0 || channel_full->boost_count < channel_full->unrestrict_boost_count)) { - slow_mode_delay_expires_in = max(channel_full->slow_mode_next_send_date - G()->server_time(), 1e-3); - } - auto bot_commands = transform(channel_full->bot_commands, [td = td_](const BotCommands &commands) { - return commands.get_bot_commands_object(td); - }); - bool has_hidden_participants = channel_full->has_hidden_participants || !channel_full->can_get_participants; - return td_api::make_object( - 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, - can_toggle_channel_aggressive_anti_spam(channel_id, channel_full).is_ok(), channel_full->is_all_history_available, - channel_full->has_aggressive_anti_spam_enabled, channel_full->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(this), 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()); -} - -tl_object_ptr ContactsManager::get_secret_chat_state_object(SecretChatState state) { - switch (state) { - case SecretChatState::Waiting: - return make_tl_object(); - case SecretChatState::Active: - return make_tl_object(); - case SecretChatState::Closed: - case SecretChatState::Unknown: - return make_tl_object(); - default: - UNREACHABLE(); - return nullptr; - } -} - -td_api::object_ptr ContactsManager::get_update_secret_chat_object( - SecretChatId secret_chat_id, const SecretChat *secret_chat) { - if (secret_chat == nullptr) { - return get_update_unknown_secret_chat_object(secret_chat_id); - } - return td_api::make_object(get_secret_chat_object(secret_chat_id, secret_chat)); -} - -td_api::object_ptr ContactsManager::get_update_unknown_secret_chat_object( - SecretChatId secret_chat_id) { - return td_api::make_object(td_api::make_object( - secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, string(), 0)); -} - -int32 ContactsManager::get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const { - if (secret_chat_id.is_valid() && get_secret_chat(secret_chat_id) == nullptr && - unknown_secret_chats_.count(secret_chat_id) == 0) { - LOG(ERROR) << "Have no information about " << secret_chat_id << " from " << source; - unknown_secret_chats_.insert(secret_chat_id); - send_closure(G()->td(), &Td::send_update, get_update_unknown_secret_chat_object(secret_chat_id)); - } - return secret_chat_id.get(); -} - -tl_object_ptr ContactsManager::get_secret_chat_object(SecretChatId secret_chat_id) { - return get_secret_chat_object(secret_chat_id, get_secret_chat(secret_chat_id)); -} - -tl_object_ptr ContactsManager::get_secret_chat_object(SecretChatId secret_chat_id, - const SecretChat *secret_chat) { - if (secret_chat == nullptr) { - return nullptr; - } - get_user_force(secret_chat->user_id, "get_secret_chat_object"); - return get_secret_chat_object_const(secret_chat_id, secret_chat); -} - -tl_object_ptr ContactsManager::get_secret_chat_object_const(SecretChatId secret_chat_id, - const SecretChat *secret_chat) const { - return td_api::make_object(secret_chat_id.get(), - get_user_id_object(secret_chat->user_id, "secretChat"), - get_secret_chat_state_object(secret_chat->state), - secret_chat->is_outbound, secret_chat->key_hash, secret_chat->layer); -} - -void ContactsManager::get_support_user(Promise> &&promise) { - if (support_user_id_.is_valid()) { - return promise.set_value(get_user_object(support_user_id_)); - } - - auto query_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &ContactsManager::on_get_support_user, result.move_as_ok(), std::move(promise)); - } - }); - td_->create_handler(std::move(query_promise))->send(); -} - -void ContactsManager::on_get_support_user(UserId user_id, Promise> &&promise) { - TRY_STATUS_PROMISE(promise, G()->close_status()); - - const User *u = get_user(user_id); - if (u == nullptr) { - return promise.set_error(Status::Error(500, "Can't find support user")); - } - if (!u->is_support) { - LOG(ERROR) << "Receive non-support " << user_id << ", but expected a support user"; - } - - support_user_id_ = user_id; - promise.set_value(get_user_object(user_id, u)); -} - -void ContactsManager::get_current_state(vector> &updates) const { - for (auto user_id : unknown_users_) { - if (!have_min_user(user_id)) { - updates.push_back(get_update_unknown_user_object(user_id)); - } - } - for (auto chat_id : unknown_chats_) { - if (!have_chat(chat_id)) { - updates.push_back(get_update_unknown_basic_group_object(chat_id)); - } - } - for (auto channel_id : unknown_channels_) { - if (!have_channel(channel_id)) { - updates.push_back(get_update_unknown_supergroup_object(channel_id)); - } - } - for (auto secret_chat_id : unknown_secret_chats_) { - if (!have_secret_chat(secret_chat_id)) { - updates.push_back(get_update_unknown_secret_chat_object(secret_chat_id)); - } - } - - users_.foreach([&](const UserId &user_id, const unique_ptr &user) { - updates.push_back(get_update_user_object(user_id, user.get())); - }); - channels_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel) { - updates.push_back(get_update_supergroup_object(channel_id, channel.get())); - }); - // chat objects can contain channel_id, so they must be sent after channels - chats_.foreach([&](const ChatId &chat_id, const unique_ptr &chat) { - updates.push_back(td_api::make_object(get_basic_group_object_const(chat_id, chat.get()))); - }); - // secret chat objects contain user_id, so they must be sent after users - secret_chats_.foreach([&](const SecretChatId &secret_chat_id, const unique_ptr &secret_chat) { - updates.push_back( - td_api::make_object(get_secret_chat_object_const(secret_chat_id, secret_chat.get()))); - }); - - users_full_.foreach([&](const UserId &user_id, const unique_ptr &user_full) { - updates.push_back(td_api::make_object( - user_id.get(), get_user_full_info_object(user_id, user_full.get()))); - }); - channels_full_.foreach([&](const ChannelId &channel_id, const unique_ptr &channel_full) { - updates.push_back(td_api::make_object( - channel_id.get(), get_supergroup_full_info_object(channel_id, channel_full.get()))); - }); - chats_full_.foreach([&](const ChatId &chat_id, const unique_ptr &chat_full) { - updates.push_back(td_api::make_object( - chat_id.get(), get_basic_group_full_info_object(chat_id, chat_full.get()))); - }); -} - -} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ContactsManager.h b/lib/tgchat/ext/td/td/telegram/ContactsManager.h deleted file mode 100644 index 3707c587..00000000 --- a/lib/tgchat/ext/td/td/telegram/ContactsManager.h +++ /dev/null @@ -1,1990 +0,0 @@ -// -// 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/AccentColorId.h" -#include "td/telegram/AccessRights.h" -#include "td/telegram/BotCommand.h" -#include "td/telegram/BotMenuButton.h" -#include "td/telegram/ChannelId.h" -#include "td/telegram/ChannelType.h" -#include "td/telegram/ChatId.h" -#include "td/telegram/Contact.h" -#include "td/telegram/CustomEmojiId.h" -#include "td/telegram/DialogId.h" -#include "td/telegram/DialogInviteLink.h" -#include "td/telegram/DialogLocation.h" -#include "td/telegram/DialogParticipant.h" -#include "td/telegram/EmojiStatus.h" -#include "td/telegram/files/FileId.h" -#include "td/telegram/files/FileSourceId.h" -#include "td/telegram/FolderId.h" -#include "td/telegram/Location.h" -#include "td/telegram/MessageFullId.h" -#include "td/telegram/MessageId.h" -#include "td/telegram/MessageTtl.h" -#include "td/telegram/net/DcId.h" -#include "td/telegram/Photo.h" -#include "td/telegram/PremiumGiftOption.h" -#include "td/telegram/PublicDialogType.h" -#include "td/telegram/QueryCombiner.h" -#include "td/telegram/QueryMerger.h" -#include "td/telegram/RestrictionReason.h" -#include "td/telegram/SecretChatId.h" -#include "td/telegram/StickerSetId.h" -#include "td/telegram/StoryId.h" -#include "td/telegram/SuggestedAction.h" -#include "td/telegram/td_api.h" -#include "td/telegram/telegram_api.h" -#include "td/telegram/UserId.h" -#include "td/telegram/Usernames.h" - -#include "td/actor/actor.h" -#include "td/actor/MultiPromise.h" -#include "td/actor/MultiTimeout.h" - -#include "td/utils/common.h" -#include "td/utils/FlatHashMap.h" -#include "td/utils/FlatHashSet.h" -#include "td/utils/HashTableUtils.h" -#include "td/utils/Hints.h" -#include "td/utils/Promise.h" -#include "td/utils/Status.h" -#include "td/utils/StringBuilder.h" -#include "td/utils/Time.h" -#include "td/utils/WaitFreeHashMap.h" -#include "td/utils/WaitFreeHashSet.h" - -#include -#include -#include - -namespace td { - -struct BinlogEvent; -class BusinessAwayMessage; -class BusinessGreetingMessage; -class BusinessInfo; -class BusinessWorkHours; -struct MinChannel; -class Td; - -class ContactsManager final : public Actor { - public: - ContactsManager(Td *td, ActorShared<> parent); - ContactsManager(const ContactsManager &) = delete; - ContactsManager &operator=(const ContactsManager &) = delete; - ContactsManager(ContactsManager &&) = delete; - ContactsManager &operator=(ContactsManager &&) = delete; - ~ContactsManager() final; - - static UserId load_my_id(); - - static UserId get_user_id(const tl_object_ptr &user); - static ChatId get_chat_id(const tl_object_ptr &chat); - static ChannelId get_channel_id(const tl_object_ptr &chat); - static DialogId get_dialog_id(const tl_object_ptr &chat); - - vector get_channel_ids(vector> &&chats, const char *source); - - Result> get_input_user(UserId user_id) const; - - tl_object_ptr get_input_user_force(UserId user_id) const; - - // TODO get_input_chat ??? - - tl_object_ptr get_input_channel(ChannelId channel_id) const; - - tl_object_ptr get_input_peer_user(UserId user_id, AccessRights access_rights) const; - bool have_input_peer_user(UserId user_id, AccessRights access_rights) const; - - tl_object_ptr get_input_peer_chat(ChatId chat_id, AccessRights access_rights) const; - bool have_input_peer_chat(ChatId chat_id, AccessRights access_rights) const; - - tl_object_ptr get_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const; - bool have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const; - - tl_object_ptr get_input_encrypted_chat(SecretChatId secret_chat_id, - AccessRights access_rights) const; - bool have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const; - - bool is_user_received_from_server(UserId user_id) const; - bool is_chat_received_from_server(ChatId chat_id) const; - bool is_channel_received_from_server(ChannelId channel_id) const; - - const DialogPhoto *get_user_dialog_photo(UserId user_id); - const DialogPhoto *get_chat_dialog_photo(ChatId chat_id) const; - const DialogPhoto *get_channel_dialog_photo(ChannelId channel_id) const; - const DialogPhoto *get_secret_chat_dialog_photo(SecretChatId secret_chat_id); - - AccentColorId get_channel_accent_color_id(ChannelId channel_id) const; - - int32 get_user_accent_color_id_object(UserId user_id) const; - int32 get_chat_accent_color_id_object(ChatId chat_id) const; - int32 get_channel_accent_color_id_object(ChannelId channel_id) const; - int32 get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const; - - CustomEmojiId get_user_background_custom_emoji_id(UserId user_id) const; - CustomEmojiId get_chat_background_custom_emoji_id(ChatId chat_id) const; - CustomEmojiId get_channel_background_custom_emoji_id(ChannelId channel_id) const; - CustomEmojiId get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const; - - int32 get_user_profile_accent_color_id_object(UserId user_id) const; - int32 get_chat_profile_accent_color_id_object(ChatId chat_id) const; - int32 get_channel_profile_accent_color_id_object(ChannelId channel_id) const; - int32 get_secret_chat_profile_accent_color_id_object(SecretChatId secret_chat_id) const; - - CustomEmojiId get_user_profile_background_custom_emoji_id(UserId user_id) const; - CustomEmojiId get_chat_profile_background_custom_emoji_id(ChatId chat_id) const; - CustomEmojiId get_channel_profile_background_custom_emoji_id(ChannelId channel_id) const; - CustomEmojiId get_secret_chat_profile_background_custom_emoji_id(SecretChatId secret_chat_id) const; - - string get_user_title(UserId user_id) const; - string get_chat_title(ChatId chat_id) const; - string get_channel_title(ChannelId channel_id) const; - string get_secret_chat_title(SecretChatId secret_chat_id) const; - - RestrictedRights get_user_default_permissions(UserId user_id) const; - RestrictedRights get_chat_default_permissions(ChatId chat_id) const; - RestrictedRights get_channel_default_permissions(ChannelId channel_id) const; - RestrictedRights get_secret_chat_default_permissions(SecretChatId secret_chat_id) const; - - td_api::object_ptr get_user_emoji_status_object(UserId user_id) const; - td_api::object_ptr get_chat_emoji_status_object(ChatId chat_id) const; - td_api::object_ptr get_channel_emoji_status_object(ChannelId channel_id) const; - td_api::object_ptr get_secret_chat_emoji_status_object(SecretChatId secret_chat_id) const; - - string get_user_about(UserId user_id); - string get_chat_about(ChatId chat_id); - string get_channel_about(ChannelId channel_id); - string get_secret_chat_about(SecretChatId secret_chat_id); - - bool get_chat_has_protected_content(ChatId chat_id) const; - bool get_channel_has_protected_content(ChannelId channel_id) const; - - bool get_user_stories_hidden(UserId user_id) const; - bool get_channel_stories_hidden(ChannelId channel_id) const; - - bool can_poll_user_active_stories(UserId user_id) const; - bool can_poll_channel_active_stories(ChannelId channel_id) const; - - string get_user_private_forward_name(UserId user_id); - - bool get_user_voice_messages_forbidden(UserId user_id) const; - - bool get_user_read_dates_private(UserId user_id); - - string get_user_search_text(UserId user_id) const; - - string get_channel_search_text(ChannelId channel_id) const; - - void for_each_secret_chat_with_user(UserId user_id, const std::function &f); - - string get_user_first_username(UserId user_id) const; - string get_channel_first_username(ChannelId channel_id) const; - string get_channel_editable_username(ChannelId channel_id) const; - - int32 get_secret_chat_date(SecretChatId secret_chat_id) const; - int32 get_secret_chat_ttl(SecretChatId secret_chat_id) const; - UserId get_secret_chat_user_id(SecretChatId secret_chat_id) const; - bool get_secret_chat_is_outbound(SecretChatId secret_chat_id) const; - SecretChatState get_secret_chat_state(SecretChatId secret_chat_id) const; - int32 get_secret_chat_layer(SecretChatId secret_chat_id) const; - FolderId get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const; - - void can_send_message_to_user(UserId user_id, bool force, - Promise> &&promise); - - void allow_send_message_to_user(UserId user_id); - - void on_imported_contacts(int64 random_id, Result> result); - - void on_deleted_contacts(const vector &deleted_contact_user_ids); - - void on_get_contacts(tl_object_ptr &&new_contacts); - - void on_get_contacts_failed(Status error); - - void on_get_contacts_statuses(vector> &&statuses); - - void reload_contacts(bool force); - - void on_get_user(tl_object_ptr &&user, const char *source); - void on_get_users(vector> &&users, const char *source); - - void on_get_is_premium_required_to_contact_users(vector &&user_ids, vector &&is_premium_required, - Promise &&promise); - - void on_binlog_user_event(BinlogEvent &&event); - void on_binlog_chat_event(BinlogEvent &&event); - void on_binlog_channel_event(BinlogEvent &&event); - void on_binlog_secret_chat_event(BinlogEvent &&event); - - void on_get_user_full(tl_object_ptr &&user); - - void on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, - vector> photos); - - void on_get_chat(tl_object_ptr &&chat, const char *source); - void on_get_chats(vector> &&chats, const char *source); - - void on_get_chat_full(tl_object_ptr &&chat_full, Promise &&promise); - void on_get_chat_full_failed(ChatId chat_id); - void on_get_channel_full_failed(ChannelId channel_id); - - void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about); - void on_update_accent_color_success(bool for_profile, AccentColorId accent_color_id, - CustomEmojiId background_custom_emoji_id); - - void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames); - void on_update_user_phone_number(UserId user_id, string &&phone_number); - void on_update_user_emoji_status(UserId user_id, tl_object_ptr &&emoji_status); - void on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); - void on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id); - void on_update_user_stories_hidden(UserId user_id, bool stories_hidden); - void on_update_user_online(UserId user_id, tl_object_ptr &&status); - void on_update_user_local_was_online(UserId user_id, int32 local_was_online); - // use on_update_dialog_is_blocked instead - void on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories); - void on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories); - void on_update_user_common_chat_count(UserId user_id, int32 common_chat_count); - void on_update_user_location(UserId user_id, DialogLocation &&location); - void on_update_user_work_hours(UserId user_id, BusinessWorkHours &&work_hours); - void on_update_user_away_message(UserId user_id, BusinessAwayMessage &&away_message); - void on_update_user_greeting_message(UserId user_id, BusinessGreetingMessage &&greeting_message); - void on_update_user_need_phone_number_privacy_exception(UserId user_id, bool need_phone_number_privacy_exception); - void on_update_user_wallpaper_overridden(UserId user_id, bool wallpaper_overridden); - - void on_set_profile_photo(UserId user_id, tl_object_ptr &&photo, bool is_fallback, - int64 old_photo_id, Promise &&promise); - - void on_delete_profile_photo(int64 profile_photo_id, Promise promise); - - void on_ignored_restriction_reasons_changed(); - - void on_get_chat_participants(tl_object_ptr &&participants, bool from_update); - void on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, int32 version); - void on_update_chat_description(ChatId chat_id, string &&description); - void on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, int32 version); - void on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version); - void on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, int32 version); - void on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version); - - void on_update_channel_participant_count(ChannelId channel_id, int32 participant_count); - void on_update_channel_editable_username(ChannelId channel_id, string &&username); - void on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames); - void on_update_channel_story_ids(ChannelId channel_id, StoryId max_active_story_id, StoryId max_read_story_id); - void on_update_channel_max_read_story_id(ChannelId channel_id, StoryId max_read_story_id); - void on_update_channel_stories_hidden(ChannelId channel_id, bool stories_hidden); - void on_update_channel_description(ChannelId channel_id, string &&description); - void on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id); - void on_update_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id); - void on_update_channel_unrestrict_boost_count(ChannelId channel_id, int32 unrestrict_boost_count); - void on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id); - void on_update_channel_location(ChannelId channel_id, const DialogLocation &location); - void on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, Promise &&promise); - void on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date); - void on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, - Promise &&promise); - void on_update_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, - Promise &&promise); - void on_update_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, - Promise &&promise); - void on_update_channel_has_pinned_stories(ChannelId channel_id, bool has_pinned_stories); - void on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions); - void on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count); - - int32 on_update_peer_located(vector> &&peers, bool from_update); - - void on_update_bot_commands(DialogId dialog_id, UserId bot_user_id, - vector> &&bot_commands); - - void on_update_bot_menu_button(UserId bot_user_id, tl_object_ptr &&bot_menu_button); - - void speculative_add_channel_participants(ChannelId channel_id, const vector &added_user_ids, - UserId inviter_user_id, int32 date, bool by_me); - - void speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me); - - void invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, const char *source); - - bool on_get_channel_error(ChannelId channel_id, const Status &status, const char *source); - - void on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link); - - void on_get_created_public_channels(PublicDialogType type, vector> &&chats); - - void on_get_dialogs_for_discussion(vector> &&chats); - - void on_get_inactive_channels(vector> &&chats, Promise &&promise); - - void remove_inactive_channel(ChannelId channel_id); - - void register_message_users(MessageFullId message_full_id, vector user_ids); - - void register_message_channels(MessageFullId message_full_id, vector channel_ids); - - void unregister_message_users(MessageFullId message_full_id, vector user_ids); - - void unregister_message_channels(MessageFullId message_full_id, vector channel_ids); - - bool can_use_premium_custom_emoji(DialogId dialog_id) const; - - UserId get_my_id() const; - - void set_my_online_status(bool is_online, bool send_update, bool is_local); - - struct MyOnlineStatusInfo { - bool is_online_local = false; - bool is_online_remote = false; - int32 was_online_local = 0; - int32 was_online_remote = 0; - }; - - MyOnlineStatusInfo get_my_online_status() const; - - static UserId get_service_notifications_user_id(); - - UserId add_service_notifications_user(); - - static UserId get_replies_bot_user_id(); - - static UserId get_anonymous_bot_user_id(); - - static UserId get_channel_bot_user_id(); - - static UserId get_anti_spam_bot_user_id(); - - UserId add_anonymous_bot_user(); - - UserId add_channel_bot_user(); - - static ChannelId get_unsupported_channel_id(); - - void update_chat_online_member_count(ChatId chat_id, bool is_from_server); - - void on_update_channel_bot_user_ids(ChannelId channel_id, vector &&bot_user_ids); - - void on_update_username_is_active(UserId user_id, string &&username, bool is_active, Promise &&promise); - - void on_update_active_usernames_order(UserId user_id, vector &&usernames, Promise &&promise); - - void on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, - Promise &&promise); - - void on_deactivate_channel_usernames(ChannelId channel_id, Promise &&promise); - - void on_update_channel_active_usernames_order(ChannelId channel_id, vector &&usernames, - Promise &&promise); - - void on_update_online_status_privacy(); - - void on_update_phone_number_privacy(); - - void invalidate_user_full(UserId user_id); - - void add_contact(Contact contact, bool share_phone_number, Promise &&promise); - - std::pair, vector> import_contacts(const vector &contacts, int64 &random_id, - Promise &&promise); - - std::pair> search_contacts(const string &query, int32 limit, Promise &&promise); - - void remove_contacts(const vector &user_ids, Promise &&promise); - - void remove_contacts_by_phone_number(vector user_phone_numbers, vector user_ids, - Promise &&promise); - - int32 get_imported_contact_count(Promise &&promise); - - std::pair, vector> change_imported_contacts(vector &contacts, int64 &random_id, - Promise &&promise); - - void clear_imported_contacts(Promise &&promise); - - void on_update_contacts_reset(); - - vector get_close_friends(Promise &&promise); - - void set_close_friends(vector user_ids, Promise &&promise); - - void on_set_close_friends(const vector &user_ids, Promise &&promise); - - UserId search_user_by_phone_number(string phone_number, Promise &&promise); - - void on_resolved_phone_number(const string &phone_number, UserId user_id); - - void share_phone_number(UserId user_id, Promise &&promise); - - void search_dialogs_nearby(const Location &location, Promise> &&promise); - - void set_location(const Location &location, Promise &&promise); - - static void set_location_visibility(Td *td); - - void get_is_location_visible(Promise &&promise); - - void register_suggested_profile_photo(const Photo &photo); - - FileId get_profile_photo_file_id(int64 photo_id) const; - - void set_bot_profile_photo(UserId bot_user_id, const td_api::object_ptr &input_photo, - Promise &&promise); - - void set_profile_photo(const td_api::object_ptr &input_photo, bool is_fallback, - Promise &&promise); - - void set_user_profile_photo(UserId user_id, const td_api::object_ptr &input_photo, - bool only_suggest, Promise &&promise); - - void send_update_profile_photo_query(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, - Promise &&promise); - - void delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise); - - void set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, - Promise &&promise); - - void set_profile_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, - Promise &&promise); - - void set_name(const string &first_name, const string &last_name, Promise &&promise); - - void set_bio(const string &bio, Promise &&promise); - - void set_username(const string &username, Promise &&promise); - - void toggle_username_is_active(string &&username, bool is_active, Promise &&promise); - - void reorder_usernames(vector &&usernames, Promise &&promise); - - void toggle_bot_username_is_active(UserId bot_user_id, string &&username, bool is_active, Promise &&promise); - - void reorder_bot_usernames(UserId bot_user_id, vector &&usernames, Promise &&promise); - - void set_emoji_status(const EmojiStatus &emoji_status, Promise &&promise); - - void set_chat_description(ChatId chat_id, const string &description, Promise &&promise); - - void set_channel_username(ChannelId channel_id, const string &username, Promise &&promise); - - void toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active, - Promise &&promise); - - void disable_all_channel_usernames(ChannelId channel_id, Promise &&promise); - - void reorder_channel_usernames(ChannelId channel_id, vector &&usernames, Promise &&promise); - - void set_channel_accent_color(ChannelId channel_id, AccentColorId accent_color_id, - CustomEmojiId background_custom_emoji_id, Promise &&promise); - - void set_channel_profile_accent_color(ChannelId channel_id, AccentColorId profile_accent_color_id, - CustomEmojiId profile_background_custom_emoji_id, Promise &&promise); - - void set_channel_emoji_status(ChannelId channel_id, const EmojiStatus &emoji_status, Promise &&promise); - - void set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise); - - void set_channel_emoji_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise &&promise); - - 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_join_to_send(ChannelId channel_id, bool joint_to_send, Promise &&promise); - - void toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise &&promise); - - void toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available, - Promise &&promise); - - void toggle_channel_has_hidden_participants(ChannelId channel_id, bool has_hidden_participants, - Promise &&promise); - - void toggle_channel_has_aggressive_anti_spam_enabled(ChannelId channel_id, bool has_aggressive_anti_spam_enabled, - Promise &&promise); - - void toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise &&promise); - - void convert_channel_to_gigagroup(ChannelId channel_id, Promise &&promise); - - void set_channel_description(ChannelId channel_id, const string &description, Promise &&promise); - - void set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, Promise &&promise); - - void set_channel_location(ChannelId dialog_id, const DialogLocation &location, Promise &&promise); - - void set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise &&promise); - - void report_channel_spam(ChannelId channel_id, const vector &message_ids, Promise &&promise); - - void report_channel_anti_spam_false_positive(ChannelId channel_id, MessageId message_id, Promise &&promise); - - void delete_chat(ChatId chat_id, Promise &&promise); - - void delete_channel(ChannelId channel_id, Promise &&promise); - - void get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise &&promise); - - bool can_get_channel_message_statistics(DialogId dialog_id) const; - - bool can_get_channel_story_statistics(DialogId dialog_id) const; - - void migrate_dialog_to_megagroup(DialogId dialog_id, Promise> &&promise); - - void get_channel_recommendations(DialogId dialog_id, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise); - - void get_created_public_dialogs(PublicDialogType type, Promise> &&promise, - bool from_binlog); - - void open_channel_recommended_channel(DialogId dialog_id, DialogId opened_dialog_id, Promise &&promise); - - void check_created_public_dialogs_limit(PublicDialogType type, Promise &&promise); - - void reload_created_public_dialogs(PublicDialogType type, Promise> &&promise); - - vector get_dialogs_for_discussion(Promise &&promise); - - vector get_inactive_channels(Promise &&promise); - - void dismiss_dialog_suggested_action(SuggestedAction action, Promise &&promise); - - bool is_user_contact(UserId user_id, bool is_mutual = false) const; - - bool is_user_premium(UserId user_id) const; - - bool is_user_deleted(UserId user_id) const; - - bool is_user_support(UserId user_id) const; - - bool is_user_bot(UserId user_id) const; - - struct BotData { - string username; - bool can_be_edited; - bool can_join_groups; - bool can_read_all_group_messages; - bool is_inline; - bool need_location; - bool can_be_added_to_attach_menu; - }; - Result get_bot_data(UserId user_id) const TD_WARN_UNUSED_RESULT; - - bool is_user_online(UserId user_id, int32 tolerance = 0, int32 unix_time = 0) const; - - int32 get_user_was_online(UserId user_id, int32 unix_time = 0) const; - - bool is_user_status_exact(UserId user_id) const; - - bool can_report_user(UserId user_id) const; - - bool have_user(UserId user_id) const; - bool have_min_user(UserId user_id) const; - bool have_user_force(UserId user_id, const char *source); - - static void send_get_me_query(Td *td, Promise &&promise); - UserId get_me(Promise &&promise); - bool get_user(UserId user_id, int left_tries, Promise &&promise); - void reload_user(UserId user_id, Promise &&promise, const char *source); - void load_user_full(UserId user_id, bool force, Promise &&promise, const char *source); - FileSourceId get_user_full_file_source_id(UserId user_id); - void reload_user_full(UserId user_id, Promise &&promise, const char *source); - - void get_user_profile_photos(UserId user_id, int32 offset, int32 limit, - Promise> &&promise); - void reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise); - FileSourceId get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id); - - void create_new_chat(const vector &user_ids, const string &title, MessageTtl message_ttl, - Promise> &&promise); - - bool have_chat(ChatId chat_id) const; - bool have_chat_force(ChatId chat_id, const char *source); - bool get_chat(ChatId chat_id, int left_tries, Promise &&promise); - void reload_chat(ChatId chat_id, Promise &&promise, const char *source); - void load_chat_full(ChatId chat_id, bool force, Promise &&promise, const char *source); - FileSourceId get_chat_full_file_source_id(ChatId chat_id); - void reload_chat_full(ChatId chat_id, Promise &&promise, const char *source); - - int32 get_chat_date(ChatId chat_id) const; - int32 get_chat_participant_count(ChatId chat_id) const; - bool get_chat_is_active(ChatId chat_id) const; - ChannelId get_chat_migrated_to_channel_id(ChatId chat_id) const; - DialogParticipantStatus get_chat_status(ChatId chat_id) const; - DialogParticipantStatus get_chat_permissions(ChatId chat_id) const; - bool is_appointed_chat_administrator(ChatId chat_id) const; - const DialogParticipant *get_chat_participant(ChatId chat_id, UserId user_id) const; - const vector *get_chat_participants(ChatId chat_id) const; - - void create_new_channel(const string &title, bool is_forum, bool is_megagroup, const string &description, - const DialogLocation &location, bool for_import, MessageTtl message_ttl, - Promise> &&promise); - - bool have_min_channel(ChannelId channel_id) const; - const MinChannel *get_min_channel(ChannelId channel_id) const; - void add_min_channel(ChannelId channel_id, const MinChannel &min_channel); - - bool have_channel(ChannelId channel_id) const; - bool have_channel_force(ChannelId channel_id, const char *source); - bool get_channel(ChannelId channel_id, int left_tries, Promise &&promise); - void reload_channel(ChannelId channel_id, Promise &&promise, const char *source); - void load_channel_full(ChannelId channel_id, bool force, Promise &&promise, const char *source); - FileSourceId get_channel_full_file_source_id(ChannelId channel_id); - void reload_channel_full(ChannelId channel_id, Promise &&promise, const char *source); - - bool is_channel_public(ChannelId channel_id) const; - - void create_new_secret_chat(UserId user_id, Promise> &&promise); - - bool have_secret_chat(SecretChatId secret_chat_id) const; - bool have_secret_chat_force(SecretChatId secret_chat_id, const char *source); - bool get_secret_chat(SecretChatId secret_chat_id, bool force, Promise &&promise); - bool get_secret_chat_full(SecretChatId secret_chat_id, Promise &&promise); - - ChannelType get_channel_type(ChannelId channel_id) const; - bool is_broadcast_channel(ChannelId channel_id) const; - bool is_megagroup_channel(ChannelId channel_id) const; - bool is_forum_channel(ChannelId channel_id) const; - int32 get_channel_date(ChannelId channel_id) const; - DialogParticipantStatus get_channel_status(ChannelId channel_id) const; - DialogParticipantStatus get_channel_permissions(ChannelId channel_id) const; - bool get_channel_is_verified(ChannelId channel_id) const; - bool get_channel_is_scam(ChannelId channel_id) const; - 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_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; - ChannelId get_channel_linked_channel_id(ChannelId channel_id, const char *source); - int32 get_channel_slow_mode_delay(ChannelId channel_id, const char *source); - bool get_channel_effective_has_hidden_participants(ChannelId channel_id, const char *source); - int32 get_channel_my_boost_count(ChannelId channel_id); - - void get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise); - - void speculative_add_channel_user(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status, - const DialogParticipantStatus &old_status); - - int64 get_user_id_object(UserId user_id, const char *source) const; - - tl_object_ptr get_user_object(UserId user_id) const; - - vector get_user_ids_object(const vector &user_ids, const char *source) const; - - tl_object_ptr get_users_object(int32 total_count, const vector &user_ids) const; - - tl_object_ptr get_user_full_info_object(UserId user_id) const; - - int64 get_basic_group_id_object(ChatId chat_id, const char *source) const; - - tl_object_ptr get_basic_group_object(ChatId chat_id); - - tl_object_ptr get_basic_group_full_info_object(ChatId chat_id) const; - - int64 get_supergroup_id_object(ChannelId channel_id, const char *source) const; - - tl_object_ptr get_supergroup_object(ChannelId channel_id) const; - - tl_object_ptr get_supergroup_full_info_object(ChannelId channel_id) const; - - int32 get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const; - - tl_object_ptr get_secret_chat_object(SecretChatId secret_chat_id); - - void on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, SecretChatState state, - bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer, - FolderId initial_folder_id); - - tl_object_ptr get_chat_member_object(const DialogParticipant &dialog_participant, - const char *source) const; - - void get_support_user(Promise> &&promise); - - void repair_chat_participants(ChatId chat_id); - - void get_current_state(vector> &updates) const; - - private: - struct User { - string first_name; - string last_name; - Usernames usernames; - string phone_number; - int64 access_hash = -1; - EmojiStatus emoji_status; - EmojiStatus last_sent_emoji_status; - - ProfilePhoto photo; - - vector restriction_reasons; - string inline_query_placeholder; - int32 bot_info_version = -1; - - AccentColorId accent_color_id; - CustomEmojiId background_custom_emoji_id; - AccentColorId profile_accent_color_id; - CustomEmojiId profile_background_custom_emoji_id; - - int32 was_online = 0; - int32 local_was_online = 0; - - double max_active_story_id_next_reload_time = 0.0; - StoryId max_active_story_id; - StoryId max_read_story_id; - - string language_code; - - FlatHashSet photo_ids; - - static constexpr uint32 CACHE_VERSION = 4; - uint32 cache_version = 0; - - bool is_min_access_hash = true; - bool is_received = false; - bool is_verified = false; - bool is_premium = false; - bool is_support = false; - bool is_deleted = true; - bool is_bot = true; - bool can_join_groups = true; - bool can_read_all_group_messages = true; - bool can_be_edited_bot = false; - bool is_inline_bot = false; - bool need_location_bot = false; - bool is_scam = false; - bool is_fake = false; - bool is_contact = false; - bool is_mutual_contact = false; - bool is_close_friend = false; - bool need_apply_min_photo = false; - bool can_be_added_to_attach_menu = false; - bool attach_menu_enabled = false; - bool stories_hidden = false; - bool contact_require_premium = false; - - bool is_photo_inited = false; - - bool is_repaired = false; // whether cached value is rechecked - - bool is_name_changed = true; - bool is_username_changed = true; - bool is_photo_changed = true; - bool is_accent_color_changed = true; - bool is_phone_number_changed = true; - bool is_emoji_status_changed = true; - bool is_is_contact_changed = true; - bool is_is_mutual_contact_changed = true; - bool is_is_deleted_changed = true; - bool is_is_premium_changed = true; - bool is_stories_hidden_changed = true; - bool is_full_info_changed = false; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_status_changed = true; - bool is_online_status_changed = true; // whether online/offline has changed - bool is_update_user_sent = false; - - bool is_saved = false; // is current user version being saved/is saved to the database - bool is_being_saved = false; // is current user being saved to the database - bool is_status_saved = false; // is current user status being saved/is saved to the database - - bool is_received_from_server = false; // true, if the user was received from the server and not the database - - uint64 log_event_id = 0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - // do not forget to update drop_user_full and on_get_user_full - struct UserFull { - Photo photo; - Photo fallback_photo; - Photo personal_photo; - - string about; - string private_forward_name; - string description; - Photo description_photo; - FileId description_animation_file_id; - vector registered_file_ids; - FileSourceId file_source_id; - - vector premium_gift_options; - - unique_ptr menu_button; - vector commands; - AdministratorRights group_administrator_rights; - AdministratorRights broadcast_administrator_rights; - - int32 common_chat_count = 0; - - unique_ptr business_info; - - bool is_blocked = false; - bool is_blocked_for_stories = false; - bool can_be_called = false; - bool supports_video_calls = false; - bool has_private_calls = false; - bool can_pin_messages = true; - bool need_phone_number_privacy_exception = false; - bool wallpaper_overridden = false; - bool voice_messages_forbidden = false; - bool has_pinned_stories = false; - bool read_dates_private = false; - bool contact_require_premium = false; - - bool is_common_chat_count_changed = true; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_send_update = true; // have new changes that need only to be sent to the client - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_update_user_full_sent = false; - - double expires_at = 0.0; - - bool is_expired() const { - return expires_at < Time::now(); - } - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct Chat { - string title; - DialogPhoto photo; - int32 participant_count = 0; - int32 date = 0; - int32 version = -1; - int32 default_permissions_version = -1; - int32 pinned_message_version = -1; - ChannelId migrated_to_channel_id; - - DialogParticipantStatus status = DialogParticipantStatus::Banned(0); - RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, ChannelType::Unknown}; - - static constexpr uint32 CACHE_VERSION = 4; - uint32 cache_version = 0; - - bool is_active = false; - bool noforwards = false; - - bool is_title_changed = true; - bool is_photo_changed = true; - bool is_default_permissions_changed = true; - bool is_status_changed = true; - bool is_is_active_changed = true; - bool is_noforwards_changed = true; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_update_basic_group_sent = false; - - bool is_repaired = false; // whether cached value is rechecked - - bool is_saved = false; // is current chat version being saved/is saved to the database - bool is_being_saved = false; // is current chat being saved to the database - - bool is_received_from_server = false; // true, if the chat was received from the server and not the database - - uint64 log_event_id = 0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - // do not forget to update drop_chat_full and on_get_chat_full - struct ChatFull { - int32 version = -1; - UserId creator_user_id; - vector participants; - - Photo photo; - vector registered_photo_file_ids; - FileSourceId file_source_id; - - string description; - - DialogInviteLink invite_link; - - vector bot_commands; - - bool can_set_username = false; - - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_send_update = true; // have new changes that need only to be sent to the client - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_update_chat_full_sent = false; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct Channel { - int64 access_hash = 0; - string title; - DialogPhoto photo; - EmojiStatus emoji_status; - EmojiStatus last_sent_emoji_status; - AccentColorId accent_color_id; - CustomEmojiId background_custom_emoji_id; - AccentColorId profile_accent_color_id; - CustomEmojiId profile_background_custom_emoji_id; - Usernames usernames; - vector restriction_reasons; - DialogParticipantStatus status = DialogParticipantStatus::Banned(0); - RestrictedRights default_permissions{false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, ChannelType::Unknown}; - int32 date = 0; - int32 participant_count = 0; - int32 boost_level = 0; - - double max_active_story_id_next_reload_time = 0.0; - StoryId max_active_story_id; - StoryId max_read_story_id; - - static constexpr uint32 CACHE_VERSION = 10; - uint32 cache_version = 0; - - bool has_linked_channel = false; - bool has_location = false; - bool sign_messages = false; - bool is_slow_mode_enabled = false; - bool noforwards = false; - bool can_be_deleted = false; - bool join_to_send = false; - bool join_request = false; - bool stories_hidden = false; - - bool is_megagroup = false; - bool is_gigagroup = false; - bool is_forum = false; - bool is_verified = false; - bool is_scam = false; - bool is_fake = false; - - bool is_title_changed = true; - bool is_username_changed = true; - bool is_photo_changed = true; - bool is_emoji_status_changed = true; - bool is_accent_color_changed = true; - bool is_default_permissions_changed = true; - bool is_status_changed = true; - bool is_stories_hidden_changed = true; - bool is_has_location_changed = true; - bool is_noforwards_changed = true; - bool is_creator_changed = true; - bool had_read_access = true; - bool was_member = false; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_update_supergroup_sent = false; - - bool is_repaired = false; // whether cached value is rechecked - - bool is_saved = false; // is current channel version being saved/is saved to the database - bool is_being_saved = false; // is current channel being saved to the database - - bool is_received_from_server = false; // true, if the channel was received from the server and not the database - - uint64 log_event_id = 0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - // do not forget to update invalidate_channel_full and on_get_chat_full - struct ChannelFull { - Photo photo; - vector registered_photo_file_ids; - FileSourceId file_source_id; - - string description; - int32 participant_count = 0; - int32 administrator_count = 0; - int32 restricted_count = 0; - int32 banned_count = 0; - int32 boost_count = 0; - int32 unrestrict_boost_count = 0; - - DialogInviteLink invite_link; - - vector bot_commands; - - uint32 speculative_version = 1; - uint32 repair_request_version = 0; - - StickerSetId sticker_set_id; - StickerSetId emoji_sticker_set_id; - - ChannelId linked_channel_id; - - DialogLocation location; - - DcId stats_dc_id; - - int32 slow_mode_delay = 0; - int32 slow_mode_next_send_date = 0; - - MessageId migrated_from_max_message_id; - ChatId migrated_from_chat_id; - - vector bot_user_ids; - - bool can_get_participants = false; - bool has_hidden_participants = false; - bool can_set_username = false; - bool can_set_sticker_set = false; - bool can_set_location = false; - bool can_view_statistics = false; - bool is_can_view_statistics_inited = false; - bool is_all_history_available = true; - bool has_aggressive_anti_spam_enabled = false; - bool can_be_deleted = false; - bool has_pinned_stories = false; - - bool is_slow_mode_next_send_date_changed = true; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_send_update = true; // have new changes that need only to be sent to the client - bool need_save_to_database = true; // have new changes that need only to be saved to the database - bool is_update_channel_full_sent = false; - - double expires_at = 0.0; - - bool is_expired() const { - return expires_at < Time::now(); - } - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct SecretChat { - int64 access_hash = 0; - UserId user_id; - SecretChatState state = SecretChatState::Unknown; - string key_hash; - int32 ttl = 0; - int32 date = 0; - int32 layer = 0; - FolderId initial_folder_id; - - bool is_outbound = false; - - bool is_ttl_changed = true; - bool is_state_changed = true; - bool is_being_updated = false; - bool is_changed = true; // have new changes that need to be sent to the client and database - bool need_save_to_database = true; // have new changes that need only to be saved to the database - - bool is_saved = false; // is current secret chat version being saved/is saved to the database - bool is_being_saved = false; // is current secret chat being saved to the database - - uint64 log_event_id = 0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - struct PendingGetPhotoRequest { - int32 offset = 0; - int32 limit = 0; - int32 retry_count = 0; - Promise> promise; - }; - - struct UserPhotos { - vector photos; - int32 count = -1; - int32 offset = -1; - - vector pending_requests; - }; - - struct DialogNearby { - DialogId dialog_id; - int32 distance; - - DialogNearby(DialogId dialog_id, int32 distance) : dialog_id(dialog_id), distance(distance) { - } - - bool operator<(const DialogNearby &other) const { - return distance < other.distance || (distance == other.distance && dialog_id.get() < other.dialog_id.get()); - } - - bool operator==(const DialogNearby &other) const { - return distance == other.distance && dialog_id == other.dialog_id; - } - - bool operator!=(const DialogNearby &other) const { - return !(*this == other); - } - }; - - struct RecommendedDialogs { - int32 total_count_ = 0; - vector dialog_ids_; - double next_reload_time_ = 0.0; - - template - void store(StorerT &storer) const; - - template - void parse(ParserT &parser); - }; - - class UserLogEvent; - class ChatLogEvent; - class ChannelLogEvent; - class SecretChatLogEvent; - - static constexpr int32 MAX_GET_PROFILE_PHOTOS = 100; // server side limit - static constexpr size_t MAX_NAME_LENGTH = 64; // server side limit for first/last name - static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title - static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description - - static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit - static constexpr int32 CHANNEL_RECOMMENDATIONS_CACHE_TIME = 86400; // some reasonable limit - - // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used - static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0; - static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1; - static constexpr int32 USER_FLAG_HAS_LAST_NAME = 1 << 2; - static constexpr int32 USER_FLAG_HAS_USERNAME = 1 << 3; - static constexpr int32 USER_FLAG_HAS_PHONE_NUMBER = 1 << 4; - static constexpr int32 USER_FLAG_HAS_PHOTO = 1 << 5; - static constexpr int32 USER_FLAG_HAS_STATUS = 1 << 6; - static constexpr int32 USER_FLAG_HAS_BOT_INFO_VERSION = 1 << 14; - static constexpr int32 USER_FLAG_IS_ME = 1 << 10; - static constexpr int32 USER_FLAG_IS_CONTACT = 1 << 11; - static constexpr int32 USER_FLAG_IS_MUTUAL_CONTACT = 1 << 12; - static constexpr int32 USER_FLAG_IS_DELETED = 1 << 13; - static constexpr int32 USER_FLAG_IS_BOT = 1 << 14; - static constexpr int32 USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED = 1 << 15; - static constexpr int32 USER_FLAG_IS_PRIVATE_BOT = 1 << 16; - static constexpr int32 USER_FLAG_IS_VERIFIED = 1 << 17; - static constexpr int32 USER_FLAG_IS_RESTRICTED = 1 << 18; - static constexpr int32 USER_FLAG_IS_INLINE_BOT = 1 << 19; - static constexpr int32 USER_FLAG_IS_INACCESSIBLE = 1 << 20; - static constexpr int32 USER_FLAG_NEED_LOCATION_BOT = 1 << 21; - static constexpr int32 USER_FLAG_HAS_LANGUAGE_CODE = 1 << 22; - static constexpr int32 USER_FLAG_IS_SUPPORT = 1 << 23; - static constexpr int32 USER_FLAG_IS_SCAM = 1 << 24; - static constexpr int32 USER_FLAG_NEED_APPLY_MIN_PHOTO = 1 << 25; - static constexpr int32 USER_FLAG_IS_FAKE = 1 << 26; - static constexpr int32 USER_FLAG_IS_ATTACH_MENU_BOT = 1 << 27; - static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28; - static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29; - static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30; - static constexpr int32 USER_FLAG_HAS_USERNAMES = 1 << 0; - static constexpr int32 USER_FLAG_CAN_BE_EDITED_BOT = 1 << 1; - static constexpr int32 USER_FLAG_IS_CLOSE_FRIEND = 1 << 2; - - static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0; - static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1; - static constexpr int32 USER_FULL_FLAG_HAS_PHOTO = 1 << 2; - static constexpr int32 USER_FULL_FLAG_HAS_BOT_INFO = 1 << 3; - static constexpr int32 USER_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 6; - static constexpr int32 USER_FULL_FLAG_CAN_PIN_MESSAGE = 1 << 7; - static constexpr int32 USER_FULL_FLAG_HAS_FOLDER_ID = 1 << 11; - static constexpr int32 USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 12; - static constexpr int32 USER_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14; - static constexpr int32 USER_FULL_FLAG_HAS_PRIVATE_FORWARD_NAME = 1 << 16; - static constexpr int32 USER_FULL_FLAG_HAS_GROUP_ADMINISTRATOR_RIGHTS = 1 << 17; - static constexpr int32 USER_FULL_FLAG_HAS_BROADCAST_ADMINISTRATOR_RIGHTS = 1 << 18; - static constexpr int32 USER_FULL_FLAG_HAS_VOICE_MESSAGES_FORBIDDEN = 1 << 20; - static constexpr int32 USER_FULL_FLAG_HAS_PERSONAL_PHOTO = 1 << 21; - static constexpr int32 USER_FULL_FLAG_HAS_FALLBACK_PHOTO = 1 << 22; - - static constexpr int32 CHAT_FLAG_USER_IS_CREATOR = 1 << 0; - static constexpr int32 CHAT_FLAG_USER_HAS_LEFT = 1 << 2; - // static constexpr int32 CHAT_FLAG_ADMINISTRATORS_ENABLED = 1 << 3; - // static constexpr int32 CHAT_FLAG_IS_ADMINISTRATOR = 1 << 4; - static constexpr int32 CHAT_FLAG_IS_DEACTIVATED = 1 << 5; - static constexpr int32 CHAT_FLAG_WAS_MIGRATED = 1 << 6; - static constexpr int32 CHAT_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23; - static constexpr int32 CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24; - static constexpr int32 CHAT_FLAG_NOFORWARDS = 1 << 25; - - static constexpr int32 CHAT_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 6; - static constexpr int32 CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 8; - static constexpr int32 CHAT_FULL_FLAG_HAS_FOLDER_ID = 1 << 11; - static constexpr int32 CHAT_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 12; - static constexpr int32 CHAT_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14; - static constexpr int32 CHAT_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 17; - static constexpr int32 CHAT_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 18; - - static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0; - static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2; - static constexpr int32 CHANNEL_FLAG_IS_BROADCAST = 1 << 5; - static constexpr int32 CHANNEL_FLAG_HAS_USERNAME = 1 << 6; - static constexpr int32 CHANNEL_FLAG_IS_VERIFIED = 1 << 7; - static constexpr int32 CHANNEL_FLAG_IS_MEGAGROUP = 1 << 8; - static constexpr int32 CHANNEL_FLAG_IS_RESTRICTED = 1 << 9; - // static constexpr int32 CHANNEL_FLAG_ANYONE_CAN_INVITE = 1 << 10; - static constexpr int32 CHANNEL_FLAG_SIGN_MESSAGES = 1 << 11; - static constexpr int32 CHANNEL_FLAG_IS_MIN = 1 << 12; - static constexpr int32 CHANNEL_FLAG_HAS_ACCESS_HASH = 1 << 13; - static constexpr int32 CHANNEL_FLAG_HAS_ADMIN_RIGHTS = 1 << 14; - static constexpr int32 CHANNEL_FLAG_HAS_BANNED_RIGHTS = 1 << 15; - static constexpr int32 CHANNEL_FLAG_HAS_UNBAN_DATE = 1 << 16; - static constexpr int32 CHANNEL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 17; - static constexpr int32 CHANNEL_FLAG_IS_SCAM = 1 << 19; - static constexpr int32 CHANNEL_FLAG_HAS_LINKED_CHAT = 1 << 20; - static constexpr int32 CHANNEL_FLAG_HAS_LOCATION = 1 << 21; - static constexpr int32 CHANNEL_FLAG_IS_SLOW_MODE_ENABLED = 1 << 22; - static constexpr int32 CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23; - static constexpr int32 CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24; - static constexpr int32 CHANNEL_FLAG_IS_FAKE = 1 << 25; - static constexpr int32 CHANNEL_FLAG_IS_GIGAGROUP = 1 << 26; - static constexpr int32 CHANNEL_FLAG_NOFORWARDS = 1 << 27; - static constexpr int32 CHANNEL_FLAG_JOIN_TO_SEND = 1 << 28; - static constexpr int32 CHANNEL_FLAG_JOIN_REQUEST = 1 << 29; - static constexpr int32 CHANNEL_FLAG_IS_FORUM = 1 << 30; - static constexpr int32 CHANNEL_FLAG_HAS_USERNAMES = 1 << 0; - - static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_BANNED_COUNT = 1 << 2; - static constexpr int32 CHANNEL_FULL_FLAG_CAN_GET_PARTICIPANTS = 1 << 3; - static constexpr int32 CHANNEL_FULL_FLAG_MIGRATED_FROM = 1 << 4; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 5; - static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_USERNAME = 1 << 6; - static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_STICKER_SET = 1 << 7; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_STICKER_SET = 1 << 8; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID = 1 << 9; - static constexpr int32 CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN = 1 << 10; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_FOLDER_ID = 1 << 11; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID = 1 << 12; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_ONLINE_MEMBER_COUNT = 1 << 13; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID = 1 << 14; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_LOCATION = 1 << 15; - static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_LOCATION = 1 << 16; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_SLOW_MODE_DELAY = 1 << 17; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_SLOW_MODE_NEXT_SEND_DATE = 1 << 18; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 19; - static constexpr int32 CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS = 1 << 20; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 21; - static constexpr int32 CHANNEL_FULL_FLAG_IS_BLOCKED = 1 << 22; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_EXPORTED_INVITE = 1 << 23; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 24; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 28; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_DEFAULT_SEND_AS = 1 << 29; - static constexpr int32 CHANNEL_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 30; - static constexpr int32 CHANNEL_FULL_FLAG2_HAS_ANTISPAM = 1 << 1; - static constexpr int32 CHANNEL_FULL_FLAG2_ARE_PARTICIPANTS_HIDDEN = 1 << 2; - - static constexpr int32 USER_FULL_EXPIRE_TIME = 60; - static constexpr int32 CHANNEL_FULL_EXPIRE_TIME = 60; - - static constexpr int32 ACCOUNT_UPDATE_FIRST_NAME = 1 << 0; - static constexpr int32 ACCOUNT_UPDATE_LAST_NAME = 1 << 1; - static constexpr int32 ACCOUNT_UPDATE_ABOUT = 1 << 2; - - bool have_input_peer_user(const User *u, UserId user_id, AccessRights access_rights) const; - static bool have_input_peer_chat(const Chat *c, AccessRights access_rights); - bool have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights, - bool from_linked = false) const; - static bool have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights); - - tl_object_ptr get_simple_input_peer(DialogId dialog_id) const; - - const User *get_user(UserId user_id) const; - User *get_user(UserId user_id); - User *get_user_force(UserId user_id, const char *source); - User *get_user_force_impl(UserId user_id, const char *source); - - User *add_user(UserId user_id); - - const UserFull *get_user_full(UserId user_id) const; - UserFull *get_user_full(UserId user_id); - UserFull *get_user_full_force(UserId user_id, const char *source); - - UserFull *add_user_full(UserId user_id); - - void send_get_user_full_query(UserId user_id, tl_object_ptr &&input_user, - Promise &&promise, const char *source); - - const Chat *get_chat(ChatId chat_id) const; - Chat *get_chat(ChatId chat_id); - Chat *get_chat_force(ChatId chat_id, const char *source); - - Chat *add_chat(ChatId chat_id); - - const ChatFull *get_chat_full(ChatId chat_id) const; - ChatFull *get_chat_full(ChatId chat_id); - ChatFull *get_chat_full_force(ChatId chat_id, const char *source); - - ChatFull *add_chat_full(ChatId chat_id); - - void send_get_chat_full_query(ChatId chat_id, Promise &&promise, const char *source); - - void on_migrate_chat_to_megagroup(ChatId chat_id, Promise> &&promise); - - const Channel *get_channel(ChannelId channel_id) const; - Channel *get_channel(ChannelId channel_id); - Channel *get_channel_force(ChannelId channel_id, const char *source); - - Channel *add_channel(ChannelId channel_id, const char *source); - - const ChannelFull *get_channel_full(ChannelId channel_id) const; - const ChannelFull *get_channel_full_const(ChannelId channel_id) const; - ChannelFull *get_channel_full(ChannelId channel_id, bool only_local, const char *source); - ChannelFull *get_channel_full_force(ChannelId channel_id, bool only_local, const char *source); - - ChannelFull *add_channel_full(ChannelId channel_id); - - void send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, Promise &&promise, - const char *source); - - void on_create_new_secret_chat(SecretChatId secret_chat_id, Promise> &&promise); - - const SecretChat *get_secret_chat(SecretChatId secret_chat_id) const; - SecretChat *get_secret_chat(SecretChatId secret_chat_id); - SecretChat *get_secret_chat_force(SecretChatId secret_chat_id, const char *source); - - SecretChat *add_secret_chat(SecretChatId secret_chat_id); - - static string get_user_search_text(const User *u); - - static DialogParticipantStatus get_chat_status(const Chat *c); - DialogParticipantStatus get_chat_permissions(const Chat *c) const; - - static ChannelType get_channel_type(const Channel *c); - 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_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); - static bool get_channel_join_request(const Channel *c); - - void set_my_id(UserId my_id); - - void on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise); - - void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name); - void on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames); - void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number); - void on_update_user_photo(User *u, UserId user_id, tl_object_ptr &&photo, - const char *source); - void on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id); - void on_update_user_background_custom_emoji_id(User *u, UserId user_id, CustomEmojiId background_custom_emoji_id); - void on_update_user_profile_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id); - void on_update_user_profile_background_custom_emoji_id(User *u, UserId user_id, - CustomEmojiId background_custom_emoji_id); - void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status); - void on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); - void on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id); - void on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden); - void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, - bool is_close_friend); - void on_update_user_online(User *u, UserId user_id, tl_object_ptr &&status); - void on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online); - - void do_update_user_photo(User *u, UserId user_id, tl_object_ptr &&photo, - const char *source); - void do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, bool invalidate_photo_cache, - const char *source); - void apply_pending_user_photo(User *u, UserId user_id); - - void set_profile_photo_impl(UserId user_id, const td_api::object_ptr &input_photo, - bool is_fallback, bool only_suggest, Promise &&promise); - - void upload_profile_photo(UserId user_id, FileId file_id, bool is_fallback, bool only_suggest, bool is_animation, - double main_frame_timestamp, Promise &&promise, int reupload_count = 0, - vector bad_parts = {}); - - void on_upload_profile_photo(FileId file_id, tl_object_ptr input_file); - void on_upload_profile_photo_error(FileId file_id, Status status); - - void register_user_photo(User *u, UserId user_id, const Photo &photo); - - static void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, - bool is_blocked_for_stories); - static void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count); - static void on_update_user_full_location(UserFull *user_full, UserId user_id, DialogLocation &&location); - static void on_update_user_full_work_hours(UserFull *user_full, UserId user_id, BusinessWorkHours &&work_hours); - void on_update_user_full_away_message(UserFull *user_full, UserId user_id, BusinessAwayMessage &&away_message) const; - void on_update_user_full_greeting_message(UserFull *user_full, UserId user_id, - BusinessGreetingMessage &&greeting_message) const; - static void on_update_user_full_commands(UserFull *user_full, UserId user_id, - vector> &&bot_commands); - static void on_update_user_full_menu_button(UserFull *user_full, UserId user_id, - tl_object_ptr &&bot_menu_button); - void on_update_user_full_need_phone_number_privacy_exception(UserFull *user_full, UserId user_id, - bool need_phone_number_privacy_exception) const; - void on_update_user_full_wallpaper_overridden(UserFull *user_full, UserId user_id, bool wallpaper_overridden) const; - - UserPhotos *add_user_photos(UserId user_id); - void send_get_user_photos_query(UserId user_id, const UserPhotos *user_photos); - void on_get_user_profile_photos(UserId user_id, Result &&result); - int64 get_user_full_profile_photo_id(const UserFull *user_full); - void add_set_profile_photo_to_cache(UserId user_id, Photo &&photo, bool is_fallback); - bool delete_my_profile_photo_from_cache(int64 profile_photo_id); - void drop_user_full_photos(UserFull *user_full, UserId user_id, int64 expected_photo_id, const char *source); - void drop_user_photos(UserId user_id, bool is_empty, const char *source); - void drop_user_full(UserId user_id); - - void on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status); - static void on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions, - int32 version); - void on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version, - const string &debug_str); - void on_update_chat_photo(Chat *c, ChatId chat_id, tl_object_ptr &&chat_photo_ptr); - void on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache); - static void on_update_chat_title(Chat *c, ChatId chat_id, string &&title); - static void on_update_chat_active(Chat *c, ChatId chat_id, bool is_active); - static void on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id); - static void on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards); - - void on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo); - bool on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version); - void on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector participants, - int32 version, bool from_update); - void on_update_chat_full_invite_link(ChatFull *chat_full, - tl_object_ptr &&invite_link); - - void on_update_channel_photo(Channel *c, ChannelId channel_id, - tl_object_ptr &&chat_photo_ptr); - void on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache); - void on_update_channel_emoji_status(Channel *c, ChannelId channel_id, EmojiStatus emoji_status); - void on_update_channel_accent_color_id(Channel *c, ChannelId channel_id, AccentColorId accent_color_id); - void on_update_channel_background_custom_emoji_id(Channel *c, ChannelId channel_id, - CustomEmojiId background_custom_emoji_id); - void on_update_channel_profile_accent_color_id(Channel *c, ChannelId channel_id, - AccentColorId profile_accent_color_id); - void on_update_channel_profile_background_custom_emoji_id(Channel *c, ChannelId channel_id, - CustomEmojiId profile_background_custom_emoji_id); - static void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title); - void on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames); - void on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status); - static void on_update_channel_default_permissions(Channel *c, ChannelId channel_id, - RestrictedRights default_permissions); - static void on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location); - static void on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards); - void on_update_channel_stories_hidden(Channel *c, ChannelId channel_id, bool stories_hidden); - void on_update_channel_story_ids_impl(Channel *c, ChannelId channel_id, StoryId max_active_story_id, - StoryId max_read_story_id); - void on_update_channel_max_read_story_id(Channel *c, ChannelId channel_id, StoryId max_read_story_id); - - void on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo); - void on_update_channel_full_invite_link(ChannelFull *channel_full, - tl_object_ptr &&invite_link); - void on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id, - ChannelId linked_channel_id); - void on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location); - void on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, int32 slow_mode_delay, - int32 slow_mode_next_send_date); - static void on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full, - int32 slow_mode_next_send_date); - static void on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id, - vector &&bot_user_ids); - - void toggle_username_is_active_impl(string &&username, bool is_active, Promise &&promise); - - void reorder_usernames_impl(vector &&usernames, Promise &&promise); - - void on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status, - const DialogParticipantStatus &new_status); - void on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames, - const Usernames &new_usernames); - - void remove_linked_channel_id(ChannelId channel_id); - ChannelId get_linked_channel_id(ChannelId channel_id) const; - - static bool speculative_add_count(int32 &count, int32 delta_count, int32 min_count = 0); - - void speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, bool by_me); - - void drop_chat_full(ChatId chat_id); - - void do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, bool need_drop_slow_mode_delay); - - void update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server); - - void on_get_chat_empty(telegram_api::chatEmpty &chat, const char *source); - void on_get_chat(telegram_api::chat &chat, const char *source); - void on_get_chat_forbidden(telegram_api::chatForbidden &chat, const char *source); - void on_get_channel(telegram_api::channel &channel, const char *source); - void on_get_channel_forbidden(telegram_api::channelForbidden &channel, const char *source); - - void save_user(User *u, UserId user_id, bool from_binlog); - static string get_user_database_key(UserId user_id); - static string get_user_database_value(const User *u); - void save_user_to_database(User *u, UserId user_id); - void save_user_to_database_impl(User *u, UserId user_id, string value); - void on_save_user_to_database(UserId user_id, bool success); - void load_user_from_database(User *u, UserId user_id, Promise promise); - void load_user_from_database_impl(UserId user_id, Promise promise); - void on_load_user_from_database(UserId user_id, string value, bool force); - - void save_chat(Chat *c, ChatId chat_id, bool from_binlog); - static string get_chat_database_key(ChatId chat_id); - static string get_chat_database_value(const Chat *c); - void save_chat_to_database(Chat *c, ChatId chat_id); - void save_chat_to_database_impl(Chat *c, ChatId chat_id, string value); - void on_save_chat_to_database(ChatId chat_id, bool success); - void load_chat_from_database(Chat *c, ChatId chat_id, Promise promise); - void load_chat_from_database_impl(ChatId chat_id, Promise promise); - void on_load_chat_from_database(ChatId chat_id, string value, bool force); - - void save_channel(Channel *c, ChannelId channel_id, bool from_binlog); - static string get_channel_database_key(ChannelId channel_id); - static string get_channel_database_value(const Channel *c); - void save_channel_to_database(Channel *c, ChannelId channel_id); - void save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value); - void on_save_channel_to_database(ChannelId channel_id, bool success); - void load_channel_from_database(Channel *c, ChannelId channel_id, Promise promise); - void load_channel_from_database_impl(ChannelId channel_id, Promise promise); - void on_load_channel_from_database(ChannelId channel_id, string value, bool force); - - void save_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog); - static string get_secret_chat_database_key(SecretChatId secret_chat_id); - static string get_secret_chat_database_value(const SecretChat *c); - void save_secret_chat_to_database(SecretChat *c, SecretChatId secret_chat_id); - void save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value); - void on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success); - void load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, Promise promise); - void load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise promise); - void on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force); - - static void save_user_full(const UserFull *user_full, UserId user_id); - static string get_user_full_database_key(UserId user_id); - static string get_user_full_database_value(const UserFull *user_full); - void on_load_user_full_from_database(UserId user_id, string value); - - static void save_chat_full(const ChatFull *chat_full, ChatId chat_id); - static string get_chat_full_database_key(ChatId chat_id); - static string get_chat_full_database_value(const ChatFull *chat_full); - void on_load_chat_full_from_database(ChatId chat_id, string value); - - static void save_channel_full(const ChannelFull *channel_full, ChannelId channel_id); - static string get_channel_full_database_key(ChannelId channel_id); - static string get_channel_full_database_value(const ChannelFull *channel_full); - void on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source); - - void update_user(User *u, UserId user_id, bool from_binlog = false, bool from_database = false); - void update_chat(Chat *c, ChatId chat_id, bool from_binlog = false, bool from_database = false); - void update_channel(Channel *c, ChannelId channel_id, bool from_binlog = false, bool from_database = false); - void update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog = false, - bool from_database = false); - - void update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database = false); - void update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database = false); - void update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source, - bool from_database = false); - - bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, bool only_participants) const; - - bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const; - - static bool is_user_premium(const User *u); - - static bool is_user_deleted(const User *u); - - static bool is_user_support(const User *u); - - static bool is_user_bot(const User *u); - - int32 get_user_was_online(const User *u, UserId user_id, int32 unix_time) const; - - int64 get_contacts_hash(); - - void update_contacts_hints(const User *u, UserId user_id, bool from_database); - - void save_next_contacts_sync_date(); - - void save_contacts_to_database(); - - void load_contacts(Promise &&promise); - - void on_load_contacts_from_database(string value); - - void on_get_contacts_finished(size_t expected_contact_count); - - void do_import_contacts(vector contacts, int64 random_id, Promise &&promise); - - void on_import_contacts_finished(int64 random_id, vector imported_contact_user_ids, - vector unimported_contact_invites); - - void load_imported_contacts(Promise &&promise); - - void on_load_imported_contacts_from_database(string value); - - void on_load_imported_contacts_finished(); - - void on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, - std::pair, vector> &&to_add, Promise &&promise); - - vector> get_chats_nearby_object( - const vector &dialogs_nearby) const; - - void send_update_users_nearby() const; - - void on_get_dialogs_nearby(Result> result, - Promise> &&promise); - - void try_send_set_location_visibility_query(); - - void on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code); - - void set_location_visibility_expire_date(int32 expire_date); - - void on_get_is_location_visible(Result> &&result, Promise &&promise); - - void update_is_location_visible(); - - bool is_suitable_recommended_channel(DialogId dialog_id) const; - - bool is_suitable_recommended_channel(ChannelId channel_id) const; - - bool are_suitable_recommended_dialogs(const RecommendedDialogs &recommended_dialogs) const; - - static string get_channel_recommendations_database_key(ChannelId channel_id); - - void load_channel_recommendations(ChannelId channel_id, bool use_database, bool return_local, - Promise> &&chats_promise, - Promise> &&count_promise); - - void fail_load_channel_recommendations_queries(ChannelId channel_id, Status &&error); - - void finish_load_channel_recommendations_queries(ChannelId channel_id, int32 total_count, - vector dialog_ids); - - void on_load_channel_recommendations_from_database(ChannelId channel_id, string value); - - void reload_channel_recommendations(ChannelId channel_id); - - void on_get_channel_recommendations(ChannelId channel_id, - Result>>> &&r_chats); - - static bool is_channel_public(const Channel *c); - - static bool is_suitable_created_public_channel(PublicDialogType type, const Channel *c); - - static void return_created_public_dialogs(Promise> &&promise, - const vector &channel_ids); - - void finish_get_created_public_dialogs(PublicDialogType type, Result &&result); - - void update_created_public_channels(Channel *c, ChannelId channel_id); - - void save_created_public_channels(PublicDialogType type); - - void update_created_public_broadcasts(); - - bool update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link); - - vector get_bot_commands(vector> &&bot_infos, - const vector *participants); - - static const DialogParticipant *get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id); - - void finish_get_chat_participant(ChatId chat_id, UserId user_id, Promise &&promise); - - void remove_dialog_suggested_action(SuggestedAction action); - - void on_dismiss_suggested_action(SuggestedAction action, Result &&result); - - bool need_poll_user_active_stories(const User *u, UserId user_id) const; - - static bool get_user_has_unread_stories(const User *u); - - td_api::object_ptr get_update_user_object(UserId user_id, const User *u) const; - - td_api::object_ptr get_update_unknown_user_object(UserId user_id) const; - - td_api::object_ptr get_user_status_object(UserId user_id, const User *u, int32 unix_time) const; - - tl_object_ptr get_user_object(UserId user_id, const User *u) const; - - tl_object_ptr get_user_full_info_object(UserId user_id, const UserFull *user_full) const; - - td_api::object_ptr get_update_basic_group_object(ChatId chat_id, const Chat *c); - - static td_api::object_ptr get_update_unknown_basic_group_object(ChatId chat_id); - - tl_object_ptr get_basic_group_object(ChatId chat_id, const Chat *c); - - tl_object_ptr get_basic_group_object_const(ChatId chat_id, const Chat *c) const; - - tl_object_ptr get_basic_group_full_info_object(ChatId chat_id, - const ChatFull *chat_full) const; - - bool need_poll_channel_active_stories(const Channel *c, ChannelId channel_id) const; - - static bool get_channel_has_unread_stories(const Channel *c); - - td_api::object_ptr get_update_supergroup_object(ChannelId channel_id, - const Channel *c) const; - - td_api::object_ptr get_update_unknown_supergroup_object(ChannelId channel_id) const; - - static tl_object_ptr get_supergroup_object(ChannelId channel_id, const Channel *c); - - Status can_hide_chat_participants(ChatId chat_id) const; - - Status can_hide_channel_participants(ChannelId channel_id, const ChannelFull *channel_full) const; - - Status can_toggle_chat_aggressive_anti_spam(ChatId chat_id) const; - - Status can_toggle_channel_aggressive_anti_spam(ChannelId channel_id, const ChannelFull *channel_full) const; - - tl_object_ptr get_supergroup_full_info_object(ChannelId channel_id, - const ChannelFull *channel_full) const; - - static tl_object_ptr get_secret_chat_state_object(SecretChatState state); - - td_api::object_ptr get_update_secret_chat_object(SecretChatId secret_chat_id, - const SecretChat *secret_chat); - - static td_api::object_ptr get_update_unknown_secret_chat_object( - SecretChatId secret_chat_id); - - tl_object_ptr get_secret_chat_object(SecretChatId secret_chat_id, const SecretChat *secret_chat); - - tl_object_ptr get_secret_chat_object_const(SecretChatId secret_chat_id, - const SecretChat *secret_chat) const; - - vector get_dialog_ids(vector> &&chats, const char *source); - - void on_create_inactive_channels(vector &&channel_ids, Promise &&promise); - - void update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable); - - void get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, Promise &&promise); - - void on_get_support_user(UserId user_id, Promise> &&promise); - - static void on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); - - static void on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); - - static void on_channel_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long); - - static void on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long); - - static void on_user_nearby_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); - - static void on_slow_mode_delay_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long); - - void on_user_online_timeout(UserId user_id); - - void on_user_emoji_status_timeout(UserId user_id); - - void on_channel_emoji_status_timeout(ChannelId channel_id); - - void on_channel_unban_timeout(ChannelId channel_id); - - void on_user_nearby_timeout(UserId user_id); - - void on_slow_mode_delay_timeout(ChannelId channel_id); - - void start_up() final; - - void tear_down() final; - - Td *td_; - ActorShared<> parent_; - UserId my_id_; - UserId support_user_id_; - int32 my_was_online_local_ = 0; - - WaitFreeHashMap, UserIdHash> users_; - WaitFreeHashMap, UserIdHash> users_full_; - WaitFreeHashMap, UserIdHash> user_photos_; - mutable FlatHashSet unknown_users_; - WaitFreeHashMap, UserIdHash> pending_user_photos_; - struct UserIdPhotoIdHash { - uint32 operator()(const std::pair &pair) const { - return combine_hashes(UserIdHash()(pair.first), Hash()(pair.second)); - } - }; - WaitFreeHashMap, FileSourceId, UserIdPhotoIdHash> user_profile_photo_file_source_ids_; - FlatHashMap my_photo_file_id_; - WaitFreeHashMap user_full_file_source_ids_; - - WaitFreeHashMap, ChatIdHash> chats_; - WaitFreeHashMap, ChatIdHash> chats_full_; - mutable FlatHashSet unknown_chats_; - WaitFreeHashMap chat_full_file_source_ids_; - - WaitFreeHashMap, ChannelIdHash> min_channels_; - WaitFreeHashMap, ChannelIdHash> channels_; - WaitFreeHashMap, ChannelIdHash> channels_full_; - mutable FlatHashSet unknown_channels_; - WaitFreeHashSet invalidated_channels_full_; - WaitFreeHashMap channel_full_file_source_ids_; - - WaitFreeHashMap, SecretChatIdHash> secret_chats_; - mutable FlatHashSet unknown_secret_chats_; - - FlatHashMap, UserIdHash> secret_chats_with_user_; - - FlatHashMap channel_recommended_dialogs_; - FlatHashMap>>, ChannelIdHash> - get_channel_recommendations_queries_; - FlatHashMap>>, ChannelIdHash> - get_channel_recommendation_count_queries_[2]; - - bool created_public_channels_inited_[2] = {false, false}; - vector created_public_channels_[2]; - vector>> get_created_public_channels_queries_[2]; - - bool dialogs_for_discussion_inited_ = false; - vector dialogs_for_discussion_; - - bool inactive_channel_ids_inited_ = false; - vector inactive_channel_ids_; - - FlatHashMap>, UserIdHash> load_user_from_database_queries_; - FlatHashSet loaded_from_database_users_; - FlatHashSet unavailable_user_fulls_; - - FlatHashMap>, ChatIdHash> load_chat_from_database_queries_; - FlatHashSet loaded_from_database_chats_; - FlatHashSet unavailable_chat_fulls_; - - FlatHashMap>, ChannelIdHash> load_channel_from_database_queries_; - FlatHashSet loaded_from_database_channels_; - FlatHashSet unavailable_channel_fulls_; - - FlatHashMap>, SecretChatIdHash> load_secret_chat_from_database_queries_; - FlatHashSet loaded_from_database_secret_chats_; - - QueryMerger get_user_queries_{"GetUserMerger", 3, 50}; - QueryMerger get_chat_queries_{"GetChatMerger", 3, 50}; - QueryMerger get_channel_queries_{"GetChannelMerger", 100, 1}; // can't merge getChannel queries without access hash - - QueryMerger get_is_premium_required_to_contact_queries_{"GetIsPremiumRequiredToContactMerger", 3, 100}; - - QueryCombiner get_user_full_queries_{"GetUserFullCombiner", 2.0}; - QueryCombiner get_chat_full_queries_{"GetChatFullCombiner", 2.0}; - - FlatHashMap, DialogIdHash> dialog_suggested_actions_; - FlatHashMap>, DialogIdHash> dismiss_suggested_action_queries_; - - class UploadProfilePhotoCallback; - std::shared_ptr upload_profile_photo_callback_; - - struct UploadedProfilePhoto { - UserId user_id; - bool is_fallback; - bool only_suggest; - double main_frame_timestamp; - bool is_animation; - int reupload_count; - Promise promise; - - UploadedProfilePhoto(UserId user_id, bool is_fallback, bool only_suggest, double main_frame_timestamp, - bool is_animation, int32 reupload_count, Promise promise) - : user_id(user_id) - , is_fallback(is_fallback) - , only_suggest(only_suggest) - , main_frame_timestamp(main_frame_timestamp) - , is_animation(is_animation) - , reupload_count(reupload_count) - , promise(std::move(promise)) { - } - }; - FlatHashMap uploaded_profile_photos_; - - struct ImportContactsTask { - Promise promise_; - vector input_contacts_; - vector imported_user_ids_; - vector unimported_contact_invites_; - }; - FlatHashMap> import_contact_tasks_; - - FlatHashMap, vector>> imported_contacts_; - - FlatHashMap resolved_phone_numbers_; - - FlatHashMap, UserIdHash> user_messages_; - FlatHashMap, ChannelIdHash> channel_messages_; - - bool are_contacts_loaded_ = false; - int32 next_contacts_sync_date_ = 0; - Hints contacts_hints_; // search contacts by first name, last name and usernames - vector> load_contacts_queries_; - MultiPromiseActor load_contact_users_multipromise_{"LoadContactUsersMultiPromiseActor"}; - int32 saved_contact_count_ = -1; - - int32 was_online_local_ = 0; - int32 was_online_remote_ = 0; - - bool are_imported_contacts_loaded_ = false; - vector> load_imported_contacts_queries_; - MultiPromiseActor load_imported_contact_users_multipromise_{"LoadImportedContactUsersMultiPromiseActor"}; - vector all_imported_contacts_; - bool are_imported_contacts_changing_ = false; - bool need_clear_imported_contacts_ = false; - - vector users_nearby_; - vector channels_nearby_; - FlatHashSet all_users_nearby_; - - int32 location_visibility_expire_date_ = 0; - int32 pending_location_visibility_expire_date_ = -1; - bool is_set_location_visibility_request_sent_ = false; - Location last_user_location_; - - FlatHashMap user_full_contact_require_premium_; - - WaitFreeHashMap linked_channel_ids_; - - WaitFreeHashSet restricted_user_ids_; - WaitFreeHashSet restricted_channel_ids_; - - vector next_all_imported_contacts_; - vector imported_contacts_unique_id_; - vector imported_contacts_pos_; - - vector imported_contact_user_ids_; // result of change_imported_contacts - vector unimported_contact_invites_; // result of change_imported_contacts - - MultiTimeout user_online_timeout_{"UserOnlineTimeout"}; - MultiTimeout user_emoji_status_timeout_{"UserEmojiStatusTimeout"}; - MultiTimeout channel_emoji_status_timeout_{"ChannelEmojiStatusTimeout"}; - MultiTimeout channel_unban_timeout_{"ChannelUnbanTimeout"}; - MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"}; - MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"}; -}; - -} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Dependencies.cpp b/lib/tgchat/ext/td/td/telegram/Dependencies.cpp index b352d893..bbd43b05 100644 --- a/lib/tgchat/ext/td/td/telegram/Dependencies.cpp +++ b/lib/tgchat/ext/td/td/telegram/Dependencies.cpp @@ -6,10 +6,11 @@ // #include "td/telegram/Dependencies.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/telegram/WebPagesManager.h" #include "td/utils/common.h" @@ -92,7 +93,7 @@ void Dependencies::add_message_sender_dependencies(DialogId dialog_id) { bool Dependencies::resolve_force(Td *td, const char *source, bool ignore_errors) const { bool success = true; for (auto user_id : user_ids) { - if (!td->contacts_manager_->have_user_force(user_id, source)) { + if (!td->user_manager_->have_user_force(user_id, source)) { if (!ignore_errors) { LOG(ERROR) << "Can't find " << user_id << " from " << source; } @@ -100,7 +101,7 @@ bool Dependencies::resolve_force(Td *td, const char *source, bool ignore_errors) } } for (auto chat_id : chat_ids) { - if (!td->contacts_manager_->have_chat_force(chat_id, source)) { + if (!td->chat_manager_->have_chat_force(chat_id, source)) { if (!ignore_errors) { LOG(ERROR) << "Can't find " << chat_id << " from " << source; } @@ -108,8 +109,8 @@ bool Dependencies::resolve_force(Td *td, const char *source, bool ignore_errors) } } for (auto channel_id : channel_ids) { - if (!td->contacts_manager_->have_channel_force(channel_id, source)) { - if (td->contacts_manager_->have_min_channel(channel_id)) { + if (!td->chat_manager_->have_channel_force(channel_id, source)) { + if (td->chat_manager_->have_min_channel(channel_id)) { LOG(INFO) << "Can't find " << channel_id << " from " << source << ", but have it as a min-channel"; continue; } @@ -120,7 +121,7 @@ bool Dependencies::resolve_force(Td *td, const char *source, bool ignore_errors) } } for (auto secret_chat_id : secret_chat_ids) { - if (!td->contacts_manager_->have_secret_chat_force(secret_chat_id, source)) { + if (!td->user_manager_->have_secret_chat_force(secret_chat_id, source)) { if (!ignore_errors) { LOG(ERROR) << "Can't find " << secret_chat_id << " from " << source; } diff --git a/lib/tgchat/ext/td/td/telegram/DeviceTokenManager.cpp b/lib/tgchat/ext/td/td/telegram/DeviceTokenManager.cpp index b68524dd..241485dd 100644 --- a/lib/tgchat/ext/td/td/telegram/DeviceTokenManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DeviceTokenManager.cpp @@ -133,7 +133,9 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DeviceTokenManage void DeviceTokenManager::register_device(tl_object_ptr device_token_ptr, const vector &other_user_ids, Promise> promise) { - CHECK(device_token_ptr != nullptr); + if (device_token_ptr == nullptr) { + return promise.set_error(Status::Error(400, "Device token must be non-empty")); + } TokenType token_type; string token; bool is_app_sandbox = false; diff --git a/lib/tgchat/ext/td/td/telegram/DialogAction.cpp b/lib/tgchat/ext/td/td/telegram/DialogAction.cpp index 210d4e2a..3d217dd8 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogAction.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogAction.cpp @@ -421,6 +421,7 @@ bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_con case MessageContentType::GiveawayResults: case MessageContentType::GiveawayWinners: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; default: UNREACHABLE(); diff --git a/lib/tgchat/ext/td/td/telegram/DialogActionBar.cpp b/lib/tgchat/ext/td/td/telegram/DialogActionBar.cpp index f2b746f5..75cb342b 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogActionBar.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogActionBar.cpp @@ -6,8 +6,9 @@ // #include "td/telegram/DialogActionBar.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/logging.h" @@ -18,11 +19,6 @@ unique_ptr DialogActionBar::create(bool can_report_spam, bool c bool can_unarchive, int32 distance, bool can_invite_members, string join_request_dialog_title, bool is_join_request_broadcast, int32 join_request_date) { - if (!can_report_spam && !can_add_contact && !can_block_user && !can_share_phone_number && !can_report_location && - !can_invite_members && join_request_dialog_title.empty()) { - return nullptr; - } - auto action_bar = make_unique(); action_bar->can_report_spam_ = can_report_spam; action_bar->can_add_contact_ = can_add_contact; @@ -35,6 +31,9 @@ unique_ptr DialogActionBar::create(bool can_report_spam, bool c action_bar->join_request_dialog_title_ = std::move(join_request_dialog_title); action_bar->is_join_request_broadcast_ = is_join_request_broadcast; action_bar->join_request_date_ = join_request_date; + if (action_bar->is_empty()) { + return nullptr; + } return action_bar; } @@ -96,8 +95,8 @@ void DialogActionBar::fix(Td *td, DialogId dialog_id, bool is_dialog_blocked, Fo } } if (can_invite_members_) { - if (dialog_type != DialogType::Chat && (dialog_type != DialogType::Channel || - td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()))) { + if (dialog_type != DialogType::Chat && + (dialog_type != DialogType::Channel || td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()))) { LOG(ERROR) << "Receive can_invite_members in " << dialog_id; can_invite_members_ = false; } else if (can_report_spam_ || can_add_contact_ || can_block_user_ || can_share_phone_number_ || can_unarchive_) { @@ -113,9 +112,9 @@ void DialogActionBar::fix(Td *td, DialogId dialog_id, bool is_dialog_blocked, Fo } if (dialog_type == DialogType::User) { auto user_id = dialog_id.get_user_id(); - bool is_me = user_id == td->contacts_manager_->get_my_id(); - bool is_deleted = td->contacts_manager_->is_user_deleted(user_id); - bool is_contact = td->contacts_manager_->is_user_contact(user_id); + bool is_me = user_id == td->user_manager_->get_my_id(); + bool is_deleted = td->user_manager_->is_user_deleted(user_id); + bool is_contact = td->user_manager_->is_user_contact(user_id); if (is_me || is_dialog_blocked) { can_report_spam_ = false; can_unarchive_ = false; diff --git a/lib/tgchat/ext/td/td/telegram/DialogActionManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogActionManager.cpp index fa07adc9..b37ecf1e 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogActionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogActionManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/BusinessConnectionManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessageSender.h" @@ -20,6 +20,7 @@ #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/emoji.h" @@ -34,6 +35,7 @@ namespace td { class SetTypingQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; + BusinessConnectionId business_connection_id_; int32 generation_ = 0; public: @@ -41,16 +43,22 @@ class SetTypingQuery final : public Td::ResultHandler { } NetQueryRef send(DialogId dialog_id, tl_object_ptr &&input_peer, - MessageId top_thread_message_id, tl_object_ptr &&action) { + MessageId top_thread_message_id, BusinessConnectionId business_connection_id, + tl_object_ptr &&action) { dialog_id_ = dialog_id; + business_connection_id_ = business_connection_id; CHECK(input_peer != nullptr); int32 flags = 0; if (top_thread_message_id.is_valid()) { flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK; } - auto query = G()->net_query_creator().create(telegram_api::messages_setTyping( - flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get(), std::move(action))); + + auto query = G()->net_query_creator().create_with_prefix( + business_connection_id.get_invoke_prefix(), + telegram_api::messages_setTyping(flags, std::move(input_peer), + top_thread_message_id.get_server_message_id().get(), std::move(action)), + td_->business_connection_manager_->get_business_connection_dc_id(business_connection_id)); query->total_timeout_limit_ = 2; auto result = query.get_weak(); generation_ = result.generation(); @@ -76,7 +84,8 @@ class SetTypingQuery final : public Td::ResultHandler { return promise_.set_value(Unit()); } - if (!td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) { + if (!business_connection_id_.is_valid() && + !td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) { LOG(INFO) << "Receive error for set typing: " << status; } promise_.set_error(std::move(status)); @@ -171,7 +180,7 @@ void DialogActionManager::on_dialog_action(DialogId dialog_id, MessageId top_thr } if (typing_dialog_type == DialogType::User) { - if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) { + if (!td_->user_manager_->have_min_user(typing_dialog_id.get_user_id())) { LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id(); return; } @@ -189,13 +198,13 @@ void DialogActionManager::on_dialog_action(DialogId dialog_id, MessageId top_thr bool is_canceled = action == DialogAction(); if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) { - td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date); + td_->user_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date); } if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { CHECK(typing_dialog_type == DialogType::User); auto user_id = typing_dialog_id.get_user_id(); - if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) && + if (!td_->user_manager_->is_user_bot(user_id) && !td_->user_manager_->is_user_status_exact(user_id) && !td_->messages_manager_->is_dialog_opened(dialog_id) && !is_canceled) { return; } @@ -216,8 +225,7 @@ void DialogActionManager::on_dialog_action(DialogId dialog_id, MessageId top_thr return; } - if (!(typing_dialog_type == DialogType::User && - td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) && + if (!(typing_dialog_type == DialogType::User && td_->user_manager_->is_user_bot(typing_dialog_id.get_user_id())) && !it->action.is_canceled_by_message_of_type(message_content_type)) { return; } @@ -284,9 +292,14 @@ void DialogActionManager::send_update_chat_action(DialogId dialog_id, MessageId action.get_chat_action_object())); } -void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, +void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, + BusinessConnectionId business_connection_id, DialogAction action, Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "send_dialog_action")) { + bool as_business = business_connection_id.is_valid(); + if (as_business) { + TRY_STATUS_PROMISE(promise, + td_->business_connection_manager_->check_business_connection(business_connection_id, dialog_id)); + } else if (!td_->dialog_manager_->have_dialog_force(dialog_id, "send_dialog_action")) { return promise.set_error(Status::Error(400, "Chat not found")); } if (top_thread_message_id != MessageId() && @@ -294,18 +307,23 @@ void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_t return promise.set_error(Status::Error(400, "Invalid message thread specified")); } - if (td_->dialog_manager_->is_forum_channel(dialog_id) && !top_thread_message_id.is_valid()) { + if (!as_business && td_->dialog_manager_->is_forum_channel(dialog_id) && !top_thread_message_id.is_valid()) { top_thread_message_id = MessageId(ServerMessageId(1)); } tl_object_ptr input_peer; if (action == DialogAction::get_speaking_action()) { + if (as_business) { + return promise.set_error(Status::Error(400, "Can't use the action")); + } input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise.set_error(Status::Error(400, "Have no access to the chat")); } + } else if (as_business) { + input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Know); } else { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Write)) { if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Have no write access to the chat")); } @@ -321,6 +339,7 @@ void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_t } if (dialog_id.get_type() == DialogType::SecretChat) { + CHECK(!as_business); send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(), action.get_secret_input_send_message_action()); promise.set_value(Unit()); @@ -329,9 +348,9 @@ void DialogActionManager::send_dialog_action(DialogId dialog_id, MessageId top_t CHECK(input_peer != nullptr); - auto new_query_ref = - td_->create_handler(std::move(promise)) - ->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action()); + auto new_query_ref = td_->create_handler(std::move(promise)) + ->send(dialog_id, std::move(input_peer), top_thread_message_id, business_connection_id, + action.get_input_send_message_action()); if (td_->auth_manager_->is_bot()) { return; } diff --git a/lib/tgchat/ext/td/td/telegram/DialogActionManager.h b/lib/tgchat/ext/td/td/telegram/DialogActionManager.h index 3cd87a6c..ecebf59b 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogActionManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogActionManager.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/BusinessConnectionId.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogId.h" #include "td/telegram/MessageContentType.h" @@ -31,8 +32,8 @@ class DialogActionManager final : public Actor { DialogAction action, int32 date, MessageContentType message_content_type = MessageContentType::None); - void send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action, - Promise &&promise); + void send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, + BusinessConnectionId business_connection_id, DialogAction action, Promise &&promise); void cancel_send_dialog_action_queries(DialogId dialog_id); diff --git a/lib/tgchat/ext/td/td/telegram/DialogAdministrator.cpp b/lib/tgchat/ext/td/td/telegram/DialogAdministrator.cpp index db2d2593..ed044cc0 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogAdministrator.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogAdministrator.cpp @@ -6,16 +6,16 @@ // #include "td/telegram/DialogAdministrator.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/UserManager.h" namespace td { td_api::object_ptr DialogAdministrator::get_chat_administrator_object( - const ContactsManager *contacts_manager) const { - CHECK(contacts_manager != nullptr); + const UserManager *user_manager) const { + CHECK(user_manager != nullptr); CHECK(user_id_.is_valid()); return td_api::make_object( - contacts_manager->get_user_id_object(user_id_, "get_chat_administrator_object"), rank_, is_creator_); + user_manager->get_user_id_object(user_id_, "get_chat_administrator_object"), rank_, is_creator_); } StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &administrator) { diff --git a/lib/tgchat/ext/td/td/telegram/DialogAdministrator.h b/lib/tgchat/ext/td/td/telegram/DialogAdministrator.h index bae43ca9..a9e0f6b4 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogAdministrator.h +++ b/lib/tgchat/ext/td/td/telegram/DialogAdministrator.h @@ -15,7 +15,7 @@ namespace td { -class ContactsManager; +class UserManager; class DialogAdministrator { UserId user_id_; @@ -31,8 +31,7 @@ class DialogAdministrator { : user_id_(user_id), rank_(rank), is_creator_(is_creator) { } - td_api::object_ptr get_chat_administrator_object( - const ContactsManager *contacts_manager) const; + td_api::object_ptr get_chat_administrator_object(const UserManager *user_manager) const; UserId get_user_id() const { return user_id_; diff --git a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp index 34ea302d..a02ee1af 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogEventLog.cpp @@ -9,8 +9,8 @@ #include "td/telegram/AccentColorId.h" #include "td/telegram/BackgroundInfo.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ChatReactions.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogLocation.h" #include "td/telegram/DialogManager.h" @@ -30,6 +30,7 @@ #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" @@ -54,7 +55,7 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - invite_link.get_chat_invite_link_object(td->contacts_manager_.get()), action->via_chatlist_); + invite_link.get_chat_invite_link_object(td->user_manager_.get()), action->via_chatlist_); } case telegram_api::channelAdminLogEventActionParticipantJoinByRequest::ID: { auto action = move_tl_object_as(action_ptr); @@ -65,27 +66,26 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - td->contacts_manager_->get_user_id_object(approver_user_id, "chatEventMemberJoinedByRequest"), - invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + td->user_manager_->get_user_id_object(approver_user_id, "chatEventMemberJoinedByRequest"), + invite_link.get_chat_invite_link_object(td->user_manager_.get())); } case telegram_api::channelAdminLogEventActionParticipantLeave::ID: return td_api::make_object(); case telegram_api::channelAdminLogEventActionParticipantInvite::ID: { auto action = move_tl_object_as(action_ptr); DialogParticipant dialog_participant(std::move(action->participant_), - td->contacts_manager_->get_channel_type(channel_id)); + td->chat_manager_->get_channel_type(channel_id)); if (!dialog_participant.is_valid() || dialog_participant.dialog_id_.get_type() != DialogType::User) { LOG(ERROR) << "Wrong invite: " << dialog_participant; return nullptr; } return td_api::make_object( - td->contacts_manager_->get_user_id_object(dialog_participant.dialog_id_.get_user_id(), - "chatEventMemberInvited"), + td->user_manager_->get_user_id_object(dialog_participant.dialog_id_.get_user_id(), "chatEventMemberInvited"), dialog_participant.status_.get_chat_member_status_object()); } case telegram_api::channelAdminLogEventActionParticipantToggleBan::ID: { auto action = move_tl_object_as(action_ptr); - auto channel_type = td->contacts_manager_->get_channel_type(channel_id); + auto channel_type = td->chat_manager_->get_channel_type(channel_id); DialogParticipant old_dialog_participant(std::move(action->prev_participant_), channel_type); DialogParticipant new_dialog_participant(std::move(action->new_participant_), channel_type); if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_) { @@ -103,7 +103,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionParticipantToggleAdmin::ID: { auto action = move_tl_object_as(action_ptr); - auto channel_type = td->contacts_manager_->get_channel_type(channel_id); + auto channel_type = td->chat_manager_->get_channel_type(channel_id); DialogParticipant old_dialog_participant(std::move(action->prev_participant_), channel_type); DialogParticipant new_dialog_participant(std::move(action->new_participant_), channel_type); if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_) { @@ -116,8 +116,8 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - td->contacts_manager_->get_user_id_object(old_dialog_participant.dialog_id_.get_user_id(), - "chatEventMemberPromoted"), + td->user_manager_->get_user_id_object(old_dialog_participant.dialog_id_.get_user_id(), + "chatEventMemberPromoted"), old_dialog_participant.status_.get_chat_member_status_object(), new_dialog_participant.status_.get_chat_member_status_object()); } @@ -151,7 +151,7 @@ static td_api::object_ptr get_chat_event_action_object( } case telegram_api::channelAdminLogEventActionDefaultBannedRights::ID: { auto action = move_tl_object_as(action_ptr); - auto channel_type = td->contacts_manager_->get_channel_type(channel_id); + auto channel_type = td->chat_manager_->get_channel_type(channel_id); auto old_permissions = RestrictedRights(action->prev_banned_rights_, channel_type); auto new_permissions = RestrictedRights(action->new_banned_rights_, channel_type); return td_api::make_object(old_permissions.get_chat_permissions_object(), @@ -281,8 +281,8 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - old_invite_link.get_chat_invite_link_object(td->contacts_manager_.get()), - new_invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + old_invite_link.get_chat_invite_link_object(td->user_manager_.get()), + new_invite_link.get_chat_invite_link_object(td->user_manager_.get())); } case telegram_api::channelAdminLogEventActionExportedInviteRevoke::ID: { auto action = move_tl_object_as(action_ptr); @@ -292,7 +292,7 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + invite_link.get_chat_invite_link_object(td->user_manager_.get())); } case telegram_api::channelAdminLogEventActionExportedInviteDelete::ID: { auto action = move_tl_object_as(action_ptr); @@ -302,7 +302,7 @@ static td_api::object_ptr get_chat_event_action_object( return nullptr; } return td_api::make_object( - invite_link.get_chat_invite_link_object(td->contacts_manager_.get())); + invite_link.get_chat_invite_link_object(td->user_manager_.get())); } case telegram_api::channelAdminLogEventActionStartGroupCall::ID: { auto action = move_tl_object_as(action_ptr); @@ -367,11 +367,11 @@ 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_)); - ChatReactions new_available_reactions(std::move(action->new_value_)); + ChatReactions old_available_reactions(std::move(action->prev_value_), 0); + ChatReactions new_available_reactions(std::move(action->new_value_), 0); return td_api::make_object( - old_available_reactions.get_chat_available_reactions_object(), - new_available_reactions.get_chat_available_reactions_object()); + old_available_reactions.get_chat_available_reactions_object(td), + new_available_reactions.get_chat_available_reactions_object(td)); } case telegram_api::channelAdminLogEventActionToggleForum::ID: { auto action = move_tl_object_as(action_ptr); @@ -490,7 +490,7 @@ class GetChannelAdminLogQuery final : public Td::ResultHandler { vector> input_users) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; @@ -513,8 +513,8 @@ class GetChannelAdminLogQuery final : public Td::ResultHandler { auto events = result_ptr.move_as_ok(); LOG(INFO) << "Receive in " << channel_id_ << ' ' << to_string(events); - td_->contacts_manager_->on_get_users(std::move(events->users_), "on_get_event_log"); - td_->contacts_manager_->on_get_chats(std::move(events->chats_), "on_get_event_log"); + td_->user_manager_->on_get_users(std::move(events->users_), "on_get_event_log"); + td_->chat_manager_->on_get_chats(std::move(events->chats_), "on_get_event_log"); auto anti_spam_user_id = UserId(G()->get_option_integer("anti_spam_bot_user_id")); auto result = td_api::make_object(); @@ -530,7 +530,7 @@ class GetChannelAdminLogQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive invalid " << user_id; continue; } - LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Receive unknown " << user_id; + LOG_IF(ERROR, !td_->user_manager_->have_user(user_id)) << "Receive unknown " << user_id; DialogId actor_dialog_id; auto action = get_chat_event_action_object(td_, channel_id_, std::move(event->action_), actor_dialog_id); @@ -541,7 +541,7 @@ class GetChannelAdminLogQuery final : public Td::ResultHandler { action->get_id() == td_api::chatEventMessageDeleted::ID) { static_cast(action.get())->can_report_anti_spam_false_positive_ = true; } - if (user_id == ContactsManager::get_channel_bot_user_id() && actor_dialog_id.is_valid() && + if (user_id == UserManager::get_channel_bot_user_id() && actor_dialog_id.is_valid() && actor_dialog_id.get_type() != DialogType::User) { user_id = UserId(); } else { @@ -556,7 +556,7 @@ class GetChannelAdminLogQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdminLogQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdminLogQuery"); promise_.set_error(std::move(status)); } }; @@ -631,17 +631,17 @@ void get_dialog_event_log(Td *td, DialogId dialog_id, const string &query, int64 } auto channel_id = dialog_id.get_channel_id(); - if (!td->contacts_manager_->have_channel(channel_id)) { + if (!td->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } - if (!td->contacts_manager_->get_channel_status(channel_id).is_administrator()) { + if (!td->chat_manager_->get_channel_status(channel_id).is_administrator()) { return promise.set_error(Status::Error(400, "Not enough rights to get event log")); } vector> input_users; for (auto user_id : user_ids) { - TRY_RESULT_PROMISE(promise, input_user, td->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td->user_manager_->get_input_user(user_id)); input_users.push_back(std::move(input_user)); } diff --git a/lib/tgchat/ext/td/td/telegram/DialogFilter.cpp b/lib/tgchat/ext/td/td/telegram/DialogFilter.cpp index 32cf7d3b..d903cbe0 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogFilter.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogFilter.cpp @@ -6,13 +6,14 @@ // #include "td/telegram/DialogFilter.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/emoji.h" @@ -533,12 +534,12 @@ unique_ptr DialogFilter::merge_dialog_filter_changes(const DialogF auto merge_ordered_changes = [dialog_filter_id](auto &new_dialog_ids, auto old_server_dialog_ids, auto new_server_dialog_ids) { if (old_server_dialog_ids == new_server_dialog_ids) { - LOG(INFO) << "Pinned chats was not changed remotely in " << dialog_filter_id << ", keep local changes"; + LOG(INFO) << "Pinned chats were not changed remotely in " << dialog_filter_id << ", keep local changes"; return; } if (InputDialogId::are_equivalent(new_dialog_ids, old_server_dialog_ids)) { - LOG(INFO) << "Pinned chats was not changed locally in " << dialog_filter_id << ", keep remote changes"; + LOG(INFO) << "Pinned chats were not changed locally in " << dialog_filter_id << ", keep remote changes"; size_t kept_server_dialogs = 0; FlatHashSet removed_dialog_ids; @@ -692,20 +693,18 @@ void DialogFilter::sort_input_dialog_ids(const Td *td, const char *source) { excluded_dialog_ids_.clear(); } - auto sort_input_dialog_ids = [contacts_manager = - td->contacts_manager_.get()](vector &input_dialog_ids) { - std::sort(input_dialog_ids.begin(), input_dialog_ids.end(), - [contacts_manager](InputDialogId lhs, InputDialogId rhs) { - auto get_order = [contacts_manager](InputDialogId input_dialog_id) { - auto dialog_id = input_dialog_id.get_dialog_id(); - if (dialog_id.get_type() != DialogType::SecretChat) { - return dialog_id.get() * 10; - } - auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - return DialogId(user_id).get() * 10 + 1; - }; - return get_order(lhs) < get_order(rhs); - }); + auto sort_input_dialog_ids = [user_manager = td->user_manager_.get()](vector &input_dialog_ids) { + std::sort(input_dialog_ids.begin(), input_dialog_ids.end(), [user_manager](InputDialogId lhs, InputDialogId rhs) { + auto get_order = [user_manager](InputDialogId input_dialog_id) { + auto dialog_id = input_dialog_id.get_dialog_id(); + if (dialog_id.get_type() != DialogType::SecretChat) { + return dialog_id.get() * 10; + } + auto user_id = user_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + return DialogId(user_id).get() * 10 + 1; + }; + return get_order(lhs) < get_order(rhs); + }); }; sort_input_dialog_ids(excluded_dialog_ids_); @@ -735,16 +734,16 @@ vector DialogFilter::get_dialogs_for_invite_link(Td *td) { case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); // the user can manage invite links in the chat - is_good = td->contacts_manager_->get_chat_status(chat_id).can_manage_invite_links(); + is_good = td->chat_manager_->get_chat_status(chat_id).can_manage_invite_links(); break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); // the user can manage invite links in the chat // or the chat is a public chat, which can be joined without administrator approval - is_good = td->contacts_manager_->get_channel_status(channel_id).can_manage_invite_links() || - (td->contacts_manager_->is_channel_public(channel_id) && - !td->contacts_manager_->get_channel_join_request(channel_id)); + is_good = td->chat_manager_->get_channel_status(channel_id).can_manage_invite_links() || + (td->chat_manager_->is_channel_public(channel_id) && + !td->chat_manager_->get_channel_join_request(channel_id)); break; } default: @@ -773,7 +772,7 @@ bool DialogFilter::need_dialog(const Td *td, const DialogFilterDialogInfo &dialo return false; } if (dialog_id.get_type() == DialogType::SecretChat) { - auto user_id = td->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { auto user_dialog_id = DialogId(user_id); if (is_dialog_included(user_dialog_id)) { @@ -798,10 +797,10 @@ bool DialogFilter::need_dialog(const Td *td, const DialogFilterDialogInfo &dialo switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); - if (td->contacts_manager_->is_user_bot(user_id)) { + if (td->user_manager_->is_user_bot(user_id)) { return include_bots_; } - if (user_id == td->contacts_manager_->get_my_id() || td->contacts_manager_->is_user_contact(user_id)) { + if (user_id == td->user_manager_->get_my_id() || td->user_manager_->is_user_contact(user_id)) { return include_contacts_; } return include_non_contacts_; @@ -809,14 +808,13 @@ bool DialogFilter::need_dialog(const Td *td, const DialogFilterDialogInfo &dialo case DialogType::Chat: return include_groups_; case DialogType::Channel: - return td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()) ? include_channels_ - : include_groups_; + return td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()) ? include_channels_ : include_groups_; case DialogType::SecretChat: { - auto user_id = td->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - if (td->contacts_manager_->is_user_bot(user_id)) { + auto user_id = td->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + if (td->user_manager_->is_user_bot(user_id)) { return include_bots_; } - if (td->contacts_manager_->is_user_contact(user_id)) { + if (td->user_manager_->is_user_contact(user_id)) { return include_contacts_; } return include_non_contacts_; diff --git a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp index a2a2309a..86a77e9a 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogFilterManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogFilter.h" #include "td/telegram/DialogFilter.hpp" #include "td/telegram/DialogFilterInviteLink.h" @@ -23,6 +23,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/actor/MultiPromise.h" @@ -207,8 +208,8 @@ class GetExportedChatlistInvitesQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetExportedChatlistInvitesQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetExportedChatlistInvitesQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetExportedChatlistInvitesQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetExportedChatlistInvitesQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetExportedChatlistInvitesQuery"); auto result = td_api::make_object(); for (auto &invite : ptr->invites_) { result->invite_links_.push_back( @@ -424,8 +425,8 @@ class GetChatlistUpdatesQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetChatlistUpdatesQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetChatlistUpdatesQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetChatlistUpdatesQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetChatlistUpdatesQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetChatlistUpdatesQuery"); auto missing_dialog_ids = td_->dialog_manager_->get_peers_dialog_ids(std::move(ptr->missing_peers_), true); promise_.set_value(td_->dialog_manager_->get_chats_object(-1, missing_dialog_ids, "GetChatlistUpdatesQuery")); } @@ -517,8 +518,8 @@ class GetDialogsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetDialogsQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery"); td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_), std::move(promise_)); } @@ -2130,8 +2131,8 @@ void DialogFilterManager::on_get_chatlist_invite( break; } - td_->contacts_manager_->on_get_users(std::move(users), "on_get_chatlist_invite"); - td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_chatlist_invite"); + td_->user_manager_->on_get_users(std::move(users), "on_get_chatlist_invite"); + td_->chat_manager_->on_get_chats(std::move(chats), "on_get_chatlist_invite"); auto missing_dialog_ids = td_->dialog_manager_->get_peers_dialog_ids(std::move(missing_peers), true); auto already_dialog_ids = td_->dialog_manager_->get_peers_dialog_ids(std::move(already_peers)); @@ -2146,12 +2147,8 @@ void DialogFilterManager::add_dialog_filter_by_invite_link(const string &invite_ return promise.set_error(Status::Error(400, "Wrong invite link")); } for (auto dialog_id : dialog_ids) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "add_dialog_filter_by_invite_link")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Know)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Know, + "add_dialog_filter_by_invite_link")); } CHECK(!invite_link.empty()); @@ -2180,12 +2177,8 @@ void DialogFilterManager::add_dialog_filter_new_chats(DialogFilterId dialog_filt return promise.set_error(Status::Error(400, "Chat folder must be shareable")); } for (auto dialog_id : dialog_ids) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "add_dialog_filter_new_chats")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Know)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Know, + "add_dialog_filter_new_chats")); } if (dialog_ids.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp index 14770410..f0e0b21b 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.cpp @@ -6,8 +6,8 @@ // #include "td/telegram/DialogInviteLink.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/LinkManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -99,15 +99,15 @@ bool DialogInviteLink::is_valid_invite_link(Slice invite_link, bool allow_trunca } td_api::object_ptr DialogInviteLink::get_chat_invite_link_object( - const ContactsManager *contacts_manager) const { - CHECK(contacts_manager != nullptr); + const UserManager *user_manager) const { + CHECK(user_manager != nullptr); if (!is_valid()) { return nullptr; } return td_api::make_object( - invite_link_, title_, contacts_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_, + 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_); } diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h index 7aa22016..04e304c3 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLink.h @@ -17,7 +17,7 @@ namespace td { -class ContactsManager; +class UserManager; class DialogInviteLink { string invite_link_; @@ -45,7 +45,7 @@ class DialogInviteLink { static bool is_valid_invite_link(Slice invite_link, bool allow_truncated = false); - td_api::object_ptr get_chat_invite_link_object(const ContactsManager *contacts_manager) const; + td_api::object_ptr get_chat_invite_link_object(const UserManager *user_manager) const; bool is_valid() const { return !invite_link_.empty() && creator_user_id_.is_valid() && date_ > 0; diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp index f190dfd7..92befbe6 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.cpp @@ -9,7 +9,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogInviteLink.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -19,6 +19,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" @@ -113,9 +114,7 @@ class ExportChatInviteQuery final : public Td::ResultHandler { bool is_permanent) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); int32 flags = 0; if (expire_date > 0) { @@ -151,13 +150,13 @@ class ExportChatInviteQuery final : public Td::ResultHandler { if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } - if (invite_link.get_creator_user_id() != td_->contacts_manager_->get_my_id()) { + if (invite_link.get_creator_user_id() != td_->user_manager_->get_my_id()) { return on_error(Status::Error(500, "Receive invalid invite link creator")); } if (invite_link.is_permanent()) { - td_->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, invite_link); + td_->dialog_invite_link_manager_->on_get_permanent_dialog_invite_link(dialog_id_, invite_link); } - promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + promise_.set_value(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); } void on_error(Status status) final { @@ -179,9 +178,7 @@ class EditChatInviteLinkQuery final : public Td::ResultHandler { bool creates_join_request) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); int32 flags = telegram_api::messages_editExportedChatInvite::EXPIRE_DATE_MASK | telegram_api::messages_editExportedChatInvite::USAGE_LIMIT_MASK | @@ -207,13 +204,13 @@ class EditChatInviteLinkQuery final : public Td::ResultHandler { auto invite = move_tl_object_as(result); - td_->contacts_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery"); + td_->user_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery"); DialogInviteLink invite_link(std::move(invite->invite_), false, "EditChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } - promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + promise_.set_value(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); } void on_error(Status status) final { @@ -234,9 +231,7 @@ class GetExportedChatInviteQuery final : public Td::ResultHandler { void send(DialogId dialog_id, const string &invite_link) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_getExportedChatInvite(std::move(input_peer), invite_link))); @@ -256,14 +251,14 @@ class GetExportedChatInviteQuery final : public Td::ResultHandler { auto result = move_tl_object_as(result_ptr.ok_ref()); LOG(INFO) << "Receive result for GetExportedChatInviteQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery"); DialogInviteLink invite_link(std::move(result->invite_), 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")); } - promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + promise_.set_value(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); } void on_error(Status status) final { @@ -285,9 +280,7 @@ class GetExportedChatInvitesQuery final : public Td::ResultHandler { const string &offset_invite_link, int32 limit) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); int32 flags = 0; if (!offset_invite_link.empty() || offset_date != 0) { @@ -311,7 +304,7 @@ class GetExportedChatInvitesQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetExportedChatInvitesQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInvitesQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetExportedChatInvitesQuery"); int32 total_count = result->count_; if (total_count < static_cast(result->invites_.size())) { @@ -326,7 +319,7 @@ class GetExportedChatInvitesQuery final : public Td::ResultHandler { total_count--; continue; } - invite_links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + invite_links.push_back(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); } promise_.set_value(td_api::make_object(total_count, std::move(invite_links))); } @@ -349,9 +342,7 @@ class GetChatAdminWithInvitesQuery final : public Td::ResultHandler { void send(DialogId dialog_id) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create(telegram_api::messages_getAdminsWithInvites(std::move(input_peer)))); } @@ -365,7 +356,7 @@ class GetChatAdminWithInvitesQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetChatAdminWithInvitesQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatAdminWithInvitesQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetChatAdminWithInvitesQuery"); vector> invite_link_counts; for (auto &admin : result->admins_) { @@ -375,7 +366,7 @@ class GetChatAdminWithInvitesQuery final : public Td::ResultHandler { continue; } invite_link_counts.push_back(td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkCount"), admin->invites_count_, + td_->user_manager_->get_user_id_object(user_id, "chatInviteLinkCount"), admin->invites_count_, admin->revoked_invites_count_)); } promise_.set_value(td_api::make_object(std::move(invite_link_counts))); @@ -399,11 +390,9 @@ class GetChatInviteImportersQuery final : public Td::ResultHandler { void send(DialogId dialog_id, const string &invite_link, 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); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); - auto r_input_user = td_->contacts_manager_->get_input_user(offset_user_id); + auto r_input_user = td_->user_manager_->get_input_user(offset_user_id); if (r_input_user.is_error()) { r_input_user = make_tl_object(); } @@ -423,7 +412,7 @@ class GetChatInviteImportersQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetChatInviteImportersQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatInviteImportersQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetChatInviteImportersQuery"); int32 total_count = result->count_; if (total_count < static_cast(result->importers_.size())) { @@ -441,9 +430,8 @@ class GetChatInviteImportersQuery final : public Td::ResultHandler { continue; } invite_link_members.push_back(td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkMember"), importer->date_, - importer->via_chatlist_, - td_->contacts_manager_->get_user_id_object(approver_user_id, "chatInviteLinkMember"))); + td_->user_manager_->get_user_id_object(user_id, "chatInviteLinkMember"), importer->date_, + importer->via_chatlist_, td_->user_manager_->get_user_id_object(approver_user_id, "chatInviteLinkMember"))); } promise_.set_value(td_api::make_object(total_count, std::move(invite_link_members))); } @@ -466,9 +454,7 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { void send(DialogId dialog_id, const string &invite_link) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); int32 flags = telegram_api::messages_editExportedChatInvite::REVOKED_MASK; send_query(G()->net_query_creator().create(telegram_api::messages_editExportedChatInvite( @@ -489,19 +475,19 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { case telegram_api::messages_exportedChatInvite::ID: { auto invite = move_tl_object_as(result); - td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); + td_->user_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery"); DialogInviteLink invite_link(std::move(invite->invite_), false, "RevokeChatInviteLinkQuery"); if (!invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } - links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + links.push_back(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); break; } case telegram_api::messages_exportedChatInviteReplaced::ID: { auto invite = move_tl_object_as(result); - td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery replaced"); + 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, @@ -509,12 +495,12 @@ class RevokeChatInviteLinkQuery final : public Td::ResultHandler { if (!invite_link.is_valid() || !new_invite_link.is_valid()) { return on_error(Status::Error(500, "Receive invalid invite link")); } - if (new_invite_link.get_creator_user_id() == td_->contacts_manager_->get_my_id() && + if (new_invite_link.get_creator_user_id() == td_->user_manager_->get_my_id() && new_invite_link.is_permanent()) { - td_->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, new_invite_link); + td_->dialog_invite_link_manager_->on_get_permanent_dialog_invite_link(dialog_id_, new_invite_link); } - links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); - links.push_back(new_invite_link.get_chat_invite_link_object(td_->contacts_manager_.get())); + links.push_back(invite_link.get_chat_invite_link_object(td_->user_manager_.get())); + links.push_back(new_invite_link.get_chat_invite_link_object(td_->user_manager_.get())); break; } default: @@ -541,9 +527,7 @@ class DeleteExportedChatInviteQuery final : public Td::ResultHandler { void send(DialogId dialog_id, const string &invite_link) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_deleteExportedChatInvite(std::move(input_peer), invite_link))); @@ -575,9 +559,7 @@ class DeleteRevokedExportedChatInvitesQuery final : public Td::ResultHandler { void send(DialogId dialog_id, tl_object_ptr &&input_user) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::messages_deleteRevokedExportedChatInvites(std::move(input_peer), std::move(input_user)))); @@ -646,7 +628,7 @@ void DialogInviteLinkManager::check_dialog_invite_link(const string &invite_link if (it != invite_link_infos_.end()) { auto dialog_id = it->second->dialog_id; if (!force && dialog_id.get_type() == DialogType::Chat && - !td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) { + !td_->chat_manager_->get_chat_is_active(dialog_id.get_chat_id())) { invite_link_infos_.erase(it); } else { return promise.set_value(Unit()); @@ -687,12 +669,12 @@ void DialogInviteLinkManager::on_get_dialog_invite_link_info( chat = std::move(chat_invite_peek->chat_); accessible_before_date = chat_invite_peek->expires_; } - auto chat_id = ContactsManager::get_chat_id(chat); + auto chat_id = ChatManager::get_chat_id(chat); if (chat_id != ChatId() && !chat_id.is_valid()) { LOG(ERROR) << "Receive invalid " << chat_id; chat_id = ChatId(); } - auto channel_id = ContactsManager::get_channel_id(chat); + auto channel_id = ChatManager::get_channel_id(chat); if (channel_id != ChannelId() && !channel_id.is_valid()) { LOG(ERROR) << "Receive invalid " << channel_id; channel_id = ChannelId(); @@ -702,7 +684,7 @@ void DialogInviteLinkManager::on_get_dialog_invite_link_info( << to_string(chat); accessible_before_date = 0; } - td_->contacts_manager_->on_get_chat(std::move(chat), "chatInviteAlready"); + td_->chat_manager_->on_get_chat(std::move(chat), "chatInviteAlready"); CHECK(chat_id == ChatId() || channel_id == ChannelId()); @@ -727,13 +709,13 @@ void DialogInviteLinkManager::on_get_dialog_invite_link_info( auto chat_invite = telegram_api::move_object_as(chat_invite_ptr); vector participant_user_ids; for (auto &user : chat_invite->participants_) { - auto user_id = ContactsManager::get_user_id(user); + auto user_id = UserManager::get_user_id(user); if (!user_id.is_valid()) { LOG(ERROR) << "Receive invalid " << user_id; continue; } - td_->contacts_manager_->on_get_user(std::move(user), "chatInvite"); + td_->user_manager_->on_get_user(std::move(user), "chatInvite"); participant_user_ids.push_back(user_id); } @@ -816,25 +798,25 @@ td_api::object_ptr DialogInviteLinkManager::get_chat auto chat_id = dialog_id.get_chat_id(); is_chat = true; - title = td_->contacts_manager_->get_chat_title(chat_id); - photo = td_->contacts_manager_->get_chat_dialog_photo(chat_id); - participant_count = td_->contacts_manager_->get_chat_participant_count(chat_id); - is_member = td_->contacts_manager_->get_chat_status(chat_id).is_member(); - accent_color_id_object = td_->contacts_manager_->get_chat_accent_color_id_object(chat_id); + title = td_->chat_manager_->get_chat_title(chat_id); + photo = td_->chat_manager_->get_chat_dialog_photo(chat_id); + participant_count = td_->chat_manager_->get_chat_participant_count(chat_id); + is_member = td_->chat_manager_->get_chat_status(chat_id).is_member(); + accent_color_id_object = td_->chat_manager_->get_chat_accent_color_id_object(chat_id); break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - title = td_->contacts_manager_->get_channel_title(channel_id); - photo = td_->contacts_manager_->get_channel_dialog_photo(channel_id); - is_public = td_->contacts_manager_->is_channel_public(channel_id); - is_megagroup = td_->contacts_manager_->is_megagroup_channel(channel_id); - participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id); - is_member = td_->contacts_manager_->get_channel_status(channel_id).is_member(); - is_verified = td_->contacts_manager_->get_channel_is_verified(channel_id); - is_scam = td_->contacts_manager_->get_channel_is_scam(channel_id); - is_fake = td_->contacts_manager_->get_channel_is_fake(channel_id); - accent_color_id_object = td_->contacts_manager_->get_channel_accent_color_id_object(channel_id); + title = td_->chat_manager_->get_channel_title(channel_id); + photo = td_->chat_manager_->get_channel_dialog_photo(channel_id); + is_public = td_->chat_manager_->is_channel_public(channel_id); + is_megagroup = td_->chat_manager_->is_megagroup_channel(channel_id); + participant_count = td_->chat_manager_->get_channel_participant_count(channel_id); + is_member = td_->chat_manager_->get_channel_status(channel_id).is_member(); + is_verified = td_->chat_manager_->get_channel_is_verified(channel_id); + is_scam = td_->chat_manager_->get_channel_is_scam(channel_id); + is_fake = td_->chat_manager_->get_channel_is_fake(channel_id); + accent_color_id_object = td_->chat_manager_->get_channel_accent_color_id_object(channel_id); break; } default: @@ -850,8 +832,8 @@ td_api::object_ptr DialogInviteLinkManager::get_chat accent_color_id_object = td_->theme_manager_->get_accent_color_id_object(invite_link_info->accent_color_id); description = invite_link_info->description; participant_count = invite_link_info->participant_count; - member_user_ids = td_->contacts_manager_->get_user_ids_object(invite_link_info->participant_user_ids, - "get_chat_invite_link_info_object"); + member_user_ids = td_->user_manager_->get_user_ids_object(invite_link_info->participant_user_ids, + "get_chat_invite_link_info_object"); creates_join_request = invite_link_info->creates_join_request; is_public = invite_link_info->is_public; is_verified = invite_link_info->is_verified; @@ -923,19 +905,18 @@ void DialogInviteLinkManager::remove_dialog_access_by_invite_link(DialogId dialo } Status DialogInviteLinkManager::can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "can_manage_dialog_invite_links")) { - return Status::Error(400, "Chat not found"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "can_manage_dialog_invite_links")); switch (dialog_id.get_type()) { case DialogType::User: return Status::Error(400, "Can't invite members to a private chat"); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - if (!td_->contacts_manager_->get_chat_is_active(chat_id)) { + if (!td_->chat_manager_->get_chat_is_active(chat_id)) { return Status::Error(400, "Chat is deactivated"); } - auto status = td_->contacts_manager_->get_chat_status(chat_id); + auto status = td_->chat_manager_->get_chat_status(chat_id); bool have_rights = creator_only ? status.is_creator() : status.can_manage_invite_links(); if (!have_rights) { return Status::Error(400, "Not enough rights to manage chat invite link"); @@ -944,7 +925,7 @@ Status DialogInviteLinkManager::can_manage_dialog_invite_links(DialogId dialog_i } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - auto status = td_->contacts_manager_->get_channel_status(channel_id); + auto status = td_->chat_manager_->get_channel_status(channel_id); bool have_rights = creator_only ? status.is_creator() : status.can_manage_invite_links(); if (!have_rights) { return Status::Error(400, "Not enough rights to manage chat invite link"); @@ -960,12 +941,27 @@ Status DialogInviteLinkManager::can_manage_dialog_invite_links(DialogId dialog_i return Status::OK(); } +void DialogInviteLinkManager::on_get_permanent_dialog_invite_link(DialogId dialog_id, + const DialogInviteLink &invite_link) { + switch (dialog_id.get_type()) { + case DialogType::Chat: + return td_->chat_manager_->on_update_chat_permanent_invite_link(dialog_id.get_chat_id(), invite_link); + case DialogType::Channel: + return td_->chat_manager_->on_update_channel_permanent_invite_link(dialog_id.get_channel_id(), invite_link); + case DialogType::User: + case DialogType::SecretChat: + case DialogType::None: + default: + UNREACHABLE(); + } +} + void DialogInviteLinkManager::export_dialog_invite_link(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit, bool creates_join_request, bool is_permanent, Promise> &&promise) { - td_->contacts_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 { + 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 { @@ -1030,8 +1026,8 @@ void DialogInviteLinkManager::get_dialog_invite_links(DialogId dialog_id, UserId int32 offset_date, const string &offset_invite_link, int32 limit, Promise> &&promise) { TRY_STATUS_PROMISE(promise, - can_manage_dialog_invite_links(dialog_id, creator_user_id != td_->contacts_manager_->get_my_id())); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(creator_user_id)); + can_manage_dialog_invite_links(dialog_id, creator_user_id != td_->user_manager_->get_my_id())); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(creator_user_id)); if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); @@ -1090,8 +1086,8 @@ void DialogInviteLinkManager::delete_revoked_dialog_invite_link(DialogId dialog_ void DialogInviteLinkManager::delete_all_revoked_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, Promise &&promise) { TRY_STATUS_PROMISE(promise, - can_manage_dialog_invite_links(dialog_id, creator_user_id != td_->contacts_manager_->get_my_id())); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(creator_user_id)); + can_manage_dialog_invite_links(dialog_id, creator_user_id != td_->user_manager_->get_my_id())); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(creator_user_id)); td_->create_handler(std::move(promise)) ->send(dialog_id, std::move(input_user)); diff --git a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h index b073aa84..860a8204 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogInviteLinkManager.h @@ -24,6 +24,7 @@ namespace td { +class DialogInviteLink; class Td; class DialogInviteLinkManager final : public Actor { @@ -47,6 +48,8 @@ class DialogInviteLinkManager final : public Actor { td_api::object_ptr get_chat_invite_link_info_object(const string &invite_link); + void on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link); + bool have_dialog_access_by_invite_link(DialogId dialog_id) const; void remove_dialog_access_by_invite_link(DialogId dialog_id); diff --git a/lib/tgchat/ext/td/td/telegram/DialogManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogManager.cpp index ee2152d1..d8dc69c8 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogManager.cpp @@ -7,16 +7,18 @@ #include "td/telegram/DialogManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/BotCommand.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" +#include "td/telegram/OptionManager.h" #include "td/telegram/ReportReason.h" #include "td/telegram/SecretChatId.h" #include "td/telegram/SecretChatsManager.h" @@ -25,6 +27,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Usernames.h" #include "td/utils/algorithm.h" @@ -76,7 +79,7 @@ class CheckChannelUsernameQuery final : public Td::ResultHandler { channel_id_ = channel_id; telegram_api::object_ptr input_channel; if (channel_id.is_valid()) { - input_channel = td_->contacts_manager_->get_input_channel(channel_id); + input_channel = td_->chat_manager_->get_input_channel(channel_id); } else { input_channel = telegram_api::make_object(); } @@ -96,7 +99,7 @@ class CheckChannelUsernameQuery final : public Td::ResultHandler { void on_error(Status status) final { if (channel_id_.is_valid()) { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "CheckChannelUsernameQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "CheckChannelUsernameQuery"); } promise_.set_error(std::move(status)); } @@ -121,8 +124,8 @@ class ResolveUsernameQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(DEBUG) << "Receive result for ResolveUsernameQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolveUsernameQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolveUsernameQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "ResolveUsernameQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "ResolveUsernameQuery"); promise_.set_value(DialogId(ptr->peer_)); } @@ -132,6 +135,65 @@ class ResolveUsernameQuery final : public Td::ResultHandler { } }; +class DismissSuggestionQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit DismissSuggestionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(SuggestedAction action) { + dialog_id_ = action.dialog_id_; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Read); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create( + telegram_api::help_dismissSuggestion(std::move(input_peer), action.get_suggested_action_str()))); + } + + 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, "DismissSuggestionQuery"); + promise_.set_error(std::move(status)); + } +}; + +class MigrateChatQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit MigrateChatQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChatId chat_id) { + send_query(G()->net_query_creator().create(telegram_api::messages_migrateChat(chat_id.get()), {{chat_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 MigrateChatQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class EditDialogTitleQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -149,7 +211,7 @@ class EditDialogTitleQuery final : public Td::ResultHandler { break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_editTitle(std::move(input_channel), title))); break; @@ -212,7 +274,7 @@ class EditDialogPhotoQuery final : public Td::ResultHandler { break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_editPhoto(std::move(input_channel), std::move(input_chat_photo)))); @@ -491,7 +553,7 @@ void DialogManager::tear_down() { } DialogId DialogManager::get_my_dialog_id() const { - return DialogId(td_->contacts_manager_->get_my_id()); + return DialogId(td_->user_manager_->get_my_id()); } InputDialogId DialogManager::get_input_dialog_id(DialogId dialog_id) const { @@ -504,15 +566,40 @@ InputDialogId DialogManager::get_input_dialog_id(DialogId dialog_id) const { } } +Status DialogManager::check_dialog_access(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights, + const char *source) const { + if (!have_dialog_force(dialog_id, source)) { + if (!dialog_id.is_valid()) { + return Status::Error(400, "Invalid chat identifier specified"); + } + return Status::Error(400, "Chat not found"); + } + return check_dialog_access_in_memory(dialog_id, allow_secret_chats, access_rights); +} + +Status DialogManager::check_dialog_access_in_memory(DialogId dialog_id, bool allow_secret_chats, + AccessRights access_rights) const { + if (!have_input_peer(dialog_id, allow_secret_chats, access_rights)) { + if (dialog_id.get_type() == DialogType::SecretChat && !allow_secret_chats) { + return Status::Error(400, "Not supported in secret chats"); + } + if (access_rights == AccessRights::Write || access_rights == AccessRights::Edit) { + return Status::Error(400, "Have no write access to the chat"); + } + return Status::Error(400, "Can't access the chat"); + } + return Status::OK(); +} + tl_object_ptr DialogManager::get_input_peer(DialogId dialog_id, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_input_peer_user(dialog_id.get_user_id(), access_rights); + return td_->user_manager_->get_input_peer_user(dialog_id.get_user_id(), access_rights); case DialogType::Chat: - return td_->contacts_manager_->get_input_peer_chat(dialog_id.get_chat_id(), access_rights); + return td_->chat_manager_->get_input_peer_chat(dialog_id.get_chat_id(), access_rights); case DialogType::Channel: - return td_->contacts_manager_->get_input_peer_channel(dialog_id.get_channel_id(), access_rights); + return td_->chat_manager_->get_input_peer_channel(dialog_id.get_channel_id(), access_rights); case DialogType::SecretChat: return nullptr; case DialogType::None: @@ -597,7 +684,7 @@ tl_object_ptr DialogManager::get_input_encrypt switch (dialog_id.get_type()) { case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); - return td_->contacts_manager_->get_input_encrypted_chat(secret_chat_id, access_rights); + return td_->user_manager_->get_input_encrypted_chat(secret_chat_id, access_rights); } case DialogType::User: case DialogType::Chat: @@ -609,23 +696,26 @@ tl_object_ptr DialogManager::get_input_encrypt } } -bool DialogManager::have_input_peer(DialogId dialog_id, AccessRights access_rights) const { +bool DialogManager::have_input_peer(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); - return td_->contacts_manager_->have_input_peer_user(user_id, access_rights); + return td_->user_manager_->have_input_peer_user(user_id, access_rights); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); - return td_->contacts_manager_->have_input_peer_chat(chat_id, access_rights); + return td_->chat_manager_->have_input_peer_chat(chat_id, access_rights); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); - return td_->contacts_manager_->have_input_peer_channel(channel_id, access_rights); + return td_->chat_manager_->have_input_peer_channel(channel_id, access_rights); } case DialogType::SecretChat: { + if (!allow_secret_chats) { + return false; + } SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); - return td_->contacts_manager_->have_input_encrypted_peer(secret_chat_id, access_rights); + return td_->user_manager_->have_input_encrypted_peer(secret_chat_id, access_rights); } case DialogType::None: return false; @@ -662,19 +752,19 @@ bool DialogManager::have_dialog_info(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); - return td_->contacts_manager_->have_user(user_id); + return td_->user_manager_->have_user(user_id); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); - return td_->contacts_manager_->have_chat(chat_id); + return td_->chat_manager_->have_chat(chat_id); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); - return td_->contacts_manager_->have_channel(channel_id); + return td_->chat_manager_->have_channel(channel_id); } case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); - return td_->contacts_manager_->have_secret_chat(secret_chat_id); + return td_->user_manager_->have_secret_chat(secret_chat_id); } case DialogType::None: default: @@ -685,11 +775,11 @@ bool DialogManager::have_dialog_info(DialogId dialog_id) const { bool DialogManager::is_dialog_info_received_from_server(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->is_user_received_from_server(dialog_id.get_user_id()); + return td_->user_manager_->is_user_received_from_server(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->is_chat_received_from_server(dialog_id.get_chat_id()); + return td_->chat_manager_->is_chat_received_from_server(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->is_channel_received_from_server(dialog_id.get_channel_id()); + return td_->chat_manager_->is_channel_received_from_server(dialog_id.get_channel_id()); default: return false; } @@ -699,19 +789,19 @@ bool DialogManager::have_dialog_info_force(DialogId dialog_id, const char *sourc switch (dialog_id.get_type()) { case DialogType::User: { UserId user_id = dialog_id.get_user_id(); - return td_->contacts_manager_->have_user_force(user_id, source); + return td_->user_manager_->have_user_force(user_id, source); } case DialogType::Chat: { ChatId chat_id = dialog_id.get_chat_id(); - return td_->contacts_manager_->have_chat_force(chat_id, source); + return td_->chat_manager_->have_chat_force(chat_id, source); } case DialogType::Channel: { ChannelId channel_id = dialog_id.get_channel_id(); - return td_->contacts_manager_->have_channel_force(channel_id, source); + return td_->chat_manager_->have_channel_force(channel_id, source); } case DialogType::SecretChat: { SecretChatId secret_chat_id = dialog_id.get_secret_chat_id(); - return td_->contacts_manager_->have_secret_chat_force(secret_chat_id, source); + return td_->user_manager_->have_secret_chat_force(secret_chat_id, source); } case DialogType::None: default: @@ -722,12 +812,11 @@ bool DialogManager::have_dialog_info_force(DialogId dialog_id, const char *sourc void DialogManager::reload_dialog_info(DialogId dialog_id, Promise &&promise) { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->reload_user(dialog_id.get_user_id(), std::move(promise), "reload_dialog_info"); + return td_->user_manager_->reload_user(dialog_id.get_user_id(), std::move(promise), "reload_dialog_info"); case DialogType::Chat: - return td_->contacts_manager_->reload_chat(dialog_id.get_chat_id(), std::move(promise), "reload_dialog_info"); + return td_->chat_manager_->reload_chat(dialog_id.get_chat_id(), std::move(promise), "reload_dialog_info"); case DialogType::Channel: - return td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), std::move(promise), - "reload_dialog_info"); + return td_->chat_manager_->reload_channel(dialog_id.get_channel_id(), std::move(promise), "reload_dialog_info"); default: return promise.set_error(Status::Error("Invalid chat identifier to reload")); } @@ -736,16 +825,16 @@ void DialogManager::reload_dialog_info(DialogId dialog_id, Promise &&promi void DialogManager::get_dialog_info_full(DialogId dialog_id, Promise &&promise, const char *source) { switch (dialog_id.get_type()) { case DialogType::User: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_user_full, dialog_id.get_user_id(), false, + send_closure_later(td_->user_manager_actor_, &UserManager::load_user_full, dialog_id.get_user_id(), false, std::move(promise), source); return; case DialogType::Chat: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_chat_full, dialog_id.get_chat_id(), false, + send_closure_later(td_->chat_manager_actor_, &ChatManager::load_chat_full, dialog_id.get_chat_id(), false, std::move(promise), source); return; case DialogType::Channel: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_channel_full, dialog_id.get_channel_id(), - false, std::move(promise), source); + send_closure_later(td_->chat_manager_actor_, &ChatManager::load_channel_full, dialog_id.get_channel_id(), false, + std::move(promise), source); return; case DialogType::SecretChat: return promise.set_value(Unit()); @@ -764,16 +853,16 @@ void DialogManager::reload_dialog_info_full(DialogId dialog_id, const char *sour LOG(INFO) << "Reload full info about " << dialog_id << " from " << source; switch (dialog_id.get_type()) { case DialogType::User: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_user_full, dialog_id.get_user_id(), + send_closure_later(td_->user_manager_actor_, &UserManager::reload_user_full, dialog_id.get_user_id(), Promise(), source); return; case DialogType::Chat: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_chat_full, dialog_id.get_chat_id(), + send_closure_later(td_->chat_manager_actor_, &ChatManager::reload_chat_full, dialog_id.get_chat_id(), Promise(), source); return; case DialogType::Channel: - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_channel_full, - dialog_id.get_channel_id(), Promise(), source); + send_closure_later(td_->chat_manager_actor_, &ChatManager::reload_channel_full, dialog_id.get_channel_id(), + Promise(), source); return; case DialogType::SecretChat: return; @@ -811,26 +900,26 @@ td_api::object_ptr DialogManager::get_chats_object(const std::pai return get_chats_object(dialog_ids.first, dialog_ids.second, source); } -td_api::object_ptr DialogManager::get_chat_type_object(DialogId dialog_id) const { +td_api::object_ptr DialogManager::get_chat_type_object(DialogId dialog_id, const char *source) const { switch (dialog_id.get_type()) { case DialogType::User: return td_api::make_object( - td_->contacts_manager_->get_user_id_object(dialog_id.get_user_id(), "chatTypePrivate")); + td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), source)); case DialogType::Chat: return td_api::make_object( - td_->contacts_manager_->get_basic_group_id_object(dialog_id.get_chat_id(), "chatTypeBasicGroup")); + td_->chat_manager_->get_basic_group_id_object(dialog_id.get_chat_id(), source)); case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); return td_api::make_object( - td_->contacts_manager_->get_supergroup_id_object(channel_id, "chatTypeSupergroup"), - !td_->contacts_manager_->is_megagroup_channel(channel_id)); + td_->chat_manager_->get_supergroup_id_object(channel_id, source), + !td_->chat_manager_->is_megagroup_channel(channel_id)); } case DialogType::SecretChat: { auto secret_chat_id = dialog_id.get_secret_chat_id(); - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); + auto user_id = td_->user_manager_->get_secret_chat_user_id(secret_chat_id); return td_api::make_object( - td_->contacts_manager_->get_secret_chat_id_object(secret_chat_id, "chatTypeSecret"), - td_->contacts_manager_->get_user_id_object(user_id, "chatTypeSecret")); + td_->user_manager_->get_secret_chat_id_object(secret_chat_id, source), + td_->user_manager_->get_user_id_object(user_id, source)); } case DialogType::None: default: @@ -855,6 +944,49 @@ NotificationSettingsScope DialogManager::get_dialog_notification_setting_scope(D } } +void DialogManager::migrate_dialog_to_megagroup(DialogId dialog_id, + Promise> &&promise) { + if (!have_dialog_force(dialog_id, "migrate_dialog_to_megagroup")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + if (dialog_id.get_type() != DialogType::Chat) { + return promise.set_error(Status::Error(400, "Only basic group chats can be converted to supergroup")); + } + + auto chat_id = dialog_id.get_chat_id(); + if (!td_->chat_manager_->get_chat_status(chat_id).is_creator()) { + return promise.set_error(Status::Error(400, "Need creator rights in the chat")); + } + if (td_->chat_manager_->get_chat_migrated_to_channel_id(chat_id).is_valid()) { + return on_migrate_chat_to_megagroup(chat_id, std::move(promise)); + } + + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), chat_id, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &DialogManager::on_migrate_chat_to_megagroup, chat_id, std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(chat_id); +} + +void DialogManager::on_migrate_chat_to_megagroup(ChatId chat_id, Promise> &&promise) { + auto channel_id = td_->chat_manager_->get_chat_migrated_to_channel_id(chat_id); + if (!channel_id.is_valid()) { + LOG(ERROR) << "Can't find the supergroup to which the basic group has migrated"; + return promise.set_error(Status::Error(500, "Supergroup not found")); + } + if (!td_->chat_manager_->have_channel(channel_id)) { + LOG(ERROR) << "Can't find info about the supergroup to which the basic group has migrated"; + return promise.set_error(Status::Error(500, "Supergroup info is not found")); + } + + auto dialog_id = DialogId(channel_id); + force_create_dialog(dialog_id, "on_migrate_chat_to_megagroup"); + promise.set_value(td_->messages_manager_->get_chat_object(dialog_id, "on_migrate_chat_to_megagroup")); +} + bool DialogManager::is_anonymous_administrator(DialogId dialog_id, string *author_signature) const { CHECK(dialog_id.is_valid()); @@ -870,7 +1002,7 @@ bool DialogManager::is_anonymous_administrator(DialogId dialog_id, string *autho return false; } - auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()); if (!status.is_anonymous()) { return false; } @@ -886,7 +1018,7 @@ bool DialogManager::is_group_dialog(DialogId dialog_id) const { case DialogType::Chat: return true; case DialogType::Channel: - return td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id()); + return td_->chat_manager_->is_megagroup_channel(dialog_id.get_channel_id()); default: return false; } @@ -894,7 +1026,7 @@ bool DialogManager::is_group_dialog(DialogId dialog_id) const { bool DialogManager::is_forum_channel(DialogId dialog_id) const { return dialog_id.get_type() == DialogType::Channel && - td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id()); + td_->chat_manager_->is_forum_channel(dialog_id.get_channel_id()); } bool DialogManager::is_broadcast_channel(DialogId dialog_id) const { @@ -902,7 +1034,7 @@ bool DialogManager::is_broadcast_channel(DialogId dialog_id) const { return false; } - return td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()); + return td_->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()); } bool DialogManager::on_get_dialog_error(DialogId dialog_id, const Status &status, const char *source) { @@ -928,7 +1060,7 @@ bool DialogManager::on_get_dialog_error(DialogId dialog_id, const Status &status // to be implemented if necessary break; case DialogType::Channel: - return td_->contacts_manager_->on_get_channel_error(dialog_id.get_channel_id(), status, source); + return td_->chat_manager_->on_get_channel_error(dialog_id.get_channel_id(), status, source); case DialogType::None: // to be implemented if necessary break; @@ -947,9 +1079,9 @@ void DialogManager::delete_dialog(DialogId dialog_id, Promise &&promise) { case DialogType::User: return td_->messages_manager_->delete_dialog_history(dialog_id, true, true, std::move(promise)); case DialogType::Chat: - return td_->contacts_manager_->delete_chat(dialog_id.get_chat_id(), std::move(promise)); + return td_->chat_manager_->delete_chat(dialog_id.get_chat_id(), std::move(promise)); case DialogType::Channel: - return td_->contacts_manager_->delete_channel(dialog_id.get_channel_id(), std::move(promise)); + return td_->chat_manager_->delete_channel(dialog_id.get_channel_id(), std::move(promise)); case DialogType::SecretChat: send_closure(td_->secret_chats_manager_, &SecretChatsManager::cancel_chat, dialog_id.get_secret_chat_id(), true, std::move(promise)); @@ -962,13 +1094,13 @@ void DialogManager::delete_dialog(DialogId dialog_id, Promise &&promise) { string DialogManager::get_dialog_title(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_title(dialog_id.get_user_id()); + return td_->user_manager_->get_user_title(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_title(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_title(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_title(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_title(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_title(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_title(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -979,13 +1111,13 @@ string DialogManager::get_dialog_title(DialogId dialog_id) const { const DialogPhoto *DialogManager::get_dialog_photo(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_dialog_photo(dialog_id.get_user_id()); + return td_->user_manager_->get_user_dialog_photo(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_dialog_photo(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_dialog_photo(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_dialog_photo(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_dialog_photo(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_dialog_photo(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_dialog_photo(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -996,13 +1128,13 @@ const DialogPhoto *DialogManager::get_dialog_photo(DialogId dialog_id) const { int32 DialogManager::get_dialog_accent_color_id_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_accent_color_id_object(dialog_id.get_user_id()); + return td_->user_manager_->get_user_accent_color_id_object(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_accent_color_id_object(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_accent_color_id_object(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_accent_color_id_object(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_accent_color_id_object(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_accent_color_id_object(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_accent_color_id_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1013,13 +1145,13 @@ int32 DialogManager::get_dialog_accent_color_id_object(DialogId dialog_id) const CustomEmojiId DialogManager::get_dialog_background_custom_emoji_id(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_background_custom_emoji_id(dialog_id.get_user_id()); + return td_->user_manager_->get_user_background_custom_emoji_id(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_background_custom_emoji_id(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_background_custom_emoji_id(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_background_custom_emoji_id(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_background_custom_emoji_id(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_background_custom_emoji_id(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_background_custom_emoji_id(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1030,13 +1162,13 @@ CustomEmojiId DialogManager::get_dialog_background_custom_emoji_id(DialogId dial int32 DialogManager::get_dialog_profile_accent_color_id_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_profile_accent_color_id_object(dialog_id.get_user_id()); + return td_->user_manager_->get_user_profile_accent_color_id_object(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_profile_accent_color_id_object(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_profile_accent_color_id_object(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_profile_accent_color_id_object(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_profile_accent_color_id_object(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_profile_accent_color_id_object(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_profile_accent_color_id_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1047,13 +1179,13 @@ int32 DialogManager::get_dialog_profile_accent_color_id_object(DialogId dialog_i CustomEmojiId DialogManager::get_dialog_profile_background_custom_emoji_id(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_profile_background_custom_emoji_id(dialog_id.get_user_id()); + return td_->user_manager_->get_user_profile_background_custom_emoji_id(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_profile_background_custom_emoji_id(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_profile_background_custom_emoji_id(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_profile_background_custom_emoji_id(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_profile_background_custom_emoji_id(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_profile_background_custom_emoji_id(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_profile_background_custom_emoji_id(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1064,13 +1196,13 @@ CustomEmojiId DialogManager::get_dialog_profile_background_custom_emoji_id(Dialo RestrictedRights DialogManager::get_dialog_default_permissions(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id()); + return td_->user_manager_->get_user_default_permissions(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_default_permissions(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_default_permissions(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_default_permissions(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_default_permissions(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1082,13 +1214,13 @@ RestrictedRights DialogManager::get_dialog_default_permissions(DialogId dialog_i td_api::object_ptr DialogManager::get_dialog_emoji_status_object(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_emoji_status_object(dialog_id.get_user_id()); + return td_->user_manager_->get_user_emoji_status_object(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_emoji_status_object(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_emoji_status_object(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_emoji_status_object(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_emoji_status_object(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_emoji_status_object(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_emoji_status_object(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1099,13 +1231,13 @@ td_api::object_ptr DialogManager::get_dialog_emoji_status_o string DialogManager::get_dialog_about(DialogId dialog_id) { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_about(dialog_id.get_user_id()); + return td_->user_manager_->get_user_about(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_about(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_about(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_about(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_about(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_secret_chat_about(dialog_id.get_secret_chat_id()); + return td_->user_manager_->get_secret_chat_about(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); @@ -1116,14 +1248,14 @@ string DialogManager::get_dialog_about(DialogId dialog_id) { string DialogManager::get_dialog_search_text(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->get_user_search_text(dialog_id.get_user_id()); + return td_->user_manager_->get_user_search_text(dialog_id.get_user_id()); case DialogType::Chat: - return td_->contacts_manager_->get_chat_title(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_title(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_search_text(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_search_text(dialog_id.get_channel_id()); case DialogType::SecretChat: - return td_->contacts_manager_->get_user_search_text( - td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); + return td_->user_manager_->get_user_search_text( + td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); case DialogType::None: default: UNREACHABLE(); @@ -1136,9 +1268,9 @@ bool DialogManager::get_dialog_has_protected_content(DialogId dialog_id) const { case DialogType::User: return false; case DialogType::Chat: - return td_->contacts_manager_->get_chat_has_protected_content(dialog_id.get_chat_id()); + return td_->chat_manager_->get_chat_has_protected_content(dialog_id.get_chat_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_has_protected_content(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_has_protected_content(dialog_id.get_channel_id()); case DialogType::SecretChat: return false; case DialogType::None: @@ -1157,20 +1289,20 @@ bool DialogManager::is_dialog_action_unneeded(DialogId dialog_id) const { if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) { UserId user_id = dialog_type == DialogType::User ? dialog_id.get_user_id() - : td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - if (td_->contacts_manager_->is_user_deleted(user_id)) { + : td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + if (td_->user_manager_->is_user_deleted(user_id)) { return true; } - if (td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_support(user_id)) { + if (td_->user_manager_->is_user_bot(user_id) && !td_->user_manager_->is_user_support(user_id)) { return true; } - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { return true; } if (!td_->auth_manager_->is_bot()) { - if (td_->contacts_manager_->is_user_status_exact(user_id)) { - if (!td_->contacts_manager_->is_user_online(user_id, 30)) { + if (td_->user_manager_->is_user_status_exact(user_id)) { + if (!td_->user_manager_->is_user_online(user_id, 30)) { return true; } } else { @@ -1196,15 +1328,15 @@ void DialogManager::set_dialog_title(DialogId dialog_id, const string &title, Pr return promise.set_error(Status::Error(400, "Can't change private chat title")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || - (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { + (td_->auth_manager_->is_bot() && !td_->chat_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat title")); } break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat title")); } @@ -1237,15 +1369,15 @@ void DialogManager::set_dialog_photo(DialogId dialog_id, const td_api::object_pt return promise.set_error(Status::Error(400, "Can't change private chat photo")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || - (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { + (td_->auth_manager_->is_bot() && !td_->chat_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat photo")); } break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat photo")); } @@ -1265,7 +1397,7 @@ void DialogManager::set_dialog_photo(DialogId dialog_id, const td_api::object_pt switch (input_photo->get_id()) { case td_api::inputChatPhotoPrevious::ID: { auto photo = static_cast(input_photo.get()); - auto file_id = td_->contacts_manager_->get_profile_photo_file_id(photo->chat_photo_id_); + auto file_id = td_->user_manager_->get_profile_photo_file_id(photo->chat_photo_id_); if (!file_id.is_valid()) { return promise.set_error(Status::Error(400, "Unknown profile photo ID specified")); } @@ -1441,15 +1573,14 @@ void DialogManager::set_dialog_accent_color(DialogId dialog_id, AccentColorId ac switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { - return td_->contacts_manager_->set_accent_color(accent_color_id, background_custom_emoji_id, - std::move(promise)); + return td_->user_manager_->set_accent_color(accent_color_id, background_custom_emoji_id, std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: - return td_->contacts_manager_->set_channel_accent_color(dialog_id.get_channel_id(), accent_color_id, - background_custom_emoji_id, std::move(promise)); + return td_->chat_manager_->set_channel_accent_color(dialog_id.get_channel_id(), accent_color_id, + background_custom_emoji_id, std::move(promise)); case DialogType::SecretChat: break; case DialogType::None: @@ -1469,14 +1600,14 @@ void DialogManager::set_dialog_profile_accent_color(DialogId dialog_id, AccentCo switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { - return td_->contacts_manager_->set_profile_accent_color(profile_accent_color_id, - profile_background_custom_emoji_id, std::move(promise)); + return td_->user_manager_->set_profile_accent_color(profile_accent_color_id, profile_background_custom_emoji_id, + std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: - return td_->contacts_manager_->set_channel_profile_accent_color( + return td_->chat_manager_->set_channel_profile_accent_color( dialog_id.get_channel_id(), profile_accent_color_id, profile_background_custom_emoji_id, std::move(promise)); case DialogType::SecretChat: break; @@ -1490,12 +1621,7 @@ void DialogManager::set_dialog_profile_accent_color(DialogId dialog_id, AccentCo void DialogManager::set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr &permissions, Promise &&promise) { - if (!have_dialog_force(dialog_id, "set_dialog_permissions")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, check_dialog_access(dialog_id, false, AccessRights::Write, "set_dialog_permissions")); if (permissions == nullptr) { return promise.set_error(Status::Error(400, "New permissions must be non-empty")); @@ -1507,7 +1633,7 @@ void DialogManager::set_dialog_permissions(DialogId dialog_id, return promise.set_error(Status::Error(400, "Can't change private chat permissions")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions")); } @@ -1517,7 +1643,7 @@ void DialogManager::set_dialog_permissions(DialogId dialog_id, if (is_broadcast_channel(dialog_id)) { return promise.set_error(Status::Error(400, "Can't change channel chat permissions")); } - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions")); } @@ -1525,7 +1651,6 @@ void DialogManager::set_dialog_permissions(DialogId dialog_id, break; } case DialogType::SecretChat: - return promise.set_error(Status::Error(400, "Can't change secret chat permissions")); case DialogType::None: default: UNREACHABLE(); @@ -1551,14 +1676,13 @@ void DialogManager::set_dialog_emoji_status(DialogId dialog_id, const EmojiStatu switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == get_my_dialog_id()) { - return td_->contacts_manager_->set_emoji_status(emoji_status, std::move(promise)); + return td_->user_manager_->set_emoji_status(emoji_status, std::move(promise)); } break; case DialogType::Chat: break; case DialogType::Channel: - return td_->contacts_manager_->set_channel_emoji_status(dialog_id.get_channel_id(), emoji_status, - std::move(promise)); + return td_->chat_manager_->set_channel_emoji_status(dialog_id.get_channel_id(), emoji_status, std::move(promise)); case DialogType::SecretChat: break; case DialogType::None: @@ -1570,32 +1694,28 @@ void DialogManager::set_dialog_emoji_status(DialogId dialog_id, const EmojiStatu void DialogManager::toggle_dialog_has_protected_content(DialogId dialog_id, bool has_protected_content, Promise &&promise) { - if (!have_dialog_force(dialog_id, "toggle_dialog_has_protected_content")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, + check_dialog_access(dialog_id, false, AccessRights::Read, "toggle_dialog_has_protected_content")); switch (dialog_id.get_type()) { case DialogType::User: - case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't restrict saving content in the chat")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_status(chat_id); + auto status = td_->chat_manager_->get_chat_status(chat_id); if (!status.is_creator()) { return promise.set_error(Status::Error(400, "Only owner can restrict saving content")); } break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()); if (!status.is_creator()) { return promise.set_error(Status::Error(400, "Only owner can restrict saving content")); } break; } + case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); @@ -1619,10 +1739,9 @@ void DialogManager::set_dialog_description(DialogId dialog_id, const string &des case DialogType::User: return promise.set_error(Status::Error(400, "Can't change private chat description")); case DialogType::Chat: - return td_->contacts_manager_->set_chat_description(dialog_id.get_chat_id(), description, std::move(promise)); + return td_->chat_manager_->set_chat_description(dialog_id.get_chat_id(), description, std::move(promise)); case DialogType::Channel: - return td_->contacts_manager_->set_channel_description(dialog_id.get_channel_id(), description, - std::move(promise)); + return td_->chat_manager_->set_channel_description(dialog_id.get_channel_id(), description, std::move(promise)); case DialogType::SecretChat: return promise.set_error(Status::Error(400, "Can't change secret chat description")); case DialogType::None: @@ -1642,7 +1761,7 @@ void DialogManager::set_dialog_location(DialogId dialog_id, const DialogLocation case DialogType::SecretChat: return promise.set_error(Status::Error(400, "The chat can't have location")); case DialogType::Channel: - return td_->contacts_manager_->set_channel_location(dialog_id.get_channel_id(), location, std::move(promise)); + return td_->chat_manager_->set_channel_location(dialog_id.get_channel_id(), location, std::move(promise)); case DialogType::None: default: UNREACHABLE(); @@ -1653,11 +1772,11 @@ bool DialogManager::can_report_dialog(DialogId dialog_id) const { // doesn't include possibility of report from action bar switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->can_report_user(dialog_id.get_user_id()); + return td_->user_manager_->can_report_user(dialog_id.get_user_id()); case DialogType::Chat: return false; case DialogType::Channel: - return !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_creator(); + return !td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).is_creator(); case DialogType::SecretChat: return false; case DialogType::None: @@ -1669,13 +1788,7 @@ bool DialogManager::can_report_dialog(DialogId dialog_id) const { void DialogManager::report_dialog(DialogId dialog_id, const vector &message_ids, ReportReason &&reason, Promise &&promise) { - if (!have_dialog_force(dialog_id, "report_dialog")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, check_dialog_access(dialog_id, true, AccessRights::Read, "report_dialog")); MessagesManager::ReportDialogFromActionBar report_from_action_bar; if (reason.is_spam() && message_ids.empty()) { @@ -1713,13 +1826,7 @@ void DialogManager::report_dialog(DialogId dialog_id, const vector &m void DialogManager::report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason, Promise &&promise) { - if (!have_dialog_force(dialog_id, "report_dialog_photo")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, check_dialog_access(dialog_id, false, AccessRights::Read, "report_dialog_photo")); if (!can_report_dialog(dialog_id)) { return promise.set_error(Status::Error(400, "Chat photo can't be reported")); @@ -1744,15 +1851,15 @@ Status DialogManager::can_pin_messages(DialogId dialog_id) const { break; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_pin_messages() || - (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { + (td_->auth_manager_->is_bot() && !td_->chat_manager_->is_appointed_chat_administrator(chat_id))) { return Status::Error(400, "Not enough rights to manage pinned messages in the chat"); } break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); bool can_pin = is_broadcast_channel(dialog_id) ? status.can_edit_messages() : status.can_pin_messages(); if (!can_pin) { return Status::Error(400, "Not enough rights to manage pinned messages in the chat"); @@ -1765,21 +1872,35 @@ Status DialogManager::can_pin_messages(DialogId dialog_id) const { default: UNREACHABLE(); } - if (!have_input_peer(dialog_id, AccessRights::Write)) { + if (!have_input_peer(dialog_id, false, AccessRights::Write)) { return Status::Error(400, "Not enough rights"); } return Status::OK(); } +bool DialogManager::can_use_premium_custom_emoji_in_dialog(DialogId dialog_id) const { + if (td_->auth_manager_->is_bot()) { + return true; + } + if (dialog_id == get_my_dialog_id() || td_->option_manager_->get_option_boolean("is_premium")) { + return true; + } + if (dialog_id.get_type() == DialogType::Channel && + td_->chat_manager_->can_use_premium_custom_emoji_in_channel(dialog_id.get_channel_id())) { + return true; + } + return false; +} + bool DialogManager::is_dialog_removed_from_dialog_list(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::User: break; case DialogType::Chat: - return !td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id()); + return !td_->chat_manager_->get_chat_is_active(dialog_id.get_chat_id()); case DialogType::Channel: - return !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_member(); + return !td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).is_member(); case DialogType::SecretChat: break; case DialogType::None: @@ -1790,6 +1911,40 @@ bool DialogManager::is_dialog_removed_from_dialog_list(DialogId dialog_id) const return false; } +void DialogManager::on_update_dialog_bot_commands( + DialogId dialog_id, UserId bot_user_id, vector> &&bot_commands) { + if (!bot_user_id.is_valid()) { + LOG(ERROR) << "Receive updateBotCommands about invalid " << bot_user_id; + return; + } + if (!td_->user_manager_->have_user_force(bot_user_id, "on_update_dialog_bot_commands") || + !td_->user_manager_->is_user_bot(bot_user_id)) { + return; + } + if (td_->auth_manager_->is_bot()) { + return; + } + + switch (dialog_id.get_type()) { + case DialogType::User: + if (DialogId(bot_user_id) != dialog_id) { + LOG(ERROR) << "Receive commands of " << bot_user_id << " in " << dialog_id; + return; + } + return td_->user_manager_->on_update_user_commands(bot_user_id, std::move(bot_commands)); + case DialogType::Chat: + return td_->chat_manager_->on_update_chat_bot_commands(dialog_id.get_chat_id(), + BotCommands(bot_user_id, std::move(bot_commands))); + case DialogType::Channel: + return td_->chat_manager_->on_update_channel_bot_commands(dialog_id.get_channel_id(), + BotCommands(bot_user_id, std::move(bot_commands))); + case DialogType::SecretChat: + default: + LOG(ERROR) << "Receive updateBotCommands in " << dialog_id; + break; + } +} + void DialogManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames) { LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames; @@ -1815,7 +1970,8 @@ void DialogManager::on_dialog_usernames_received(DialogId dialog_id, const Usern void DialogManager::check_dialog_username(DialogId dialog_id, const string &username, Promise &&promise) { - if (dialog_id != DialogId() && !have_dialog_force(dialog_id, "check_dialog_username")) { + if (dialog_id != DialogId() && dialog_id.get_type() != DialogType::User && + !have_dialog_force(dialog_id, "check_dialog_username")) { return promise.set_error(Status::Error(400, "Chat not found")); } @@ -1828,10 +1984,10 @@ void DialogManager::check_dialog_username(DialogId dialog_id, const string &user } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_status(channel_id).is_creator()) { + if (!td_->chat_manager_->get_channel_status(channel_id).is_creator()) { return promise.set_error(Status::Error(400, "Not enough rights to change username")); } - if (username == td_->contacts_manager_->get_channel_editable_username(channel_id)) { + if (username == td_->chat_manager_->get_channel_editable_username(channel_id)) { return promise.set_value(CheckDialogUsernameResult::Ok); } break; @@ -1971,7 +2127,7 @@ void DialogManager::on_resolved_username(const string &username, Result promise) { CHECK(username.empty() == channel_id.is_valid()); - bool have_dialog = username.empty() ? td_->contacts_manager_->have_channel_force(channel_id, "resolve_dialog") + bool have_dialog = username.empty() ? td_->chat_manager_->have_channel_force(channel_id, "resolve_dialog") : get_resolved_dialog_by_username(username).is_valid(); if (!have_dialog) { auto query_promise = PromiseCreator::lambda( @@ -1982,7 +2138,7 @@ void DialogManager::resolve_dialog(const string &username, ChannelId channel_id, send_closure(actor_id, &DialogManager::on_resolve_dialog, username, channel_id, std::move(promise)); }); if (username.empty()) { - td_->contacts_manager_->reload_channel(channel_id, std::move(query_promise), "resolve_dialog"); + td_->chat_manager_->reload_channel(channel_id, std::move(query_promise), "resolve_dialog"); } else { send_resolve_dialog_username_query(username, std::move(query_promise)); } @@ -1997,7 +2153,7 @@ void DialogManager::on_resolve_dialog(const string &username, ChannelId channel_ DialogId dialog_id; if (username.empty()) { - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(500, "Chat info not found")); } @@ -2056,12 +2212,11 @@ DialogId DialogManager::search_public_dialog(const string &username_to_search, b return DialogId(); } - if (have_input_peer(dialog_id, AccessRights::Read)) { + if (have_input_peer(dialog_id, false, AccessRights::Read)) { if (!force && reload_voice_chat_on_search_usernames_.count(username)) { reload_voice_chat_on_search_usernames_.erase(username); if (dialog_id.get_type() == DialogType::Channel) { - td_->contacts_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise), - "search_public_dialog"); + td_->chat_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise), "search_public_dialog"); return DialogId(); } } @@ -2102,8 +2257,7 @@ void DialogManager::drop_username(const string &username) { auto resolved_username = resolved_usernames_.get(cleaned_username); if (resolved_username.dialog_id.is_valid()) { auto dialog_id = resolved_username.dialog_id; - if (have_input_peer(dialog_id, AccessRights::Read)) { - CHECK(dialog_id.get_type() != DialogType::SecretChat); + if (have_input_peer(dialog_id, false, AccessRights::Read)) { reload_dialog_info_full(dialog_id, "drop_username"); } @@ -2111,4 +2265,85 @@ void DialogManager::drop_username(const string &username) { } } +void DialogManager::set_dialog_pending_suggestions(DialogId dialog_id, vector &&pending_suggestions) { + if (dismiss_suggested_action_queries_.count(dialog_id) != 0) { + return; + } + auto it = dialog_suggested_actions_.find(dialog_id); + if (it == dialog_suggested_actions_.end() && !pending_suggestions.empty()) { + return; + } + vector suggested_actions; + for (auto &action_str : pending_suggestions) { + SuggestedAction suggested_action(action_str, dialog_id); + if (!suggested_action.is_empty()) { + if (suggested_action == SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, dialog_id} && + (dialog_id.get_type() != DialogType::Channel || + !td_->chat_manager_->can_convert_channel_to_gigagroup(dialog_id.get_channel_id()))) { + LOG(INFO) << "Skip ConvertToGigagroup suggested action"; + } else { + suggested_actions.push_back(suggested_action); + } + } + } + if (it == dialog_suggested_actions_.end()) { + it = dialog_suggested_actions_.emplace(dialog_id, vector()).first; + } + update_suggested_actions(it->second, std::move(suggested_actions)); + if (it->second.empty()) { + dialog_suggested_actions_.erase(it); + } +} + +void DialogManager::remove_dialog_suggested_action(SuggestedAction action) { + auto it = dialog_suggested_actions_.find(action.dialog_id_); + if (it == dialog_suggested_actions_.end()) { + return; + } + remove_suggested_action(it->second, action); + if (it->second.empty()) { + dialog_suggested_actions_.erase(it); + } +} + +void DialogManager::dismiss_dialog_suggested_action(SuggestedAction action, Promise &&promise) { + auto dialog_id = action.dialog_id_; + TRY_STATUS_PROMISE(promise, + check_dialog_access(dialog_id, false, AccessRights::Read, "dismiss_dialog_suggested_action")); + + auto it = dialog_suggested_actions_.find(dialog_id); + if (it == dialog_suggested_actions_.end() || !td::contains(it->second, action)) { + return promise.set_value(Unit()); + } + + auto action_str = action.get_suggested_action_str(); + if (action_str.empty()) { + return promise.set_value(Unit()); + } + + auto &queries = dismiss_suggested_action_queries_[dialog_id]; + queries.push_back(std::move(promise)); + if (queries.size() == 1) { + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), action](Result &&result) { + send_closure(actor_id, &DialogManager::on_dismiss_suggested_action, action, std::move(result)); + }); + td_->create_handler(std::move(query_promise))->send(std::move(action)); + } +} + +void DialogManager::on_dismiss_suggested_action(SuggestedAction action, Result &&result) { + auto it = dismiss_suggested_action_queries_.find(action.dialog_id_); + CHECK(it != dismiss_suggested_action_queries_.end()); + auto promises = std::move(it->second); + dismiss_suggested_action_queries_.erase(it); + + if (result.is_error()) { + return fail_promises(promises, result.move_as_error()); + } + + remove_dialog_suggested_action(action); + + set_promises(promises); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/DialogManager.h b/lib/tgchat/ext/td/td/telegram/DialogManager.h index 83b7f9a4..5ee26de1 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogManager.h @@ -9,6 +9,7 @@ #include "td/telegram/AccentColorId.h" #include "td/telegram/AccessRights.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChatId.h" #include "td/telegram/CustomEmojiId.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogLocation.h" @@ -19,8 +20,10 @@ #include "td/telegram/MessageId.h" #include "td/telegram/NotificationSettingsScope.h" #include "td/telegram/Photo.h" +#include "td/telegram/SuggestedAction.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" #include "td/actor/actor.h" @@ -69,7 +72,12 @@ class DialogManager final : public Actor { tl_object_ptr get_input_encrypted_chat(DialogId dialog_id, AccessRights access_rights) const; - bool have_input_peer(DialogId dialog_id, AccessRights access_rights) const; + Status check_dialog_access(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights, + const char *source) const; + + Status check_dialog_access_in_memory(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights) const; + + bool have_input_peer(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights) const; bool have_dialog_force(DialogId dialog_id, const char *source) const; @@ -103,10 +111,12 @@ class DialogManager final : public Actor { td_api::object_ptr get_chats_object(const std::pair> &dialog_ids, const char *source) const; - td_api::object_ptr get_chat_type_object(DialogId dialog_id) const; + td_api::object_ptr get_chat_type_object(DialogId dialog_id, const char *source) const; NotificationSettingsScope get_dialog_notification_setting_scope(DialogId dialog_id) const; + void migrate_dialog_to_megagroup(DialogId dialog_id, Promise> &&promise); + bool is_anonymous_administrator(DialogId dialog_id, string *author_signature) const; bool is_group_dialog(DialogId dialog_id) const; @@ -174,11 +184,16 @@ class DialogManager final : public Actor { Status can_pin_messages(DialogId dialog_id) const; + bool can_use_premium_custom_emoji_in_dialog(DialogId dialog_id) const; + bool is_dialog_removed_from_dialog_list(DialogId dialog_id) const; void upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation, double main_frame_timestamp, bool is_reupload, Promise &&promise, vector bad_parts = {}); + void on_update_dialog_bot_commands(DialogId dialog_id, UserId bot_user_id, + vector> &&bot_commands); + void on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames); void on_dialog_usernames_received(DialogId dialog_id, const Usernames &usernames, bool from_database); @@ -206,6 +221,12 @@ class DialogManager final : public Actor { void reload_voice_chat_on_search(const string &username); + void set_dialog_pending_suggestions(DialogId dialog_id, vector &&pending_suggestions); + + void dismiss_dialog_suggested_action(SuggestedAction action, Promise &&promise); + + void remove_dialog_suggested_action(SuggestedAction action); + private: static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title @@ -213,6 +234,8 @@ class DialogManager final : public Actor { void tear_down() final; + void on_migrate_chat_to_megagroup(ChatId chat_id, Promise> &&promise); + void on_upload_dialog_photo(FileId file_id, telegram_api::object_ptr input_file); void on_upload_dialog_photo_error(FileId file_id, Status status); @@ -229,6 +252,8 @@ class DialogManager final : public Actor { void on_resolve_dialog(const string &username, ChannelId channel_id, Promise &&promise); + void on_dismiss_suggested_action(SuggestedAction action, Result &&result); + class UploadDialogPhotoCallback; std::shared_ptr upload_dialog_photo_callback_; @@ -264,6 +289,9 @@ class DialogManager final : public Actor { FlatHashMap>> resolve_dialog_username_queries_; + FlatHashMap, DialogIdHash> dialog_suggested_actions_; + FlatHashMap>, DialogIdHash> dismiss_suggested_action_queries_; + Td *td_; ActorShared<> parent_; }; diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp index 8b7adb00..7c7b1f22 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipant.cpp @@ -6,7 +6,7 @@ // #include "td/telegram/DialogParticipant.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Global.h" #include "td/telegram/misc.h" #include "td/telegram/Td.h" @@ -827,7 +827,7 @@ td_api::object_ptr DialogParticipants::get_chat_members_obj vector> chat_members; chat_members.reserve(participants_.size()); for (auto &participant : participants_) { - chat_members.push_back(td->contacts_manager_->get_chat_member_object(participant, source)); + chat_members.push_back(td->chat_manager_->get_chat_member_object(participant, source)); } return td_api::make_object(total_count_, std::move(chat_members)); diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipantFilter.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipantFilter.cpp index 3dd63424..32f6581c 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipantFilter.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipantFilter.cpp @@ -6,10 +6,10 @@ // #include "td/telegram/DialogParticipantFilter.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogParticipant.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" namespace td { @@ -119,7 +119,7 @@ bool DialogParticipantFilter::is_dialog_participant_suitable(const Td *td, const switch (type_) { case Type::Contacts: return participant.dialog_id_.get_type() == DialogType::User && - td->contacts_manager_->is_user_contact(participant.dialog_id_.get_user_id()); + td->user_manager_->is_user_contact(participant.dialog_id_.get_user_id()); case Type::Administrators: return participant.status_.is_administrator(); case Type::Members: @@ -132,7 +132,7 @@ bool DialogParticipantFilter::is_dialog_participant_suitable(const Td *td, const return true; case Type::Bots: return participant.dialog_id_.get_type() == DialogType::User && - td->contacts_manager_->is_user_bot(participant.dialog_id_.get_user_id()); + td->user_manager_->is_user_bot(participant.dialog_id_.get_user_id()); default: UNREACHABLE(); return false; diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp index 15ac8bfc..df50409c 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.cpp @@ -11,18 +11,20 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/ChannelParticipantFilter.h" #include "td/telegram/ChannelType.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" +#include "td/telegram/MissingInvitee.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/db/SqliteKeyValueAsync.h" @@ -95,11 +97,9 @@ class GetChatJoinRequestsQuery final : public Td::ResultHandler { invite_link.empty() && query.empty() && offset_date == 0 && !offset_user_id.is_valid() && limit >= 3; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); - auto r_input_user = td_->contacts_manager_->get_input_user(offset_user_id); + auto r_input_user = td_->user_manager_->get_input_user(offset_user_id); if (r_input_user.is_error()) { r_input_user = make_tl_object(); } @@ -125,7 +125,7 @@ class GetChatJoinRequestsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetChatJoinRequestsQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatJoinRequestsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetChatJoinRequestsQuery"); int32 total_count = result->count_; if (total_count < static_cast(result->importers_.size())) { @@ -146,7 +146,7 @@ class GetChatJoinRequestsQuery final : public Td::ResultHandler { recent_requesters.push_back(user_id.get()); } join_requests.push_back(td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "chatJoinRequest"), request->date_, request->about_)); + td_->user_manager_->get_user_id_object(user_id, "chatJoinRequest"), request->date_, request->about_)); } if (is_full_list_) { td_->messages_manager_->on_update_dialog_pending_join_requests(dialog_id_, total_count, @@ -172,11 +172,9 @@ class HideChatJoinRequestQuery final : public Td::ResultHandler { void send(DialogId dialog_id, UserId user_id, bool approve) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); - TRY_RESULT_PROMISE(promise_, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise_, input_user, td_->user_manager_->get_input_user(user_id)); int32 flags = 0; if (approve) { @@ -214,9 +212,7 @@ class HideAllChatJoinRequestsQuery final : public Td::ResultHandler { void send(DialogId dialog_id, const string &invite_link, bool approve) { dialog_id_ = dialog_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); - if (input_peer == nullptr) { - return on_error(Status::Error(400, "Can't access the chat")); - } + CHECK(input_peer != nullptr); int32 flags = 0; if (approve) { @@ -255,7 +251,7 @@ class GetChannelAdministratorsQuery final : public Td::ResultHandler { } void send(ChannelId channel_id, int64 hash) { - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Supergroup not found")); } @@ -279,10 +275,10 @@ class GetChannelAdministratorsQuery final : public Td::ResultHandler { switch (participants_ptr->get_id()) { case telegram_api::channels_channelParticipants::ID: { auto participants = telegram_api::move_object_as(participants_ptr); - td_->contacts_manager_->on_get_users(std::move(participants->users_), "GetChannelAdministratorsQuery"); - td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "GetChannelAdministratorsQuery"); + td_->user_manager_->on_get_users(std::move(participants->users_), "GetChannelAdministratorsQuery"); + td_->chat_manager_->on_get_chats(std::move(participants->chats_), "GetChannelAdministratorsQuery"); - auto channel_type = td_->contacts_manager_->get_channel_type(channel_id_); + auto channel_type = td_->chat_manager_->get_channel_type(channel_id_); vector administrators; administrators.reserve(participants->participants_.size()); for (auto &participant : participants->participants_) { @@ -296,8 +292,8 @@ class GetChannelAdministratorsQuery final : public Td::ResultHandler { dialog_participant.status_.get_rank(), dialog_participant.status_.is_creator()); } - td_->contacts_manager_->on_update_channel_administrator_count(channel_id_, - narrow_cast(administrators.size())); + td_->chat_manager_->on_update_channel_administrator_count(channel_id_, + narrow_cast(administrators.size())); td_->dialog_participant_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrators), true, false); @@ -313,7 +309,7 @@ class GetChannelAdministratorsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery"); promise_.set_error(std::move(status)); } }; @@ -328,7 +324,7 @@ class GetChannelParticipantQuery final : public Td::ResultHandler { } void send(ChannelId channel_id, DialogId participant_dialog_id, tl_object_ptr &&input_peer) { - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Supergroup not found")); } @@ -350,10 +346,9 @@ class GetChannelParticipantQuery final : public Td::ResultHandler { auto participant = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetChannelParticipantQuery: " << to_string(participant); - td_->contacts_manager_->on_get_users(std::move(participant->users_), "GetChannelParticipantQuery"); - td_->contacts_manager_->on_get_chats(std::move(participant->chats_), "GetChannelParticipantQuery"); - DialogParticipant result(std::move(participant->participant_), - td_->contacts_manager_->get_channel_type(channel_id_)); + td_->user_manager_->on_get_users(std::move(participant->users_), "GetChannelParticipantQuery"); + td_->chat_manager_->on_get_chats(std::move(participant->chats_), "GetChannelParticipantQuery"); + DialogParticipant result(std::move(participant->participant_), td_->chat_manager_->get_channel_type(channel_id_)); if (!result.is_valid()) { LOG(ERROR) << "Receive invalid " << result; return promise_.set_error(Status::Error(500, "Receive invalid chat member")); @@ -368,7 +363,7 @@ class GetChannelParticipantQuery final : public Td::ResultHandler { } if (participant_dialog_id_.get_type() != DialogType::Channel) { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantQuery"); } promise_.set_error(std::move(status)); } @@ -385,7 +380,7 @@ class GetChannelParticipantsQuery final : public Td::ResultHandler { } void send(ChannelId channel_id, const ChannelParticipantFilter &filter, int32 offset, int32 limit) { - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Supergroup not found")); } @@ -417,18 +412,19 @@ class GetChannelParticipantsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantsQuery"); promise_.set_error(std::move(status)); } }; class AddChatUserQuery final : public Td::ResultHandler { - Promise promise_; + Promise> promise_; ChatId chat_id_; UserId user_id_; public: - explicit AddChatUserQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit AddChatUserQuery(Promise> &&promise) + : promise_(std::move(promise)) { } void send(ChatId chat_id, UserId user_id, tl_object_ptr &&input_user, int32 forward_limit) { @@ -446,15 +442,15 @@ class AddChatUserQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for AddChatUserQuery: " << to_string(ptr); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + td_->updates_manager_->on_get_updates( + std::move(ptr->updates_), + PromiseCreator::lambda( + [missing_invitees = MissingInvitees(std::move(ptr->missing_invitees_)) + .get_failed_to_add_members_object(td_->user_manager_.get()), + promise = std::move(promise_)](Result) mutable { promise.set_value(std::move(missing_invitees)); })); } void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && status.message() == "USER_PRIVACY_RESTRICTED") { - td_->dialog_participant_manager_->send_update_add_chat_members_privacy_forbidden(DialogId(chat_id_), {user_id_}, - "AddChatUserQuery"); - return promise_.set_error(Status::Error(406, "USER_PRIVACY_RESTRICTED")); - } promise_.set_error(std::move(status)); } }; @@ -493,12 +489,6 @@ class EditChatAdminQuery final : public Td::ResultHandler { } void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && status.message() == "USER_PRIVACY_RESTRICTED") { - // impossible now, because the user must be in the chat already - td_->dialog_participant_manager_->send_update_add_chat_members_privacy_forbidden(DialogId(chat_id_), {user_id_}, - "EditChatAdminQuery"); - return promise_.set_error(Status::Error(406, "USER_PRIVACY_RESTRICTED")); - } promise_.set_error(std::move(status)); } }; @@ -545,7 +535,7 @@ class JoinChannelQuery final : public Td::ResultHandler { void send(ChannelId channel_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query( G()->net_query_creator().create(telegram_api::channels_joinChannel(std::move(input_channel)), {{channel_id}})); @@ -563,25 +553,26 @@ class JoinChannelQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "JoinChannelQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "JoinChannelQuery"); promise_.set_error(std::move(status)); } }; class InviteToChannelQuery final : public Td::ResultHandler { - Promise promise_; + Promise> promise_; ChannelId channel_id_; vector user_ids_; public: - explicit InviteToChannelQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit InviteToChannelQuery(Promise> &&promise) + : promise_(std::move(promise)) { } void send(ChannelId channel_id, vector user_ids, vector> &&input_users) { channel_id_ = channel_id; user_ids_ = std::move(user_ids); - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_inviteToChannel(std::move(input_channel), std::move(input_users)))); @@ -595,31 +586,18 @@ class InviteToChannelQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for InviteToChannelQuery: " << to_string(ptr); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery"); - auto user_ids = td_->updates_manager_->extract_group_invite_privacy_forbidden_updates(ptr); - auto promise = PromiseCreator::lambda([dialog_id = DialogId(channel_id_), user_ids = std::move(user_ids), - promise = std::move(promise_)](Result &&result) mutable { - if (result.is_error()) { - return promise.set_error(result.move_as_error()); - } - promise.set_value(Unit()); - if (!user_ids.empty()) { - send_closure(G()->dialog_participant_manager(), - &DialogParticipantManager::send_update_add_chat_members_privacy_forbidden, dialog_id, - std::move(user_ids), "InviteToChannelQuery"); - } - }); - td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise)); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery"); + td_->updates_manager_->on_get_updates( + std::move(std::move(ptr->updates_)), + PromiseCreator::lambda( + [missing_invitees = MissingInvitees(std::move(ptr->missing_invitees_)) + .get_failed_to_add_members_object(td_->user_manager_.get()), + promise = std::move(promise_)](Result) mutable { promise.set_value(std::move(missing_invitees)); })); } void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && status.message() == "USER_PRIVACY_RESTRICTED") { - td_->dialog_participant_manager_->send_update_add_chat_members_privacy_forbidden( - DialogId(channel_id_), std::move(user_ids_), "InviteToChannelQuery"); - return promise_.set_error(Status::Error(406, "USER_PRIVACY_RESTRICTED")); - } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "InviteToChannelQuery"); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "InviteToChannelQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery"); promise_.set_error(std::move(status)); } }; @@ -639,7 +617,7 @@ class EditChannelAdminQuery final : public Td::ResultHandler { channel_id_ = channel_id; user_id_ = user_id; status_ = status; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_editAdmin( std::move(input_channel), std::move(input_user), status.get_chat_admin_rights(), status.get_rank()))); @@ -653,19 +631,14 @@ class EditChannelAdminQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditChannelAdminQuery: " << to_string(ptr); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery"); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); td_->dialog_participant_manager_->on_set_channel_participant_status(channel_id_, DialogId(user_id_), status_); } void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && status.message() == "USER_PRIVACY_RESTRICTED") { - td_->dialog_participant_manager_->send_update_add_chat_members_privacy_forbidden( - DialogId(channel_id_), {user_id_}, "EditChannelAdminQuery"); - return promise_.set_error(Status::Error(406, "USER_PRIVACY_RESTRICTED")); - } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelAdminQuery"); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditChannelAdminQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery"); promise_.set_error(std::move(status)); } }; @@ -685,7 +658,7 @@ class EditChannelBannedQuery final : public Td::ResultHandler { channel_id_ = channel_id; participant_dialog_id_ = participant_dialog_id; status_ = status; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create(telegram_api::channels_editBanned( std::move(input_channel), std::move(input_peer), status.get_chat_banned_rights()))); @@ -699,16 +672,16 @@ class EditChannelBannedQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditChannelBannedQuery: " << to_string(ptr); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery"); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); td_->dialog_participant_manager_->on_set_channel_participant_status(channel_id_, participant_dialog_id_, status_); } void on_error(Status status) final { if (participant_dialog_id_.get_type() != DialogType::Channel) { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelBannedQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditChannelBannedQuery"); } - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery"); promise_.set_error(std::move(status)); } }; @@ -723,7 +696,7 @@ class LeaveChannelQuery final : public Td::ResultHandler { void send(ChannelId channel_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query( G()->net_query_creator().create(telegram_api::channels_leaveChannel(std::move(input_channel)), {{channel_id}})); @@ -742,9 +715,10 @@ class LeaveChannelQuery final : public Td::ResultHandler { void on_error(Status status) final { if (status.message() == "USER_NOT_PARTICIPANT") { - return td_->contacts_manager_->reload_channel(channel_id_, std::move(promise_), "LeaveChannelQuery"); + return td_->chat_manager_->reload_channel(channel_id_, std::move(promise_), "LeaveChannelQuery"); } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "LeaveChannelQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "LeaveChannelQuery"); + td_->chat_manager_->reload_channel_full(channel_id_, Promise(), "LeaveChannelQuery"); promise_.set_error(std::move(status)); } }; @@ -757,7 +731,7 @@ class CanEditChannelCreatorQuery final : public Td::ResultHandler { } void send() { - auto r_input_user = td_->contacts_manager_->get_input_user(td_->contacts_manager_->get_my_id()); + auto r_input_user = td_->user_manager_->get_input_user(td_->user_manager_->get_my_id()); CHECK(r_input_user.is_ok()); send_query(G()->net_query_creator().create(telegram_api::channels_editCreator( telegram_api::make_object(), r_input_user.move_as_ok(), @@ -793,11 +767,11 @@ class EditChannelCreatorQuery final : public Td::ResultHandler { tl_object_ptr input_check_password) { channel_id_ = channel_id; user_id_ = user_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Have no access to the chat")); } - TRY_RESULT_PROMISE(promise_, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise_, input_user, td_->user_manager_->get_input_user(user_id)); send_query(G()->net_query_creator().create( telegram_api::channels_editCreator(std::move(input_channel), std::move(input_user), std::move(input_check_password)), @@ -812,17 +786,12 @@ class EditChannelCreatorQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for EditChannelCreatorQuery: " << to_string(ptr); - td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelCreatorQuery"); + td_->chat_manager_->invalidate_channel_full(channel_id_, false, "EditChannelCreatorQuery"); td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } void on_error(Status status) final { - if (!td_->auth_manager_->is_bot() && status.message() == "USER_PRIVACY_RESTRICTED") { - td_->dialog_participant_manager_->send_update_add_chat_members_privacy_forbidden( - DialogId(channel_id_), {user_id_}, "EditChannelCreatorQuery"); - return promise_.set_error(Status::Error(406, "USER_PRIVACY_RESTRICTED")); - } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelCreatorQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditChannelCreatorQuery"); promise_.set_error(std::move(status)); } }; @@ -869,8 +838,8 @@ void DialogParticipantManager::on_update_dialog_online_member_count_timeout(Dial } if (dialog_id.get_type() == DialogType::Channel && !td_->dialog_manager_->is_broadcast_channel(dialog_id)) { - auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); - auto has_hidden_participants = td_->contacts_manager_->get_channel_effective_has_hidden_participants( + auto participant_count = td_->chat_manager_->get_channel_participant_count(dialog_id.get_channel_id()); + auto has_hidden_participants = td_->chat_manager_->get_channel_effective_has_hidden_participants( dialog_id.get_channel_id(), "on_update_dialog_online_member_count_timeout"); if (participant_count == 0 || participant_count >= 195 || has_hidden_participants) { td_->create_handler()->send(dialog_id); @@ -882,7 +851,7 @@ void DialogParticipantManager::on_update_dialog_online_member_count_timeout(Dial } if (dialog_id.get_type() == DialogType::Chat) { // we need actual online status state, so we need to reget chat participants - td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id()); + td_->chat_manager_->repair_chat_participants(dialog_id.get_chat_id()); return; } } @@ -948,14 +917,14 @@ void DialogParticipantManager::set_dialog_online_member_count(DialogId dialog_id switch (dialog_id.get_type()) { case DialogType::Chat: { - auto participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id()); + auto participant_count = td_->chat_manager_->get_chat_participant_count(dialog_id.get_chat_id()); if (online_member_count > participant_count) { online_member_count = participant_count; } break; } case DialogType::Channel: { - auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); + auto participant_count = td_->chat_manager_->get_channel_participant_count(dialog_id.get_channel_id()); if (participant_count != 0 && online_member_count > participant_count) { online_member_count = participant_count; } @@ -1021,7 +990,7 @@ void DialogParticipantManager::update_user_online_member_count(UserId user_id) { switch (dialog_id.get_type()) { case DialogType::Chat: - td_->contacts_manager_->update_chat_online_member_count(dialog_id.get_chat_id(), false); + td_->chat_manager_->update_chat_online_member_count(dialog_id.get_chat_id(), false); break; case DialogType::Channel: update_channel_online_member_count(dialog_id.get_channel_id(), false); @@ -1045,9 +1014,9 @@ void DialogParticipantManager::update_user_online_member_count(UserId user_id) { } void DialogParticipantManager::update_channel_online_member_count(ChannelId channel_id, bool is_from_server) { - if (!td_->contacts_manager_->is_megagroup_channel(channel_id) || - td_->contacts_manager_->get_channel_effective_has_hidden_participants(channel_id, - "update_channel_online_member_count")) { + if (!td_->chat_manager_->is_megagroup_channel(channel_id) || + td_->chat_manager_->get_channel_effective_has_hidden_participants(channel_id, + "update_channel_online_member_count")) { return; } @@ -1072,8 +1041,8 @@ void DialogParticipantManager::update_dialog_online_member_count(const vectorcontacts_manager_->is_user_deleted(user_id) && !td_->contacts_manager_->is_user_bot(user_id)) { - if (td_->contacts_manager_->is_user_online(user_id, 0, unix_time)) { + if (!td_->user_manager_->is_user_deleted(user_id) && !td_->user_manager_->is_user_bot(user_id)) { + if (td_->user_manager_->is_user_online(user_id, 0, unix_time)) { online_member_count++; } if (is_from_server) { @@ -1089,9 +1058,8 @@ void DialogParticipantManager::update_dialog_online_member_count(const vectordialog_manager_->have_dialog_force(dialog_id, "can_manage_dialog_join_requests")) { - return Status::Error(400, "Chat not found"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "can_manage_dialog_join_requests")); switch (dialog_id.get_type()) { case DialogType::SecretChat: @@ -1099,16 +1067,16 @@ Status DialogParticipantManager::can_manage_dialog_join_requests(DialogId dialog return Status::Error(400, "The chat can't have join requests"); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - if (!td_->contacts_manager_->get_chat_is_active(chat_id)) { + if (!td_->chat_manager_->get_chat_is_active(chat_id)) { return Status::Error(400, "Chat is deactivated"); } - if (!td_->contacts_manager_->get_chat_status(chat_id).can_manage_invite_links()) { + if (!td_->chat_manager_->get_chat_status(chat_id).can_manage_invite_links()) { return Status::Error(400, "Not enough rights to manage chat join requests"); } break; } case DialogType::Channel: - if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_manage_invite_links()) { + if (!td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).can_manage_invite_links()) { return Status::Error(400, "Not enough rights to manage chat join requests"); } break; @@ -1196,7 +1164,7 @@ void DialogParticipantManager::speculative_update_dialog_administrators(DialogId td_api::object_ptr DialogParticipantManager::get_chat_administrators_object( const vector &dialog_administrators) { auto administrator_objects = transform(dialog_administrators, [this](const DialogAdministrator &administrator) { - return administrator.get_chat_administrator_object(td_->contacts_manager_.get()); + return administrator.get_chat_administrator_object(td_->user_manager_.get()); }); return td_api::make_object(std::move(administrator_objects)); } @@ -1272,7 +1240,7 @@ void DialogParticipantManager::on_load_dialog_administrators_from_database( auto lock_promise = load_users_multipromise.get_promise(); for (auto &administrator : administrators) { - td_->contacts_manager_->get_user(administrator.get_user_id(), 3, load_users_multipromise.get_promise()); + td_->user_manager_->get_user(administrator.get_user_id(), 3, load_users_multipromise.get_promise()); } lock_promise.set_value(Unit()); @@ -1331,7 +1299,7 @@ void DialogParticipantManager::reload_dialog_administrators( Promise> &&promise) { auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::Chat && - !td_->contacts_manager_->get_chat_permissions(dialog_id.get_chat_id()).is_member()) { + !td_->chat_manager_->get_chat_permissions(dialog_id.get_chat_id()).is_member()) { return promise.set_value(td_api::make_object()); } auto query_promise = PromiseCreator::lambda( @@ -1347,13 +1315,13 @@ void DialogParticipantManager::reload_dialog_administrators( }); switch (dialog_type) { case DialogType::Chat: - td_->contacts_manager_->load_chat_full(dialog_id.get_chat_id(), false, std::move(query_promise), - "reload_dialog_administrators"); + td_->chat_manager_->load_chat_full(dialog_id.get_chat_id(), false, std::move(query_promise), + "reload_dialog_administrators"); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (td_->contacts_manager_->is_broadcast_channel(channel_id) && - !td_->contacts_manager_->get_channel_status(channel_id).is_administrator()) { + if (td_->chat_manager_->is_broadcast_channel(channel_id) && + !td_->chat_manager_->get_channel_status(channel_id).is_administrator()) { return query_promise.set_error(Status::Error(400, "Administrator list is inaccessible")); } auto hash = get_vector_hash(transform(dialog_administrators, [](const DialogAdministrator &administrator) { @@ -1381,7 +1349,7 @@ void DialogParticipantManager::on_reload_dialog_administrators( } void DialogParticipantManager::send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date, - const DialogInviteLink &invite_link, + const DialogInviteLink &invite_link, bool via_join_request, bool via_dialog_filter_invite_link, const DialogParticipant &old_dialog_participant, const DialogParticipant &new_dialog_participant) { @@ -1390,25 +1358,25 @@ void DialogParticipantManager::send_update_chat_member(DialogId dialog_id, UserI send_closure(G()->td(), &Td::send_update, td_api::make_object( td_->dialog_manager_->get_chat_id_object(dialog_id, "updateChatMember"), - td_->contacts_manager_->get_user_id_object(agent_user_id, "updateChatMember"), date, - invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()), via_dialog_filter_invite_link, - td_->contacts_manager_->get_chat_member_object(old_dialog_participant, "updateChatMember old"), - td_->contacts_manager_->get_chat_member_object(new_dialog_participant, "updateChatMember new"))); + td_->user_manager_->get_user_id_object(agent_user_id, "updateChatMember"), date, + invite_link.get_chat_invite_link_object(td_->user_manager_.get()), via_join_request, + via_dialog_filter_invite_link, + td_->chat_manager_->get_chat_member_object(old_dialog_participant, "updateChatMember old"), + td_->chat_manager_->get_chat_member_object(new_dialog_participant, "updateChatMember new"))); } void DialogParticipantManager::on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped, bool force) { CHECK(td_->auth_manager_->is_bot()); - if (date <= 0 || !td_->contacts_manager_->have_user_force(user_id, "on_update_bot_stopped")) { + if (date <= 0 || !td_->user_manager_->have_user_force(user_id, "on_update_bot_stopped")) { LOG(ERROR) << "Receive invalid updateBotStopped by " << user_id << " at " << date; return; } - auto my_user_id = td_->contacts_manager_->get_my_id(); - if (!td_->contacts_manager_->have_user_force(my_user_id, "on_update_bot_stopped 2")) { + auto my_user_id = td_->user_manager_->get_my_id(); + if (!td_->user_manager_->have_user_force(my_user_id, "on_update_bot_stopped 2")) { if (!force) { - td_->contacts_manager_->get_me( - PromiseCreator::lambda([actor_id = actor_id(this), user_id, date, is_stopped](Unit) { - send_closure(actor_id, &DialogParticipantManager::on_update_bot_stopped, user_id, date, is_stopped, true); - })); + td_->user_manager_->get_me(PromiseCreator::lambda([actor_id = actor_id(this), user_id, date, is_stopped](Unit) { + send_closure(actor_id, &DialogParticipantManager::on_update_bot_stopped, user_id, date, is_stopped, true); + })); return; } LOG(ERROR) << "Have no self-user to process updateBotStopped"; @@ -1420,12 +1388,12 @@ void DialogParticipantManager::on_update_bot_stopped(UserId user_id, int32 date, std::swap(old_dialog_participant.status_, new_dialog_participant.status_); } - send_update_chat_member(DialogId(user_id), user_id, date, DialogInviteLink(), false, old_dialog_participant, + send_update_chat_member(DialogId(user_id), user_id, date, DialogInviteLink(), false, false, old_dialog_participant, new_dialog_participant); } void DialogParticipantManager::on_update_chat_participant( - ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link, + ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link, bool via_join_request, telegram_api::object_ptr old_participant, telegram_api::object_ptr new_participant) { CHECK(td_->auth_manager_->is_bot()); @@ -1436,12 +1404,12 @@ void DialogParticipantManager::on_update_chat_participant( return; } - if (!td_->contacts_manager_->have_chat(chat_id)) { + if (!td_->chat_manager_->have_chat(chat_id)) { LOG(ERROR) << "Receive updateChatParticipant in unknown " << chat_id; return; } - auto chat_date = td_->contacts_manager_->get_chat_date(chat_id); - auto chat_status = td_->contacts_manager_->get_chat_status(chat_id); + auto chat_date = td_->chat_manager_->get_chat_date(chat_id); + auto chat_status = td_->chat_manager_->get_chat_status(chat_id); auto is_creator = chat_status.is_creator(); DialogParticipant old_dialog_participant; @@ -1462,19 +1430,19 @@ void DialogParticipantManager::on_update_chat_participant( LOG(ERROR) << "Receive wrong updateChatParticipant: " << old_dialog_participant << " -> " << new_dialog_participant; return; } - if (new_dialog_participant.dialog_id_ == DialogId(td_->contacts_manager_->get_my_id()) && + if (new_dialog_participant.dialog_id_ == DialogId(td_->user_manager_->get_my_id()) && new_dialog_participant.status_ != chat_status && false) { LOG(ERROR) << "Have status " << chat_status << " after receiving updateChatParticipant in " << chat_id << " by " << user_id << " at " << date << " from " << old_dialog_participant << " to " << new_dialog_participant; } - send_update_chat_member(DialogId(chat_id), user_id, date, invite_link, false, old_dialog_participant, - new_dialog_participant); + send_update_chat_member(DialogId(chat_id), user_id, date, invite_link, via_join_request, false, + old_dialog_participant, new_dialog_participant); } void DialogParticipantManager::on_update_channel_participant( - ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link, bool via_dialog_filter_invite_link, - telegram_api::object_ptr old_participant, + ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link, bool via_join_request, + bool via_dialog_filter_invite_link, telegram_api::object_ptr old_participant, telegram_api::object_ptr new_participant) { CHECK(td_->auth_manager_->is_bot()); if (!channel_id.is_valid() || !user_id.is_valid() || date <= 0 || @@ -1483,14 +1451,14 @@ void DialogParticipantManager::on_update_channel_participant( << ": " << to_string(old_participant) << " -> " << to_string(new_participant); return; } - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { LOG(ERROR) << "Receive updateChannelParticipant in unknown " << channel_id; return; } DialogParticipant old_dialog_participant; DialogParticipant new_dialog_participant; - auto channel_type = td_->contacts_manager_->get_channel_type(channel_id); + auto channel_type = td_->chat_manager_->get_channel_type(channel_id); if (old_participant != nullptr) { old_dialog_participant = DialogParticipant(std::move(old_participant), channel_type); if (new_participant == nullptr) { @@ -1508,7 +1476,7 @@ void DialogParticipantManager::on_update_channel_participant( << new_dialog_participant; return; } - if (new_dialog_participant.status_.is_administrator() && user_id == td_->contacts_manager_->get_my_id() && + if (new_dialog_participant.status_.is_administrator() && user_id == td_->user_manager_->get_my_id() && !new_dialog_participant.status_.can_be_edited()) { LOG(ERROR) << "Fix wrong can_be_edited in " << new_dialog_participant << " from " << channel_id << " changed from " << old_dialog_participant; @@ -1522,7 +1490,7 @@ void DialogParticipantManager::on_update_channel_participant( add_channel_participant_to_cache(channel_id, new_dialog_participant, true); } - auto channel_status = td_->contacts_manager_->get_channel_status(channel_id); + auto channel_status = td_->chat_manager_->get_channel_status(channel_id); if (new_dialog_participant.dialog_id_ == td_->dialog_manager_->get_my_dialog_id() && new_dialog_participant.status_ != channel_status && false) { LOG(ERROR) << "Have status " << channel_status << " after receiving updateChannelParticipant in " << channel_id @@ -1530,14 +1498,14 @@ void DialogParticipantManager::on_update_channel_participant( << new_dialog_participant; } - send_update_chat_member(DialogId(channel_id), user_id, date, invite_link, via_dialog_filter_invite_link, - old_dialog_participant, new_dialog_participant); + send_update_chat_member(DialogId(channel_id), user_id, date, invite_link, via_join_request, + via_dialog_filter_invite_link, old_dialog_participant, new_dialog_participant); } void DialogParticipantManager::on_update_chat_invite_requester(DialogId dialog_id, UserId user_id, string about, int32 date, DialogInviteLink invite_link) { CHECK(td_->auth_manager_->is_bot()); - if (date <= 0 || !td_->contacts_manager_->have_user_force(user_id, "on_update_chat_invite_requester") || + if (date <= 0 || !td_->user_manager_->have_user_force(user_id, "on_update_chat_invite_requester") || !td_->dialog_manager_->have_dialog_info_force(dialog_id, "on_update_chat_invite_requester")) { LOG(ERROR) << "Receive invalid updateBotChatInviteRequester by " << user_id << " in " << dialog_id << " at " << date; @@ -1551,9 +1519,9 @@ void DialogParticipantManager::on_update_chat_invite_requester(DialogId dialog_i td_api::make_object( td_->dialog_manager_->get_chat_id_object(dialog_id, "updateNewChatJoinRequest"), td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "updateNewChatJoinRequest"), date, about), + td_->user_manager_->get_user_id_object(user_id, "updateNewChatJoinRequest"), date, about), td_->dialog_manager_->get_chat_id_object(user_dialog_id, "updateNewChatJoinRequest 2"), - invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()))); + invite_link.get_chat_invite_link_object(td_->user_manager_.get()))); } void DialogParticipantManager::get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, @@ -1573,13 +1541,12 @@ void DialogParticipantManager::finish_get_dialog_participant( auto participant_dialog_id = dialog_participant.dialog_id_; bool is_user = participant_dialog_id.get_type() == DialogType::User; - if ((is_user && !td_->contacts_manager_->have_user(participant_dialog_id.get_user_id())) || + if ((is_user && !td_->user_manager_->have_user(participant_dialog_id.get_user_id())) || (!is_user && !td_->messages_manager_->have_dialog(participant_dialog_id))) { return promise.set_error(Status::Error(400, "Member not found")); } - promise.set_value( - td_->contacts_manager_->get_chat_member_object(dialog_participant, "finish_get_dialog_participant")); + promise.set_value(td_->chat_manager_->get_chat_member_object(dialog_participant, "finish_get_dialog_participant")); } void DialogParticipantManager::do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, @@ -1591,7 +1558,7 @@ void DialogParticipantManager::do_get_dialog_participant(DialogId dialog_id, Dia switch (dialog_id.get_type()) { case DialogType::User: { - auto my_user_id = td_->contacts_manager_->get_my_id(); + auto my_user_id = td_->user_manager_->get_my_id(); auto peer_user_id = dialog_id.get_user_id(); if (participant_dialog_id == DialogId(my_user_id)) { return promise.set_value(DialogParticipant::private_member(my_user_id, peer_user_id)); @@ -1606,13 +1573,13 @@ void DialogParticipantManager::do_get_dialog_participant(DialogId dialog_id, Dia if (participant_dialog_id.get_type() != DialogType::User) { return promise.set_value(DialogParticipant::left(participant_dialog_id)); } - return td_->contacts_manager_->get_chat_participant(dialog_id.get_chat_id(), participant_dialog_id.get_user_id(), - std::move(promise)); + return td_->chat_manager_->get_chat_participant(dialog_id.get_chat_id(), participant_dialog_id.get_user_id(), + std::move(promise)); case DialogType::Channel: return get_channel_participant(dialog_id.get_channel_id(), participant_dialog_id, std::move(promise)); case DialogType::SecretChat: { - auto my_user_id = td_->contacts_manager_->get_my_id(); - auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto my_user_id = td_->user_manager_->get_my_id(); + auto peer_user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (participant_dialog_id == DialogId(my_user_id)) { return promise.set_value(DialogParticipant::private_member(my_user_id, peer_user_id)); } @@ -1645,23 +1612,38 @@ void DialogParticipantManager::get_channel_participant(ChannelId channel_id, Dia } } - auto on_result_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, promise = std::move(promise)]( - Result r_dialog_participant) mutable { - TRY_RESULT_PROMISE(promise, dialog_participant, std::move(r_dialog_participant)); - send_closure(actor_id, &DialogParticipantManager::finish_get_channel_participant, channel_id, - std::move(dialog_participant), std::move(promise)); - }); + if (td_->auth_manager_->is_bot() && participant_dialog_id == td_->dialog_manager_->get_my_dialog_id() && + td_->chat_manager_->have_channel(channel_id)) { + // bots don't need inviter information + td_->chat_manager_->reload_channel(channel_id, Auto(), "get_channel_participant"); + return promise.set_value(DialogParticipant{participant_dialog_id, participant_dialog_id.get_user_id(), + td_->chat_manager_->get_channel_date(channel_id), + td_->chat_manager_->get_channel_status(channel_id)}); + } + + auto on_result_promise = + PromiseCreator::lambda([actor_id = actor_id(this), channel_id, participant_dialog_id, + promise = std::move(promise)](Result r_dialog_participant) mutable { + TRY_RESULT_PROMISE(promise, dialog_participant, std::move(r_dialog_participant)); + send_closure(actor_id, &DialogParticipantManager::finish_get_channel_participant, channel_id, + participant_dialog_id, std::move(dialog_participant), std::move(promise)); + }); td_->create_handler(std::move(on_result_promise)) ->send(channel_id, participant_dialog_id, std::move(input_peer)); } -void DialogParticipantManager::finish_get_channel_participant(ChannelId channel_id, +void DialogParticipantManager::finish_get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id, DialogParticipant &&dialog_participant, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); CHECK(dialog_participant.is_valid()); // checked in GetChannelParticipantQuery + if (dialog_participant.dialog_id_ != participant_dialog_id) { + LOG(ERROR) << "Receive " << dialog_participant.dialog_id_ << " in " << channel_id << " instead of requested " + << participant_dialog_id; + return promise.set_error(Status::Error(500, "Data is unavailable")); + } LOG(INFO) << "Receive " << dialog_participant.dialog_id_ << " as a member of a channel " << channel_id; @@ -1688,8 +1670,7 @@ std::pair> DialogParticipantManager::search_among_dialog hints.add(dialog_id.get(), td_->dialog_manager_->get_dialog_search_text(dialog_id)); } if (dialog_id.get_type() == DialogType::User) { - hints.set_rating(dialog_id.get(), - -td_->contacts_manager_->get_user_was_online(dialog_id.get_user_id(), unix_time)); + hints.set_rating(dialog_id.get(), -td_->user_manager_->get_user_was_online(dialog_id.get_user_id(), unix_time)); } } @@ -1700,7 +1681,7 @@ std::pair> DialogParticipantManager::search_among_dialog DialogParticipants DialogParticipantManager::search_private_chat_participants(UserId peer_user_id, const string &query, int32 limit, DialogParticipantFilter filter) const { - auto my_user_id = td_->contacts_manager_->get_my_id(); + auto my_user_id = td_->user_manager_->get_my_id(); vector dialog_ids; if (filter.is_dialog_participant_suitable(td_, DialogParticipant::private_member(my_user_id, peer_user_id))) { dialog_ids.push_back(DialogId(my_user_id)); @@ -1733,7 +1714,7 @@ void DialogParticipantManager::search_chat_participants(ChatId chat_id, const st std::move(promise)); } }); - td_->contacts_manager_->load_chat_full(chat_id, false, std::move(load_chat_full_promise), "search_chat_participants"); + td_->chat_manager_->load_chat_full(chat_id, false, std::move(load_chat_full_promise), "search_chat_participants"); } void DialogParticipantManager::do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, @@ -1741,7 +1722,7 @@ void DialogParticipantManager::do_search_chat_participants(ChatId chat_id, const Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - const auto *participants = td_->contacts_manager_->get_chat_participants(chat_id); + const auto *participants = td_->chat_manager_->get_chat_participants(chat_id); if (participants == nullptr) { return promise.set_error(Status::Error(500, "Can't find basic group full info")); } @@ -1783,8 +1764,8 @@ void DialogParticipantManager::get_channel_participants(ChannelId channel_id, return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); } - if (td_->contacts_manager_->is_broadcast_channel(channel_id) && - !td_->contacts_manager_->get_channel_status(channel_id).is_administrator()) { + if (td_->chat_manager_->is_broadcast_channel(channel_id) && + !td_->chat_manager_->get_channel_status(channel_id).is_administrator()) { return promise.set_error(Status::Error(400, "Member list is inaccessible")); } @@ -1811,18 +1792,18 @@ void DialogParticipantManager::on_get_channel_participants( Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - td_->contacts_manager_->on_get_users(std::move(channel_participants->users_), "on_get_channel_participants"); - td_->contacts_manager_->on_get_chats(std::move(channel_participants->chats_), "on_get_channel_participants"); + td_->user_manager_->on_get_users(std::move(channel_participants->users_), "on_get_channel_participants"); + td_->chat_manager_->on_get_chats(std::move(channel_participants->chats_), "on_get_channel_participants"); int32 total_count = channel_participants->count_; auto participants = std::move(channel_participants->participants_); LOG(INFO) << "Receive " << participants.size() << " " << filter << " members in " << channel_id; bool is_full = offset == 0 && static_cast(participants.size()) < limit && total_count < limit; bool has_hidden_participants = - td_->contacts_manager_->get_channel_effective_has_hidden_participants(channel_id, "on_get_channel_participants"); + td_->chat_manager_->get_channel_effective_has_hidden_participants(channel_id, "on_get_channel_participants"); bool is_full_recent = is_full && filter.is_recent() && !has_hidden_participants; - auto channel_type = td_->contacts_manager_->get_channel_type(channel_id); + auto channel_type = td_->chat_manager_->get_channel_type(channel_id); vector result; for (auto &participant_ptr : participants) { auto debug_participant = to_string(participant_ptr); @@ -1832,15 +1813,15 @@ void DialogParticipantManager::on_get_channel_participants( if (participant.dialog_id_.get_type() == DialogType::User) { participant_user_id = participant.dialog_id_.get_user_id(); } - if (!participant.is_valid() || (filter.is_bots() && !td_->contacts_manager_->is_user_bot(participant_user_id)) || + if (!participant.is_valid() || (filter.is_bots() && !td_->user_manager_->is_user_bot(participant_user_id)) || (filter.is_administrators() && !participant.status_.is_administrator()) || ((filter.is_recent() || filter.is_contacts() || filter.is_search()) && !participant.status_.is_member()) || - (filter.is_contacts() && !td_->contacts_manager_->is_user_contact(participant_user_id)) || + (filter.is_contacts() && !td_->user_manager_->is_user_contact(participant_user_id)) || (filter.is_restricted() && !participant.status_.is_restricted()) || (filter.is_banned() && !participant.status_.is_banned())) { bool skip_error = ((filter.is_administrators() || filter.is_bots()) && - td_->contacts_manager_->is_user_deleted(participant_user_id)) || - (filter.is_contacts() && participant_user_id == td_->contacts_manager_->get_my_id()); + td_->user_manager_->is_user_deleted(participant_user_id)) || + (filter.is_contacts() && participant_user_id == td_->user_manager_->get_my_id()); if (!skip_error) { LOG(ERROR) << "Receive " << participant << ", while searching for " << filter << " in " << channel_id << " with offset " << offset << " and limit " << limit << ": " << oneline(debug_participant); @@ -1861,7 +1842,7 @@ void DialogParticipantManager::on_get_channel_participants( total_count = static_cast(result.size()); } - auto is_megagroup = td_->contacts_manager_->is_megagroup_channel(channel_id); + auto is_megagroup = td_->chat_manager_->is_megagroup_channel(channel_id); const auto max_participant_count = is_megagroup ? 975 : 195; auto participant_count = filter.is_recent() && !has_hidden_participants && total_count != 0 && total_count < max_participant_count @@ -1881,7 +1862,7 @@ void DialogParticipantManager::on_get_channel_participants( administrators.emplace_back(participant_user_id, participant.status_.get_rank(), participant.status_.is_creator()); } - if (is_full_recent && td_->contacts_manager_->is_user_bot(participant_user_id)) { + if (is_full_recent && td_->user_manager_->is_user_bot(participant_user_id)) { bot_user_ids.push_back(participant_user_id); } } @@ -1910,7 +1891,7 @@ void DialogParticipantManager::on_get_channel_participants( on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), true, false); } if (filter.is_bots() || is_full_recent) { - td_->contacts_manager_->on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids)); + td_->chat_manager_->on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids)); } } if (have_channel_participant_cache(channel_id)) { @@ -1920,10 +1901,10 @@ void DialogParticipantManager::on_get_channel_participants( } if (participant_count != -1) { - td_->contacts_manager_->on_update_channel_participant_count(channel_id, participant_count); + td_->chat_manager_->on_update_channel_participant_count(channel_id, participant_count); } if (administrator_count != -1) { - td_->contacts_manager_->on_update_channel_administrator_count(channel_id, administrator_count); + td_->chat_manager_->on_update_channel_administrator_count(channel_id, administrator_count); } if (!additional_query.empty()) { @@ -1984,7 +1965,7 @@ void DialogParticipantManager::search_dialog_participants(DialogId dialog_id, co } } case DialogType::SecretChat: { - auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto peer_user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); return promise.set_value(search_private_chat_participants(peer_user_id, query, limit, filter)); } case DialogType::None: @@ -1994,8 +1975,9 @@ void DialogParticipantManager::search_dialog_participants(DialogId dialog_id, co } } -void DialogParticipantManager::add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, - Promise &&promise) { +void DialogParticipantManager::add_dialog_participant( + DialogId dialog_id, UserId user_id, int32 forward_limit, + Promise> &&promise) { if (!td_->dialog_manager_->have_dialog_force(dialog_id, "add_dialog_participant")) { return promise.set_error(Status::Error(400, "Chat not found")); } @@ -2016,8 +1998,9 @@ void DialogParticipantManager::add_dialog_participant(DialogId dialog_id, UserId } } -void DialogParticipantManager::add_dialog_participants(DialogId dialog_id, const vector &user_ids, - Promise &&promise) { +void DialogParticipantManager::add_dialog_participants( + DialogId dialog_id, const vector &user_ids, + Promise> &&promise) { if (!td_->dialog_manager_->have_dialog_force(dialog_id, "add_dialog_participants")) { return promise.set_error(Status::Error(400, "Chat not found")); } @@ -2111,11 +2094,11 @@ void DialogParticipantManager::leave_dialog(DialogId dialog_id, Promise && case DialogType::User: return promise.set_error(Status::Error(400, "Can't leave private chats")); case DialogType::Chat: - return delete_chat_participant(dialog_id.get_chat_id(), td_->contacts_manager_->get_my_id(), false, + return delete_chat_participant(dialog_id.get_chat_id(), td_->user_manager_->get_my_id(), false, std::move(promise)); case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - auto old_status = td_->contacts_manager_->get_channel_status(channel_id); + auto old_status = td_->chat_manager_->get_channel_status(channel_id); auto new_status = old_status; new_status.set_is_member(false); return restrict_channel_participant(channel_id, td_->dialog_manager_->get_my_dialog_id(), std::move(new_status), @@ -2129,10 +2112,26 @@ void DialogParticipantManager::leave_dialog(DialogId dialog_id, Promise && } } +Promise> DialogParticipantManager::wrap_failed_to_add_members_promise( + Promise &&promise) { + return PromiseCreator::lambda( + [promise = std::move(promise)](Result> &&result) mutable { + if (result.is_ok()) { + if (result.ok()->failed_to_add_members_.empty()) { + promise.set_value(Unit()); + } else { + promise.set_error(Status::Error(403, "USER_PRIVACY_RESTRICTED")); + } + } else { + promise.set_error(result.move_as_error()); + } + }); +} + void DialogParticipantManager::add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, - Promise &&promise) { - if (!td_->contacts_manager_->get_chat_is_active(chat_id)) { - if (!td_->contacts_manager_->have_chat(chat_id)) { + Promise> &&promise) { + if (!td_->chat_manager_->get_chat_is_active(chat_id)) { + if (!td_->chat_manager_->have_chat(chat_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } return promise.set_error(Status::Error(400, "Chat is deactivated")); @@ -2140,8 +2139,8 @@ void DialogParticipantManager::add_chat_participant(ChatId chat_id, UserId user_ if (forward_limit < 0) { return promise.set_error(Status::Error(400, "Can't forward negative number of messages")); } - auto permissions = td_->contacts_manager_->get_chat_permissions(chat_id); - if (user_id != td_->contacts_manager_->get_my_id()) { + auto permissions = td_->chat_manager_->get_chat_permissions(chat_id); + if (user_id != td_->user_manager_->get_my_id()) { if (!permissions.can_invite_users()) { return promise.set_error(Status::Error(400, "Not enough rights to invite members to the group chat")); } @@ -2150,7 +2149,7 @@ void DialogParticipantManager::add_chat_participant(ChatId chat_id, UserId user_ } // TODO upper bound on forward_limit - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); // TODO invoke after td_->create_handler(std::move(promise)) @@ -2171,8 +2170,8 @@ void DialogParticipantManager::set_chat_participant_status(ChatId chat_id, UserI return promise.set_error(Status::Error(400, "Can't restrict users in basic group chats")); } - if (!td_->contacts_manager_->get_chat_is_active(chat_id)) { - if (!td_->contacts_manager_->have_chat(chat_id)) { + if (!td_->chat_manager_->get_chat_is_active(chat_id)) { + if (!td_->chat_manager_->have_chat(chat_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } return promise.set_error(Status::Error(400, "Chat is deactivated")); @@ -2189,37 +2188,41 @@ void DialogParticipantManager::set_chat_participant_status(ChatId chat_id, UserI true, std::move(promise)); } }); - return td_->contacts_manager_->load_chat_full(chat_id, false, std::move(load_chat_full_promise), - "set_chat_participant_status"); + return td_->chat_manager_->load_chat_full(chat_id, false, std::move(load_chat_full_promise), + "set_chat_participant_status"); } - auto participant = td_->contacts_manager_->get_chat_participant(chat_id, user_id); + auto participant = td_->chat_manager_->get_chat_participant(chat_id, user_id); if (participant == nullptr && !status.is_administrator()) { // the user isn't a member, but needs to be added - return add_chat_participant(chat_id, user_id, 0, std::move(promise)); + return add_chat_participant(chat_id, user_id, 0, wrap_failed_to_add_members_promise(std::move(promise))); } - auto permissions = td_->contacts_manager_->get_chat_permissions(chat_id); + auto permissions = td_->chat_manager_->get_chat_permissions(chat_id); if (!permissions.can_promote_members()) { return promise.set_error(Status::Error(400, "Need owner rights in the group chat")); } - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { return promise.set_error(Status::Error(400, "Can't promote or demote self")); } if (participant == nullptr) { // the user must be added first CHECK(status.is_administrator()); - auto add_chat_participant_promise = PromiseCreator::lambda( - [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result &&result) mutable { + auto add_chat_participant_promise = + PromiseCreator::lambda([actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)]( + Result> &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); + } else if (!result.ok()->failed_to_add_members_.empty()) { + promise.set_error(Status::Error(403, "USER_PRIVACY_RESTRICTED")); } else { send_closure(actor_id, &DialogParticipantManager::send_edit_chat_admin_query, chat_id, user_id, true, std::move(promise)); } }); + return add_chat_participant(chat_id, user_id, 0, std::move(add_chat_participant_promise)); } @@ -2229,7 +2232,7 @@ void DialogParticipantManager::set_chat_participant_status(ChatId chat_id, UserI void DialogParticipantManager::send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(chat_id, user_id, std::move(input_user), is_administrator); @@ -2237,15 +2240,15 @@ void DialogParticipantManager::send_edit_chat_admin_query(ChatId chat_id, UserId void DialogParticipantManager::delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise &&promise) { - if (!td_->contacts_manager_->get_chat_is_active(chat_id)) { - if (!td_->contacts_manager_->have_chat(chat_id)) { + if (!td_->chat_manager_->get_chat_is_active(chat_id)) { + if (!td_->chat_manager_->have_chat(chat_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } return promise.set_error(Status::Error(400, "Chat is deactivated")); } - auto my_id = td_->contacts_manager_->get_my_id(); - auto permissions = td_->contacts_manager_->get_chat_permissions(chat_id); + auto my_id = td_->user_manager_->get_my_id(); + auto permissions = td_->chat_manager_->get_chat_permissions(chat_id); if (permissions.is_left()) { if (user_id == my_id) { if (revoke_messages) { @@ -2281,51 +2284,56 @@ void DialogParticipantManager::delete_chat_participant(ChatId chat_id, UserId us } } */ - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); // TODO invoke after td_->create_handler(std::move(promise))->send(chat_id, std::move(input_user), revoke_messages); } -void DialogParticipantManager::add_channel_participant(ChannelId channel_id, UserId user_id, - const DialogParticipantStatus &old_status, - Promise &&promise) { +void DialogParticipantManager::add_channel_participant( + ChannelId channel_id, UserId user_id, const DialogParticipantStatus &old_status, + Promise> &&promise) { if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Bots can't add new chat members")); } - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { // join the channel - auto my_status = td_->contacts_manager_->get_channel_status(channel_id); + auto my_status = td_->chat_manager_->get_channel_status(channel_id); if (my_status.is_banned()) { return promise.set_error(Status::Error(400, "Can't return to kicked from chat")); } if (my_status.is_member()) { - return promise.set_value(Unit()); + return promise.set_value(MissingInvitees().get_failed_to_add_members_object(td_->user_manager_.get())); } auto &queries = join_channel_queries_[channel_id]; queries.push_back(std::move(promise)); if (queries.size() == 1u) { - if (!td_->contacts_manager_->get_channel_join_request(channel_id)) { - auto new_status = my_status; + auto new_status = my_status; + bool was_speculatively_updated = false; + if (!td_->chat_manager_->get_channel_join_request(channel_id)) { new_status.set_is_member(true); + was_speculatively_updated = true; speculative_add_channel_user(channel_id, user_id, new_status, my_status); } - auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id](Result result) { - send_closure(actor_id, &DialogParticipantManager::on_join_channel, channel_id, std::move(result)); + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, was_speculatively_updated, + old_status = std::move(my_status), + new_status = std::move(new_status)](Result result) mutable { + send_closure(actor_id, &DialogParticipantManager::on_join_channel, channel_id, was_speculatively_updated, + std::move(old_status), std::move(new_status), std::move(result)); }); td_->create_handler(std::move(query_promise))->send(channel_id); } return; } - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_invite_users()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_invite_users()) { return promise.set_error(Status::Error(400, "Not enough rights to invite members to the supergroup chat")); } @@ -2335,7 +2343,9 @@ void DialogParticipantManager::add_channel_participant(ChannelId channel_id, Use td_->create_handler(std::move(promise))->send(channel_id, {user_id}, std::move(input_users)); } -void DialogParticipantManager::on_join_channel(ChannelId channel_id, Result &&result) { +void DialogParticipantManager::on_join_channel(ChannelId channel_id, bool was_speculatively_updated, + DialogParticipantStatus &&old_status, + DialogParticipantStatus &&new_status, Result &&result) { G()->ignore_result_if_closing(result); auto it = join_channel_queries_.find(channel_id); @@ -2345,31 +2355,37 @@ void DialogParticipantManager::on_join_channel(ChannelId channel_id, Resultuser_manager_.get())); + } } else { + if (was_speculatively_updated) { + speculative_add_channel_user(channel_id, td_->user_manager_->get_my_id(), old_status, new_status); + } fail_promises(promises, result.move_as_error()); } } -void DialogParticipantManager::add_channel_participants(ChannelId channel_id, const vector &user_ids, - Promise &&promise) { +void DialogParticipantManager::add_channel_participants( + ChannelId channel_id, const vector &user_ids, + Promise> &&promise) { if (td_->auth_manager_->is_bot()) { return promise.set_error(Status::Error(400, "Bots can't add new chat members")); } - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_invite_users()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_invite_users()) { return promise.set_error(Status::Error(400, "Not enough rights to invite members to the supergroup chat")); } vector> input_users; for (auto user_id : user_ids) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { // can't invite self continue; } @@ -2380,7 +2396,7 @@ void DialogParticipantManager::add_channel_participants(ChannelId channel_id, co } if (input_users.empty()) { - return promise.set_value(Unit()); + return promise.set_value(MissingInvitees().get_failed_to_add_members_object(td_->user_manager_.get())); } td_->create_handler(std::move(promise))->send(channel_id, user_ids, std::move(input_users)); @@ -2389,17 +2405,15 @@ void DialogParticipantManager::add_channel_participants(ChannelId channel_id, co void DialogParticipantManager::set_channel_participant_status( ChannelId channel_id, DialogId participant_dialog_id, td_api::object_ptr &&chat_member_status, Promise &&promise) { - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } - auto new_status = - get_dialog_participant_status(chat_member_status, td_->contacts_manager_->get_channel_type(channel_id)); + auto new_status = get_dialog_participant_status(chat_member_status, td_->chat_manager_->get_channel_type(channel_id)); if (participant_dialog_id == td_->dialog_manager_->get_my_dialog_id()) { // fast path is needed, because get_channel_status may return Creator, while GetChannelParticipantQuery returning Left return set_channel_participant_status_impl(channel_id, participant_dialog_id, std::move(new_status), - td_->contacts_manager_->get_channel_status(channel_id), - std::move(promise)); + td_->chat_manager_->get_channel_status(channel_id), std::move(promise)); } if (participant_dialog_id.get_type() != DialogType::User) { if (new_status.is_administrator() || new_status.is_member() || new_status.is_restricted()) { @@ -2415,7 +2429,6 @@ void DialogParticipantManager::set_channel_participant_status( auto on_result_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, participant_dialog_id, new_status, promise = std::move(promise)](Result r_dialog_participant) mutable { - // ResultHandlers are cleared before managers, so it is safe to capture this if (r_dialog_participant.is_error()) { return promise.set_error(r_dialog_participant.move_as_error()); } @@ -2449,13 +2462,13 @@ void DialogParticipantManager::set_channel_participant_status_impl(ChannelId cha if (!new_status.is_creator()) { return promise.set_error(Status::Error(400, "Can't remove chat owner")); } - auto user_id = td_->contacts_manager_->get_my_id(); + auto user_id = td_->user_manager_->get_my_id(); if (participant_dialog_id != DialogId(user_id)) { return promise.set_error(Status::Error(400, "Not enough rights to edit chat owner rights")); } if (new_status.is_member() == old_status.is_member()) { // change rank and is_anonymous - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); CHECK(r_input_user.is_ok()); td_->create_handler(std::move(promise)) ->send(channel_id, user_id, r_input_user.move_as_ok(), new_status); @@ -2511,7 +2524,8 @@ void DialogParticipantManager::set_channel_participant_status_impl(ChannelId cha if (participant_dialog_id.get_type() != DialogType::User) { return promise.set_error(Status::Error(400, "Can't add chats as chat members")); } - return add_channel_participant(channel_id, participant_dialog_id.get_user_id(), old_status, std::move(promise)); + return add_channel_participant(channel_id, participant_dialog_id.get_user_id(), old_status, + wrap_failed_to_add_members_promise(std::move(promise))); } } @@ -2520,14 +2534,14 @@ void DialogParticipantManager::promote_channel_participant(ChannelId channel_id, const DialogParticipantStatus &old_status, Promise &&promise) { LOG(INFO) << "Promote " << user_id << " in " << channel_id << " from " << old_status << " to " << new_status; - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { if (new_status.is_administrator()) { return promise.set_error(Status::Error(400, "Can't promote self")); } CHECK(new_status.is_member()); // allow to demote self. TODO is it allowed server-side? } else { - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_promote_members()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_promote_members()) { return promise.set_error(Status::Error(400, "Not enough rights")); } @@ -2535,7 +2549,7 @@ void DialogParticipantManager::promote_channel_participant(ChannelId channel_id, CHECK(!new_status.is_creator()); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); speculative_add_channel_user(channel_id, user_id, new_status, old_status); td_->create_handler(std::move(promise)) @@ -2550,10 +2564,10 @@ void DialogParticipantManager::restrict_channel_participant(ChannelId channel_id LOG(INFO) << "Restrict " << participant_dialog_id << " in " << channel_id << " from " << old_status << " to " << new_status; - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { return promise.set_error(Status::Error(400, "Chat info not found")); } - auto my_status = td_->contacts_manager_->get_channel_status(channel_id); + auto my_status = td_->chat_manager_->get_channel_status(channel_id); if (!my_status.is_member() && !my_status.is_creator()) { if (participant_dialog_id == td_->dialog_manager_->get_my_dialog_id()) { if (new_status.is_member()) { @@ -2599,7 +2613,7 @@ void DialogParticipantManager::restrict_channel_participant(ChannelId channel_id CHECK(!old_status.is_creator()); CHECK(!new_status.is_creator()); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_restrict_members()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_restrict_members()) { return promise.set_error(Status::Error(400, "Not enough rights to restrict/unrestrict chat member")); } @@ -2646,13 +2660,14 @@ void DialogParticipantManager::restrict_channel_participant(ChannelId channel_id create_actor( "AddChannelParticipantSleepActor", 1.0, PromiseCreator::lambda([actor_id, channel_id, participant_dialog_id, old_status = std::move(old_status), - promise = std::move(promise)](Result<> result) mutable { + promise = std::move(promise)](Result result) mutable { if (result.is_error()) { return promise.set_error(result.move_as_error()); } send_closure(actor_id, &DialogParticipantManager::add_channel_participant, channel_id, - participant_dialog_id.get_user_id(), old_status, std::move(promise)); + participant_dialog_id.get_user_id(), old_status, + wrap_failed_to_add_members_promise(std::move(promise))); })) .release(); }); @@ -2684,17 +2699,7 @@ void DialogParticipantManager::speculative_add_channel_user(ChannelId channel_id const DialogParticipantStatus &old_status) { speculative_update_dialog_administrators(DialogId(channel_id), user_id, new_status, old_status); - td_->contacts_manager_->speculative_add_channel_user(channel_id, user_id, new_status, old_status); -} - -void DialogParticipantManager::send_update_add_chat_members_privacy_forbidden(DialogId dialog_id, - vector user_ids, - const char *source) { - td_->dialog_manager_->force_create_dialog(dialog_id, source); - send_closure(G()->td(), &Td::send_update, - td_api::make_object( - td_->dialog_manager_->get_chat_id_object(dialog_id, "updateAddChatMembersPrivacyForbidden"), - td_->contacts_manager_->get_user_ids_object(user_ids, source))); + td_->chat_manager_->speculative_add_channel_user(channel_id, user_id, new_status, old_status); } void DialogParticipantManager::on_channel_participant_cache_timeout_callback(void *dialog_participant_manager_ptr, @@ -2734,7 +2739,7 @@ bool DialogParticipantManager::have_channel_participant_cache(ChannelId channel_ if (!td_->auth_manager_->is_bot()) { return false; } - return td_->contacts_manager_->get_channel_status(channel_id).is_administrator(); + return td_->chat_manager_->get_channel_status(channel_id).is_administrator(); } void DialogParticipantManager::add_channel_participant_to_cache(ChannelId channel_id, @@ -2876,7 +2881,7 @@ void DialogParticipantManager::update_cached_channel_participant_status(ChannelI } } if (!is_found && status.is_member()) { - participants.emplace_back(DialogId(user_id), td_->contacts_manager_->get_my_id(), G()->unix_time(), status); + participants.emplace_back(DialogId(user_id), td_->user_manager_->get_my_id(), G()->unix_time(), status); update_channel_online_member_count(channel_id, false); } } @@ -2938,13 +2943,13 @@ void DialogParticipantManager::transfer_dialog_ownership(DialogId dialog_id, Use if (!td_->dialog_manager_->have_dialog_force(dialog_id, "transfer_dialog_ownership")) { return promise.set_error(Status::Error(400, "Chat not found")); } - if (!td_->contacts_manager_->have_user_force(user_id, "transfer_dialog_ownership")) { + if (!td_->user_manager_->have_user_force(user_id, "transfer_dialog_ownership")) { return promise.set_error(Status::Error(400, "User not found")); } - if (td_->contacts_manager_->is_user_bot(user_id)) { + if (td_->user_manager_->is_user_bot(user_id)) { return promise.set_error(Status::Error(400, "User is a bot")); } - if (td_->contacts_manager_->is_user_deleted(user_id)) { + if (td_->user_manager_->is_user_deleted(user_id)) { return promise.set_error(Status::Error(400, "User is deleted")); } if (password.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.h b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.h index c7b554e1..1e85aefe 100644 --- a/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.h +++ b/lib/tgchat/ext/td/td/telegram/DialogParticipantManager.h @@ -85,11 +85,12 @@ class DialogParticipantManager final : public Actor { void on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped, bool force = false); void on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link, + bool via_join_request, telegram_api::object_ptr old_participant, telegram_api::object_ptr new_participant); void on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link, - bool via_dialog_filter_invite_link, + bool via_join_request, bool via_dialog_filter_invite_link, telegram_api::object_ptr old_participant, telegram_api::object_ptr new_participant); @@ -109,9 +110,14 @@ class DialogParticipantManager final : public Actor { void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantFilter filter, Promise &&promise); - void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise &&promise); + static Promise> wrap_failed_to_add_members_promise( + Promise &&promise); - void add_dialog_participants(DialogId dialog_id, const vector &user_ids, Promise &&promise); + void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, + Promise> &&promise); + + void add_dialog_participants(DialogId dialog_id, const vector &user_ids, + Promise> &&promise); void set_dialog_participant_status(DialogId dialog_id, DialogId participant_dialog_id, td_api::object_ptr &&chat_member_status, @@ -125,8 +131,6 @@ class DialogParticipantManager final : public Actor { void on_set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id, DialogParticipantStatus status); - void send_update_add_chat_members_privacy_forbidden(DialogId dialog_id, vector user_ids, const char *source); - bool have_channel_participant_cache(ChannelId channel_id) const; void add_channel_participant_to_cache(ChannelId channel_id, const DialogParticipant &dialog_participant, @@ -187,8 +191,8 @@ class DialogParticipantManager final : public Actor { Promise> &&promise); void send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date, - const DialogInviteLink &invite_link, bool via_dialog_filter_invite_link, - const DialogParticipant &old_dialog_participant, + const DialogInviteLink &invite_link, bool via_join_request, + bool via_dialog_filter_invite_link, const DialogParticipant &old_dialog_participant, const DialogParticipant &new_dialog_participant); void do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, @@ -197,8 +201,8 @@ class DialogParticipantManager final : public Actor { void finish_get_dialog_participant(DialogParticipant &&dialog_participant, Promise> &&promise); - void finish_get_channel_participant(ChannelId channel_id, DialogParticipant &&dialog_participant, - Promise &&promise); + void finish_get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id, + DialogParticipant &&dialog_participant, Promise &&promise); std::pair> search_among_dialogs(const vector &dialog_ids, const string &query, int32 limit) const; @@ -221,18 +225,21 @@ class DialogParticipantManager final : public Actor { void set_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status, bool is_recursive, Promise &&promise); - void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise &&promise); + void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, + Promise> &&promise); void send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator, Promise &&promise); void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise &&promise); void add_channel_participant(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &old_status, - Promise &&promise); + Promise> &&promise); - void on_join_channel(ChannelId channel_id, Result &&result); + void on_join_channel(ChannelId channel_id, bool was_speculatively_updated, DialogParticipantStatus &&old_status, + DialogParticipantStatus &&new_status, Result &&result); - void add_channel_participants(ChannelId channel_id, const vector &user_ids, Promise &&promise); + void add_channel_participants(ChannelId channel_id, const vector &user_ids, + Promise> &&promise); void set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id, td_api::object_ptr &&chat_member_status, @@ -299,7 +306,8 @@ class DialogParticipantManager final : public Actor { FlatHashMap, ChannelIdHash> cached_channel_participants_; - FlatHashMap>, ChannelIdHash> join_channel_queries_; + FlatHashMap>>, ChannelIdHash> + join_channel_queries_; MultiTimeout update_dialog_online_member_count_timeout_{"UpdateDialogOnlineMemberCountTimeout"}; MultiTimeout channel_participant_cache_timeout_{"ChannelParticipantCacheTimeout"}; diff --git a/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp b/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp index c045c660..0bce1b51 100644 --- a/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp +++ b/lib/tgchat/ext/td/td/telegram/DraftMessage.cpp @@ -13,7 +13,6 @@ #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/MessagesManager.h" -#include "td/telegram/misc.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" @@ -57,7 +56,7 @@ class SaveDraftMessageQuery final : public Td::ResultHandler { flags |= telegram_api::messages_saveDraft::INVERT_MEDIA_MASK; } input_message_entities = get_input_message_entities( - td_->contacts_manager_.get(), draft_message->input_message_text_.text.entities, "SaveDraftMessageQuery"); + td_->user_manager_.get(), draft_message->input_message_text_.text.entities, "SaveDraftMessageQuery"); if (!input_message_entities.empty()) { flags |= telegram_api::messages_saveDraft::ENTITIES_MASK; } @@ -409,16 +408,8 @@ DraftMessage::DraftMessage(Td *td, telegram_api::object_ptrdate_; message_input_reply_to_ = MessageInputReplyTo(td, std::move(draft_message->reply_to_)); - auto entities = - get_message_entities(td->contacts_manager_.get(), std::move(draft_message->entities_), "draftMessage"); - auto status = fix_formatted_text(draft_message->message_, entities, true, true, true, true, true); - if (status.is_error()) { - LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft_message->message_; - if (!clean_input_string(draft_message->message_)) { - draft_message->message_.clear(); - } - entities = find_entities(draft_message->message_, false, true); - } + auto draft_text = get_formatted_text(td->user_manager_.get(), std::move(draft_message->message_), + std::move(draft_message->entities_), true, true, "DraftMessage"); string web_page_url; bool force_small_media = false; bool force_large_media = false; @@ -435,9 +426,8 @@ DraftMessage::DraftMessage(Td *td, telegram_api::object_ptrforce_large_media_; } } - input_message_text_ = InputMessageText(FormattedText{std::move(draft_message->message_), std::move(entities)}, - std::move(web_page_url), draft_message->no_webpage_, force_small_media, - force_large_media, draft_message->invert_media_, false); + input_message_text_ = InputMessageText(std::move(draft_text), std::move(web_page_url), draft_message->no_webpage_, + force_small_media, force_large_media, draft_message->invert_media_, false); } Result> DraftMessage::get_draft_message( @@ -448,7 +438,7 @@ Result> DraftMessage::get_draft_message( } auto result = make_unique(); - result->message_input_reply_to_ = td->messages_manager_->get_message_input_reply_to( + result->message_input_reply_to_ = td->messages_manager_->create_message_input_reply_to( dialog_id, top_thread_message_id, std::move(draft_message->reply_to_), true); auto input_message_content = std::move(draft_message->input_message_text_); diff --git a/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp b/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp index 32ce42a8..40073172 100644 --- a/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp +++ b/lib/tgchat/ext/td/td/telegram/DraftMessage.hpp @@ -10,6 +10,7 @@ #include "td/telegram/InputMessageText.hpp" #include "td/telegram/MessageInputReplyTo.hpp" +#include "td/telegram/MessageQuote.h" #include "td/telegram/Version.h" #include "td/utils/tl_helpers.h" @@ -59,7 +60,7 @@ void DraftMessage::parse(ParserT &parser) { if (has_legacy_reply_to_message_id) { MessageId legacy_reply_to_message_id; td::parse(legacy_reply_to_message_id, parser); - message_input_reply_to_ = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), FormattedText(), 0}; + message_input_reply_to_ = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), MessageQuote()}; } if (has_input_message_text) { td::parse(input_message_text_, parser); diff --git a/lib/tgchat/ext/td/td/telegram/EmojiGroup.cpp b/lib/tgchat/ext/td/td/telegram/EmojiGroup.cpp index 935cdcaf..0bfc32c4 100644 --- a/lib/tgchat/ext/td/td/telegram/EmojiGroup.cpp +++ b/lib/tgchat/ext/td/td/telegram/EmojiGroup.cpp @@ -14,24 +14,54 @@ namespace td { -EmojiGroup::EmojiGroup(telegram_api::object_ptr &&emoji_group) - : title_(std::move(emoji_group->title_)) - , icon_custom_emoji_id_(emoji_group->icon_emoji_id_) - , emojis_(std::move(emoji_group->emoticons_)) { +EmojiGroup::EmojiGroup(telegram_api::object_ptr &&emoji_group_ptr) { + switch (emoji_group_ptr->get_id()) { + case telegram_api::emojiGroup::ID: { + auto emoji_group = telegram_api::move_object_as(emoji_group_ptr); + title_ = std::move(emoji_group->title_); + icon_custom_emoji_id_ = CustomEmojiId(emoji_group->icon_emoji_id_); + emojis_ = std::move(emoji_group->emoticons_); + break; + } + case telegram_api::emojiGroupGreeting::ID: { + auto emoji_group = telegram_api::move_object_as(emoji_group_ptr); + title_ = std::move(emoji_group->title_); + icon_custom_emoji_id_ = CustomEmojiId(emoji_group->icon_emoji_id_); + emojis_ = std::move(emoji_group->emoticons_); + is_greeting_ = true; + break; + } + case telegram_api::emojiGroupPremium::ID: { + auto emoji_group = telegram_api::move_object_as(emoji_group_ptr); + title_ = std::move(emoji_group->title_); + icon_custom_emoji_id_ = CustomEmojiId(emoji_group->icon_emoji_id_); + is_premium_ = true; + break; + } + default: + UNREACHABLE(); + } } td_api::object_ptr EmojiGroup::get_emoji_category_object( StickersManager *stickers_manager) const { + auto source = [&]() -> td_api::object_ptr { + if (is_premium_) { + return td_api::make_object(); + } + return td_api::make_object(vector(emojis_)); + }(); return td_api::make_object( - title_, stickers_manager->get_custom_emoji_sticker_object(icon_custom_emoji_id_), vector(emojis_)); + title_, stickers_manager->get_custom_emoji_sticker_object(icon_custom_emoji_id_), std::move(source), + is_greeting_); } EmojiGroupList::EmojiGroupList(string used_language_codes, int32 hash, - vector> &&emoji_groups) + vector> &&emoji_groups) : used_language_codes_(std::move(used_language_codes)) , hash_(hash) , emoji_groups_(transform(std::move(emoji_groups), - [](telegram_api::object_ptr &&emoji_group) { + [](telegram_api::object_ptr &&emoji_group) { return EmojiGroup(std::move(emoji_group)); })) , next_reload_time_(Time::now() + 3600) { diff --git a/lib/tgchat/ext/td/td/telegram/EmojiGroup.h b/lib/tgchat/ext/td/td/telegram/EmojiGroup.h index 4ff99f01..4634bbad 100644 --- a/lib/tgchat/ext/td/td/telegram/EmojiGroup.h +++ b/lib/tgchat/ext/td/td/telegram/EmojiGroup.h @@ -20,11 +20,13 @@ class EmojiGroup { string title_; CustomEmojiId icon_custom_emoji_id_; vector emojis_; + bool is_greeting_ = false; + bool is_premium_ = false; public: EmojiGroup() = default; - explicit EmojiGroup(telegram_api::object_ptr &&emoji_group); + explicit EmojiGroup(telegram_api::object_ptr &&emoji_group); td_api::object_ptr get_emoji_category_object(StickersManager *stickers_manager) const; @@ -49,7 +51,7 @@ class EmojiGroupList { EmojiGroupList() = default; EmojiGroupList(string used_language_codes, int32 hash, - vector> &&emoji_groups); + vector> &&emoji_groups); td_api::object_ptr get_emoji_categories_object(StickersManager *stickers_manager) const; diff --git a/lib/tgchat/ext/td/td/telegram/EmojiGroup.hpp b/lib/tgchat/ext/td/td/telegram/EmojiGroup.hpp index 8bb9260d..26b8f654 100644 --- a/lib/tgchat/ext/td/td/telegram/EmojiGroup.hpp +++ b/lib/tgchat/ext/td/td/telegram/EmojiGroup.hpp @@ -7,6 +7,7 @@ #pragma once #include "td/telegram/EmojiGroup.h" +#include "td/telegram/Version.h" #include "td/utils/tl_helpers.h" @@ -14,16 +15,36 @@ namespace td { template void EmojiGroup::store(StorerT &storer) const { + bool has_emojis = !emojis_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_greeting_); + STORE_FLAG(is_premium_); + STORE_FLAG(has_emojis); + END_STORE_FLAGS(); td::store(title_, storer); td::store(icon_custom_emoji_id_, storer); - td::store(emojis_, storer); + if (has_emojis) { + td::store(emojis_, storer); + } } template void EmojiGroup::parse(ParserT &parser) { + bool has_emojis; + if (parser.version() >= static_cast(Version::SupportMoreEmojiGroups)) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_greeting_); + PARSE_FLAG(is_premium_); + PARSE_FLAG(has_emojis); + END_PARSE_FLAGS(); + } else { + has_emojis = true; + } td::parse(title_, parser); td::parse(icon_custom_emoji_id_, parser); - td::parse(emojis_, parser); + if (has_emojis) { + td::parse(emojis_, parser); + } } template diff --git a/lib/tgchat/ext/td/td/telegram/EmojiGroupType.cpp b/lib/tgchat/ext/td/td/telegram/EmojiGroupType.cpp index 900d5020..578baa78 100644 --- a/lib/tgchat/ext/td/td/telegram/EmojiGroupType.cpp +++ b/lib/tgchat/ext/td/td/telegram/EmojiGroupType.cpp @@ -19,6 +19,8 @@ EmojiGroupType get_emoji_group_type(const td_api::object_ptr &type); diff --git a/lib/tgchat/ext/td/td/telegram/FactCheck.cpp b/lib/tgchat/ext/td/td/telegram/FactCheck.cpp new file mode 100644 index 00000000..c926a706 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/FactCheck.cpp @@ -0,0 +1,67 @@ +// +// 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/FactCheck.h" + +#include "td/telegram/Dependencies.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +namespace td { + +FactCheck::~FactCheck() = default; + +unique_ptr FactCheck::get_fact_check(Td *td, telegram_api::object_ptr &&fact_check, + bool is_bot) { + if (is_bot || fact_check == nullptr || fact_check->hash_ == 0) { + return nullptr; + } + auto result = make_unique(); + result->country_code_ = std::move(fact_check->country_); + if (fact_check->text_ != nullptr) { + result->text_ = get_formatted_text(td->user_manager_.get(), std::move(fact_check->text_), true, false, "factCheck"); + } + result->hash_ = fact_check->hash_; + result->need_check_ = fact_check->need_check_; + return result; +} + +void FactCheck::update_from(const FactCheck &old_fact_check) { + if (!need_check_ || old_fact_check.need_check_ || hash_ != old_fact_check.hash_) { + return; + } + need_check_ = false; + country_code_ = old_fact_check.country_code_; + text_ = old_fact_check.text_; +} + +void FactCheck::add_dependencies(Dependencies &dependencies) const { + add_formatted_text_dependencies(dependencies, &text_); +} + +td_api::object_ptr FactCheck::get_fact_check_object() const { + if (is_empty() || need_check_) { + return nullptr; + } + return td_api::make_object(get_formatted_text_object(text_, true, -1), country_code_); +} + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs) { + if (lhs == nullptr) { + return rhs == nullptr; + } + if (rhs == nullptr) { + return false; + } + return lhs->country_code_ == rhs->country_code_ && lhs->text_ == rhs->text_ && lhs->hash_ == rhs->hash_ && + lhs->need_check_ == rhs->need_check_; +} + +bool operator!=(const unique_ptr &lhs, const unique_ptr &rhs) { + return !(lhs == rhs); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/FactCheck.h b/lib/tgchat/ext/td/td/telegram/FactCheck.h new file mode 100644 index 00000000..30c3c4c7 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/FactCheck.h @@ -0,0 +1,65 @@ +// +// 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/MessageEntity.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" + +namespace td { + +class Dependencies; + +class Td; + +class FactCheck { + string country_code_; + FormattedText text_; + int64 hash_ = 0; + bool need_check_ = false; + + friend bool operator==(const unique_ptr &lhs, const unique_ptr &rhs); + + public: + FactCheck() = default; + FactCheck(const FactCheck &) = delete; + FactCheck &operator=(const FactCheck &) = delete; + FactCheck(FactCheck &&) = default; + FactCheck &operator=(FactCheck &&) = default; + ~FactCheck(); + + static unique_ptr get_fact_check(Td *td, telegram_api::object_ptr &&fact_check, + bool is_bot); + + bool is_empty() const { + return hash_ == 0; + } + + bool need_check() const { + return need_check_; + } + + void update_from(const FactCheck &old_fact_check); + + void add_dependencies(Dependencies &dependencies) const; + + td_api::object_ptr get_fact_check_object() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs); + +bool operator!=(const unique_ptr &lhs, const unique_ptr &rhs); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/FactCheck.hpp b/lib/tgchat/ext/td/td/telegram/FactCheck.hpp new file mode 100644 index 00000000..a3861748 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/FactCheck.hpp @@ -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/FactCheck.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void FactCheck::store(StorerT &storer) const { + CHECK(!is_empty()); + bool has_country_code = !country_code_.empty(); + bool has_text = !text_.text.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(need_check_); + STORE_FLAG(has_country_code); + STORE_FLAG(has_text); + END_STORE_FLAGS(); + td::store(hash_, storer); + if (has_country_code) { + td::store(country_code_, storer); + } + if (has_text) { + td::store(text_, storer); + } +} + +template +void FactCheck::parse(ParserT &parser) { + bool has_country_code; + bool has_text; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(need_check_); + PARSE_FLAG(has_country_code); + PARSE_FLAG(has_text); + END_PARSE_FLAGS(); + td::parse(hash_, parser); + if (has_country_code) { + td::parse(country_code_, parser); + } + if (has_text) { + td::parse(text_, parser); + } + if (is_empty()) { + parser.set_error("Load an empty fact check"); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/FileReferenceManager.cpp b/lib/tgchat/ext/td/td/telegram/FileReferenceManager.cpp index 9d8cb156..8faaaaeb 100644 --- a/lib/tgchat/ext/td/td/telegram/FileReferenceManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/FileReferenceManager.cpp @@ -9,8 +9,8 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AttachMenuManager.h" #include "td/telegram/BackgroundManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/files/FileManager.h" #include "td/telegram/Global.h" @@ -21,6 +21,7 @@ #include "td/telegram/StickersManager.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/telegram/WebPageId.h" #include "td/telegram/WebPagesManager.h" @@ -324,16 +325,16 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source std::move(promise), "FileSourceMessage", nullptr); }, [&](const FileSourceUserPhoto &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user_profile_photo, source.user_id, + send_closure_later(G()->user_manager(), &UserManager::reload_user_profile_photo, source.user_id, source.photo_id, std::move(promise)); }, [&](const FileSourceChatPhoto &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_chat, source.chat_id, std::move(promise), + send_closure_later(G()->chat_manager(), &ChatManager::reload_chat, source.chat_id, std::move(promise), "FileSourceChatPhoto"); }, [&](const FileSourceChannelPhoto &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_channel, source.channel_id, - std::move(promise), "FileSourceChannelPhoto"); + send_closure_later(G()->chat_manager(), &ChatManager::reload_channel, source.channel_id, std::move(promise), + "FileSourceChannelPhoto"); }, [&](const FileSourceWallpapers &source) { promise.set_error(Status::Error("Can't repair old wallpapers")); }, [&](const FileSourceWebPage &source) { @@ -361,11 +362,11 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source source.access_hash, std::move(promise)); }, [&](const FileSourceChatFull &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_chat_full, source.chat_id, - std::move(promise), "FileSourceChatFull"); + send_closure_later(G()->chat_manager(), &ChatManager::reload_chat_full, source.chat_id, std::move(promise), + "FileSourceChatFull"); }, [&](const FileSourceChannelFull &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_channel_full, source.channel_id, + send_closure_later(G()->chat_manager(), &ChatManager::reload_channel_full, source.channel_id, std::move(promise), "FileSourceChannelFull"); }, [&](const FileSourceAppConfig &source) { @@ -376,8 +377,8 @@ void FileReferenceManager::send_query(Destination dest, FileSourceId file_source std::move(promise)); }, [&](const FileSourceUserFull &source) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user_full, source.user_id, - std::move(promise), "FileSourceUserFull"); + send_closure_later(G()->user_manager(), &UserManager::reload_user_full, source.user_id, std::move(promise), + "FileSourceUserFull"); }, [&](const FileSourceAttachMenuBot &source) { send_closure_later(G()->attach_menu_manager(), &AttachMenuManager::reload_attach_menu_bot, source.user_id, diff --git a/lib/tgchat/ext/td/td/telegram/FileReferenceManager.hpp b/lib/tgchat/ext/td/td/telegram/FileReferenceManager.hpp index cef314f1..2cfa47fb 100644 --- a/lib/tgchat/ext/td/td/telegram/FileReferenceManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/FileReferenceManager.hpp @@ -11,7 +11,7 @@ #include "td/telegram/BackgroundManager.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileSourceId.h" #include "td/telegram/MessageFullId.h" @@ -24,6 +24,7 @@ #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/telegram/WebPagesManager.h" #include "td/utils/common.h" @@ -82,7 +83,7 @@ FileSourceId FileReferenceManager::parse_file_source(Td *td, ParserT &parser) { int64 photo_id; td::parse(user_id, parser); td::parse(photo_id, parser); - return td->contacts_manager_->get_user_profile_photo_file_source_id(user_id, photo_id); + return td->user_manager_->get_user_profile_photo_file_source_id(user_id, photo_id); } case 2: { ChatId chat_id; @@ -120,12 +121,12 @@ FileSourceId FileReferenceManager::parse_file_source(Td *td, ParserT &parser) { case 10: { ChatId chat_id; td::parse(chat_id, parser); - return td->contacts_manager_->get_chat_full_file_source_id(chat_id); + return td->chat_manager_->get_chat_full_file_source_id(chat_id); } case 11: { ChannelId channel_id; td::parse(channel_id, parser); - return td->contacts_manager_->get_channel_full_file_source_id(channel_id); + return td->chat_manager_->get_channel_full_file_source_id(channel_id); } case 12: return td->stickers_manager_->get_app_config_file_source_id(); @@ -134,7 +135,7 @@ FileSourceId FileReferenceManager::parse_file_source(Td *td, ParserT &parser) { case 14: { UserId user_id; td::parse(user_id, parser); - return td->contacts_manager_->get_user_full_file_source_id(user_id); + return td->user_manager_->get_user_full_file_source_id(user_id); } case 15: { UserId user_id; diff --git a/lib/tgchat/ext/td/td/telegram/ForumTopicManager.cpp b/lib/tgchat/ext/td/td/telegram/ForumTopicManager.cpp index 878a9a1c..f4a9b3d4 100644 --- a/lib/tgchat/ext/td/td/telegram/ForumTopicManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ForumTopicManager.cpp @@ -9,7 +9,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/CustomEmojiId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/ForumTopic.h" @@ -29,6 +29,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -75,7 +76,7 @@ class CreateForumTopicQuery final : public Td::ResultHandler { random_id_ = Random::secure_int64(); } while (random_id_ == 0); - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_createForumTopic(flags, std::move(input_channel), title, icon_color, @@ -117,7 +118,7 @@ class CreateForumTopicQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "CreateForumTopicQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "CreateForumTopicQuery"); promise_.set_error(std::move(status)); } }; @@ -136,7 +137,7 @@ class EditForumTopicQuery final : public Td::ResultHandler { channel_id_ = channel_id; top_thread_message_id_ = top_thread_message_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; @@ -157,7 +158,7 @@ class EditForumTopicQuery final : public Td::ResultHandler { channel_id_ = channel_id; top_thread_message_id_ = top_thread_message_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = telegram_api::channels_editForumTopic::CLOSED_MASK; @@ -172,7 +173,7 @@ class EditForumTopicQuery final : public Td::ResultHandler { channel_id_ = channel_id; top_thread_message_id_ = MessageId(ServerMessageId(1)); - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = telegram_api::channels_editForumTopic::HIDDEN_MASK; @@ -198,7 +199,7 @@ class EditForumTopicQuery final : public Td::ResultHandler { if (status.message() == "TOPIC_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { return promise_.set_value(Unit()); } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditForumTopicQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "EditForumTopicQuery"); promise_.set_error(std::move(status)); } }; @@ -214,7 +215,7 @@ class UpdatePinnedForumTopicQuery final : public Td::ResultHandler { void send(ChannelId channel_id, MessageId top_thread_message_id, bool is_pinned) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( @@ -238,7 +239,7 @@ class UpdatePinnedForumTopicQuery final : public Td::ResultHandler { if (status.message() == "PINNED_TOPIC_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { return promise_.set_value(Unit()); } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdatePinnedForumTopicQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "UpdatePinnedForumTopicQuery"); promise_.set_error(std::move(status)); } }; @@ -254,7 +255,7 @@ class ReorderPinnedForumTopicsQuery final : public Td::ResultHandler { void send(ChannelId channel_id, const vector &top_thread_message_ids) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = telegram_api::channels_reorderPinnedForumTopics::FORCE_MASK; @@ -279,7 +280,7 @@ class ReorderPinnedForumTopicsQuery final : public Td::ResultHandler { if (status.message() == "PINNED_TOPICS_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { return promise_.set_value(Unit()); } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReorderPinnedForumTopicsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReorderPinnedForumTopicsQuery"); promise_.set_error(std::move(status)); } }; @@ -298,7 +299,7 @@ class GetForumTopicQuery final : public Td::ResultHandler { channel_id_ = channel_id; top_thread_message_id_ = top_thread_message_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( @@ -316,8 +317,8 @@ class GetForumTopicQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetForumTopicQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetForumTopicQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetForumTopicQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetForumTopicQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetForumTopicQuery"); if (ptr->topics_.size() != 1u) { return promise_.set_value(nullptr); @@ -345,7 +346,7 @@ class GetForumTopicQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetForumTopicQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetForumTopicQuery"); promise_.set_error(std::move(status)); } }; @@ -363,7 +364,7 @@ class GetForumTopicsQuery final : public Td::ResultHandler { MessageId offset_top_thread_message_id, int32 limit) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; @@ -386,8 +387,8 @@ class GetForumTopicsQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetForumTopicsQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetForumTopicsQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetForumTopicsQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetForumTopicsQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetForumTopicsQuery"); MessagesInfo messages_info; messages_info.messages = std::move(ptr->messages_); @@ -412,7 +413,7 @@ class GetForumTopicsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetForumTopicsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetForumTopicsQuery"); promise_.set_error(std::move(status)); } }; @@ -501,7 +502,7 @@ void ForumTopicManager::create_forum_topic(DialogId dialog_id, string &&title, TRY_STATUS_PROMISE(promise, is_forum(dialog_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_create_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_create_topics()) { return promise.set_error(Status::Error(400, "Not enough rights to create a topic")); } @@ -550,7 +551,7 @@ void ForumTopicManager::edit_forum_topic(DialogId dialog_id, MessageId top_threa TRY_STATUS_PROMISE(promise, can_be_message_thread_id(top_thread_message_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_edit_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_edit_topics()) { auto topic_info = get_topic_info(dialog_id, top_thread_message_id); if (topic_info != nullptr && !topic_info->is_outgoing()) { return promise.set_error(Status::Error(400, "Not enough rights to edit the topic")); @@ -782,7 +783,7 @@ void ForumTopicManager::get_forum_topic_link(DialogId dialog_id, MessageId top_t sb << LinkManager::get_t_me_url(); bool is_public = false; - auto dialog_username = td_->contacts_manager_->get_channel_first_username(channel_id); + auto dialog_username = td_->chat_manager_->get_channel_first_username(channel_id); if (!dialog_username.empty()) { sb << dialog_username; is_public = true; @@ -856,7 +857,7 @@ void ForumTopicManager::toggle_forum_topic_is_closed(DialogId dialog_id, Message TRY_STATUS_PROMISE(promise, can_be_message_thread_id(top_thread_message_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_edit_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_edit_topics()) { auto topic_info = get_topic_info(dialog_id, top_thread_message_id); if (topic_info != nullptr && !topic_info->is_outgoing()) { return promise.set_error(Status::Error(400, "Not enough rights to close or open the topic")); @@ -870,7 +871,7 @@ void ForumTopicManager::toggle_forum_topic_is_hidden(DialogId dialog_id, bool is TRY_STATUS_PROMISE(promise, is_forum(dialog_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_edit_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_edit_topics()) { return promise.set_error(Status::Error(400, "Not enough rights to close or open the topic")); } @@ -883,7 +884,7 @@ void ForumTopicManager::toggle_forum_topic_is_pinned(DialogId dialog_id, Message TRY_STATUS_PROMISE(promise, can_be_message_thread_id(top_thread_message_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_pin_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_pin_topics()) { return promise.set_error(Status::Error(400, "Not enough rights to pin or unpin the topic")); } @@ -899,7 +900,7 @@ void ForumTopicManager::set_pinned_forum_topics(DialogId dialog_id, vectorcontacts_manager_->get_channel_permissions(channel_id).can_pin_topics()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_pin_topics()) { return promise.set_error(Status::Error(400, "Not enough rights to reorder forum topics")); } @@ -912,7 +913,7 @@ void ForumTopicManager::delete_forum_topic(DialogId dialog_id, MessageId top_thr TRY_STATUS_PROMISE(promise, can_be_message_thread_id(top_thread_message_id)); auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_delete_messages()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_delete_messages()) { auto topic_info = get_topic_info(dialog_id, top_thread_message_id); if (topic_info != nullptr && !topic_info->is_outgoing()) { return promise.set_error(Status::Error(400, "Not enough rights to delete the topic")); @@ -1070,7 +1071,7 @@ Status ForumTopicManager::is_forum(DialogId dialog_id) { return Status::Error(400, "Chat not found"); } if (dialog_id.get_type() != DialogType::Channel || - !td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id())) { + !td_->chat_manager_->is_forum_channel(dialog_id.get_channel_id())) { return Status::Error(400, "The chat is not a forum"); } return Status::OK(); @@ -1078,7 +1079,7 @@ Status ForumTopicManager::is_forum(DialogId dialog_id) { bool ForumTopicManager::can_be_forum(DialogId dialog_id) const { return dialog_id.get_type() == DialogType::Channel && - td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id()); + td_->chat_manager_->is_megagroup_channel(dialog_id.get_channel_id()); } Status ForumTopicManager::can_be_message_thread_id(MessageId top_thread_message_id) { diff --git a/lib/tgchat/ext/td/td/telegram/Game.cpp b/lib/tgchat/ext/td/td/telegram/Game.cpp index c275b54c..9150a992 100644 --- a/lib/tgchat/ext/td/td/telegram/Game.cpp +++ b/lib/tgchat/ext/td/td/telegram/Game.cpp @@ -7,13 +7,13 @@ #include "td/telegram/Game.h" #include "td/telegram/AnimationsManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/misc.h" #include "td/telegram/Photo.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/common.h" #include "td/utils/logging.h" @@ -91,7 +91,7 @@ bool Game::has_input_media() const { } tl_object_ptr Game::get_input_media_game(const Td *td) const { - auto input_user = td->contacts_manager_->get_input_user_force(bot_user_id_); + auto input_user = td->user_manager_->get_input_user_force(bot_user_id_); return make_tl_object( make_tl_object(std::move(input_user), short_name_)); } @@ -113,14 +113,14 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Game &game) { << ", photo = " << game.photo_ << ", animation_file_id = " << game.animation_file_id_ << "]"; } -Result process_input_message_game(const ContactsManager *contacts_manager, +Result process_input_message_game(const UserManager *user_manager, tl_object_ptr &&input_message_content) { CHECK(input_message_content != nullptr); CHECK(input_message_content->get_id() == td_api::inputMessageGame::ID); auto input_message_game = move_tl_object_as(input_message_content); UserId bot_user_id(input_message_game->bot_user_id_); - TRY_STATUS(contacts_manager->get_input_user(bot_user_id)); + TRY_STATUS(user_manager->get_input_user(bot_user_id)); if (!clean_input_string(input_message_game->game_short_name_)) { return Status::Error(400, "Game short name must be encoded in UTF-8"); diff --git a/lib/tgchat/ext/td/td/telegram/Game.h b/lib/tgchat/ext/td/td/telegram/Game.h index f728b233..4178981e 100644 --- a/lib/tgchat/ext/td/td/telegram/Game.h +++ b/lib/tgchat/ext/td/td/telegram/Game.h @@ -20,7 +20,7 @@ namespace td { -class ContactsManager; +class UserManager; class Td; class Game { @@ -79,7 +79,7 @@ bool operator!=(const Game &lhs, const Game &rhs); StringBuilder &operator<<(StringBuilder &string_builder, const Game &game); -Result process_input_message_game(const ContactsManager *contacts_manager, +Result process_input_message_game(const UserManager *user_manager, tl_object_ptr &&input_message_content) TD_WARN_UNUSED_RESULT; diff --git a/lib/tgchat/ext/td/td/telegram/GameManager.cpp b/lib/tgchat/ext/td/td/telegram/GameManager.cpp index bbbff97f..ced8cadc 100644 --- a/lib/tgchat/ext/td/td/telegram/GameManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/GameManager.cpp @@ -9,7 +9,6 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ChainId.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -21,6 +20,7 @@ #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" @@ -203,16 +203,14 @@ void GameManager::set_game_score(MessageFullId message_full_id, bool edit_messag bool force, Promise> &&promise) { CHECK(td_->auth_manager_->is_bot()); + auto dialog_id = message_full_id.get_dialog_id(); + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Edit, "set_game_score")); if (!td_->messages_manager_->have_message_force(message_full_id, "set_game_score")) { return promise.set_error(Status::Error(400, "Message not found")); } - auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); if (!td_->messages_manager_->can_set_game_score(message_full_id)) { return promise.set_error(Status::Error(400, "Game score can't be set")); @@ -243,7 +241,7 @@ void GameManager::set_inline_game_score(const string &inline_message_id, bool ed return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_bot_inline_message_id), edit_message, std::move(input_user), score, force); @@ -253,20 +251,20 @@ void GameManager::get_game_high_scores(MessageFullId message_full_id, UserId use Promise> &&promise) { CHECK(td_->auth_manager_->is_bot()); + auto dialog_id = message_full_id.get_dialog_id(); + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "get_game_high_scores")); + if (!td_->messages_manager_->have_message_force(message_full_id, "get_game_high_scores")) { return promise.set_error(Status::Error(400, "Message not found")); } - auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } auto message_id = message_full_id.get_message_id(); - if (message_id.is_scheduled() || !message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat) { + if (message_id.is_scheduled() || !message_id.is_server()) { return promise.set_error(Status::Error(400, "Wrong message identifier specified")); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise))->send(dialog_id, message_id, std::move(input_user)); } @@ -280,7 +278,7 @@ void GameManager::get_inline_game_high_scores(const string &inline_message_id, U return promise.set_error(Status::Error(400, "Invalid inline message identifier specified")); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_bot_inline_message_id), std::move(input_user)); @@ -288,7 +286,7 @@ void GameManager::get_inline_game_high_scores(const string &inline_message_id, U td_api::object_ptr GameManager::get_game_high_scores_object( telegram_api::object_ptr &&high_scores) { - td_->contacts_manager_->on_get_users(std::move(high_scores->users_), "get_game_high_scores_object"); + td_->user_manager_->on_get_users(std::move(high_scores->users_), "get_game_high_scores_object"); auto result = td_api::make_object(); for (const auto &high_score : high_scores->scores_) { @@ -300,7 +298,7 @@ td_api::object_ptr GameManager::get_game_high_scores_obj continue; } result->scores_.push_back(make_tl_object( - position, td_->contacts_manager_->get_user_id_object(user_id, "get_game_high_scores_object"), score)); + position, td_->user_manager_->get_user_id_object(user_id, "get_game_high_scores_object"), score)); } return result; } diff --git a/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp index b0b04727..480bd055 100644 --- a/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp +++ b/lib/tgchat/ext/td/td/telegram/GiveawayParameters.cpp @@ -7,7 +7,7 @@ #include "td/telegram/GiveawayParameters.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" @@ -29,9 +29,8 @@ Result GiveawayParameters::get_boosted_channel_id(Td *td, DialogId di return Status::Error(400, "Can't boost the chat"); } auto channel_id = dialog_id.get_channel_id(); - auto status = td->contacts_manager_->get_channel_status(channel_id); - if (td->contacts_manager_->is_broadcast_channel(channel_id) ? !status.can_post_messages() - : !status.is_administrator()) { + auto status = td->chat_manager_->get_channel_status(channel_id); + if (td->chat_manager_->is_broadcast_channel(channel_id) ? !status.can_post_messages() : !status.is_administrator()) { return Status::Error(400, "Not enough rights in the chat"); } return channel_id; diff --git a/lib/tgchat/ext/td/td/telegram/Global.cpp b/lib/tgchat/ext/td/td/telegram/Global.cpp index 587bed20..269f633f 100644 --- a/lib/tgchat/ext/td/td/telegram/Global.cpp +++ b/lib/tgchat/ext/td/td/telegram/Global.cpp @@ -13,6 +13,7 @@ #include "td/telegram/OptionManager.h" #include "td/telegram/StateManager.h" #include "td/telegram/TdDb.h" +#include "td/telegram/UpdatesManager.h" #include "td/utils/format.h" #include "td/utils/logging.h" @@ -343,6 +344,10 @@ void Global::add_location_access_hash(double latitude, double longitude, int64 a location_access_hashes_[get_location_key(latitude, longitude)] = access_hash; } +void Global::notify_speed_limited(bool is_upload) { + send_closure(updates_manager_, &UpdatesManager::notify_speed_limited, is_upload); +} + double get_global_server_time() { return G()->server_time(); } diff --git a/lib/tgchat/ext/td/td/telegram/Global.h b/lib/tgchat/ext/td/td/telegram/Global.h index a2b29635..0947b712 100644 --- a/lib/tgchat/ext/td/td/telegram/Global.h +++ b/lib/tgchat/ext/td/td/telegram/Global.h @@ -37,11 +37,12 @@ class AuthManager; class AutosaveManager; class BackgroundManager; class BoostManager; +class BusinessConnectionManager; class BusinessManager; class CallManager; +class ChatManager; class ConfigManager; class ConnectionCreator; -class ContactsManager; class DialogActionManager; class DialogFilterManager; class DialogInviteLinkManager; @@ -62,6 +63,7 @@ class NotificationManager; class NotificationSettingsManager; class OptionManager; class PasswordManager; +class PeopleNearbyManager; class QuickReplyManager; class ReactionManager; class SavedMessagesManager; @@ -79,6 +81,7 @@ class TimeZoneManager; class TopDialogManager; class TranscriptionManager; class UpdatesManager; +class UserManager; class WebPagesManager; class Global final : public ActorContext { @@ -232,6 +235,13 @@ class Global final : public ActorContext { boost_manager_ = boost_manager; } + ActorId business_connection_manager() const { + return business_connection_manager_; + } + void set_business_connection_manager(ActorId business_connection_manager) { + business_connection_manager_ = business_connection_manager; + } + ActorId business_manager() const { return business_manager_; } @@ -246,6 +256,13 @@ class Global final : public ActorContext { call_manager_ = call_manager; } + ActorId chat_manager() const { + return chat_manager_; + } + void set_chat_manager(ActorId chat_manager) { + chat_manager_ = chat_manager; + } + ActorId config_manager() const { return config_manager_; } @@ -253,13 +270,6 @@ class Global final : public ActorContext { config_manager_ = config_manager; } - ActorId contacts_manager() const { - return contacts_manager_; - } - void set_contacts_manager(ActorId contacts_manager) { - contacts_manager_ = contacts_manager; - } - ActorId dialog_action_manager() const { return dialog_action_manager_; } @@ -391,6 +401,13 @@ class Global final : public ActorContext { password_manager_ = password_manager; } + ActorId people_nearby_manager() const { + return people_nearby_manager_; + } + void set_people_nearby_manager(ActorId people_nearby_manager) { + people_nearby_manager_ = people_nearby_manager; + } + ActorId quick_reply_manager() const { return quick_reply_manager_; } @@ -482,6 +499,13 @@ class Global final : public ActorContext { updates_manager_ = updates_manager; } + ActorId user_manager() const { + return user_manager_; + } + void set_user_manager(ActorId user_manager) { + user_manager_ = user_manager; + } + ActorId web_pages_manager() const { return web_pages_manager_; } @@ -602,6 +626,8 @@ class Global final : public ActorContext { store_all_files_in_files_directory_ = flag; } + void notify_speed_limited(bool is_upload); + private: std::shared_ptr dh_config_; @@ -615,10 +641,11 @@ class Global final : public ActorContext { ActorId autosave_manager_; ActorId background_manager_; ActorId boost_manager_; + ActorId business_connection_manager_; ActorId business_manager_; ActorId call_manager_; + ActorId chat_manager_; ActorId config_manager_; - ActorId contacts_manager_; ActorId dialog_action_manager_; ActorId dialog_filter_manager_; ActorId dialog_invite_link_manager_; @@ -637,6 +664,7 @@ class Global final : public ActorContext { ActorId notification_manager_; ActorId notification_settings_manager_; ActorId password_manager_; + ActorId people_nearby_manager_; ActorId quick_reply_manager_; ActorId reaction_manager_; ActorId saved_messages_manager_; @@ -650,6 +678,7 @@ class Global final : public ActorContext { ActorId top_dialog_manager_; ActorId transcription_manager_; ActorId updates_manager_; + ActorId user_manager_; ActorId web_pages_manager_; ActorOwn connection_creator_; ActorOwn temp_auth_key_watchdog_; diff --git a/lib/tgchat/ext/td/td/telegram/GroupCallManager.cpp b/lib/tgchat/ext/td/td/telegram/GroupCallManager.cpp index b60236ae..a18193d9 100644 --- a/lib/tgchat/ext/td/td/telegram/GroupCallManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/GroupCallManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogManager.h" @@ -24,6 +24,7 @@ #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -141,8 +142,8 @@ class GetGroupCallJoinAsQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetGroupCallJoinAsQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetGroupCallJoinAsQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetGroupCallJoinAsQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetGroupCallJoinAsQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetGroupCallJoinAsQuery"); promise_.set_value(convert_message_senders_object(td_, ptr->peers_)); } @@ -1101,7 +1102,7 @@ void GroupCallManager::on_send_speaking_action_timeout(GroupCallId group_call_id pending_send_speaking_action_timeout_.add_timeout_in(group_call_id.get(), 4.0); - td_->dialog_action_manager_->send_dialog_action(group_call->dialog_id, MessageId(), + td_->dialog_action_manager_->send_dialog_action(group_call->dialog_id, MessageId(), {}, DialogAction::get_speaking_action(), Promise()); } @@ -1222,22 +1223,14 @@ GroupCallManager::GroupCall *GroupCallManager::get_group_call(InputGroupCallId i } Status GroupCallManager::can_join_group_calls(DialogId dialog_id) const { - if (!dialog_id.is_valid()) { - return Status::Error(400, "Invalid chat identifier specified"); - } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_group_call_join_as")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access chat"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "can_join_group_calls")); switch (dialog_id.get_type()) { case DialogType::Chat: case DialogType::Channel: break; case DialogType::User: - case DialogType::SecretChat: return Status::Error(400, "Chat can't have a voice chat"); + case DialogType::SecretChat: case DialogType::None: default: UNREACHABLE(); @@ -1250,14 +1243,14 @@ Status GroupCallManager::can_manage_group_calls(DialogId dialog_id) const { switch (dialog_id.get_type()) { case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - if (!td_->contacts_manager_->get_chat_permissions(chat_id).can_manage_calls()) { + if (!td_->chat_manager_->get_chat_permissions(chat_id).can_manage_calls()) { return Status::Error(400, "Not enough rights in the chat"); } break; } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_manage_calls()) { + if (!td_->chat_manager_->get_channel_permissions(channel_id).can_manage_calls()) { return Status::Error(400, "Not enough rights in the chat"); } break; @@ -1322,7 +1315,7 @@ void GroupCallManager::set_group_call_default_join_as(DialogId dialog_id, Dialog default: return promise.set_error(Status::Error(400, "Invalid default participant identifier specified")); } - if (!td_->dialog_manager_->have_input_peer(as_dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(as_dialog_id, false, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access specified default participant chat")); } @@ -1332,16 +1325,8 @@ void GroupCallManager::set_group_call_default_join_as(DialogId dialog_id, Dialog void GroupCallManager::create_voice_chat(DialogId dialog_id, string title, int32 start_date, bool is_rtmp_stream, Promise &&promise) { - if (!dialog_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); - } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "create_voice_chat")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access chat")); - } - + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "create_voice_chat")); TRY_STATUS_PROMISE(promise, can_manage_group_calls(dialog_id)); title = clean_name(title, MAX_TITLE_LENGTH); @@ -1361,16 +1346,8 @@ void GroupCallManager::create_voice_chat(DialogId dialog_id, string title, int32 void GroupCallManager::get_voice_chat_rtmp_stream_url(DialogId dialog_id, bool revoke, Promise> &&promise) { - if (!dialog_id.is_valid()) { - return promise.set_error(Status::Error(400, "Invalid chat identifier specified")); - } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_voice_chat_rtmp_stream_url")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access chat")); - } - + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "get_voice_chat_rtmp_stream_url")); TRY_STATUS_PROMISE(promise, can_manage_group_calls(dialog_id)); td_->create_handler(std::move(promise))->send(dialog_id, revoke); @@ -1460,8 +1437,8 @@ void GroupCallManager::finish_get_group_call(InputGroupCallId input_group_call_i load_group_call_queries_.erase(it); if (result.is_ok()) { - td_->contacts_manager_->on_get_users(std::move(result.ok_ref()->users_), "finish_get_group_call"); - td_->contacts_manager_->on_get_chats(std::move(result.ok_ref()->chats_), "finish_get_group_call"); + td_->user_manager_->on_get_users(std::move(result.ok_ref()->users_), "finish_get_group_call"); + td_->chat_manager_->on_get_chats(std::move(result.ok_ref()->chats_), "finish_get_group_call"); if (update_group_call(result.ok()->call_, DialogId()) != input_group_call_id) { LOG(ERROR) << "Expected " << input_group_call_id << ", but received " << to_string(result.ok()); @@ -1605,8 +1582,8 @@ void GroupCallManager::on_get_group_call_participants( LOG(INFO) << "Receive group call participants: " << to_string(participants); CHECK(participants != nullptr); - td_->contacts_manager_->on_get_users(std::move(participants->users_), "on_get_group_call_participants"); - td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "on_get_group_call_participants"); + td_->user_manager_->on_get_users(std::move(participants->users_), "on_get_group_call_participants"); + td_->chat_manager_->on_get_chats(std::move(participants->chats_), "on_get_group_call_participants"); if (!need_group_call_participants(input_group_call_id)) { return; @@ -2642,7 +2619,7 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, DialogId as_di if (as_dialog_id != my_dialog_id) { return promise.set_error(Status::Error(400, "Can't join voice chat as another user")); } - if (!td_->contacts_manager_->have_user_force(as_dialog_id.get_user_id(), "join_group_call")) { + if (!td_->user_manager_->have_user_force(as_dialog_id.get_user_id(), "join_group_call")) { have_as_dialog_id = false; } } else { @@ -2650,12 +2627,9 @@ void GroupCallManager::join_group_call(GroupCallId group_call_id, DialogId as_di return promise.set_error(Status::Error(400, "Join as chat not found")); } } - if (!td_->dialog_manager_->have_input_peer(as_dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(as_dialog_id, false, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the join as participant")); } - if (dialog_type == DialogType::SecretChat) { - return promise.set_error(Status::Error(400, "Can't join voice chat as a secret chat")); - } } if (group_call->is_being_left) { @@ -3520,9 +3494,9 @@ void GroupCallManager::invite_group_call_participants(GroupCallId group_call_id, TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id)); vector> input_users; - auto my_user_id = td_->contacts_manager_->get_my_id(); + auto my_user_id = td_->user_manager_->get_my_id(); for (auto user_id : user_ids) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); if (user_id == my_user_id) { // can't invite self @@ -4125,9 +4099,9 @@ void GroupCallManager::on_group_call_left_impl(GroupCall *group_call, bool need_ group_call->need_rejoin = need_rejoin && !group_call->is_being_left; if (group_call->need_rejoin && group_call->dialog_id.is_valid()) { auto dialog_id = group_call->dialog_id; - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) || (dialog_id.get_type() == DialogType::Chat && - !td_->contacts_manager_->get_chat_status(dialog_id.get_chat_id()).is_member())) { + !td_->chat_manager_->get_chat_status(dialog_id.get_chat_id()).is_member())) { group_call->need_rejoin = false; } } diff --git a/lib/tgchat/ext/td/td/telegram/HashtagHints.cpp b/lib/tgchat/ext/td/td/telegram/HashtagHints.cpp index 034f8fd6..c33ee800 100644 --- a/lib/tgchat/ext/td/td/telegram/HashtagHints.cpp +++ b/lib/tgchat/ext/td/td/telegram/HashtagHints.cpp @@ -36,13 +36,12 @@ void HashtagHints::hashtag_used(const string &hashtag) { } hashtag_used_impl(hashtag); G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(keys_to_strings(hints_.search_empty(101).second)), - Promise<>()); + Promise()); } -void HashtagHints::remove_hashtag(string hashtag, Promise<> promise) { +void HashtagHints::remove_hashtag(string hashtag, Promise promise) { if (!sync_with_db_) { - promise.set_value(Unit()); - return; + return promise.set_value(Unit()); } if (hashtag[0] == '#') { hashtag = hashtag.substr(1); @@ -51,16 +50,25 @@ void HashtagHints::remove_hashtag(string hashtag, Promise<> promise) { if (hints_.has_key(key)) { hints_.remove(key); G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(keys_to_strings(hints_.search_empty(101).second)), - Promise<>()); + Promise()); promise.set_value(Unit()); // set promise explicitly, because sqlite_pmc waits for too long before setting promise } else { promise.set_value(Unit()); } } -void HashtagHints::query(const string &prefix, int32 limit, Promise> promise) { +void HashtagHints::clear(Promise promise) { if (!sync_with_db_) { - promise.set_value(std::vector()); + return promise.set_value(Unit()); + } + hints_ = {}; + G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(vector()), Promise()); + promise.set_value(Unit()); +} + +void HashtagHints::query(const string &prefix, int32 limit, Promise> promise) { + if (!sync_with_db_) { + promise.set_value(vector()); return; } @@ -92,7 +100,7 @@ void HashtagHints::from_db(Result data, bool dummy) { if (data.is_error() || data.ok().empty()) { return; } - std::vector hashtags; + vector hashtags; auto status = unserialize(hashtags, data.ok()); if (status.is_error()) { LOG(ERROR) << "Failed to unserialize hashtag hints: " << status; @@ -104,12 +112,13 @@ void HashtagHints::from_db(Result data, bool dummy) { } } -std::vector HashtagHints::keys_to_strings(const std::vector &keys) { - std::vector result; +vector HashtagHints::keys_to_strings(const vector &keys) { + vector result; result.reserve(keys.size()); for (auto &it : keys) { result.push_back(hints_.key_to_string(it)); } return result; } + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/HashtagHints.h b/lib/tgchat/ext/td/td/telegram/HashtagHints.h index 2bca18e9..82cded84 100644 --- a/lib/tgchat/ext/td/td/telegram/HashtagHints.h +++ b/lib/tgchat/ext/td/td/telegram/HashtagHints.h @@ -21,9 +21,11 @@ class HashtagHints final : public Actor { void hashtag_used(const string &hashtag); - void remove_hashtag(string hashtag, Promise<> promise); + void remove_hashtag(string hashtag, Promise promise); - void query(const string &prefix, int32 limit, Promise> promise); + void clear(Promise promise); + + void query(const string &prefix, int32 limit, Promise> promise); private: string mode_; @@ -39,7 +41,7 @@ class HashtagHints final : public Actor { void hashtag_used_impl(const string &hashtag); void from_db(Result data, bool dummy); - std::vector keys_to_strings(const std::vector &keys); + vector keys_to_strings(const vector &keys); }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp index a7dfd465..e62e7897 100644 --- a/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/InlineQueriesManager.cpp @@ -10,8 +10,8 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Contact.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" @@ -39,6 +39,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/ThemeManager.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Venue.h" #include "td/telegram/VideosManager.h" #include "td/telegram/VoiceNotesManager.h" @@ -372,14 +373,14 @@ Result> InlineQueriesManager: return Status::Error(400, "Inline message must be non-empty"); } TRY_RESULT(reply_markup, get_reply_markup(std::move(reply_markup_ptr), true, true, false, true)); - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), reply_markup); auto constructor_id = input_message_content->get_id(); if (constructor_id == td_api::inputMessageText::ID) { TRY_RESULT(input_message_text, process_input_message_text(td_, td_->dialog_manager_->get_my_dialog_id(), std::move(input_message_content), true)); - auto entities = get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, - "get_inline_message"); + auto entities = + get_input_message_entities(td_->user_manager_.get(), input_message_text.text.entities, "get_inline_message"); if (!input_message_text.web_page_url.empty()) { int32 flags = 0; if (input_reply_markup != nullptr) { @@ -422,7 +423,7 @@ Result> InlineQueriesManager: std::move(entities), std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageContact::ID) { - TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content))); + TRY_RESULT(contact, process_input_message_contact(td_, std::move(input_message_content))); return contact.get_input_bot_inline_message_media_contact(std::move(input_reply_markup)); } if (constructor_id == td_api::inputMessageInvoice::ID) { @@ -458,10 +459,13 @@ Result> InlineQueriesManager: if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageMediaAuto::REPLY_MARKUP_MASK; } - auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "get_inline_message"); + auto entities = get_input_message_entities(td_->user_manager_.get(), caption.entities, "get_inline_message"); if (!entities.empty()) { flags |= telegram_api::inputBotInlineMessageMediaAuto::ENTITIES_MASK; } + if (extract_input_invert_media(input_message_content)) { + flags |= telegram_api::inputBotInlineMessageMediaAuto::INVERT_MEDIA_MASK; + } return make_tl_object( flags, false /*ignored*/, caption.text, std::move(entities), std::move(input_reply_markup)); } @@ -534,13 +538,13 @@ void InlineQueriesManager::answer_inline_query( case td_api::inlineQueryResultsButtonTypeStartBot::ID: { auto type = td_api::move_object_as(button->type_); if (type->parameter_.empty()) { - return promise.set_error(Status::Error(400, "Can't use empty switch_pm_parameter")); + return promise.set_error(Status::Error(400, "Can't use empty start_parameter")); } if (type->parameter_.size() > 64) { - return promise.set_error(Status::Error(400, "Too long switch_pm_parameter specified")); + return promise.set_error(Status::Error(400, "Too long start_parameter specified")); } if (!is_base64url_characters(type->parameter_)) { - return promise.set_error(Status::Error(400, "Unallowed characters in switch_pm_parameter are used")); + return promise.set_error(Status::Error(400, "Unallowed characters in start_parameter are used")); } switch_pm = telegram_api::make_object(button->text_, type->parameter_); break; @@ -581,8 +585,8 @@ void InlineQueriesManager::answer_inline_query( void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string &&url, const td_api::object_ptr &theme, string &&platform, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(bot_user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), std::move(url), theme, std::move(platform)); @@ -590,14 +594,14 @@ void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string && void InlineQueriesManager::send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, Promise &&promise) const { - TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT_PROMISE(promise, bot_data, td_->user_manager_->get_bot_data(bot_user_id)); int64 random_id; do { random_id = Random::secure_int64(); } while (random_id == 0); - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(bot_user_id)); td_->create_handler(std::move(promise)) ->send(std::move(input_user), random_id, button_text, data); @@ -776,7 +780,7 @@ Result> InlineQueriesManager:: return r_reply_markup.move_as_error(); } - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_reply_markup.ok()); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), r_reply_markup.ok()); int32 flags = 0; if (input_reply_markup != nullptr) { flags |= telegram_api::inputBotInlineMessageGame::REPLY_MARKUP_MASK; @@ -1022,7 +1026,7 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial return 0; } - auto r_bot_data = td_->contacts_manager_->get_bot_data(bot_user_id); + auto r_bot_data = td_->user_manager_->get_bot_data(bot_user_id); if (r_bot_data.is_error()) { promise.set_error(r_bot_data.move_as_error()); return 0; @@ -1050,7 +1054,7 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial return dialog_id == DialogId(bot_user_id) ? 3 : 4; case telegram_api::inputPeerChannel::ID: case telegram_api::inputPeerChannelFromMessage::ID: - return 5 + static_cast(td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())); + return 5 + static_cast(td_->chat_manager_->get_channel_type(dialog_id.get_channel_id())); default: UNREACHABLE(); return -1; @@ -1104,7 +1108,7 @@ void InlineQueriesManager::loop() { auto now = Time::now(); if (now >= next_inline_query_time_) { LOG(INFO) << "Send inline query " << pending_inline_query_->query_hash; - auto r_bot_input_user = td_->contacts_manager_->get_input_user(pending_inline_query_->bot_user_id); + auto r_bot_input_user = td_->user_manager_->get_input_user(pending_inline_query_->bot_user_id); if (r_bot_input_user.is_ok()) { if (!sent_query_.empty()) { LOG(INFO) << "Cancel inline query request"; @@ -1582,7 +1586,7 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI } LOG(INFO) << to_string(results); - td_->contacts_manager_->on_get_users(std::move(results->users_), "on_get_inline_query_results"); + td_->user_manager_->on_get_users(std::move(results->users_), "on_get_inline_query_results"); auto dialog_type = dialog_id.get_type(); bool allow_invoice = dialog_type != DialogType::SecretChat; @@ -1602,7 +1606,7 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI break; } if (dialog_type == DialogType::Channel && - td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + td_->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { continue; } if (dialog_type == DialogType::SecretChat) { @@ -1796,10 +1800,10 @@ void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserI static_cast(result->send_message_.get()); Contact c(inline_message_contact->phone_number_, inline_message_contact->first_name_, inline_message_contact->last_name_, inline_message_contact->vcard_, UserId()); - contact->contact_ = c.get_contact_object(); + contact->contact_ = c.get_contact_object(td_); } else { Contact c(std::move(result->description_), std::move(result->title_), string(), string(), UserId()); - contact->contact_ = c.get_contact_object(); + contact->contact_ = c.get_contact_object(td_); } contact->thumbnail_ = register_thumbnail(std::move(result->thumb_)); @@ -2060,7 +2064,7 @@ void InlineQueriesManager::save_recently_used_bots() { value += ','; value_ids += ','; } - value += td_->contacts_manager_->get_user_first_username(bot_user_id); + value += td_->user_manager_->get_user_first_username(bot_user_id); value_ids += to_string(bot_user_id.get()); } G()->td_db()->get_binlog_pmc()->set("recently_used_inline_bot_usernames", value); @@ -2086,13 +2090,13 @@ bool InlineQueriesManager::load_recently_used_bots(Promise &promise) { LOG(INFO) << "Load recently used inline bots " << saved_bots << '/' << saved_bot_ids; if (recently_used_bots_loaded_ == 1 && resolve_recent_inline_bots_multipromise_.promise_count() == 0) { - // queries was sent and have already been finished + // queries were sent and have already been finished auto newly_used_bots = std::move(recently_used_bot_user_ids_); recently_used_bot_user_ids_.clear(); for (auto it = bot_ids.rbegin(); it != bot_ids.rend(); ++it) { UserId user_id(to_integer(*it)); - if (td_->contacts_manager_->have_user(user_id)) { + if (td_->user_manager_->have_user(user_id)) { update_bot_usage(user_id); } else { LOG(ERROR) << "Can't find " << user_id; @@ -2120,7 +2124,7 @@ bool InlineQueriesManager::load_recently_used_bots(Promise &promise) { } else { for (auto &bot_id : bot_ids) { UserId user_id(to_integer(bot_id)); - td_->contacts_manager_->get_user(user_id, 3, resolve_recent_inline_bots_multipromise_.get_promise()); + td_->user_manager_->get_user(user_id, 3, resolve_recent_inline_bots_multipromise_.get_promise()); } } lock.set_value(Unit()); @@ -2140,7 +2144,6 @@ void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, L LOG(ERROR) << "Receive new inline query from invalid " << sender_user_id; return; } - LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive new inline query"; return; @@ -2152,7 +2155,8 @@ void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, L switch (peer_type->get_id()) { case telegram_api::inlineQueryPeerTypeSameBotPM::ID: - return td_api::make_object(sender_user_id.get()); + return td_api::make_object( + td_->user_manager_->get_user_id_object(sender_user_id, "inlineQueryPeerTypeSameBotPM")); case telegram_api::inlineQueryPeerTypeBotPM::ID: case telegram_api::inlineQueryPeerTypePM::ID: return td_api::make_object(0); @@ -2169,7 +2173,7 @@ void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, L }(); send_closure(G()->td(), &Td::send_update, make_tl_object( - query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineQuery"), + query_id, td_->user_manager_->get_user_id_object(sender_user_id, "updateNewInlineQuery"), user_location.get_location_object(), std::move(chat_type), query, offset)); } @@ -2180,14 +2184,14 @@ void InlineQueriesManager::on_chosen_result( LOG(ERROR) << "Receive chosen inline query result from invalid " << user_id; return; } - LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Receive unknown " << user_id; + LOG_IF(ERROR, !td_->user_manager_->have_user(user_id)) << "Receive unknown " << user_id; if (!td_->auth_manager_->is_bot()) { LOG(ERROR) << "Receive chosen inline query result"; return; } send_closure(G()->td(), &Td::send_update, make_tl_object( - td_->contacts_manager_->get_user_id_object(user_id, "updateNewChosenInlineResult"), + td_->user_manager_->get_user_id_object(user_id, "updateNewChosenInlineResult"), user_location.get_location_object(), query, result_id, get_inline_message_id(std::move(input_bot_inline_message_id)))); } @@ -2199,7 +2203,7 @@ bool InlineQueriesManager::update_bot_usage(UserId bot_user_id) { if (!recently_used_bot_user_ids_.empty() && recently_used_bot_user_ids_[0] == bot_user_id) { return false; } - auto r_bot_data = td_->contacts_manager_->get_bot_data(bot_user_id); + auto r_bot_data = td_->user_manager_->get_bot_data(bot_user_id); if (r_bot_data.is_error()) { return false; } diff --git a/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.cpp b/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.cpp new file mode 100644 index 00000000..7645965a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.cpp @@ -0,0 +1,50 @@ +// +// 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/InputBusinessChatLink.h" + +#include "td/telegram/DialogManager.h" +#include "td/telegram/misc.h" +#include "td/telegram/Td.h" + +#include "td/utils/logging.h" + +namespace td { + +InputBusinessChatLink::InputBusinessChatLink(const Td *td, td_api::object_ptr &&link) { + if (link == nullptr) { + return; + } + auto r_text = + get_formatted_text(td, td->dialog_manager_->get_my_dialog_id(), std::move(link->text_), false, true, true, false); + if (r_text.is_error()) { + LOG(INFO) << "Ignore draft text: " << r_text.error(); + } else { + text_ = r_text.move_as_ok(); + } + if (clean_input_string(link->title_)) { + title_ = std::move(link->title_); + } +} + +telegram_api::object_ptr InputBusinessChatLink::get_input_business_chat_link( + const UserManager *user_manager) const { + int32 flags = 0; + auto entities = get_input_message_entities(user_manager, &text_, "get_input_business_chat_link"); + if (!entities.empty()) { + flags |= telegram_api::inputBusinessChatLink::ENTITIES_MASK; + } + if (!title_.empty()) { + flags |= telegram_api::inputBusinessChatLink::TITLE_MASK; + } + return telegram_api::make_object(flags, text_.text, std::move(entities), title_); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const InputBusinessChatLink &link) { + return string_builder << '[' << link.title_ << ']'; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.h b/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.h new file mode 100644 index 00000000..1cd9d1eb --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/InputBusinessChatLink.h @@ -0,0 +1,36 @@ +// +// 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/MessageEntity.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 UserManager; + +class InputBusinessChatLink { + FormattedText text_; + string title_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const InputBusinessChatLink &link); + + public: + InputBusinessChatLink(const Td *td, td_api::object_ptr &&link); + + telegram_api::object_ptr get_input_business_chat_link( + const UserManager *user_manager) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const InputBusinessChatLink &link); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputInvoice.cpp b/lib/tgchat/ext/td/td/telegram/InputInvoice.cpp index 262969c0..495119ca 100644 --- a/lib/tgchat/ext/td/td/telegram/InputInvoice.cpp +++ b/lib/tgchat/ext/td/td/telegram/InputInvoice.cpp @@ -240,9 +240,8 @@ Result InputInvoice::process_input_message_invoice( tl_object_ptr InputInvoice::get_message_invoice_object(Td *td, bool skip_bot_commands, int32 max_media_timestamp) const { return make_tl_object( - title_, get_product_description_object(description_), get_photo_object(td->file_manager_.get(), photo_), - invoice_.currency_, total_amount_, start_parameter_, invoice_.is_test_, invoice_.need_shipping_address_, - receipt_message_id_.get(), + get_product_info_object(td, title_, description_, photo_), invoice_.currency_, total_amount_, start_parameter_, + invoice_.is_test_, invoice_.need_shipping_address_, receipt_message_id_.get(), extended_media_.get_message_extended_media_object(td, skip_bot_commands, max_media_timestamp)); } @@ -338,6 +337,9 @@ tl_object_ptr InputInvoice::get_input_media_inv return nullptr; } } + if (!provider_token_.empty()) { + flags |= telegram_api::inputMediaInvoice::PROVIDER_MASK; + } return make_tl_object( flags, title_, description_, std::move(input_web_document), invoice_.get_input_invoice(), BufferSlice(payload_), @@ -418,11 +420,13 @@ bool InputInvoice::need_poll_extended_media() const { return extended_media_.need_poll(); } -tl_object_ptr get_product_description_object(const string &description) { - FormattedText result; - result.text = description; - result.entities = find_entities(result.text, true, true); - return get_formatted_text_object(result, true, 0); +td_api::object_ptr get_product_info_object(Td *td, const string &title, const string &description, + const Photo &photo) { + FormattedText formatted_description; + formatted_description.text = description; + formatted_description.entities = find_entities(formatted_description.text, true, true); + return td_api::make_object(title, get_formatted_text_object(formatted_description, true, 0), + get_photo_object(td->file_manager_.get(), photo)); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputInvoice.h b/lib/tgchat/ext/td/td/telegram/InputInvoice.h index 768140ed..88059294 100644 --- a/lib/tgchat/ext/td/td/telegram/InputInvoice.h +++ b/lib/tgchat/ext/td/td/telegram/InputInvoice.h @@ -129,6 +129,7 @@ class InputInvoice { bool operator==(const InputInvoice &lhs, const InputInvoice &rhs); bool operator!=(const InputInvoice &lhs, const InputInvoice &rhs); -tl_object_ptr get_product_description_object(const string &description); +td_api::object_ptr get_product_info_object(Td *td, const string &title, const string &description, + const Photo &photo); } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp b/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp index 57916ab0..2fbd4b72 100644 --- a/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp +++ b/lib/tgchat/ext/td/td/telegram/InputMessageText.cpp @@ -6,7 +6,7 @@ // #include "td/telegram/InputMessageText.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/misc.h" @@ -50,7 +50,7 @@ Result process_input_message_text(const Td *td, DialogId dialo if (disable_web_page_preview || (dialog_id.get_type() == DialogType::Channel && - !td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews())) { + !td->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews())) { web_page_url.clear(); } if (web_page_url.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/LanguagePackManager.cpp b/lib/tgchat/ext/td/td/telegram/LanguagePackManager.cpp index 0336d3dd..4c95f337 100644 --- a/lib/tgchat/ext/td/td/telegram/LanguagePackManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/LanguagePackManager.cpp @@ -870,13 +870,9 @@ void LanguagePackManager::get_languages(bool only_local, auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, promise = std::move(promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - - send_closure(actor_id, &LanguagePackManager::on_get_languages, r_result.move_as_ok(), std::move(language_pack), - false, std::move(promise)); + TRY_RESULT_PROMISE(promise, result, fetch_result(std::move(r_query))); + send_closure(actor_id, &LanguagePackManager::on_get_languages, std::move(result), std::move(language_pack), false, + std::move(promise)); }); send_with_promise(G()->net_query_creator().create_unauth(telegram_api::langpack_getLanguages(language_pack_)), std::move(request_promise)); @@ -891,13 +887,9 @@ void LanguagePackManager::search_language_info(string language_code, auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, promise = std::move(promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - - LOG(INFO) << "Receive " << to_string(r_result.ok()); - send_closure(actor_id, &LanguagePackManager::on_get_language, r_result.move_as_ok(), std::move(language_pack), + TRY_RESULT_PROMISE(promise, result, fetch_result(std::move(r_query))); + LOG(INFO) << "Receive " << to_string(result); + send_closure(actor_id, &LanguagePackManager::on_get_language, std::move(result), std::move(language_pack), std::move(language_code), std::move(promise)); }); send_with_promise( @@ -1061,12 +1053,8 @@ void LanguagePackManager::on_get_language(tl_object_ptr> promise) { CHECK(lang_pack_language != nullptr); - auto r_info = get_language_info(lang_pack_language.get()); - if (r_info.is_error()) { - return promise.set_error(r_info.move_as_error()); - } - - auto result = get_language_pack_info_object(lang_pack_language->lang_code_, r_info.ok()); + TRY_RESULT_PROMISE(promise, language_info, get_language_info(lang_pack_language.get())); + auto result = get_language_pack_info_object(lang_pack_language->lang_code_, language_info); on_get_language_info(language_pack, result.get()); @@ -1083,15 +1071,15 @@ void LanguagePackManager::on_get_language(tl_object_ptrserver_language_pack_infos_) { if (info.first == lang_pack_language->lang_code_ || info.first == language_code) { - if (!(info.second == r_info.ok())) { + if (!(info.second == language_info)) { LOG(INFO) << "Language pack " << info.first << " was changed"; is_changed = true; - info.second = r_info.ok(); + info.second = language_info; } } } pack->all_server_language_pack_infos_[lang_pack_language->lang_code_] = - td::make_unique(r_info.move_as_ok()); + td::make_unique(std::move(language_info)); if (is_changed) { save_server_language_pack_infos(pack); @@ -1146,12 +1134,7 @@ void LanguagePackManager::get_language_pack_strings(string language_code, vector auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, promise = std::move(result_promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - - auto result = r_result.move_as_ok(); + TRY_RESULT_PROMISE(promise, result, fetch_result(std::move(r_query))); to_lower_inplace(result->lang_code_); LOG(INFO) << "Receive language pack " << result->lang_code_ << " from version " << result->from_version_ << " with version " << result->version_ << " of size " << result->strings_.size(); @@ -1169,13 +1152,9 @@ void LanguagePackManager::get_language_pack_strings(string language_code, vector auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, keys, promise = std::move(promise)](Result r_query) mutable { - auto r_result = fetch_result(std::move(r_query)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - + TRY_RESULT_PROMISE(promise, result, fetch_result(std::move(r_query))); send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack), - std::move(language_code), -1, false, std::move(keys), r_result.move_as_ok(), std::move(promise)); + std::move(language_code), -1, false, std::move(keys), std::move(result), std::move(promise)); }); send_with_promise(G()->net_query_creator().create_unauth( telegram_api::langpack_getStrings(language_pack_, language_code, std::move(keys))), @@ -1733,10 +1712,7 @@ void LanguagePackManager::set_custom_language(td_api::object_ptrid_); if (!is_custom_language_code(language_code)) { return promise.set_error(Status::Error(400, "Custom language pack ID must begin with 'X'")); @@ -1744,11 +1720,8 @@ void LanguagePackManager::set_custom_language(td_api::object_ptr> server_strings; for (auto &str : strings) { - auto r_result = convert_to_telegram_api(std::move(str)); - if (r_result.is_error()) { - return promise.set_error(r_result.move_as_error()); - } - server_strings.push_back(r_result.move_as_ok()); + TRY_RESULT_PROMISE(promise, result, convert_to_telegram_api(std::move(str))); + server_strings.push_back(std::move(result)); } // TODO atomic replace @@ -1761,7 +1734,7 @@ void LanguagePackManager::set_custom_language(td_api::object_ptrsecond.get(); std::lock_guard pack_lock(pack->mutex_); auto &info = pack->custom_language_pack_infos_[language_code]; - info = r_info.move_as_ok(); + info = std::move(language_info); if (!pack->pack_kv_.empty()) { pack->pack_kv_.set(language_code, get_language_info_string(info)); } @@ -1775,10 +1748,7 @@ void LanguagePackManager::edit_custom_language_info(td_api::object_ptrid_); if (!is_custom_language_code(language_code)) { return promise.set_error(Status::Error(400, "Custom language pack ID must begin with 'X'")); @@ -1794,7 +1764,7 @@ void LanguagePackManager::edit_custom_language_info(td_api::object_ptrsecond; - info = r_info.move_as_ok(); + info = std::move(language_info); if (!pack->pack_kv_.empty()) { pack->pack_kv_.set(language_code, get_language_info_string(info)); } @@ -1824,13 +1794,10 @@ void LanguagePackManager::set_custom_language_string(string language_code, vector keys{str->key_}; - auto r_str = convert_to_telegram_api(std::move(str)); - if (r_str.is_error()) { - return promise.set_error(r_str.move_as_error()); - } + TRY_RESULT_PROMISE(promise, lang_pack_string, convert_to_telegram_api(std::move(str))); vector> server_strings; - server_strings.push_back(r_str.move_as_ok()); + server_strings.push_back(std::move(lang_pack_string)); on_get_language_pack_strings(language_pack_, language_code, 1, true, std::move(keys), std::move(server_strings), Auto()); diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp index bfaa9d40..98b443e3 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.cpp @@ -11,7 +11,6 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/DialogParticipant.h" @@ -27,6 +26,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/mtproto/ProxySecret.h" @@ -111,6 +111,22 @@ static string get_url_query_slug(bool is_tg, const HttpUrlQuery &url_query) { return string(); } +static string get_url_query_draft_text(const HttpUrlQuery &url_query) { + auto text_slice = url_query.get_arg("text"); + if (text_slice.empty()) { + return string(); + } + auto text = text_slice.str(); + if (!check_utf8(text)) { + return string(); + } + text = utf8_truncate(std::move(text), 4096u); + if (text[0] == '@') { + return ' ' + text; + } + return text; +} + static AdministratorRights get_administrator_rights(Slice rights, bool for_channel) { bool can_manage_dialog = false; bool can_change_info = false; @@ -368,6 +384,18 @@ class LinkManager::InternalLinkBotStartInGroup final : public InternalLink { } }; +class LinkManager::InternalLinkBusinessChat final : public InternalLink { + string link_name_; + + td_api::object_ptr get_internal_link_type_object() const final { + return td_api::make_object(link_name_); + } + + public: + explicit InternalLinkBusinessChat(string link_name) : link_name_(std::move(link_name)) { + } +}; + class LinkManager::InternalLinkChangePhoneNumber final : public InternalLink { td_api::object_ptr get_internal_link_type_object() const final { return td_api::make_object(); @@ -626,13 +654,15 @@ class LinkManager::InternalLinkProxy final : public InternalLink { class LinkManager::InternalLinkPublicDialog final : public InternalLink { string dialog_username_; + string draft_text_; td_api::object_ptr get_internal_link_type_object() const final { - return td_api::make_object(dialog_username_); + return td_api::make_object(dialog_username_, draft_text_); } public: - explicit InternalLinkPublicDialog(string dialog_username) : dialog_username_(std::move(dialog_username)) { + InternalLinkPublicDialog(string dialog_username, string draft_text) + : dialog_username_(std::move(dialog_username)), draft_text_(std::move(draft_text)) { } }; @@ -736,13 +766,15 @@ class LinkManager::InternalLinkUnsupportedProxy final : public InternalLink { class LinkManager::InternalLinkUserPhoneNumber final : public InternalLink { string phone_number_; + string draft_text_; td_api::object_ptr get_internal_link_type_object() const final { - return td_api::make_object(phone_number_); + return td_api::make_object(phone_number_, draft_text_); } public: - explicit InternalLinkUserPhoneNumber(string phone_number) : phone_number_(std::move(phone_number)) { + InternalLinkUserPhoneNumber(Slice phone_number, string draft_text) + : phone_number_(PSTRING() << '+' << phone_number), draft_text_(std::move(draft_text)) { } }; @@ -816,16 +848,8 @@ class GetDeepLinkInfoQuery final : public Td::ResultHandler { return promise_.set_value(nullptr); case telegram_api::help_deepLinkInfo::ID: { auto info = telegram_api::move_object_as(result); - auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery"); - auto status = fix_formatted_text(info->message_, entities, true, true, true, true, true); - if (status.is_error()) { - LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_; - if (!clean_input_string(info->message_)) { - info->message_.clear(); - } - entities = find_entities(info->message_, true, true); - } - FormattedText text{std::move(info->message_), std::move(entities)}; + auto text = get_formatted_text(nullptr, std::move(info->message_), std::move(info->entities_), true, true, + "GetDeepLinkInfoQuery"); return promise_.set_value( td_api::make_object(get_formatted_text_object(text, true, -1), info->update_app_)); } @@ -877,13 +901,13 @@ class RequestUrlAuthQuery final : public Td::ResultHandler { switch (result->get_id()) { case telegram_api::urlAuthResultRequest::ID: { auto request = telegram_api::move_object_as(result); - UserId bot_user_id = ContactsManager::get_user_id(request->bot_); + UserId bot_user_id = UserManager::get_user_id(request->bot_); if (!bot_user_id.is_valid()) { return on_error(Status::Error(500, "Receive invalid bot_user_id")); } - td_->contacts_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery"); + td_->user_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery"); promise_.set_value(td_api::make_object( - url_, request->domain_, td_->contacts_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"), + url_, request->domain_, td_->user_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"), request->request_write_access_)); break; } @@ -1110,9 +1134,9 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { if (ends_with(host, ".t.me") && host.size() >= 9 && host.find('.') == host.size() - 5) { Slice subdomain(&host[0], host.size() - 5); static const FlatHashSet disallowed_subdomains( - {"addemoji", "addlist", "addstickers", "addtheme", "auth", "boost", "confirmphone", - "contact", "giftcode", "invoice", "joinchat", "login", "proxy", "setlanguage", - "share", "socks", "web", "a", "k", "z"}); + {"addemoji", "addlist", "addstickers", "addtheme", "auth", "boost", "confirmphone", + "contact", "giftcode", "invoice", "joinchat", "login", "m", "proxy", + "setlanguage", "share", "socks", "web", "a", "k", "z"}); if (is_valid_username(subdomain) && disallowed_subdomains.count(subdomain) == 0) { result.type_ = LinkType::TMe; result.query_ = PSTRING() << '/' << subdomain << http_url.query_; @@ -1306,8 +1330,8 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que // resolve?domain=&attach= // resolve?domain=&attach=&startattach= return td::make_unique( - nullptr, td::make_unique(std::move(username)), url_query.get_arg("attach").str(), - url_query.get_arg("startattach")); + nullptr, td::make_unique(std::move(username), string()), + url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } else if (url_query.has_arg("startattach")) { // resolve?domain=&startattach&choose=users+bots+groups+channels // resolve?domain=&startattach=&choose=users+bots+groups+channels @@ -1322,17 +1346,21 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que } } // resolve?domain= - return td::make_unique(std::move(username)); - } else if (is_valid_phone_number(get_arg("phone"))) { - auto user_link = td::make_unique(get_arg("phone")); - if (!url_query.get_arg("attach").empty()) { - // resolve?phone=&attach= - // resolve?phone=&attach=&startattach= - return td::make_unique( - nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach")); + return td::make_unique(std::move(username), get_url_query_draft_text(url_query)); + } else { + string phone_number_str = get_arg("phone"); + auto phone_number = phone_number_str[0] == ' ' ? Slice(phone_number_str).substr(1) : Slice(phone_number_str); + if (is_valid_phone_number(phone_number)) { + if (!url_query.get_arg("attach").empty()) { + // resolve?phone=&attach= + // resolve?phone=&attach=&startattach= + return td::make_unique( + nullptr, td::make_unique(phone_number, string()), + url_query.get_arg("attach").str(), url_query.get_arg("startattach")); + } + // resolve?phone=12345 + return td::make_unique(phone_number, get_url_query_draft_text(url_query)); } - // resolve?phone=12345 - return std::move(user_link); } } else if (path.size() == 1 && path[0] == "contact") { // contact?token= @@ -1494,6 +1522,11 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que if (has_arg("slug")) { return td::make_unique(url_query.get_arg("slug").str()); } + } else if (path.size() == 1 && path[0] == "message") { + // message?slug= + if (has_arg("slug")) { + return td::make_unique(url_query.get_arg("slug").str()); + } } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) { // msg_url?url= // msg_url?url=&text= @@ -1563,15 +1596,15 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q } else if (path[0][0] == ' ' || path[0][0] == '+') { auto invite_hash = get_url_query_hash(false, url_query); if (is_valid_phone_number(invite_hash)) { - auto user_link = td::make_unique(invite_hash); if (!url_query.get_arg("attach").empty()) { // /+?attach= // /+?attach=&startattach= return td::make_unique( - nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach")); + nullptr, td::make_unique(invite_hash, string()), + url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } // /+ - return std::move(user_link); + return td::make_unique(invite_hash, get_url_query_draft_text(url_query)); } else if (!invite_hash.empty() && is_base64url_characters(invite_hash)) { // /+ return td::make_unique(get_dialog_invite_link(invite_hash, true)); @@ -1649,6 +1682,11 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q // /giftcode/ return td::make_unique(path[1]); } + } else if (path[0] == "m") { + if (path.size() >= 2 && !path[1].empty()) { + // /m/ + return td::make_unique(path[1]); + } } else if (path[0][0] == '$') { if (path[0].size() >= 2) { // /$ @@ -1749,8 +1787,8 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q // /?attach= // /?attach=&startattach= return td::make_unique( - nullptr, td::make_unique(std::move(username)), url_query.get_arg("attach").str(), - url_query.get_arg("startattach")); + nullptr, td::make_unique(std::move(username), string()), + url_query.get_arg("attach").str(), url_query.get_arg("startattach")); } else if (url_query.has_arg("startattach")) { // /?startattach&choose=users+bots+groups+channels // /?startattach=&choose=users+bots+groups+channels @@ -1759,7 +1797,7 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q } // / - return td::make_unique(std::move(username)); + return td::make_unique(std::move(username), get_url_query_draft_text(url_query)); } return nullptr; } @@ -1904,15 +1942,21 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp if (target->link_->get_id() == td_api::internalLinkTypeUserPhoneNumber::ID) { auto user_phone_number_link = static_cast(target->link_.get()); - if (!is_valid_phone_number(user_phone_number_link->phone_number_)) { + string phone_number; + if (user_phone_number_link->phone_number_[0] == '+') { + phone_number = user_phone_number_link->phone_number_.substr(1); + } else { + phone_number = user_phone_number_link->phone_number_; + } + if (!is_valid_phone_number(phone_number)) { return Status::Error(400, "Invalid target phone number specified"); } if (is_internal) { - return PSTRING() << "tg://resolve?phone=" << user_phone_number_link->phone_number_ - << "&attach=" << link->bot_username_ << start_parameter; + return PSTRING() << "tg://resolve?phone=+" << phone_number << "&attach=" << link->bot_username_ + << start_parameter; } else { - return PSTRING() << get_t_me_url() << '+' << user_phone_number_link->phone_number_ - << "?attach=" << link->bot_username_ << start_parameter; + return PSTRING() << get_t_me_url() << '+' << phone_number << "?attach=" << link->bot_username_ + << start_parameter; } } return Status::Error(400, "Unsupported target link specified"); @@ -2019,6 +2063,14 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp return PSTRING() << get_t_me_url() << link->bot_username_ << "?startgroup" << start_parameter << admin; } } + case td_api::internalLinkTypeBusinessChat::ID: { + auto link = static_cast(type_ptr); + if (is_internal) { + return PSTRING() << "tg://message?slug=" << url_encode(link->link_name_); + } else { + return PSTRING() << get_t_me_url() << "m/" << url_encode(link->link_name_); + } + } case td_api::internalLinkTypeChangePhoneNumber::ID: if (!is_internal) { return Status::Error("HTTP link is unavailable for the link type"); @@ -2226,7 +2278,10 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp if (!is_valid_username(link->chat_username_)) { return Status::Error(400, "Invalid chat username specified"); } - return get_public_dialog_link(link->chat_username_, is_internal); + if (!check_utf8(link->draft_text_)) { + return Status::Error(400, "Draft text must be encoded in UTF-8"); + } + return get_public_dialog_link(link->chat_username_, link->draft_text_, is_internal); } case td_api::internalLinkTypeQrCodeAuthentication::ID: return Status::Error("The link must never be generated client-side"); @@ -2328,13 +2383,24 @@ Result LinkManager::get_internal_link_impl(const td_api::InternalLinkTyp } case td_api::internalLinkTypeUserPhoneNumber::ID: { auto link = static_cast(type_ptr); - if (!is_valid_phone_number(link->phone_number_)) { + string phone_number; + if (link->phone_number_[0] == '+') { + phone_number = link->phone_number_.substr(1); + } else { + phone_number = link->phone_number_; + } + if (!is_valid_phone_number(phone_number)) { return Status::Error(400, "Invalid phone number specified"); } + if (!check_utf8(link->draft_text_)) { + return Status::Error(400, "Draft text must be encoded in UTF-8"); + } if (is_internal) { - return PSTRING() << "tg://resolve?phone=" << link->phone_number_; + return PSTRING() << "tg://resolve?phone=+" << phone_number << (link->draft_text_.empty() ? "" : "&text=") + << url_encode(link->draft_text_); } else { - return PSTRING() << get_t_me_url() << '+' << link->phone_number_; + return PSTRING() << get_t_me_url() << '+' << phone_number << (link->draft_text_.empty() ? "" : "?text=") + << url_encode(link->draft_text_); } } case td_api::internalLinkTypeUserToken::ID: { @@ -2620,11 +2686,13 @@ string LinkManager::get_instant_view_link(Slice url, Slice rhash) { return PSTRING() << get_t_me_url() << "iv?url=" << url_encode(url) << "&rhash=" << url_encode(rhash); } -string LinkManager::get_public_dialog_link(Slice username, bool is_internal) { +string LinkManager::get_public_dialog_link(Slice username, Slice draft_text, bool is_internal) { if (is_internal) { - return PSTRING() << "tg://resolve?domain=" << url_encode(username); + return PSTRING() << "tg://resolve?domain=" << url_encode(username) << (draft_text.empty() ? "" : "&text=") + << url_encode(draft_text); } else { - return PSTRING() << get_t_me_url() << url_encode(username); + return PSTRING() << get_t_me_url() << url_encode(username) << (draft_text.empty() ? "" : "?text=") + << url_encode(draft_text); } } diff --git a/lib/tgchat/ext/td/td/telegram/LinkManager.h b/lib/tgchat/ext/td/td/telegram/LinkManager.h index 1d302e6e..c5273b66 100644 --- a/lib/tgchat/ext/td/td/telegram/LinkManager.h +++ b/lib/tgchat/ext/td/td/telegram/LinkManager.h @@ -98,7 +98,7 @@ class LinkManager final : public Actor { static string get_instant_view_link(Slice url, Slice rhash); - static string get_public_dialog_link(Slice username, bool is_internal); + static string get_public_dialog_link(Slice username, Slice draft_text, bool is_internal); static Result get_proxy_link(const Proxy &proxy, bool is_internal); @@ -124,6 +124,7 @@ class LinkManager final : public Actor { class InternalLinkBotAddToChannel; class InternalLinkBotStart; class InternalLinkBotStartInGroup; + class InternalLinkBusinessChat; class InternalLinkChangePhoneNumber; class InternalLinkConfirmPhone; class InternalLinkDefaultMessageAutoDeleteTimerSettings; diff --git a/lib/tgchat/ext/td/td/telegram/Location.cpp b/lib/tgchat/ext/td/td/telegram/Location.cpp index 31199705..46061086 100644 --- a/lib/tgchat/ext/td/td/telegram/Location.cpp +++ b/lib/tgchat/ext/td/td/telegram/Location.cpp @@ -10,6 +10,7 @@ #include "td/telegram/Td.h" #include +#include namespace td { @@ -157,7 +158,8 @@ Result process_input_message_location( constexpr int32 MAX_LIVE_LOCATION_PERIOD = 86400; // seconds, server side limit auto period = input_location->live_period_; - if (period != 0 && (period < MIN_LIVE_LOCATION_PERIOD || period > MAX_LIVE_LOCATION_PERIOD)) { + if (period != 0 && period != std::numeric_limits::max() && + (period < MIN_LIVE_LOCATION_PERIOD || period > MAX_LIVE_LOCATION_PERIOD)) { return Status::Error(400, "Wrong live location period specified"); } diff --git a/lib/tgchat/ext/td/td/telegram/MediaArea.cpp b/lib/tgchat/ext/td/td/telegram/MediaArea.cpp index a851ce18..461384a8 100644 --- a/lib/tgchat/ext/td/td/telegram/MediaArea.cpp +++ b/lib/tgchat/ext/td/td/telegram/MediaArea.cpp @@ -7,7 +7,7 @@ #include "td/telegram/MediaArea.h" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" @@ -235,8 +235,7 @@ telegram_api::object_ptr MediaArea::get_input_media_are } case Type::Message: if (!is_old_message_) { - auto input_channel = - td->contacts_manager_->get_input_channel(message_full_id_.get_dialog_id().get_channel_id()); + auto input_channel = td->chat_manager_->get_input_channel(message_full_id_.get_dialog_id().get_channel_id()); if (input_channel == nullptr) { return nullptr; } diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp index 6c91468d..cc78b68d 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.cpp @@ -17,8 +17,8 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Contact.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/CustomEmojiId.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" @@ -70,6 +70,8 @@ #include "td/telegram/SecureValue.h" #include "td/telegram/SecureValue.hpp" #include "td/telegram/ServerMessageId.h" +#include "td/telegram/SharedDialog.h" +#include "td/telegram/SharedDialog.hpp" #include "td/telegram/StickerFormat.h" #include "td/telegram/StickersManager.h" #include "td/telegram/StickersManager.hpp" @@ -82,6 +84,7 @@ #include "td/telegram/TopDialogManager.h" #include "td/telegram/TranscriptionManager.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Venue.h" #include "td/telegram/Version.h" #include "td/telegram/VideoNotesManager.h" @@ -112,6 +115,7 @@ #include "td/utils/utf8.h" #include +#include #include namespace td { @@ -498,7 +502,7 @@ class MessageChatSetTtl final : public MessageContent { class MessageUnsupported final : public MessageContent { public: - static constexpr int32 CURRENT_VERSION = 30; + static constexpr int32 CURRENT_VERSION = 31; int32 version = CURRENT_VERSION; MessageUnsupported() = default; @@ -1101,6 +1105,21 @@ class MessageBoostApply final : public MessageContent { } }; +class MessageDialogShared final : public MessageContent { + public: + vector shared_dialogs; + int32 button_id = 0; + + MessageDialogShared() = default; + MessageDialogShared(vector &&shared_dialogs, int32 button_id) + : shared_dialogs(std::move(shared_dialogs)), button_id(button_id) { + } + + MessageContentType get_type() const final { + return MessageContentType::DialogShared; + } +}; + template static void store(const MessageContent *content, StorerT &storer) { CHECK(content != nullptr); @@ -1657,6 +1676,14 @@ static void store(const MessageContent *content, StorerT &storer) { store(m->boost_count, storer); break; } + case MessageContentType::DialogShared: { + const auto *m = static_cast(content); + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + store(m->shared_dialogs, storer); + store(m->button_id, storer); + break; + } default: UNREACHABLE(); } @@ -2393,6 +2420,19 @@ static void parse(unique_ptr &content, ParserT &parser) { content = std::move(m); break; } + case MessageContentType::DialogShared: { + auto m = make_unique(); + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + parse(m->shared_dialogs, parser); + if (m->shared_dialogs.empty() || + any_of(m->shared_dialogs, [](const auto &shared_dialog) { return !shared_dialog.is_valid(); })) { + is_bad = true; + } + parse(m->button_id, parser); + content = std::move(m); + break; + } default: is_bad = true; @@ -2432,17 +2472,10 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, switch (bot_inline_message->get_id()) { case telegram_api::botInlineMessageText::ID: { auto inline_message = move_tl_object_as(bot_inline_message); - auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_), - "botInlineMessageText"); - auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false, false); - if (status.is_error()) { - LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message->message_; - break; - } - + auto text = get_formatted_text(td->user_manager_.get(), std::move(inline_message->message_), + std::move(inline_message->entities_), false, false, "botInlineMessageText"); result.disable_web_page_preview = inline_message->no_webpage_; result.invert_media = inline_message->invert_media_; - FormattedText text{std::move(inline_message->message_), std::move(entities)}; WebPageId web_page_id; if (!result.disable_web_page_preview) { web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(text).str()); @@ -2458,18 +2491,10 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, if (inline_message->manual_) { web_page_url = std::move(inline_message->url_); } - auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_), - "botInlineMessageMediaWebPage"); - auto status = - fix_formatted_text(inline_message->message_, entities, !web_page_url.empty(), true, true, false, false); - if (status.is_error()) { - LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageMediaWebPage " - << inline_message->message_; - break; - } - - FormattedText text{std::move(inline_message->message_), std::move(entities)}; - WebPageId web_page_id = + auto text = + get_formatted_text(td->user_manager_.get(), std::move(inline_message->message_), + std::move(inline_message->entities_), false, false, "botInlineMessageMediaWebPage"); + auto web_page_id = td->web_pages_manager_->get_web_page_by_url(web_page_url.empty() ? get_first_url(text).str() : web_page_url); result.message_content = td::make_unique( std::move(text), web_page_id, inline_message->force_small_media_, inline_message->force_large_media_, @@ -2516,7 +2541,7 @@ InlineMessageContent create_inline_message_content(Td *td, FileId file_id, case telegram_api::botInlineMessageMediaAuto::ID: { auto inline_message = move_tl_object_as(bot_inline_message); auto caption = - get_message_text(td->contacts_manager_.get(), inline_message->message_, std::move(inline_message->entities_), + get_message_text(td->user_manager_.get(), inline_message->message_, std::move(inline_message->entities_), true, false, 0, false, "create_inline_message_content"); if (allowed_media_content_id == td_api::inputMessageAnimation::ID) { result.message_content = make_unique(file_id, std::move(caption), false); @@ -2571,6 +2596,57 @@ unique_ptr create_chat_set_ttl_message_content(int32 ttl, UserId return make_unique(ttl, from_user_id); } +td_api::object_ptr extract_input_caption( + td_api::object_ptr &input_message_content) { + switch (input_message_content->get_id()) { + case td_api::inputMessageAnimation::ID: { + auto input_animation = static_cast(input_message_content.get()); + return std::move(input_animation->caption_); + } + case td_api::inputMessageAudio::ID: { + auto input_audio = static_cast(input_message_content.get()); + return std::move(input_audio->caption_); + } + case td_api::inputMessageDocument::ID: { + auto input_document = static_cast(input_message_content.get()); + return std::move(input_document->caption_); + } + case td_api::inputMessagePhoto::ID: { + auto input_photo = static_cast(input_message_content.get()); + return std::move(input_photo->caption_); + } + case td_api::inputMessageVideo::ID: { + auto input_video = static_cast(input_message_content.get()); + return std::move(input_video->caption_); + } + case td_api::inputMessageVoiceNote::ID: { + auto input_voice_note = static_cast(input_message_content.get()); + return std::move(input_voice_note->caption_); + } + default: + return nullptr; + } +} + +bool extract_input_invert_media(const td_api::object_ptr &input_message_content) { + switch (input_message_content->get_id()) { + case td_api::inputMessageAnimation::ID: { + auto input_animation = static_cast(input_message_content.get()); + return input_animation->show_caption_above_media_; + } + case td_api::inputMessagePhoto::ID: { + auto input_photo = static_cast(input_message_content.get()); + return input_photo->show_caption_above_media_; + } + case td_api::inputMessageVideo::ID: { + auto input_video = static_cast(input_message_content.get()); + return input_video->show_caption_above_media_; + } + default: + return false; + } +} + static Result create_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, FormattedText caption, FileId file_id, PhotoSize thumbnail, vector sticker_file_ids, bool is_premium) { @@ -2614,7 +2690,7 @@ static Result create_input_message_content( WebPageId web_page_id; bool can_add_web_page_previews = dialog_id.get_type() != DialogType::Channel || - td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews(); + td->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews(); if (!is_bot && !disable_web_page_preview && can_add_web_page_previews) { web_page_id = td->web_pages_manager_->get_web_page_by_url( web_page_url.empty() ? get_first_url(input_message_text.text).str() : web_page_url); @@ -2627,6 +2703,8 @@ static Result create_input_message_content( case td_api::inputMessageAnimation::ID: { auto input_animation = static_cast(input_message_content.get()); + invert_media = input_animation->show_caption_above_media_ && !is_secret; + bool has_stickers = !sticker_file_ids.empty(); td->animations_manager_->create_animation( file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids), @@ -2671,6 +2749,7 @@ static Result create_input_message_content( case td_api::inputMessagePhoto::ID: { auto input_photo = static_cast(input_message_content.get()); + invert_media = input_photo->show_caption_above_media_ && !is_secret; self_destruct_type = std::move(input_photo->self_destruct_type_); TRY_RESULT(photo, create_photo(td->file_manager_.get(), file_id, std::move(thumbnail), input_photo->width_, @@ -2695,6 +2774,7 @@ static Result create_input_message_content( case td_api::inputMessageVideo::ID: { auto input_video = static_cast(input_message_content.get()); + invert_media = input_video->show_caption_above_media_ && !is_secret; self_destruct_type = std::move(input_video->self_destruct_type_); bool has_stickers = !sticker_file_ids.empty(); @@ -2712,7 +2792,7 @@ static Result create_input_message_content( self_destruct_type = std::move(input_video_note->self_destruct_type_); auto length = input_video_note->length_; - if (length < 0 || length >= 640) { + if (length < 0 || length > 640) { return Status::Error(400, "Wrong video note length"); } @@ -2748,14 +2828,14 @@ static Result create_input_message_content( break; } case td_api::inputMessageContact::ID: { - TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content))); + TRY_RESULT(contact, process_input_message_contact(td, std::move(input_message_content))); content = make_unique(std::move(contact)); break; } case td_api::inputMessageGame::ID: { - TRY_RESULT(game, process_input_message_game(td->contacts_manager_.get(), std::move(input_message_content))); + TRY_RESULT(game, process_input_message_game(td->user_manager_.get(), std::move(input_message_content))); via_bot_user_id = game.get_bot_user_id(); - if (via_bot_user_id == td->contacts_manager_->get_my_id()) { + if (via_bot_user_id == td->user_manager_->get_my_id()) { via_bot_user_id = UserId(); } @@ -2777,13 +2857,9 @@ static Result create_input_message_content( constexpr size_t MAX_POLL_OPTION_LENGTH = 100; // server-side limit constexpr size_t MAX_POLL_OPTIONS = 10; // server-side limit auto input_poll = static_cast(input_message_content.get()); - if (!clean_input_string(input_poll->question_)) { - return Status::Error(400, "Poll question must be encoded in UTF-8"); - } - if (input_poll->question_.empty()) { - return Status::Error(400, "Poll question must be non-empty"); - } - if (utf8_length(input_poll->question_) > MAX_POLL_QUESTION_LENGTH) { + TRY_RESULT(question, + get_formatted_text(td, dialog_id, std::move(input_poll->question_), is_bot, false, true, false)); + if (utf8_length(question.text) > MAX_POLL_QUESTION_LENGTH) { return Status::Error(400, PSLICE() << "Poll question length must not exceed " << MAX_POLL_QUESTION_LENGTH); } if (input_poll->options_.size() <= 1) { @@ -2792,16 +2868,13 @@ static Result create_input_message_content( if (input_poll->options_.size() > MAX_POLL_OPTIONS) { return Status::Error(400, PSLICE() << "Poll can't have more than " << MAX_POLL_OPTIONS << " options"); } - for (auto &option : input_poll->options_) { - if (!clean_input_string(option)) { - return Status::Error(400, "Poll options must be encoded in UTF-8"); - } - if (option.empty()) { - return Status::Error(400, "Poll options must be non-empty"); - } - if (utf8_length(option) > MAX_POLL_OPTION_LENGTH) { + vector options; + for (auto &input_option : input_poll->options_) { + TRY_RESULT(option, get_formatted_text(td, dialog_id, std::move(input_option), is_bot, false, true, false)); + if (utf8_length(option.text) > MAX_POLL_OPTION_LENGTH) { return Status::Error(400, PSLICE() << "Poll options length must not exceed " << MAX_POLL_OPTION_LENGTH); } + options.push_back(std::move(option)); } bool allow_multiple_answers = false; @@ -2838,10 +2911,9 @@ static Result create_input_message_content( close_date = 0; } bool is_closed = is_bot ? input_poll->is_closed_ : false; - content = make_unique( - td->poll_manager_->create_poll(std::move(input_poll->question_), std::move(input_poll->options_), - input_poll->is_anonymous_, allow_multiple_answers, is_quiz, correct_option_id, - std::move(explanation), open_period, close_date, is_closed)); + content = make_unique(td->poll_manager_->create_poll( + std::move(question), std::move(options), input_poll->is_anonymous_, allow_multiple_answers, is_quiz, + correct_option_id, std::move(explanation), open_period, close_date, is_closed)); break; } case td_api::inputMessageStory::ID: { @@ -2855,7 +2927,7 @@ static Result create_input_message_content( if (!story_id.is_server()) { return Status::Error(400, "Story can't be forwarded"); } - if (td->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read) == nullptr) { + if (td->dialog_manager_->get_input_peer(story_sender_dialog_id, AccessRights::Read) == nullptr) { return Status::Error(400, "Can't access the story"); } content = make_unique(story_full_id, false); @@ -2979,6 +3051,36 @@ Result get_input_message_content( std::move(sticker_file_ids), is_premium); } +Status check_message_group_message_contents(const vector &message_contents) { + static constexpr size_t MAX_GROUPED_MESSAGES = 10; // server side limit + if (message_contents.size() > MAX_GROUPED_MESSAGES) { + return Status::Error(400, "Too many messages to send as an album"); + } + if (message_contents.empty()) { + return Status::Error(400, "There are no messages to send"); + } + + std::unordered_set message_content_types; + for (const auto &message_content : message_contents) { + auto message_content_type = message_content.content->get_type(); + if (!is_allowed_media_group_content(message_content_type)) { + return Status::Error(400, "Invalid message content type"); + } + if (message_content.invert_media != message_contents[0].invert_media) { + return Status::Error(400, "Parameter show_caption_above_media must be the same for all messages"); + } + message_content_types.insert(message_content_type); + } + if (message_content_types.size() > 1) { + for (auto message_content_type : message_content_types) { + if (is_homogenous_media_group_content(message_content_type)) { + return Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types"); + } + } + } + return Status::OK(); +} + bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server) { switch (content->get_type()) { case MessageContentType::Game: @@ -3039,6 +3141,7 @@ bool can_have_input_media(const Td *td, const MessageContent *content, bool is_s case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; case MessageContentType::Animation: case MessageContentType::Audio: @@ -3180,6 +3283,7 @@ SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: break; default: UNREACHABLE(); @@ -3323,6 +3427,7 @@ static tl_object_ptr get_input_media_impl( case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: break; default: UNREACHABLE(); @@ -3528,6 +3633,7 @@ void delete_message_content_thumbnail(MessageContent *content, Td *td) { case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: break; default: UNREACHABLE(); @@ -3544,18 +3650,17 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten } switch (dialog_type) { case DialogType::User: - return td->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id()); + return td->user_manager_->get_user_default_permissions(dialog_id.get_user_id()); case DialogType::Chat: - return td->contacts_manager_->get_chat_permissions(dialog_id.get_chat_id()).get_effective_restricted_rights(); + return td->chat_manager_->get_chat_permissions(dialog_id.get_chat_id()).get_effective_restricted_rights(); case DialogType::Channel: - return td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()) - .get_effective_restricted_rights(); + return td->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()).get_effective_restricted_rights(); case DialogType::SecretChat: - return td->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id()); + return td->user_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id()); case DialogType::None: default: UNREACHABLE(); - return td->contacts_manager_->get_user_default_permissions(UserId()); + return td->user_manager_->get_user_default_permissions(UserId()); } }(); @@ -3590,8 +3695,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten } break; case MessageContentType::Game: - if (dialog_type == DialogType::Channel && - td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + if (dialog_type == DialogType::Channel && td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { // return Status::Error(400, "Games can't be sent to channel chats"); } if (dialog_type == DialogType::SecretChat) { @@ -3644,13 +3748,12 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten if (!permissions.can_send_polls()) { return Status::Error(400, "Not enough rights to send polls to the chat"); } - if (dialog_type == DialogType::Channel && - td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()) && + if (dialog_type == DialogType::Channel && td->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()) && !td->poll_manager_->get_poll_is_anonymous(static_cast(content)->poll_id)) { return Status::Error(400, "Non-anonymous polls can't be sent to channel chats"); } if (dialog_type == DialogType::User && !is_forward && !td->auth_manager_->is_bot() && - !td->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { + !td->user_manager_->is_user_bot(dialog_id.get_user_id())) { return Status::Error(400, "Polls can't be sent to the private chat"); } if (dialog_type == DialogType::SecretChat) { @@ -3693,7 +3796,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten return Status::Error(400, "Not enough rights to send video notes to the chat"); } if (dialog_type == DialogType::User && - td->contacts_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) { + td->user_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) { return Status::Error(400, "User restricted receiving of voice messages"); } break; @@ -3702,7 +3805,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten return Status::Error(400, "Not enough rights to send voice notes to the chat"); } if (dialog_type == DialogType::User && - td->contacts_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) { + td->user_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) { return Status::Error(400, "User restricted receiving of video messages"); } break; @@ -3753,6 +3856,7 @@ Status can_send_message_content(DialogId dialog_id, const MessageContent *conten case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: UNREACHABLE(); } return Status::OK(); @@ -3900,6 +4004,7 @@ static int32 get_message_content_media_index_mask(const MessageContent *content, case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return 0; default: UNREACHABLE(); @@ -4183,6 +4288,8 @@ vector get_message_content_min_user_ids(const Td *td, const MessageConte break; case MessageContentType::BoostApply: break; + case MessageContentType::DialogShared: + break; default: UNREACHABLE(); break; @@ -4588,6 +4695,7 @@ void merge_message_contents(Td *td, const MessageContent *old_content, MessageCo case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: break; default: UNREACHABLE(); @@ -4738,6 +4846,7 @@ bool merge_message_content_file_id(Td *td, MessageContent *message_content, File case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type; break; default: @@ -5272,6 +5381,14 @@ void compare_message_contents(Td *td, const MessageContent *old_content, const M } break; } + case MessageContentType::DialogShared: { + const auto *lhs = static_cast(old_content); + const auto *rhs = static_cast(new_content); + if (lhs->shared_dialogs != rhs->shared_dialogs || lhs->button_id != rhs->button_id) { + need_update = true; + } + break; + } default: UNREACHABLE(); break; @@ -5341,7 +5458,7 @@ void register_message_content(Td *td, const MessageContent *content, MessageFull return td->stickers_manager_->register_premium_gift(static_cast(content)->months, message_full_id, source); case MessageContentType::SuggestProfilePhoto: - return td->contacts_manager_->register_suggested_profile_photo( + return td->user_manager_->register_suggested_profile_photo( static_cast(content)->photo); case MessageContentType::Story: return td->story_manager_->register_story(static_cast(content)->story_full_id, @@ -5996,8 +6113,8 @@ unique_ptr get_message_content(Td *td, FormattedText message, case telegram_api::messageMediaContact::ID: { auto media = move_tl_object_as(media_ptr); if (media->user_id_ != 0) { - td->contacts_manager_->get_user_id_object(UserId(media->user_id_), - "MessageMediaContact"); // to ensure updateUser + td->user_manager_->get_user_id_object(UserId(media->user_id_), + "MessageMediaContact"); // to ensure updateUser } return make_unique(Contact(std::move(media->phone_number_), std::move(media->first_name_), std::move(media->last_name_), std::move(media->vcard_), @@ -6328,7 +6445,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const case MessageContentType::Poll: if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) { return make_unique( - td->poll_manager_->dup_poll(static_cast(content)->poll_id)); + td->poll_manager_->dup_poll(dialog_id, static_cast(content)->poll_id)); } else { return make_unique(*static_cast(content)); } @@ -6434,6 +6551,7 @@ unique_ptr dup_message_content(Td *td, DialogId dialog_id, const case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return nullptr; default: UNREACHABLE(); @@ -6836,6 +6954,30 @@ unique_ptr get_action_message_content(Td *td, tl_object_ptr(action_ptr); return make_unique(max(action->boosts_, 0)); } + case telegram_api::messageActionRequestedPeerSentMe::ID: { + auto action = move_tl_object_as(action_ptr); + vector shared_dialogs; + for (auto &peer : action->peers_) { + SharedDialog shared_dialog(td, std::move(peer)); + if (shared_dialog.is_valid()) { + shared_dialogs.push_back(std::move(shared_dialog)); + } + } + if (shared_dialogs.size() > 1) { + for (auto shared_dialog : shared_dialogs) { + if (!shared_dialog.is_user()) { + shared_dialogs.clear(); + break; + } + } + } + if (shared_dialogs.empty() || shared_dialogs.size() != action->peers_.size()) { + LOG(ERROR) << "Receive invalid " << oneline(to_string(action)); + break; + } + + return td::make_unique(std::move(shared_dialogs), action->button_id_); + } default: UNREACHABLE(); } @@ -6854,7 +6996,7 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); return make_tl_object( td->animations_manager_->get_animation_object(m->file_id), - get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), m->has_spoiler, + get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), invert_media, m->has_spoiler, is_content_secret); } case MessageContentType::Audio: { @@ -6865,7 +7007,7 @@ tl_object_ptr get_message_content_object(const MessageCo } case MessageContentType::Contact: { const auto *m = static_cast(content); - return make_tl_object(m->contact.get_contact_object()); + return make_tl_object(m->contact.get_contact_object(td)); } case MessageContentType::Document: { const auto *m = static_cast(content); @@ -6884,7 +7026,7 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::LiveLocation: { const auto *m = static_cast(content); auto passed = max(G()->unix_time() - message_date, 0); - auto expires_in = max(0, m->period - passed); + auto expires_in = m->period == std::numeric_limits::max() ? m->period : max(0, m->period - passed); auto heading = expires_in == 0 ? 0 : m->heading; auto proximity_alert_radius = expires_in == 0 ? 0 : m->proximity_alert_radius; return make_tl_object(m->location.get_location_object(), m->period, expires_in, heading, @@ -6902,7 +7044,7 @@ tl_object_ptr get_message_content_object(const MessageCo return make_tl_object(); } auto caption = get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp); - return make_tl_object(std::move(photo), std::move(caption), m->has_spoiler, + return make_tl_object(std::move(photo), std::move(caption), invert_media, m->has_spoiler, is_content_secret); } case MessageContentType::Sticker: { @@ -6954,7 +7096,7 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); return make_tl_object( td->videos_manager_->get_video_object(m->file_id), - get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), m->has_spoiler, + get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), invert_media, m->has_spoiler, is_content_secret); } case MessageContentType::VideoNote: { @@ -6971,7 +7113,7 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::ChatCreate: { const auto *m = static_cast(content); return make_tl_object( - m->title, td->contacts_manager_->get_user_ids_object(m->participant_user_ids, "MessageChatCreate")); + m->title, td->user_manager_->get_user_ids_object(m->participant_user_ids, "MessageChatCreate")); } case MessageContentType::ChatChangeTitle: { const auto *m = static_cast(content); @@ -6993,7 +7135,7 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::ChatAddUsers: { const auto *m = static_cast(content); return make_tl_object( - td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageChatAddUsers")); + td->user_manager_->get_user_ids_object(m->user_ids, "MessageChatAddUsers")); } case MessageContentType::ChatJoinedByLink: { const MessageChatJoinedByLink *m = static_cast(content); @@ -7005,12 +7147,12 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::ChatDeleteUser: { const auto *m = static_cast(content); return make_tl_object( - td->contacts_manager_->get_user_id_object(m->user_id, "MessageChatDeleteMember")); + td->user_manager_->get_user_id_object(m->user_id, "MessageChatDeleteMember")); } case MessageContentType::ChatMigrateTo: { const auto *m = static_cast(content); return make_tl_object( - td->contacts_manager_->get_supergroup_id_object(m->migrated_to_channel_id, "MessageChatUpgradeTo")); + td->chat_manager_->get_supergroup_id_object(m->migrated_to_channel_id, "MessageChatUpgradeTo")); } case MessageContentType::ChannelCreate: { const auto *m = static_cast(content); @@ -7019,8 +7161,7 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::ChannelMigrateFrom: { const auto *m = static_cast(content); return make_tl_object( - m->title, - td->contacts_manager_->get_basic_group_id_object(m->migrated_from_chat_id, "MessageChatUpgradeFrom")); + m->title, td->chat_manager_->get_basic_group_id_object(m->migrated_from_chat_id, "MessageChatUpgradeFrom")); } case MessageContentType::PinMessage: { const auto *m = static_cast(content); @@ -7035,7 +7176,7 @@ tl_object_ptr get_message_content_object(const MessageCo case MessageContentType::ChatSetTtl: { const auto *m = static_cast(content); return make_tl_object( - m->ttl, td->contacts_manager_->get_user_id_object(m->from_user_id, "MessageChatSetTtl")); + m->ttl, td->user_manager_->get_user_id_object(m->from_user_id, "MessageChatSetTtl")); } case MessageContentType::Call: { const auto *m = static_cast(content); @@ -7119,7 +7260,7 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); return make_tl_object( td->group_call_manager_->get_group_call_id(m->input_group_call_id, DialogId()).get(), - td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageInviteToGroupCall")); + td->user_manager_->get_user_ids_object(m->user_ids, "MessageInviteToGroupCall")); } case MessageContentType::ChatSetTheme: { const auto *m = static_cast(content); @@ -7138,9 +7279,9 @@ tl_object_ptr get_message_content_object(const MessageCo int64 gifter_user_id = 0; if (dialog_id.get_type() == DialogType::User) { auto user_id = dialog_id.get_user_id(); - if (user_id != ContactsManager::get_service_notifications_user_id() && - !td->contacts_manager_->is_user_bot(user_id) && !td->contacts_manager_->is_user_support(user_id)) { - gifter_user_id = td->contacts_manager_->get_user_id_object(user_id, "MessageGiftPremium"); + if (user_id != UserManager::get_service_notifications_user_id() && !td->user_manager_->is_user_bot(user_id) && + !td->user_manager_->is_user_support(user_id)) { + gifter_user_id = td->user_manager_->get_user_id_object(user_id, "MessageGiftPremium"); } } return make_tl_object( @@ -7171,25 +7312,15 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); CHECK(!m->shared_dialog_ids.empty()); if (m->shared_dialog_ids[0].get_type() == DialogType::User) { - vector user_ids; + vector> users; for (auto shared_dialog_id : m->shared_dialog_ids) { - if (td->auth_manager_->is_bot()) { - user_ids.push_back(shared_dialog_id.get_user_id().get()); - } else { - user_ids.push_back( - td->contacts_manager_->get_user_id_object(shared_dialog_id.get_user_id(), "MessageRequestedDialog")); - } + users.push_back(SharedDialog(shared_dialog_id).get_shared_user_object(td)); } - return make_tl_object(std::move(user_ids), m->button_id); + return make_tl_object(std::move(users), m->button_id); } CHECK(m->shared_dialog_ids.size() == 1); - int64 chat_id; - if (td->auth_manager_->is_bot()) { - chat_id = m->shared_dialog_ids[0].get(); - } else { - chat_id = td->dialog_manager_->get_chat_id_object(m->shared_dialog_ids[0], "messageChatShared"); - } - return make_tl_object(chat_id, m->button_id); + return make_tl_object(SharedDialog(m->shared_dialog_ids[0]).get_shared_chat_object(td), + m->button_id); } case MessageContentType::WebViewWriteAccessAllowed: { const auto *m = static_cast(content); @@ -7238,7 +7369,7 @@ tl_object_ptr get_message_content_object(const MessageCo td->dialog_manager_->get_chat_id_object(DialogId(m->boosted_channel_id), "messagePremiumGiveawayWinners"), m->giveaway_message_id.get(), m->additional_dialog_count, m->winners_selection_date, m->only_new_subscribers, m->was_refunded, m->month_count, m->prize_description, m->winner_count, - td->contacts_manager_->get_user_ids_object(m->winner_user_ids, "messagePremiumGiveawayWinners"), + td->user_manager_->get_user_ids_object(m->winner_user_ids, "messagePremiumGiveawayWinners"), m->unclaimed_count); } case MessageContentType::ExpiredVideoNote: @@ -7249,6 +7380,20 @@ tl_object_ptr get_message_content_object(const MessageCo const auto *m = static_cast(content); return td_api::make_object(m->boost_count); } + case MessageContentType::DialogShared: { + const auto *m = static_cast(content); + CHECK(!m->shared_dialogs.empty()); + if (m->shared_dialogs[0].is_user()) { + vector> users; + for (const auto &shared_dialog : m->shared_dialogs) { + users.push_back(shared_dialog.get_shared_user_object(td)); + } + return td_api::make_object(std::move(users), m->button_id); + } + CHECK(m->shared_dialogs.size() == 1); + return td_api::make_object(m->shared_dialogs[0].get_shared_chat_object(td), + m->button_id); + } default: UNREACHABLE(); return nullptr; @@ -7684,6 +7829,7 @@ string get_message_content_search_text(const Td *td, const MessageContent *conte case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return string(); default: UNREACHABLE(); @@ -8028,6 +8174,8 @@ void add_message_content_dependencies(Dependencies &dependencies, const MessageC break; case MessageContentType::BoostApply: break; + case MessageContentType::DialogShared: + break; default: UNREACHABLE(); break; diff --git a/lib/tgchat/ext/td/td/telegram/MessageContent.h b/lib/tgchat/ext/td/td/telegram/MessageContent.h index c99c4196..b211ac63 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContent.h +++ b/lib/tgchat/ext/td/td/telegram/MessageContent.h @@ -110,9 +110,16 @@ unique_ptr create_screenshot_taken_message_content(); unique_ptr create_chat_set_ttl_message_content(int32 ttl, UserId from_user_id); +td_api::object_ptr extract_input_caption( + td_api::object_ptr &input_message_content); + +bool extract_input_invert_media(const td_api::object_ptr &input_message_content); + Result get_input_message_content( DialogId dialog_id, tl_object_ptr &&input_message_content, Td *td, bool is_premium); +Status check_message_group_message_contents(const vector &message_contents); + bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server); SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td, diff --git a/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp b/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp index fc8a2e4d..4deff6ca 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageContentType.cpp @@ -144,11 +144,24 @@ StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType cont return string_builder << "ExpiredVoiceNote"; case MessageContentType::BoostApply: return string_builder << "BoostApply"; + case MessageContentType::DialogShared: + return string_builder << "ChatShared"; default: return string_builder << "Invalid type " << static_cast(content_type); } } +bool is_allowed_invert_caption_message_content(MessageContentType content_type) { + switch (content_type) { + case MessageContentType::Animation: + case MessageContentType::Photo: + case MessageContentType::Video: + return true; + default: + return false; + } +} + bool is_allowed_media_group_content(MessageContentType content_type) { switch (content_type) { case MessageContentType::Audio: @@ -218,6 +231,7 @@ bool is_allowed_media_group_content(MessageContentType content_type) { case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; default: UNREACHABLE(); @@ -298,6 +312,7 @@ bool can_be_secret_message_content(MessageContentType content_type) { case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; default: UNREACHABLE(); @@ -374,6 +389,7 @@ bool is_service_message_content(MessageContentType content_type) { case MessageContentType::GiveawayLaunch: case MessageContentType::GiveawayResults: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return true; default: UNREACHABLE(); @@ -450,6 +466,7 @@ bool is_editable_message_content(MessageContentType content_type) { case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; default: UNREACHABLE(); @@ -574,6 +591,7 @@ bool can_have_message_content_caption(MessageContentType content_type) { case MessageContentType::ExpiredVideoNote: case MessageContentType::ExpiredVoiceNote: case MessageContentType::BoostApply: + case MessageContentType::DialogShared: return false; default: UNREACHABLE(); diff --git a/lib/tgchat/ext/td/td/telegram/MessageContentType.h b/lib/tgchat/ext/td/td/telegram/MessageContentType.h index 64795fe5..2a7397c8 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageContentType.h +++ b/lib/tgchat/ext/td/td/telegram/MessageContentType.h @@ -79,12 +79,15 @@ enum class MessageContentType : int32 { GiveawayWinners, ExpiredVideoNote, ExpiredVoiceNote, - BoostApply + BoostApply, + DialogShared }; // increase MessageUnsupported::CURRENT_VERSION each time a new message content type is added StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type); +bool is_allowed_invert_caption_message_content(MessageContentType content_type); + bool is_allowed_media_group_content(MessageContentType content_type); bool is_homogenous_media_group_content(MessageContentType content_type); diff --git a/lib/tgchat/ext/td/td/telegram/MessageCopyOptions.h b/lib/tgchat/ext/td/td/telegram/MessageCopyOptions.h index f25da884..33051b01 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageCopyOptions.h +++ b/lib/tgchat/ext/td/td/telegram/MessageCopyOptions.h @@ -18,6 +18,7 @@ namespace td { struct MessageCopyOptions { bool send_copy = false; bool replace_caption = false; + bool new_invert_media = false; FormattedText new_caption; MessageInputReplyTo input_reply_to; unique_ptr reply_markup; @@ -41,7 +42,8 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, MessageCopyOptio if (copy_options.send_copy) { string_builder << "CopyOptions[replace_caption = " << copy_options.replace_caption; if (copy_options.replace_caption) { - string_builder << ", new_caption = " << copy_options.new_caption; + string_builder << ", new_caption = " << copy_options.new_caption + << ", new_show_caption_above_media = " << copy_options.new_invert_media; } if (copy_options.input_reply_to.is_valid()) { string_builder << ", in reply to " << copy_options.input_reply_to; diff --git a/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp b/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp index 1699bf31..85f00352 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageEntity.cpp @@ -6,7 +6,6 @@ // #include "td/telegram/MessageEntity.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/LinkManager.h" @@ -15,6 +14,7 @@ #include "td/telegram/SecretChatLayer.h" #include "td/telegram/StickersManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/actor/MultiPromise.h" @@ -57,7 +57,8 @@ int MessageEntity::get_type_priority(Type type) { 50 /*BankCardNumber*/, 50 /*MediaTimestamp*/, 94 /*Spoiler*/, - 99 /*CustomEmoji*/}; + 99 /*CustomEmoji*/, + 0 /*ExpandableBlockQuote*/}; static_assert(sizeof(priorities) / sizeof(priorities[0]) == static_cast(MessageEntity::Type::Size), ""); return priorities[static_cast(type)]; } @@ -106,6 +107,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Ty return string_builder << "Spoiler"; case MessageEntity::Type::CustomEmoji: return string_builder << "CustomEmoji"; + case MessageEntity::Type::ExpandableBlockQuote: + return string_builder << "ExpandableBlockQuote"; default: UNREACHABLE(); return string_builder << "Impossible"; @@ -162,7 +165,7 @@ tl_object_ptr MessageEntity::get_text_entity_type_object case MessageEntity::Type::TextUrl: return make_tl_object(argument); case MessageEntity::Type::MentionName: - // can't use contacts_manager, because can be called from a static request + // can't use user_manager, because can be called from a static request return make_tl_object(user_id.get()); case MessageEntity::Type::Cashtag: return make_tl_object(); @@ -176,6 +179,8 @@ tl_object_ptr MessageEntity::get_text_entity_type_object return make_tl_object(); case MessageEntity::Type::CustomEmoji: return make_tl_object(custom_emoji_id.get()); + case MessageEntity::Type::ExpandableBlockQuote: + return make_tl_object(); default: UNREACHABLE(); return nullptr; @@ -1023,118 +1028,116 @@ static bool is_common_tld(Slice str) { "americanfamily", "amex", "amfam", "amica", "amsterdam", "analytics", "android", "anquan", "anz", "ao", "aol", "apartments", "app", "apple", "aq", "aquarelle", "ar", "arab", "aramco", "archi", "army", "arpa", "art", "arte", "as", "asda", "asia", "associates", "at", "athleta", "attorney", "au", "auction", "audi", "audible", "audio", - "auspost", "author", "auto", "autos", "avianca", "aw", "aws", "ax", "axa", "az", "azure", "ba", "baby", "baidu", - "banamex", "bananarepublic", "band", "bank", "bar", "barcelona", "barclaycard", "barclays", "barefoot", - "bargains", "baseball", "basketball", "bauhaus", "bayern", "bb", "bbc", "bbt", "bbva", "bcg", "bcn", "bd", "be", - "beats", "beauty", "beer", "bentley", "berlin", "best", "bestbuy", "bet", "bf", "bg", "bh", "bharti", "bi", - "bible", "bid", "bike", "bing", "bingo", "bio", "biz", "bj", "black", "blackfriday", "blockbuster", "blog", - "bloomberg", "blue", "bm", "bms", "bmw", "bn", "bnpparibas", "bo", "boats", "boehringer", "bofa", "bom", "bond", - "boo", "book", "booking", "bosch", "bostik", "boston", "bot", "boutique", "box", "br", "bradesco", "bridgestone", - "broadway", "broker", "brother", "brussels", "bs", "bt", "build", "builders", "business", "buy", "buzz", "bv", - "bw", "by", "bz", "bzh", "ca", "cab", "cafe", "cal", "call", "calvinklein", "cam", "camera", "camp", "canon", - "capetown", "capital", "capitalone", "car", "caravan", "cards", "care", "career", "careers", "cars", "casa", - "case", "cash", "casino", "cat", "catering", "catholic", "cba", "cbn", "cbre", "cbs", "cc", "cd", "center", - "ceo", "cern", "cf", "cfa", "cfd", "cg", "ch", "chanel", "channel", "charity", "chase", "chat", "cheap", - "chintai", "christmas", "chrome", "church", "ci", "cipriani", "circle", "cisco", "citadel", "citi", "citic", - "city", "cityeats", "ck", "cl", "claims", "cleaning", "click", "clinic", "clinique", "clothing", "cloud", "club", - "clubmed", "cm", "cn", "co", "coach", "codes", "coffee", "college", "cologne", "com", "comcast", "commbank", - "community", "company", "compare", "computer", "comsec", "condos", "construction", "consulting", "contact", - "contractors", "cooking", "cool", "coop", "corsica", "country", "coupon", "coupons", "courses", "cpa", "cr", - "credit", "creditcard", "creditunion", "cricket", "crown", "crs", "cruise", "cruises", "cu", "cuisinella", "cv", - "cw", "cx", "cy", "cymru", "cyou", "cz", "dabur", "dad", "dance", "data", "date", "dating", "datsun", "day", - "dclk", "dds", "de", "deal", "dealer", "deals", "degree", "delivery", "dell", "deloitte", "delta", "democrat", - "dental", "dentist", "desi", "design", "dev", "dhl", "diamonds", "diet", "digital", "direct", "directory", - "discount", "discover", "dish", "diy", "dj", "dk", "dm", "dnp", "do", "docs", "doctor", "dog", "domains", "dot", - "download", "drive", "dtv", "dubai", "dunlop", "dupont", "durban", "dvag", "dvr", "dz", "earth", "eat", "ec", - "eco", "edeka", "edu", "education", "ee", "eg", "email", "emerck", "energy", "engineer", "engineering", - "enterprises", "epson", "equipment", "er", "ericsson", "erni", "es", "esq", "estate", "et", "etisalat", "eu", - "eurovision", "eus", "events", "exchange", "expert", "exposed", "express", "extraspace", "fage", "fail", - "fairwinds", "faith", "family", "fan", "fans", "farm", "farmers", "fashion", "fast", "fedex", "feedback", + "auspost", "author", "auto", "autos", "aw", "aws", "ax", "axa", "az", "azure", "ba", "baby", "baidu", "banamex", + "band", "bank", "bar", "barcelona", "barclaycard", "barclays", "barefoot", "bargains", "baseball", "basketball", + "bauhaus", "bayern", "bb", "bbc", "bbt", "bbva", "bcg", "bcn", "bd", "be", "beats", "beauty", "beer", "bentley", + "berlin", "best", "bestbuy", "bet", "bf", "bg", "bh", "bharti", "bi", "bible", "bid", "bike", "bing", "bingo", + "bio", "biz", "bj", "black", "blackfriday", "blockbuster", "blog", "bloomberg", "blue", "bm", "bms", "bmw", "bn", + "bnpparibas", "bo", "boats", "boehringer", "bofa", "bom", "bond", "boo", "book", "booking", "bosch", "bostik", + "boston", "bot", "boutique", "box", "br", "bradesco", "bridgestone", "broadway", "broker", "brother", "brussels", + "bs", "bt", "build", "builders", "business", "buy", "buzz", "bv", "bw", "by", "bz", "bzh", "ca", "cab", "cafe", + "cal", "call", "calvinklein", "cam", "camera", "camp", "canon", "capetown", "capital", "capitalone", "car", + "caravan", "cards", "care", "career", "careers", "cars", "casa", "case", "cash", "casino", "cat", "catering", + "catholic", "cba", "cbn", "cbre", "cc", "cd", "center", "ceo", "cern", "cf", "cfa", "cfd", "cg", "ch", "chanel", + "channel", "charity", "chase", "chat", "cheap", "chintai", "christmas", "chrome", "church", "ci", "cipriani", + "circle", "cisco", "citadel", "citi", "citic", "city", "ck", "cl", "claims", "cleaning", "click", "clinic", + "clinique", "clothing", "cloud", "club", "clubmed", "cm", "cn", "co", "coach", "codes", "coffee", "college", + "cologne", "com", "commbank", "community", "company", "compare", "computer", "comsec", "condos", "construction", + "consulting", "contact", "contractors", "cooking", "cool", "coop", "corsica", "country", "coupon", "coupons", + "courses", "cpa", "cr", "credit", "creditcard", "creditunion", "cricket", "crown", "crs", "cruise", "cruises", + "cu", "cuisinella", "cv", "cw", "cx", "cy", "cymru", "cyou", "cz", "dabur", "dad", "dance", "data", "date", + "dating", "datsun", "day", "dclk", "dds", "de", "deal", "dealer", "deals", "degree", "delivery", "dell", + "deloitte", "delta", "democrat", "dental", "dentist", "desi", "design", "dev", "dhl", "diamonds", "diet", + "digital", "direct", "directory", "discount", "discover", "dish", "diy", "dj", "dk", "dm", "dnp", "do", "docs", + "doctor", "dog", "domains", "dot", "download", "drive", "dtv", "dubai", "dunlop", "dupont", "durban", "dvag", + "dvr", "dz", "earth", "eat", "ec", "eco", "edeka", "edu", "education", "ee", "eg", "email", "emerck", "energy", + "engineer", "engineering", "enterprises", "epson", "equipment", "er", "ericsson", "erni", "es", "esq", "estate", + "et", "eu", "eurovision", "eus", "events", "exchange", "expert", "exposed", "express", "extraspace", "fage", + "fail", "fairwinds", "faith", "family", "fan", "fans", "farm", "farmers", "fashion", "fast", "fedex", "feedback", "ferrari", "ferrero", "fi", "fidelity", "fido", "film", "final", "finance", "financial", "fire", "firestone", "firmdale", "fish", "fishing", "fit", "fitness", "fj", "fk", "flickr", "flights", "flir", "florist", "flowers", "fly", "fm", "fo", "foo", "food", "football", "ford", "forex", "forsale", "forum", "foundation", "fox", "fr", - "free", "fresenius", "frl", "frogans", "frontdoor", "frontier", "ftr", "fujitsu", "fun", "fund", "furniture", - "futbol", "fyi", "ga", "gal", "gallery", "gallo", "gallup", "game", "games", "gap", "garden", "gay", "gb", - "gbiz", "gd", "gdn", "ge", "gea", "gent", "genting", "george", "gf", "gg", "ggee", "gh", "gi", "gift", "gifts", - "gives", "giving", "gl", "glass", "gle", "global", "globo", "gm", "gmail", "gmbh", "gmo", "gmx", "gn", "godaddy", - "gold", "goldpoint", "golf", "goo", "goodyear", "goog", "google", "gop", "got", "gov", "gp", "gq", "gr", - "grainger", "graphics", "gratis", "green", "gripe", "grocery", "group", "gs", "gt", "gu", "guardian", "gucci", - "guge", "guide", "guitars", "guru", "gw", "gy", "hair", "hamburg", "hangout", "haus", "hbo", "hdfc", "hdfcbank", - "health", "healthcare", "help", "helsinki", "here", "hermes", "hiphop", "hisamitsu", "hitachi", "hiv", "hk", - "hkt", "hm", "hn", "hockey", "holdings", "holiday", "homedepot", "homegoods", "homes", "homesense", "honda", - "horse", "hospital", "host", "hosting", "hot", "hotels", "hotmail", "house", "how", "hr", "hsbc", "ht", "hu", - "hughes", "hyatt", "hyundai", "ibm", "icbc", "ice", "icu", "id", "ie", "ieee", "ifm", "ikano", "il", "im", - "imamat", "imdb", "immo", "immobilien", "in", "inc", "industries", "infiniti", "info", "ing", "ink", "institute", + "free", "fresenius", "frl", "frogans", "frontier", "ftr", "fujitsu", "fun", "fund", "furniture", "futbol", "fyi", + "ga", "gal", "gallery", "gallo", "gallup", "game", "games", "gap", "garden", "gay", "gb", "gbiz", "gd", "gdn", + "ge", "gea", "gent", "genting", "george", "gf", "gg", "ggee", "gh", "gi", "gift", "gifts", "gives", "giving", + "gl", "glass", "gle", "global", "globo", "gm", "gmail", "gmbh", "gmo", "gmx", "gn", "godaddy", "gold", + "goldpoint", "golf", "goo", "goodyear", "goog", "google", "gop", "got", "gov", "gp", "gq", "gr", "grainger", + "graphics", "gratis", "green", "gripe", "grocery", "group", "gs", "gt", "gu", "gucci", "guge", "guide", + "guitars", "guru", "gw", "gy", "hair", "hamburg", "hangout", "haus", "hbo", "hdfc", "hdfcbank", "health", + "healthcare", "help", "helsinki", "here", "hermes", "hiphop", "hisamitsu", "hitachi", "hiv", "hk", "hkt", "hm", + "hn", "hockey", "holdings", "holiday", "homedepot", "homegoods", "homes", "homesense", "honda", "horse", + "hospital", "host", "hosting", "hot", "hotels", "hotmail", "house", "how", "hr", "hsbc", "ht", "hu", "hughes", + "hyatt", "hyundai", "ibm", "icbc", "ice", "icu", "id", "ie", "ieee", "ifm", "ikano", "il", "im", "imamat", + "imdb", "immo", "immobilien", "in", "inc", "industries", "infiniti", "info", "ing", "ink", "institute", "insurance", "insure", "int", "international", "intuit", "investments", "io", "ipiranga", "iq", "ir", "irish", "is", "ismaili", "ist", "istanbul", "it", "itau", "itv", "jaguar", "java", "jcb", "je", "jeep", "jetzt", "jewelry", "jio", "jll", "jm", "jmp", "jnj", "jo", "jobs", "joburg", "jot", "joy", "jp", "jpmorgan", "jprs", "juegos", "juniper", "kaufen", "kddi", "ke", "kerryhotels", "kerrylogistics", "kerryproperties", "kfh", "kg", - "kh", "ki", "kia", "kids", "kim", "kinder", "kindle", "kitchen", "kiwi", "km", "kn", "koeln", "komatsu", - "kosher", "kp", "kpmg", "kpn", "kr", "krd", "kred", "kuokgroup", "kw", "ky", "kyoto", "kz", "la", "lacaixa", - "lamborghini", "lamer", "lancaster", "land", "landrover", "lanxess", "lasalle", "lat", "latino", "latrobe", - "law", "lawyer", "lb", "lc", "lds", "lease", "leclerc", "lefrak", "legal", "lego", "lexus", "lgbt", "li", "lidl", - "life", "lifeinsurance", "lifestyle", "lighting", "like", "lilly", "limited", "limo", "lincoln", "link", "lipsy", - "live", "living", "lk", "llc", "llp", "loan", "loans", "locker", "locus", "lol", "london", "lotte", "lotto", - "love", "lpl", "lplfinancial", "lr", "ls", "lt", "ltd", "ltda", "lu", "lundbeck", "luxe", "luxury", "lv", "ly", - "ma", "madrid", "maif", "maison", "makeup", "man", "management", "mango", "map", "market", "marketing", - "markets", "marriott", "marshalls", "mattel", "mba", "mc", "mckinsey", "md", "me", "med", "media", "meet", - "melbourne", "meme", "memorial", "men", "menu", "merckmsd", "mg", "mh", "miami", "microsoft", "mil", "mini", - "mint", "mit", "mitsubishi", "mk", "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile", "moda", "moe", - "moi", "mom", "monash", "money", "monster", "mormon", "mortgage", "moscow", "moto", "motorcycles", "mov", - "movie", "mp", "mq", "mr", "ms", "msd", "mt", "mtn", "mtr", "mu", "museum", "music", "mv", "mw", "mx", "my", - "mz", "na", "nab", "nagoya", "name", "natura", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix", - "network", "neustar", "new", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", "ngo", "nhk", "ni", - "nico", "nike", "nikon", "ninja", "nissan", "nissay", "nl", "no", "nokia", "norton", "now", "nowruz", "nowtv", - "np", "nr", "nra", "nrw", "ntt", "nu", "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", - "olayangroup", "oldnavy", "ollo", "om", "omega", "one", "ong", "onl", "online", "ooo", "open", "oracle", - "orange", "org", "organic", "origins", "osaka", "otsuka", "ott", "ovh", "pa", "page", "panasonic", "paris", - "pars", "partners", "parts", "party", "pay", "pccw", "pe", "pet", "pf", "pfizer", "pg", "ph", "pharmacy", "phd", - "philips", "phone", "photo", "photography", "photos", "physio", "pics", "pictet", "pictures", "pid", "pin", - "ping", "pink", "pioneer", "pizza", "pk", "pl", "place", "play", "playstation", "plumbing", "plus", "pm", "pn", - "pnc", "pohl", "poker", "politie", "porn", "post", "pr", "pramerica", "praxi", "press", "prime", "pro", "prod", - "productions", "prof", "progressive", "promo", "properties", "property", "protection", "pru", "prudential", "ps", - "pt", "pub", "pw", "pwc", "py", "qa", "qpon", "quebec", "quest", "racing", "radio", "re", "read", "realestate", - "realtor", "realty", "recipes", "red", "redstone", "redumbrella", "rehab", "reise", "reisen", "reit", "reliance", - "ren", "rent", "rentals", "repair", "report", "republican", "rest", "restaurant", "review", "reviews", "rexroth", - "rich", "richardli", "ricoh", "ril", "rio", "rip", "ro", "rocher", "rocks", "rodeo", "rogers", "room", "rs", - "rsvp", "ru", "rugby", "ruhr", "run", "rw", "rwe", "ryukyu", "sa", "saarland", "safe", "safety", "sakura", - "sale", "salon", "samsclub", "samsung", "sandvik", "sandvikcoromant", "sanofi", "sap", "sarl", "sas", "save", - "saxo", "sb", "sbi", "sbs", "sc", "sca", "scb", "schaeffler", "schmidt", "scholarships", "school", "schule", - "schwarz", "science", "scot", "sd", "se", "search", "seat", "secure", "security", "seek", "select", "sener", - "services", "seven", "sew", "sex", "sexy", "sfr", "sg", "sh", "shangrila", "sharp", "shaw", "shell", "shia", - "shiksha", "shoes", "shop", "shopping", "shouji", "show", "showtime", "si", "silk", "sina", "singles", "site", - "sj", "sk", "ski", "skin", "sky", "skype", "sl", "sling", "sm", "smart", "smile", "sn", "sncf", "so", "soccer", - "social", "softbank", "software", "sohu", "solar", "solutions", "song", "sony", "soy", "spa", "space", "sport", - "spot", "sr", "srl", "ss", "st", "stada", "staples", "star", "statebank", "statefarm", "stc", "stcgroup", - "stockholm", "storage", "store", "stream", "studio", "study", "style", "su", "sucks", "supplies", "supply", - "support", "surf", "surgery", "suzuki", "sv", "swatch", "swiss", "sx", "sy", "sydney", "systems", "sz", "tab", - "taipei", "talk", "taobao", "target", "tatamotors", "tatar", "tattoo", "tax", "taxi", "tc", "tci", "td", "tdk", - "team", "tech", "technology", "tel", "temasek", "tennis", "teva", "tf", "tg", "th", "thd", "theater", "theatre", - "tiaa", "tickets", "tienda", "tips", "tires", "tirol", "tj", "tjmaxx", "tjx", "tk", "tkmaxx", "tl", "tm", - "tmall", "tn", "to", "today", "tokyo", "tools", "top", "toray", "toshiba", "total", "tours", "town", "toyota", - "toys", "tr", "trade", "trading", "training", "travel", "travelers", "travelersinsurance", "trust", "trv", "tt", - "tube", "tui", "tunes", "tushu", "tv", "tvs", "tw", "tz", "ua", "ubank", "ubs", "ug", "uk", "unicom", - "university", "uno", "uol", "ups", "us", "uy", "uz", "va", "vacations", "vana", "vanguard", "vc", "ve", "vegas", - "ventures", "verisign", "versicherung", "vet", "vg", "vi", "viajes", "video", "vig", "viking", "villas", "vin", - "vip", "virgin", "visa", "vision", "viva", "vivo", "vlaanderen", "vn", "vodka", "volkswagen", "volvo", "vote", - "voting", "voto", "voyage", "vu", "wales", "walmart", "walter", "wang", "wanggou", "watch", "watches", "weather", - "weatherchannel", "webcam", "weber", "website", "wed", "wedding", "weibo", "weir", "wf", "whoswho", "wien", - "wiki", "williamhill", "win", "windows", "wine", "winners", "wme", "wolterskluwer", "woodside", "work", "works", - "world", "wow", "ws", "wtc", "wtf", "xbox", "xerox", "xfinity", "xihuan", "xin", "कॉम", "セール", "佛山", "ಭಾರತ", - "慈善", "集团", "在线", "한국", "ଭାରତ", "点看", "คอม", "ভাৰত", "ভারত", "八卦", "ישראל", "موقع", "বাংলা", "公益", - "公司", "香格里拉", "网站", "移动", "我爱你", "москва", "қаз", "католик", "онлайн", "сайт", "联通", "срб", "бг", - "бел", "קום", "时尚", "微博", "淡马锡", "ファッション", "орг", "नेट", "ストア", "アマゾン", "삼성", "சிங்கப்பூர்", - "商标", "商店", "商城", "дети", "мкд", "ею", "ポイント", "新闻", "家電", "كوم", "中文网", "中信", "中国", "中國", - "娱乐", "谷歌", "భారత్", "ලංකා", "電訊盈科", "购物", "クラウド", "ભારત", "通販", "भारतम्", "भारत", "भारोत", "网店", - "संगठन", "餐厅", "网络", "ком", "укр", "香港", "亚马逊", "食品", "飞利浦", "台湾", "台灣", "手机", "мон", - "الجزائر", "عمان", "ارامكو", "ایران", "العليان", "اتصالات", "امارات", "بازار", "موريتانيا", "پاکستان", "الاردن", - "بارت", "بھارت", "المغرب", "ابوظبي", "البحرين", "السعودية", "ڀارت", "كاثوليك", "سودان", "همراه", "عراق", - "مليسيا", "澳門", "닷컴", "政府", "شبكة", "بيتك", "عرب", "გე", "机构", "组织机构", "健康", "ไทย", "سورية", - "招聘", "рус", "рф", "تونس", "大拿", "ລາວ", "みんな", "グーグル", "ευ", "ελ", "世界", "書籍", "ഭാരതം", "ਭਾਰਤ", - "网址", "닷넷", "コム", "天主教", "游戏", "vermögensberater", "vermögensberatung", "企业", "信息", "嘉里大酒店", - "嘉里", "مصر", "قطر", "广东", "இலங்கை", "இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xxx", "xyz", "yachts", - "yahoo", "yamaxun", "yandex", "ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", "yun", "za", - "zappos", "zara", "zero", "zip", "zm", "zone", "zuerich", + "kh", "ki", "kia", "kids", "kim", "kindle", "kitchen", "kiwi", "km", "kn", "koeln", "komatsu", "kosher", "kp", + "kpmg", "kpn", "kr", "krd", "kred", "kuokgroup", "kw", "ky", "kyoto", "kz", "la", "lacaixa", "lamborghini", + "lamer", "lancaster", "land", "landrover", "lanxess", "lasalle", "lat", "latino", "latrobe", "law", "lawyer", + "lb", "lc", "lds", "lease", "leclerc", "lefrak", "legal", "lego", "lexus", "lgbt", "li", "lidl", "life", + "lifeinsurance", "lifestyle", "lighting", "like", "lilly", "limited", "limo", "lincoln", "link", "lipsy", "live", + "living", "lk", "llc", "llp", "loan", "loans", "locker", "locus", "lol", "london", "lotte", "lotto", "love", + "lpl", "lplfinancial", "lr", "ls", "lt", "ltd", "ltda", "lu", "lundbeck", "luxe", "luxury", "lv", "ly", "ma", + "madrid", "maif", "maison", "makeup", "man", "management", "mango", "map", "market", "marketing", "markets", + "marriott", "marshalls", "mattel", "mba", "mc", "mckinsey", "md", "me", "med", "media", "meet", "melbourne", + "meme", "memorial", "men", "menu", "merckmsd", "mg", "mh", "miami", "microsoft", "mil", "mini", "mint", "mit", + "mitsubishi", "mk", "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile", "moda", "moe", "moi", "mom", + "monash", "money", "monster", "mormon", "mortgage", "moscow", "moto", "motorcycles", "mov", "movie", "mp", "mq", + "mr", "ms", "msd", "mt", "mtn", "mtr", "mu", "museum", "music", "mv", "mw", "mx", "my", "mz", "na", "nab", + "nagoya", "name", "natura", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix", "network", "neustar", + "new", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng", "ngo", "nhk", "ni", "nico", "nike", "nikon", + "ninja", "nissan", "nissay", "nl", "no", "nokia", "norton", "now", "nowruz", "nowtv", "np", "nr", "nra", "nrw", + "ntt", "nu", "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", "olayangroup", "ollo", "om", "omega", + "one", "ong", "onl", "online", "ooo", "open", "oracle", "orange", "org", "organic", "origins", "osaka", "otsuka", + "ott", "ovh", "pa", "page", "panasonic", "paris", "pars", "partners", "parts", "party", "pay", "pccw", "pe", + "pet", "pf", "pfizer", "pg", "ph", "pharmacy", "phd", "philips", "phone", "photo", "photography", "photos", + "physio", "pics", "pictet", "pictures", "pid", "pin", "ping", "pink", "pioneer", "pizza", "pk", "pl", "place", + "play", "playstation", "plumbing", "plus", "pm", "pn", "pnc", "pohl", "poker", "politie", "porn", "post", "pr", + "pramerica", "praxi", "press", "prime", "pro", "prod", "productions", "prof", "progressive", "promo", + "properties", "property", "protection", "pru", "prudential", "ps", "pt", "pub", "pw", "pwc", "py", "qa", "qpon", + "quebec", "quest", "racing", "radio", "re", "read", "realestate", "realtor", "realty", "recipes", "red", + "redstone", "redumbrella", "rehab", "reise", "reisen", "reit", "reliance", "ren", "rent", "rentals", "repair", + "report", "republican", "rest", "restaurant", "review", "reviews", "rexroth", "rich", "richardli", "ricoh", + "ril", "rio", "rip", "ro", "rocks", "rodeo", "rogers", "room", "rs", "rsvp", "ru", "rugby", "ruhr", "run", "rw", + "rwe", "ryukyu", "sa", "saarland", "safe", "safety", "sakura", "sale", "salon", "samsclub", "samsung", "sandvik", + "sandvikcoromant", "sanofi", "sap", "sarl", "sas", "save", "saxo", "sb", "sbi", "sbs", "sc", "scb", "schaeffler", + "schmidt", "scholarships", "school", "schule", "schwarz", "science", "scot", "sd", "se", "search", "seat", + "secure", "security", "seek", "select", "sener", "services", "seven", "sew", "sex", "sexy", "sfr", "sg", "sh", + "shangrila", "sharp", "shaw", "shell", "shia", "shiksha", "shoes", "shop", "shopping", "shouji", "show", "si", + "silk", "sina", "singles", "site", "sj", "sk", "ski", "skin", "sky", "skype", "sl", "sling", "sm", "smart", + "smile", "sn", "sncf", "so", "soccer", "social", "softbank", "software", "sohu", "solar", "solutions", "song", + "sony", "soy", "spa", "space", "sport", "spot", "sr", "srl", "ss", "st", "stada", "staples", "star", "statebank", + "statefarm", "stc", "stcgroup", "stockholm", "storage", "store", "stream", "studio", "study", "style", "su", + "sucks", "supplies", "supply", "support", "surf", "surgery", "suzuki", "sv", "swatch", "swiss", "sx", "sy", + "sydney", "systems", "sz", "tab", "taipei", "talk", "taobao", "target", "tatamotors", "tatar", "tattoo", "tax", + "taxi", "tc", "tci", "td", "tdk", "team", "tech", "technology", "tel", "temasek", "tennis", "teva", "tf", "tg", + "th", "thd", "theater", "theatre", "tiaa", "tickets", "tienda", "tips", "tires", "tirol", "tj", "tjmaxx", "tjx", + "tk", "tkmaxx", "tl", "tm", "tmall", "tn", "to", "today", "tokyo", "tools", "top", "toray", "toshiba", "total", + "tours", "town", "toyota", "toys", "tr", "trade", "trading", "training", "travel", "travelers", + "travelersinsurance", "trust", "trv", "tt", "tube", "tui", "tunes", "tushu", "tv", "tvs", "tw", "tz", "ua", + "ubank", "ubs", "ug", "uk", "unicom", "university", "uno", "uol", "ups", "us", "uy", "uz", "va", "vacations", + "vana", "vanguard", "vc", "ve", "vegas", "ventures", "verisign", "versicherung", "vet", "vg", "vi", "viajes", + "video", "vig", "viking", "villas", "vin", "vip", "virgin", "visa", "vision", "viva", "vivo", "vlaanderen", "vn", + "vodka", "volvo", "vote", "voting", "voto", "voyage", "vu", "wales", "walmart", "walter", "wang", "wanggou", + "watch", "watches", "weather", "weatherchannel", "webcam", "weber", "website", "wed", "wedding", "weibo", "weir", + "wf", "whoswho", "wien", "wiki", "williamhill", "win", "windows", "wine", "winners", "wme", "wolterskluwer", + "woodside", "work", "works", "world", "wow", "ws", "wtc", "wtf", "xbox", "xerox", "xihuan", "xin", "कॉम", + "セール", "佛山", "ಭಾರತ", "慈善", "集团", "在线", "한국", "ଭାରତ", "点看", "คอม", "ভাৰত", "ভারত", "八卦", "ישראל", + "موقع", "বাংলা", "公益", "公司", "香格里拉", "网站", "移动", "我爱你", "москва", "қаз", "католик", "онлайн", + "сайт", "联通", "срб", "бг", "бел", "קום", "时尚", "微博", "淡马锡", "ファッション", "орг", "नेट", "ストア", + "アマゾン", "삼성", "சிங்கப்பூர்", "商标", "商店", "商城", "дети", "мкд", "ею", "ポイント", "新闻", "家電", "كوم", + "中文网", "中信", "中国", "中國", "娱乐", "谷歌", "భారత్", "ලංකා", "電訊盈科", "购物", "クラウド", "ભારત", "通販", + "भारतम्", "भारत", "भारोत", "网店", "संगठन", "餐厅", "网络", "ком", "укр", "香港", "亚马逊", "食品", "飞利浦", + "台湾", "台灣", "手机", "мон", "الجزائر", "عمان", "ارامكو", "ایران", "العليان", "امارات", "بازار", "موريتانيا", + "پاکستان", "الاردن", "بارت", "بھارت", "المغرب", "ابوظبي", "البحرين", "السعودية", "ڀارت", "كاثوليك", "سودان", + "همراه", "عراق", "مليسيا", "澳門", "닷컴", "政府", "شبكة", "بيتك", "عرب", "გე", "机构", "组织机构", "健康", + "ไทย", "سورية", "招聘", "рус", "рф", "تونس", "大拿", "ລາວ", "みんな", "グーグル", "ευ", "ελ", "世界", "書籍", + "ഭാരതം", "ਭਾਰਤ", "网址", "닷넷", "コム", "天主教", "游戏", "vermögensberater", "vermögensberatung", "企业", + "信息", "嘉里大酒店", "嘉里", "مصر", "قطر", "广东", "இலங்கை", "இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xxx", + "xyz", "yachts", "yahoo", "yamaxun", "yandex", "ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", + "yun", "za", "zappos", "zara", "zero", "zip", "zm", "zone", "zuerich", // comment for clang-format to prevent it from placing all strings on separate lines "zw"}); bool is_lower = true; @@ -1416,24 +1419,6 @@ void remove_empty_entities(vector &entities) { }); } -static bool is_allowed_quote_entity(const MessageEntity &entity) { - switch (entity.type) { - case MessageEntity::Type::Bold: - case MessageEntity::Type::Italic: - case MessageEntity::Type::Underline: - case MessageEntity::Type::Strikethrough: - case MessageEntity::Type::Spoiler: - case MessageEntity::Type::CustomEmoji: - return true; - default: - return false; - } -} - -void remove_unallowed_quote_entities(FormattedText &text) { - td::remove_if(text.entities, [](const auto &entity) { return !is_allowed_quote_entity(entity); }); -} - static int32 text_length(Slice text) { return narrow_cast(utf8_utf16_length(text)); } @@ -1469,7 +1454,8 @@ static constexpr int32 get_splittable_entities_mask() { } static constexpr int32 get_blockquote_entities_mask() { - return get_entity_type_mask(MessageEntity::Type::BlockQuote); + return get_entity_type_mask(MessageEntity::Type::BlockQuote) | + get_entity_type_mask(MessageEntity::Type::ExpandableBlockQuote); } static constexpr int32 get_continuous_entities_mask() { @@ -1499,7 +1485,7 @@ static int32 is_splittable_entity(MessageEntity::Type type) { } static int32 is_blockquote_entity(MessageEntity::Type type) { - return type == MessageEntity::Type::BlockQuote; + return (get_entity_type_mask(type) & get_blockquote_entities_mask()) != 0; } static int32 is_continuous_entity(MessageEntity::Type type) { @@ -1571,6 +1557,10 @@ static bool are_entities_valid(const vector &entities) { // continuous and blockquote can't be part of other continuous entity return false; } + if (is_blockquote_entity(entity.type) && (nested_entity_type_mask & get_blockquote_entities_mask()) != 0) { + // blockquote entities can't be nested + return false; + } if ((nested_entity_type_mask & get_splittable_entities_mask()) != 0) { // the previous nested entity may be needed to split for consistency // alternatively, better entity merging needs to be implemented @@ -1624,7 +1614,7 @@ static void remove_entities_intersecting_blockquote(vector &entit size_t left_entities = 0; for (size_t i = 0; i < entities.size(); i++) { while (blockquote_it != blockquote_entities.end() && - (blockquote_it->type != MessageEntity::Type::BlockQuote || + (!is_blockquote_entity(blockquote_it->type) || blockquote_it->offset + blockquote_it->length <= entities[i].offset)) { ++blockquote_it; } @@ -1840,6 +1830,8 @@ Slice get_first_url(const FormattedText &text) { break; case MessageEntity::Type::CustomEmoji: break; + case MessageEntity::Type::ExpandableBlockQuote: + break; default: UNREACHABLE(); } @@ -2183,8 +2175,21 @@ Result> parse_markdown_v2(string &text) { // end of an entity auto type = nested_entities.back().type; if (c == '\n' && type != MessageEntity::Type::BlockQuote) { - return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type - << " entity at byte offset " << nested_entities.back().entity_byte_offset); + if (type != MessageEntity::Type::Spoiler || !(nested_entities.back().entity_byte_offset == i - 2 || + (nested_entities.back().entity_byte_offset == i - 3 && + result_size != 0 && text[result_size - 1] == '\r'))) { + return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type + << " entity at byte offset " << nested_entities.back().entity_byte_offset); + } + nested_entities.pop_back(); + CHECK(!nested_entities.empty()); + type = nested_entities.back().type; + if (type != MessageEntity::Type::BlockQuote) { + CHECK(type != MessageEntity::Type::Spoiler); + return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type + << " entity at byte offset " << nested_entities.back().entity_byte_offset); + } + type = MessageEntity::Type::ExpandableBlockQuote; } auto argument = std::move(nested_entities.back().argument); UserId user_id; @@ -2259,6 +2264,7 @@ Result> parse_markdown_v2(string &text) { break; } case MessageEntity::Type::BlockQuote: + case MessageEntity::Type::ExpandableBlockQuote: CHECK(have_blockquote); have_blockquote = false; text[result_size++] = text[i]; @@ -2287,12 +2293,19 @@ Result> parse_markdown_v2(string &text) { } if (have_blockquote) { CHECK(!nested_entities.empty()); + auto type = MessageEntity::Type::BlockQuote; + if (nested_entities.back().type == MessageEntity::Type::Spoiler && + nested_entities.back().entity_byte_offset == text.size() - 2) { + nested_entities.pop_back(); + CHECK(!nested_entities.empty()); + type = MessageEntity::Type::ExpandableBlockQuote; + } if (nested_entities.back().type == MessageEntity::Type::BlockQuote) { have_blockquote = false; auto entity_offset = nested_entities.back().entity_offset; auto entity_length = utf16_offset - entity_offset; if (entity_length != 0) { - entities.emplace_back(MessageEntity::Type::BlockQuote, entity_offset, entity_length); + entities.emplace_back(type, entity_offset, entity_length); } nested_entities.pop_back(); } @@ -3273,7 +3286,8 @@ Result> parse_html(string &str) { break; } auto attribute_begin_pos = i; - while (!is_space(text[i]) && text[i] != '=') { + while (!is_space(text[i]) && text[i] != '=' && text[i] != '>' && text[i] != '/' && text[i] != '"' && + text[i] != '\'') { i++; } Slice attribute_name(text + attribute_begin_pos, i - attribute_begin_pos); @@ -3285,8 +3299,14 @@ Result> parse_html(string &str) { i++; } if (text[i] != '=') { - return Status::Error(400, PSLICE() << "Expected equal sign in declaration of an attribute of the tag \"" - << tag_name << "\" at byte offset " << begin_pos); + if (text[i] == 0) { + return Status::Error(400, PSLICE() + << "Unclosed start tag \"" << tag_name << "\" at byte offset " << begin_pos); + } + if (tag_name == "blockquote" && attribute_name == Slice("expandable")) { + argument = "1"; + } + continue; } i++; while (text[i] != 0 && is_space(text[i])) { @@ -3343,6 +3363,8 @@ Result> parse_html(string &str) { argument = attribute_value.substr(3); } else if (tag_name == "tg-emoji" && attribute_name == Slice("emoji-id")) { argument = std::move(attribute_value); + } else if (tag_name == "blockquote" && attribute_name == Slice("expandable")) { + argument = "1"; } } @@ -3428,7 +3450,11 @@ Result> parse_html(string &str) { nested_entities.back().argument); } } else if (tag_name == "blockquote") { - entities.emplace_back(MessageEntity::Type::BlockQuote, entity_offset, entity_length); + if (!nested_entities.back().argument.empty()) { + entities.emplace_back(MessageEntity::Type::ExpandableBlockQuote, entity_offset, entity_length); + } else { + entities.emplace_back(MessageEntity::Type::BlockQuote, entity_offset, entity_length); + } } else { UNREACHABLE(); } @@ -3438,7 +3464,7 @@ Result> parse_html(string &str) { } if (!nested_entities.empty()) { return Status::Error( - 400, PSLICE() << "Can't find end tag corresponding to start tag " << nested_entities.back().tag_name); + 400, PSLICE() << "Can't find end tag corresponding to start tag \"" << nested_entities.back().tag_name << '"'); } for (auto &entity : entities) { @@ -3501,7 +3527,7 @@ vector> get_input_secret_message_entiti break; case MessageEntity::Type::BlockQuote: if (layer >= static_cast(SecretChatLayer::NewEntities)) { - // result.push_back(make_tl_object(entity.offset, entity.length)); + // result.push_back(make_tl_object(0, false /*ignored*/, entity.offset, entity.length)); } break; case MessageEntity::Type::Code: @@ -3532,6 +3558,12 @@ vector> get_input_secret_message_entiti entity.custom_emoji_id.get())); } break; + case MessageEntity::Type::ExpandableBlockQuote: + if (layer >= static_cast(SecretChatLayer::NewEntities)) { + // result.push_back(make_tl_object( + // secret_api::messageEntityBlockquote::COLLAPSED_MASK, false /*ignored*/, entity.offset, entity.length)); + } + break; default: UNREACHABLE(); } @@ -3540,7 +3572,7 @@ vector> get_input_secret_message_entiti return result; } -Result> get_message_entities(const ContactsManager *contacts_manager, +Result> get_message_entities(const UserManager *user_manager, vector> &&input_entities, bool allow_all) { vector entities; @@ -3613,8 +3645,8 @@ Result> get_message_entities(const ContactsManager *contac } auto user_id = LinkManager::get_link_user_id(entity->url_); if (user_id.is_valid()) { - if (contacts_manager != nullptr) { - TRY_STATUS(contacts_manager->get_input_user(user_id)); + if (user_manager != nullptr) { + TRY_STATUS(user_manager->get_input_user(user_id)); } entities.emplace_back(offset, length, user_id); break; @@ -3629,8 +3661,8 @@ Result> get_message_entities(const ContactsManager *contac case td_api::textEntityTypeMentionName::ID: { auto entity = static_cast(input_entity->type_.get()); UserId user_id(entity->user_id_); - if (contacts_manager != nullptr) { - TRY_STATUS(contacts_manager->get_input_user(user_id)); + if (user_manager != nullptr) { + TRY_STATUS(user_manager->get_input_user(user_id)); } entities.emplace_back(offset, length, user_id); break; @@ -3655,6 +3687,9 @@ Result> get_message_entities(const ContactsManager *contac entities.emplace_back(MessageEntity::Type::CustomEmoji, offset, length, custom_emoji_id); break; } + case td_api::textEntityTypeExpandableBlockQuote::ID: + entities.emplace_back(MessageEntity::Type::ExpandableBlockQuote, offset, length); + break; default: UNREACHABLE(); } @@ -3666,7 +3701,7 @@ Result> get_message_entities(const ContactsManager *contac return std::move(entities); } -vector get_message_entities(const ContactsManager *contacts_manager, +vector get_message_entities(const UserManager *user_manager, vector> &&server_entities, const char *source) { vector entities; @@ -3742,7 +3777,8 @@ vector get_message_entities(const ContactsManager *contacts_manag } case telegram_api::messageEntityBlockquote::ID: { auto entity = static_cast(server_entity.get()); - entities.emplace_back(MessageEntity::Type::BlockQuote, entity->offset_, entity->length_); + auto type = entity->collapsed_ ? MessageEntity::Type::ExpandableBlockQuote : MessageEntity::Type::BlockQuote; + entities.emplace_back(type, entity->offset_, entity->length_); break; } case telegram_api::messageEntityCode::ID: { @@ -3777,11 +3813,11 @@ vector get_message_entities(const ContactsManager *contacts_manag LOG(ERROR) << "Receive invalid " << user_id << " in MentionName from " << source; continue; } - if (contacts_manager == nullptr) { + if (user_manager == nullptr) { LOG(ERROR) << "Receive unknown " << user_id << " in MentionName from " << source; continue; } - auto r_input_user = contacts_manager->get_input_user(user_id); + auto r_input_user = user_manager->get_input_user(user_id); if (r_input_user.is_error()) { LOG(ERROR) << "Receive wrong " << user_id << ": " << r_input_user.error() << " from " << source; continue; @@ -3942,27 +3978,35 @@ vector get_message_entities(Td *td, vector get_input_text_with_entities( - const ContactsManager *contacts_manager, const FormattedText &text, const char *source) { +telegram_api::object_ptr get_input_text_with_entities(const UserManager *user_manager, + const FormattedText &text, + const char *source) { return telegram_api::make_object( - text.text, get_input_message_entities(contacts_manager, text.entities, source)); + text.text, get_input_message_entities(user_manager, text.entities, source)); } -FormattedText get_formatted_text(const ContactsManager *contacts_manager, - telegram_api::object_ptr text_with_entities, - bool allow_empty, bool skip_new_entities, bool skip_bot_commands, +FormattedText get_formatted_text(const UserManager *user_manager, string &&text, + vector> &&server_entities, bool skip_media_timestamps, bool skip_trim, const char *source) { - CHECK(text_with_entities != nullptr); - auto entities = get_message_entities(contacts_manager, std::move(text_with_entities->entities_), source); - auto status = fix_formatted_text(text_with_entities->text_, entities, allow_empty, skip_new_entities, - skip_bot_commands, skip_media_timestamps, skip_trim); + auto entities = get_message_entities(user_manager, std::move(server_entities), source); + auto status = fix_formatted_text(text, entities, true, true, true, skip_media_timestamps, skip_trim); if (status.is_error()) { - if (!clean_input_string(text_with_entities->text_)) { - text_with_entities->text_.clear(); + LOG(ERROR) << "Receive error " << status << " from " << source << " while parsing \"" << text << "\"(" + << hex_encode(text) << ')'; + if (!clean_input_string(text)) { + text.clear(); } - entities = find_entities(text_with_entities->text_, skip_bot_commands, skip_media_timestamps); + entities = find_entities(text, true, skip_media_timestamps); } - return {std::move(text_with_entities->text_), std::move(entities)}; + return {std::move(text), std::move(entities)}; +} + +FormattedText get_formatted_text(const UserManager *user_manager, + telegram_api::object_ptr text_with_entities, + bool skip_media_timestamps, bool skip_trim, const char *source) { + CHECK(text_with_entities != nullptr); + return get_formatted_text(user_manager, std::move(text_with_entities->text_), + std::move(text_with_entities->entities_), skip_media_timestamps, skip_trim, source); } // like clean_input_string but also fixes entities @@ -4246,7 +4290,7 @@ static vector resplit_entities(vector &&splittable return std::move(entities); } -static void fix_entities(vector &entities) { +void fix_entities(vector &entities) { sort_entities(entities); if (are_entities_valid(entities)) { @@ -4361,7 +4405,7 @@ Status fix_formatted_text(string &text, vector &entities, bool al entities.clear(); return Status::OK(); } - return Status::Error(400, "Message must be non-empty"); + return Status::Error(400, "Text must be non-empty"); } // re-fix entities if needed after removal of some characters @@ -4417,7 +4461,7 @@ Status fix_formatted_text(string &text, vector &entities, bool al LOG_CHECK(check_utf8(text)) << text; if (!allow_empty && is_empty_string(text)) { - return Status::Error(400, "Message must be non-empty"); + return Status::Error(400, "Text must be non-empty"); } constexpr size_t LENGTH_LIMIT = 35000; // server side limit @@ -4442,11 +4486,11 @@ Status fix_formatted_text(string &text, vector &entities, bool al return Status::OK(); } -FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text, +FormattedText get_message_text(const UserManager *user_manager, string message_text, vector> &&server_entities, bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album, const char *source) { - auto entities = get_message_entities(contacts_manager, std::move(server_entities), source); + auto entities = get_message_entities(user_manager, std::move(server_entities), source); auto debug_message_text = message_text; auto debug_entities = entities; auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, skip_media_timestamps, false); @@ -4484,38 +4528,6 @@ void truncate_formatted_text(FormattedText &text, size_t length) { remove_empty_entities(text.entities); } -td_api::object_ptr extract_input_caption( - tl_object_ptr &input_message_content) { - switch (input_message_content->get_id()) { - case td_api::inputMessageAnimation::ID: { - auto input_animation = static_cast(input_message_content.get()); - return std::move(input_animation->caption_); - } - case td_api::inputMessageAudio::ID: { - auto input_audio = static_cast(input_message_content.get()); - return std::move(input_audio->caption_); - } - case td_api::inputMessageDocument::ID: { - auto input_document = static_cast(input_message_content.get()); - return std::move(input_document->caption_); - } - case td_api::inputMessagePhoto::ID: { - auto input_photo = static_cast(input_message_content.get()); - return std::move(input_photo->caption_); - } - case td_api::inputMessageVideo::ID: { - auto input_video = static_cast(input_message_content.get()); - return std::move(input_video->caption_); - } - case td_api::inputMessageVoiceNote::ID: { - auto input_voice_note = static_cast(input_message_content.get()); - return std::move(input_voice_note->caption_); - } - default: - return nullptr; - } -} - Result get_formatted_text(const Td *td, DialogId dialog_id, td_api::object_ptr &&text, bool is_bot, bool allow_empty, bool skip_media_timestamps, bool skip_trim, @@ -4528,8 +4540,8 @@ Result get_formatted_text(const Td *td, DialogId dialog_id, return Status::Error(400, "Text must be non-empty"); } - TRY_RESULT(entities, get_message_entities(td->contacts_manager_.get(), std::move(text->entities_))); - auto need_skip_bot_commands = need_always_skip_bot_commands(td->contacts_manager_.get(), dialog_id, is_bot); + TRY_RESULT(entities, get_message_entities(td->user_manager_.get(), std::move(text->entities_))); + auto need_skip_bot_commands = need_always_skip_bot_commands(td->user_manager_.get(), dialog_id, is_bot); bool parse_markdown = td->option_manager_->get_option_boolean("always_parse_markdown"); bool skip_new_entities = is_bot && td->option_manager_->get_option_integer("session_count") > 1; TRY_STATUS(fix_formatted_text(text->text_, entities, allow_empty, skip_new_entities || parse_markdown, @@ -4581,7 +4593,7 @@ bool has_bot_commands(const FormattedText *text) { return false; } -bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot) { +bool need_always_skip_bot_commands(const UserManager *user_manager, DialogId dialog_id, bool is_bot) { if (!dialog_id.is_valid()) { return true; } @@ -4592,11 +4604,11 @@ bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, Dial switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); - return user_id == ContactsManager::get_replies_bot_user_id() || !contacts_manager->is_user_bot(user_id); + return user_id == UserManager::get_replies_bot_user_id() || !user_manager->is_user_bot(user_id); } case DialogType::SecretChat: { - auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - return !user_id.is_valid() || !contacts_manager->is_user_bot(user_id); + auto user_id = user_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + return !user_id.is_valid() || !user_manager->is_user_bot(user_id); } case DialogType::Chat: case DialogType::Channel: @@ -4608,7 +4620,7 @@ bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, Dial } } -vector> get_input_message_entities(const ContactsManager *contacts_manager, +vector> get_input_message_entities(const UserManager *user_manager, const vector &entities, const char *source) { vector> result; @@ -4634,7 +4646,8 @@ vector> get_input_message_entities(co user_entity_count++; switch (entity.type) { case MessageEntity::Type::BlockQuote: - result.push_back(make_tl_object(entity.offset, entity.length)); + result.push_back( + make_tl_object(0, false /*ignored*/, entity.offset, entity.length)); break; case MessageEntity::Type::Code: result.push_back(make_tl_object(entity.offset, entity.length)); @@ -4650,11 +4663,16 @@ vector> get_input_message_entities(co make_tl_object(entity.offset, entity.length, entity.argument)); break; case MessageEntity::Type::MentionName: { - auto input_user = contacts_manager->get_input_user_force(entity.user_id); + CHECK(user_manager != nullptr); + auto input_user = user_manager->get_input_user_force(entity.user_id); result.push_back(make_tl_object(entity.offset, entity.length, std::move(input_user))); break; } + case MessageEntity::Type::ExpandableBlockQuote: + result.push_back(make_tl_object( + telegram_api::messageEntityBlockquote::COLLAPSED_MASK, false /*ignored*/, entity.offset, entity.length)); + break; default: UNREACHABLE(); } @@ -4690,11 +4708,11 @@ vector> get_input_message_entities(co return result; } -vector> get_input_message_entities(const ContactsManager *contacts_manager, +vector> get_input_message_entities(const UserManager *user_manager, const FormattedText *text, const char *source) { if (text != nullptr && !text->entities.empty()) { - return get_input_message_entities(contacts_manager, text->entities, source); + return get_input_message_entities(user_manager, text->entities, source); } return {}; } @@ -4712,11 +4730,12 @@ void remove_unallowed_entities(const Td *td, FormattedText &text, DialogId dialo } if (dialog_id.get_type() == DialogType::SecretChat) { - auto layer = td->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto layer = td->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); td::remove_if(text.entities, [layer](const MessageEntity &entity) { if (layer < static_cast(SecretChatLayer::NewEntities) && (entity.type == MessageEntity::Type::Underline || entity.type == MessageEntity::Type::Strikethrough || - entity.type == MessageEntity::Type::BlockQuote)) { + entity.type == MessageEntity::Type::BlockQuote || + entity.type == MessageEntity::Type::ExpandableBlockQuote)) { return true; } if (layer < static_cast(SecretChatLayer::SpoilerAndCustomEmojiEntities) && @@ -4731,69 +4750,9 @@ void remove_unallowed_entities(const Td *td, FormattedText &text, DialogId dialo remove_intersecting_entities(text.entities); } } - if (dialog_id != td->dialog_manager_->get_my_dialog_id() && - !td->contacts_manager_->can_use_premium_custom_emoji(dialog_id)) { - remove_premium_custom_emoji_entities(td, text.entities, false); - } -} - -int32 search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position) { - auto process_quote_entities = [](FormattedText &text, int32 length) { - remove_unallowed_quote_entities(text); - td::remove_if(text.entities, [length](const MessageEntity &entity) { - if (entity.offset < 0 || entity.offset >= length) { - return true; - } - if (entity.length <= 0 || entity.length > length - entity.offset) { - return true; - } - return false; - }); - remove_empty_entities(text.entities); - fix_entities(text.entities); - remove_empty_entities(text.entities); - }; - int32 length = text_length(text.text); - int32 quote_length = text_length(quote.text); - if (quote_length == 0 || quote_length > length) { - return -1; - } - process_quote_entities(text, length); - process_quote_entities(quote, quote_length); - - quote_position = clamp(quote_position, 0, length - 1); - vector byte_positions; - byte_positions.reserve(length); - for (size_t i = 0; i < text.text.size(); i++) { - auto c = static_cast(text.text[i]); - if (is_utf8_character_first_code_unit(c)) { - byte_positions.push_back(i); - if (c >= 0xf0) { // >= 4 bytes in symbol => surrogate pair - byte_positions.push_back(string::npos); - } - } - } - CHECK(byte_positions.size() == static_cast(length)); - auto check_position = [&text, "e, &byte_positions, length, quote_length](int32 position) { - if (position < 0 || position > length - quote_length) { - return false; - } - auto byte_position = byte_positions[position]; - if (byte_position == string::npos || text.text[byte_position] != quote.text[0] || - Slice(text.text).substr(byte_position, quote.text.size()) != quote.text) { - return false; - } - return true; - }; - for (int32 i = 0; quote_position - i >= 0 || quote_position + i + 1 <= length - quote_length; i++) { - if (check_position(quote_position - i)) { - return quote_position - i; - } - if (check_position(quote_position + i + 1)) { - return quote_position + i + 1; - } + if (!td->dialog_manager_->can_use_premium_custom_emoji_in_dialog(dialog_id)) { + remove_premium_custom_emoji_entities(td, text.entities, true); } - return -1; } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageEntity.h b/lib/tgchat/ext/td/td/telegram/MessageEntity.h index c5ab5b2d..e3ca0281 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageEntity.h +++ b/lib/tgchat/ext/td/td/telegram/MessageEntity.h @@ -24,10 +24,10 @@ namespace td { -class ContactsManager; class Dependencies; class MultiPromiseActor; class Td; +class UserManager; class MessageEntity { public: @@ -53,6 +53,7 @@ class MessageEntity { MediaTimestamp, Spoiler, CustomEmoji, + ExpandableBlockQuote, Size }; Type type = Type::Size; @@ -155,7 +156,7 @@ inline bool operator!=(const FormattedText &lhs, const FormattedText &rhs) { const FlatHashSet &get_valid_short_usernames(); -Result> get_message_entities(const ContactsManager *contacts_manager, +Result> get_message_entities(const UserManager *user_manager, vector> &&input_entities, bool allow_all = false); @@ -183,10 +184,6 @@ vector> find_media_timestamps(Slice str); // slice + me void remove_empty_entities(vector &entities); -void remove_unallowed_quote_entities(FormattedText &text); - -int32 search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position); - Slice get_first_url(const FormattedText &text); bool is_visible_url(const FormattedText &text, const string &url); @@ -201,47 +198,46 @@ FormattedText get_markdown_v3(FormattedText text); Result> parse_html(string &str); -vector> get_input_message_entities(const ContactsManager *contacts_manager, +vector> get_input_message_entities(const UserManager *user_manager, const vector &entities, const char *source); -vector> get_input_message_entities(const ContactsManager *contacts_manager, +vector> get_input_message_entities(const UserManager *user_manager, const FormattedText *text, const char *source); vector> get_input_secret_message_entities( const vector &entities, int32 layer); -vector get_message_entities(const ContactsManager *contacts_manager, - vector> &&server_entities, - const char *source); - vector get_message_entities(Td *td, vector> &&secret_entities, bool is_premium, MultiPromiseActor &load_data_multipromise); -telegram_api::object_ptr get_input_text_with_entities( - const ContactsManager *contacts_manager, const FormattedText &text, const char *source); +telegram_api::object_ptr get_input_text_with_entities(const UserManager *user_manager, + const FormattedText &text, + const char *source); -FormattedText get_formatted_text(const ContactsManager *contacts_manager, +FormattedText get_formatted_text(const UserManager *user_manager, string &&text, + vector> &&server_entities, + bool skip_media_timestamps, bool skip_trim, const char *source); + +FormattedText get_formatted_text(const UserManager *user_manager, telegram_api::object_ptr text_with_entities, - bool allow_empty, bool skip_new_entities, bool skip_bot_commands, bool skip_media_timestamps, bool skip_trim, const char *source); +void fix_entities(vector &entities); + // like clean_input_string but also validates entities Status fix_formatted_text(string &text, vector &entities, bool allow_empty, bool skip_new_entities, bool skip_bot_commands, bool skip_media_timestamps, bool skip_trim, int32 *ltrim_count = nullptr) TD_WARN_UNUSED_RESULT; -FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text, +FormattedText get_message_text(const UserManager *user_manager, string message_text, vector> &&server_entities, bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album, const char *source); void truncate_formatted_text(FormattedText &text, size_t length); -td_api::object_ptr extract_input_caption( - tl_object_ptr &input_message_content); - Result get_formatted_text(const Td *td, DialogId dialog_id, td_api::object_ptr &&text, bool is_bot, bool allow_empty, bool skip_media_timestamps, bool skip_trim, @@ -253,6 +249,6 @@ bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, bool has_bot_commands(const FormattedText *text); -bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot); +bool need_always_skip_bot_commands(const UserManager *user_manager, DialogId dialog_id, bool is_bot); } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageForwardInfo.cpp b/lib/tgchat/ext/td/td/telegram/MessageForwardInfo.cpp index 9c36cd1f..4e81a45c 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageForwardInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageForwardInfo.cpp @@ -6,13 +6,13 @@ // #include "td/telegram/MessageForwardInfo.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -33,7 +33,7 @@ void LastForwardedMessageInfo::validate() { void LastForwardedMessageInfo::hide_sender_if_needed(Td *td) { if (sender_name_.empty() && sender_dialog_id_.get_type() == DialogType::User) { - auto private_forward_name = td->contacts_manager_->get_user_private_forward_name(sender_dialog_id_.get_user_id()); + auto private_forward_name = td->user_manager_->get_user_private_forward_name(sender_dialog_id_.get_user_id()); if (!private_forward_name.empty()) { dialog_id_ = DialogId(); message_id_ = MessageId(); diff --git a/lib/tgchat/ext/td/td/telegram/MessageImportManager.cpp b/lib/tgchat/ext/td/td/telegram/MessageImportManager.cpp index 5ba528ef..c38d96d1 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageImportManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageImportManager.cpp @@ -7,7 +7,7 @@ #include "td/telegram/MessageImportManager.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" @@ -16,6 +16,7 @@ #include "td/telegram/MessageContent.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" @@ -286,16 +287,11 @@ void MessageImportManager::get_message_file_type(const string &message_file_head } Status MessageImportManager::can_import_messages(DialogId dialog_id) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "can_import_messages")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return Status::Error(400, "Have no write access to the chat"); - } + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, "can_import_messages")); switch (dialog_id.get_type()) { case DialogType::User: - if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) { + if (!td_->user_manager_->is_user_contact(dialog_id.get_user_id(), true)) { return Status::Error(400, "User must be a mutual contact"); } break; @@ -305,12 +301,11 @@ Status MessageImportManager::can_import_messages(DialogId dialog_id) { if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return Status::Error(400, "Can't import messages to channels"); } - if (!td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_change_info_and_settings()) { + if (!td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_change_info_and_settings()) { return Status::Error(400, "Not enough rights to import messages"); } break; case DialogType::SecretChat: - return Status::Error(400, "Can't import messages to secret chats"); case DialogType::None: default: UNREACHABLE(); @@ -385,9 +380,8 @@ void MessageImportManager::on_upload_imported_messages(FileId file_id, being_uploaded_imported_messages_.erase(it); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Have no write access to the chat")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Write)); FileView file_view = td_->file_manager_->get_file_view(file_id); CHECK(!file_view.is_encrypted()); @@ -437,9 +431,8 @@ void MessageImportManager::on_upload_imported_messages_error(FileId file_id, Sta void MessageImportManager::start_import_messages(DialogId dialog_id, int64 import_id, vector &&attached_file_ids, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Have no write access to the chat")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Write)); auto pending_message_import = make_unique(); pending_message_import->dialog_id = dialog_id; @@ -572,9 +565,8 @@ void MessageImportManager::on_imported_message_attachments_uploaded(int64 random auto promise = std::move(pending_message_import->promise); auto dialog_id = pending_message_import->dialog_id; - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Have no write access to the chat")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Write)); td_->create_handler(std::move(promise))->send(dialog_id, pending_message_import->import_id); } diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp index a3173024..809fb191 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.cpp @@ -11,7 +11,6 @@ #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/InputDialogId.h" -#include "td/telegram/misc.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StoryId.h" #include "td/telegram/Td.h" @@ -48,7 +47,7 @@ MessageInputReplyTo::MessageInputReplyTo(Td *td, DialogId dialog_id; if (reply_to->reply_to_peer_id_ != nullptr) { dialog_id = InputDialogId(reply_to->reply_to_peer_id_).get_dialog_id(); - if (!dialog_id.is_valid() || !td->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!dialog_id.is_valid() || !td->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } td->dialog_manager_->force_create_dialog(dialog_id, "inputReplyToMessage"); @@ -56,20 +55,7 @@ MessageInputReplyTo::MessageInputReplyTo(Td *td, message_id_ = message_id; dialog_id_ = dialog_id; - if (!reply_to->quote_text_.empty()) { - auto entities = get_message_entities(td->contacts_manager_.get(), std::move(reply_to->quote_entities_), - "inputReplyToMessage"); - auto status = fix_formatted_text(reply_to->quote_text_, entities, true, true, true, true, false); - if (status.is_error()) { - if (!clean_input_string(reply_to->quote_text_)) { - reply_to->quote_text_.clear(); - } - entities.clear(); - } - quote_ = FormattedText{std::move(reply_to->quote_text_), std::move(entities)}; - remove_unallowed_quote_entities(quote_); - quote_position_ = max(0, reply_to->quote_offset_); - } + quote_ = MessageQuote(td, reply_to); break; } default: @@ -79,7 +65,7 @@ MessageInputReplyTo::MessageInputReplyTo(Td *td, void MessageInputReplyTo::add_dependencies(Dependencies &dependencies) const { dependencies.add_dialog_and_dependencies(dialog_id_); - add_formatted_text_dependencies(dependencies, "e_); // just in case + quote_.add_dependencies(dependencies); dependencies.add_dialog_and_dependencies(story_full_id_.get_dialog_id()); // just in case } @@ -117,19 +103,11 @@ telegram_api::object_ptr MessageInputReplyTo::get_in } flags |= telegram_api::inputReplyToMessage::REPLY_TO_PEER_ID_MASK; } - if (!quote_.text.empty()) { - flags |= telegram_api::inputReplyToMessage::QUOTE_TEXT_MASK; - } - auto quote_entities = get_input_message_entities(td->contacts_manager_.get(), quote_.entities, "get_input_reply_to"); - if (!quote_entities.empty()) { - flags |= telegram_api::inputReplyToMessage::QUOTE_ENTITIES_MASK; - } - if (quote_position_ != 0) { - flags |= telegram_api::inputReplyToMessage::QUOTE_OFFSET_MASK; - } - return telegram_api::make_object( + auto result = telegram_api::make_object( flags, reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(), - std::move(input_peer), quote_.text, std::move(quote_entities), quote_position_); + std::move(input_peer), string(), Auto(), 0); + quote_.update_input_reply_to_message(td, result.get()); + return std::move(result); } // only for draft messages @@ -142,13 +120,9 @@ td_api::object_ptr MessageInputReplyTo::get_input_m if (!message_id_.is_valid() && !message_id_.is_valid_scheduled()) { return nullptr; } - td_api::object_ptr quote; - if (!quote_.text.empty()) { - quote = td_api::make_object(get_formatted_text_object(quote_, true, -1), quote_position_); - } return td_api::make_object( td->dialog_manager_->get_chat_id_object(dialog_id_, "inputMessageReplyToMessage"), message_id_.get(), - std::move(quote)); + quote_.get_input_text_quote_object()); } MessageId MessageInputReplyTo::get_same_chat_reply_to_message_id() const { @@ -165,8 +139,7 @@ MessageFullId MessageInputReplyTo::get_reply_message_full_id(DialogId owner_dial bool operator==(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs) { return lhs.message_id_ == rhs.message_id_ && lhs.dialog_id_ == rhs.dialog_id_ && - lhs.story_full_id_ == rhs.story_full_id_ && lhs.quote_ == rhs.quote_ && - lhs.quote_position_ == rhs.quote_position_; + lhs.story_full_id_ == rhs.story_full_id_ && lhs.quote_ == rhs.quote_; } bool operator!=(const MessageInputReplyTo &lhs, const MessageInputReplyTo &rhs) { @@ -179,13 +152,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const MessageInputReply if (input_reply_to.dialog_id_ != DialogId()) { string_builder << " in " << input_reply_to.dialog_id_; } - if (!input_reply_to.quote_.text.empty()) { - string_builder << " with " << input_reply_to.quote_.text.size() << " quoted bytes"; - if (input_reply_to.quote_position_ != 0) { - string_builder << " at position " << input_reply_to.quote_position_; - } - } - return string_builder; + return string_builder << input_reply_to.quote_; } if (input_reply_to.story_full_id_.is_valid()) { return string_builder << input_reply_to.story_full_id_; diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h index 78044811..c0dfaba3 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.h @@ -7,9 +7,9 @@ #pragma once #include "td/telegram/DialogId.h" -#include "td/telegram/MessageEntity.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" +#include "td/telegram/MessageQuote.h" #include "td/telegram/StoryFullId.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -26,8 +26,7 @@ class Td; class MessageInputReplyTo { MessageId message_id_; DialogId dialog_id_; - FormattedText quote_; - int32 quote_position_ = 0; + MessageQuote quote_; // or StoryFullId story_full_id_; @@ -45,12 +44,8 @@ class MessageInputReplyTo { MessageInputReplyTo &operator=(MessageInputReplyTo &&) = default; ~MessageInputReplyTo(); - MessageInputReplyTo(MessageId message_id, DialogId dialog_id, FormattedText &"e, int32 quote_position) - : message_id_(message_id) - , dialog_id_(dialog_id) - , quote_(std::move(quote)) - , quote_position_(max(0, quote_position)) { - remove_unallowed_quote_entities(quote_); + MessageInputReplyTo(MessageId message_id, DialogId dialog_id, MessageQuote quote) + : message_id_(message_id), dialog_id_(dialog_id), quote_(std::move(quote)) { } explicit MessageInputReplyTo(StoryFullId story_full_id) : story_full_id_(story_full_id) { @@ -67,18 +62,24 @@ class MessageInputReplyTo { } bool has_quote() const { - return !quote_.text.empty(); + return !quote_.is_empty(); } - void set_quote(FormattedText &"e, int32 quote_position) { + void set_quote(MessageQuote quote) { quote_ = std::move(quote); - quote_position_ = max(0, quote_position); } StoryFullId get_story_full_id() const { return story_full_id_; } + MessageInputReplyTo clone() const { + if (story_full_id_.is_valid()) { + return MessageInputReplyTo(story_full_id_); + } + return MessageInputReplyTo(message_id_, dialog_id_, quote_.clone()); + } + void add_dependencies(Dependencies &dependencies) const; telegram_api::object_ptr get_input_reply_to(Td *td, diff --git a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp index d0567a2a..54420463 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp +++ b/lib/tgchat/ext/td/td/telegram/MessageInputReplyTo.hpp @@ -9,6 +9,8 @@ #include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/MessageOrigin.hpp" +#include "td/telegram/MessageQuote.h" +#include "td/telegram/MessageQuote.hpp" #include "td/utils/tl_helpers.h" @@ -18,15 +20,15 @@ template void MessageInputReplyTo::store(StorerT &storer) const { bool has_message_id = message_id_.is_valid(); bool has_story_full_id = story_full_id_.is_valid(); - bool has_quote = !quote_.text.empty(); bool has_dialog_id = dialog_id_.is_valid(); - bool has_quote_position = quote_position_ != 0; + bool has_quote = !quote_.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_message_id); STORE_FLAG(has_story_full_id); - STORE_FLAG(has_quote); + STORE_FLAG(false); STORE_FLAG(has_dialog_id); - STORE_FLAG(has_quote_position); + STORE_FLAG(false); + STORE_FLAG(has_quote); END_STORE_FLAGS(); if (has_message_id) { td::store(message_id_, storer); @@ -34,14 +36,11 @@ void MessageInputReplyTo::store(StorerT &storer) const { if (has_story_full_id) { td::store(story_full_id_, storer); } - if (has_quote) { - td::store(quote_, storer); - } if (has_dialog_id) { td::store(dialog_id_, storer); } - if (has_quote_position) { - td::store(quote_position_, storer); + if (has_quote) { + td::store(quote_, storer); } } @@ -49,15 +48,17 @@ template void MessageInputReplyTo::parse(ParserT &parser) { bool has_message_id; bool has_story_full_id; - bool has_quote; + bool has_quote_legacy; bool has_dialog_id; - bool has_quote_position; + bool has_quote_position_legacy; + bool has_quote; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_message_id); PARSE_FLAG(has_story_full_id); - PARSE_FLAG(has_quote); + PARSE_FLAG(has_quote_legacy); PARSE_FLAG(has_dialog_id); - PARSE_FLAG(has_quote_position); + PARSE_FLAG(has_quote_position_legacy); + PARSE_FLAG(has_quote); END_PARSE_FLAGS(); if (has_message_id) { td::parse(message_id_, parser); @@ -65,15 +66,21 @@ void MessageInputReplyTo::parse(ParserT &parser) { if (has_story_full_id) { td::parse(story_full_id_, parser); } - if (has_quote) { - td::parse(quote_, parser); - remove_unallowed_quote_entities(quote_); + FormattedText quote_legacy; + if (has_quote_legacy) { + td::parse(quote_legacy, parser); } if (has_dialog_id) { td::parse(dialog_id_, parser); } - if (has_quote_position) { - td::parse(quote_position_, parser); + int32 quote_position_legacy = 0; + if (has_quote_position_legacy) { + td::parse(quote_position_legacy, parser); + } + if (has_quote) { + td::parse(quote_, parser); + } else if (has_quote_legacy) { + quote_ = MessageQuote(std::move(quote_legacy), quote_position_legacy); } } diff --git a/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp b/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp index 9e747063..41843023 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageOrigin.cpp @@ -7,12 +7,13 @@ #include "td/telegram/MessageOrigin.h" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -66,8 +67,8 @@ Result MessageOrigin::get_message_origin( return Status::Error("Forward from a non-channel"); } else { auto channel_id = sender_dialog_id.get_channel_id(); - if (!td->contacts_manager_->have_channel(channel_id)) { - LOG(ERROR) << "Receive forward from " << (td->contacts_manager_->have_min_channel(channel_id) ? "min" : "unknown") + if (!td->chat_manager_->have_channel(channel_id)) { + LOG(ERROR) << "Receive forward from " << (td->chat_manager_->have_min_channel(channel_id) ? "min" : "unknown") << ' ' << channel_id; } td->dialog_manager_->force_create_dialog(sender_dialog_id, "get_message_origin", true); @@ -94,7 +95,7 @@ td_api::object_ptr MessageOrigin::get_message_origin_obje sender_name_.empty() ? author_signature_ : sender_name_); } return td_api::make_object( - td->contacts_manager_->get_user_id_object(sender_user_id_, "messageOriginUser")); + td->user_manager_->get_user_id_object(sender_user_id_, "messageOriginUser")); } bool MessageOrigin::is_sender_hidden() const { @@ -121,7 +122,7 @@ DialogId MessageOrigin::get_sender() const { void MessageOrigin::hide_sender_if_needed(Td *td) { if (!is_sender_hidden() && !message_id_.is_valid() && !sender_dialog_id_.is_valid()) { - auto private_forward_name = td->contacts_manager_->get_user_private_forward_name(sender_user_id_); + auto private_forward_name = td->user_manager_->get_user_private_forward_name(sender_user_id_); if (!private_forward_name.empty()) { sender_user_id_ = UserId(); sender_name_ = std::move(private_forward_name); diff --git a/lib/tgchat/ext/td/td/telegram/MessageQuote.cpp b/lib/tgchat/ext/td/td/telegram/MessageQuote.cpp new file mode 100644 index 00000000..671ebd80 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageQuote.cpp @@ -0,0 +1,235 @@ +// +// 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/MessageQuote.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/algorithm.h" +#include "td/utils/misc.h" +#include "td/utils/Slice.h" +#include "td/utils/utf8.h" + +namespace td { + +MessageQuote::~MessageQuote() = default; + +MessageQuote::MessageQuote(Td *td, + telegram_api::object_ptr &input_reply_to_message) { + CHECK(input_reply_to_message != nullptr); + if (input_reply_to_message->quote_text_.empty()) { + return; + } + text_ = get_formatted_text(td->user_manager_.get(), std::move(input_reply_to_message->quote_text_), + std::move(input_reply_to_message->quote_entities_), true, false, "inputReplyToMessage"); + remove_unallowed_quote_entities(text_); + position_ = max(0, input_reply_to_message->quote_offset_); +} + +MessageQuote::MessageQuote(Td *td, telegram_api::object_ptr &reply_header) { + CHECK(reply_header != nullptr); + if (reply_header->quote_text_.empty()) { + return; + } + text_ = get_formatted_text(td->user_manager_.get(), std::move(reply_header->quote_text_), + std::move(reply_header->quote_entities_), true, false, "messageReplyHeader"); + remove_unallowed_quote_entities(text_); + position_ = max(0, reply_header->quote_offset_); + is_manual_ = reply_header->quote_; +} + +MessageQuote::MessageQuote(Td *td, td_api::object_ptr quote) { + if (quote == nullptr) { + return; + } + int32 ltrim_count = 0; + auto r_text = get_formatted_text(td, td->dialog_manager_->get_my_dialog_id(), std::move(quote->text_), + td->auth_manager_->is_bot(), true, true, false, <rim_count); + if (!r_text.is_ok() || r_text.ok().text.empty()) { + return; + } + text_ = r_text.move_as_ok(); + position_ = quote->position_; + if (0 <= position_ && position_ <= 1000000) { // some unreasonably big bound + position_ += ltrim_count; + } else { + position_ = 0; + } +} + +MessageQuote MessageQuote::clone(bool ignore_is_manual) const { + return {FormattedText(text_), position_, ignore_is_manual ? true : is_manual_}; +} + +MessageQuote MessageQuote::create_automatic_quote(Td *td, FormattedText &&text) { + truncate_formatted_text( + text, static_cast(td->option_manager_->get_option_integer("message_reply_quote_length_max"))); + return MessageQuote(std::move(text), 0, false); +} + +int MessageQuote::need_quote_changed_warning(const MessageQuote &old_quote, const MessageQuote &new_quote) { + if (old_quote.position_ != new_quote.position_ && + max(old_quote.position_, new_quote.position_) < + static_cast(min(old_quote.text_.text.size(), new_quote.text_.text.size()))) { + // quote position can't change + return 1; + } + if (old_quote.is_manual_ != new_quote.is_manual_) { + // quote manual property can't change + return 1; + } + if (old_quote.text_ != new_quote.text_) { + if (old_quote.is_manual_) { + return 1; + } + // automatic quote can change if the original message was edited + return -1; + } + return 0; +} + +void MessageQuote::add_dependencies(Dependencies &dependencies) const { + add_formatted_text_dependencies(dependencies, &text_); // just in case +} + +void MessageQuote::update_input_reply_to_message(Td *td, + telegram_api::inputReplyToMessage *input_reply_to_message) const { + CHECK(input_reply_to_message != nullptr); + if (is_empty()) { + return; + } + CHECK(is_manual_); + input_reply_to_message->flags_ |= telegram_api::inputReplyToMessage::QUOTE_TEXT_MASK; + input_reply_to_message->quote_text_ = text_.text; + input_reply_to_message->quote_entities_ = + get_input_message_entities(td->user_manager_.get(), text_.entities, "update_input_reply_to_message"); + if (!input_reply_to_message->quote_entities_.empty()) { + input_reply_to_message->flags_ |= telegram_api::inputReplyToMessage::QUOTE_ENTITIES_MASK; + } + if (position_ != 0) { + input_reply_to_message->flags_ |= telegram_api::inputReplyToMessage::QUOTE_OFFSET_MASK; + input_reply_to_message->quote_offset_ = position_; + } +} + +// only for draft messages +td_api::object_ptr MessageQuote::get_input_text_quote_object() const { + if (is_empty()) { + return nullptr; + } + CHECK(is_manual_); + return td_api::make_object(get_formatted_text_object(text_, true, -1), position_); +} + +td_api::object_ptr MessageQuote::get_text_quote_object() const { + if (is_empty()) { + return nullptr; + } + return td_api::make_object(get_formatted_text_object(text_, true, -1), position_, is_manual_); +} + +bool operator==(const MessageQuote &lhs, const MessageQuote &rhs) { + return lhs.text_ == rhs.text_ && lhs.position_ == rhs.position_ && lhs.is_manual_ == rhs.is_manual_; +} + +bool operator!=(const MessageQuote &lhs, const MessageQuote &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageQuote "e) { + if (!quote.is_empty()) { + string_builder << " with " << quote.text_.text.size() << (!quote.is_manual_ ? " automatically" : "") + << " quoted bytes"; + if (quote.position_ != 0) { + string_builder << " at position " << quote.position_; + } + } + return string_builder; +} + +void MessageQuote::remove_unallowed_quote_entities(FormattedText &text) { + auto is_allowed_quote_entity = [](const MessageEntity &entity) { + switch (entity.type) { + case MessageEntity::Type::Bold: + case MessageEntity::Type::Italic: + case MessageEntity::Type::Underline: + case MessageEntity::Type::Strikethrough: + case MessageEntity::Type::Spoiler: + case MessageEntity::Type::CustomEmoji: + return true; + default: + return false; + } + }; + + td::remove_if(text.entities, [&](const auto &entity) { return !is_allowed_quote_entity(entity); }); +} + +int32 MessageQuote::search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position) { + auto process_quote_entities = [](FormattedText &text, int32 length) { + remove_unallowed_quote_entities(text); + td::remove_if(text.entities, [length](const MessageEntity &entity) { + if (entity.offset < 0 || entity.offset >= length) { + return true; + } + if (entity.length <= 0 || entity.length > length - entity.offset) { + return true; + } + return false; + }); + remove_empty_entities(text.entities); + fix_entities(text.entities); + remove_empty_entities(text.entities); + }; + auto length = narrow_cast(utf8_utf16_length(text.text)); + auto quote_length = narrow_cast(utf8_utf16_length(quote.text)); + if (quote_length == 0 || quote_length > length) { + return -1; + } + process_quote_entities(text, length); + process_quote_entities(quote, quote_length); + + quote_position = clamp(quote_position, 0, length - 1); + vector byte_positions; + byte_positions.reserve(length); + for (size_t i = 0; i < text.text.size(); i++) { + auto c = static_cast(text.text[i]); + if (is_utf8_character_first_code_unit(c)) { + byte_positions.push_back(i); + if (c >= 0xf0) { // >= 4 bytes in symbol => surrogate pair + byte_positions.push_back(string::npos); + } + } + } + CHECK(byte_positions.size() == static_cast(length)); + auto check_position = [&text, "e, &byte_positions, length, quote_length](int32 position) { + if (position < 0 || position > length - quote_length) { + return false; + } + auto byte_position = byte_positions[position]; + if (byte_position == string::npos || text.text[byte_position] != quote.text[0] || + Slice(text.text).substr(byte_position, quote.text.size()) != quote.text) { + return false; + } + return true; + }; + for (int32 i = 0; quote_position - i >= 0 || quote_position + i + 1 <= length - quote_length; i++) { + if (check_position(quote_position - i)) { + return quote_position - i; + } + if (check_position(quote_position + i + 1)) { + return quote_position + i + 1; + } + } + return -1; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageQuote.h b/lib/tgchat/ext/td/td/telegram/MessageQuote.h new file mode 100644 index 00000000..8925aa0b --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageQuote.h @@ -0,0 +1,85 @@ +// +// 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/MessageEntity.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 MessageQuote { + FormattedText text_; + int32 position_ = 0; + bool is_manual_ = true; + + friend bool operator==(const MessageQuote &lhs, const MessageQuote &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageQuote "e); + + static void remove_unallowed_quote_entities(FormattedText &text); + + public: + MessageQuote() = default; + MessageQuote(const MessageQuote &) = delete; + MessageQuote &operator=(const MessageQuote &) = delete; + MessageQuote(MessageQuote &&) = default; + MessageQuote &operator=(MessageQuote &&) = default; + ~MessageQuote(); + + MessageQuote(FormattedText &&text, int32 position, bool is_manual = true) + : text_(std::move(text)), position_(max(0, position)), is_manual_(is_manual) { + remove_unallowed_quote_entities(text_); + } + + MessageQuote(Td *td, telegram_api::object_ptr &input_reply_to_message); + + MessageQuote(Td *td, telegram_api::object_ptr &reply_header); + + MessageQuote(Td *td, td_api::object_ptr quote); + + static MessageQuote create_automatic_quote(Td *td, FormattedText &&text); + + static int need_quote_changed_warning(const MessageQuote &old_quote, const MessageQuote &new_quote); + + static int32 search_quote(FormattedText &&text, FormattedText &"e, int32 quote_position); + + bool is_empty() const { + return text_.text.empty(); + } + + MessageQuote clone(bool ignore_is_manual = false) const; + + void add_dependencies(Dependencies &dependencies) const; + + void update_input_reply_to_message(Td *td, telegram_api::inputReplyToMessage *input_reply_to_message) const; + + td_api::object_ptr get_input_text_quote_object() const; + + td_api::object_ptr get_text_quote_object() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const MessageQuote &lhs, const MessageQuote &rhs); + +bool operator!=(const MessageQuote &lhs, const MessageQuote &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const MessageQuote "e); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageQuote.hpp b/lib/tgchat/ext/td/td/telegram/MessageQuote.hpp new file mode 100644 index 00000000..ea29ad5b --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageQuote.hpp @@ -0,0 +1,50 @@ +// +// 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/MessageQuote.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void MessageQuote::store(StorerT &storer) const { + bool has_text = !text_.text.empty(); + bool has_position = position_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_text); + STORE_FLAG(has_position); + STORE_FLAG(is_manual_); + END_STORE_FLAGS(); + if (has_text) { + td::store(text_, storer); + } + if (has_position) { + td::store(position_, storer); + } +} + +template +void MessageQuote::parse(ParserT &parser) { + bool has_text; + bool has_position; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_text); + PARSE_FLAG(has_position); + PARSE_FLAG(is_manual_); + END_PARSE_FLAGS(); + if (has_text) { + td::parse(text_, parser); + remove_unallowed_quote_entities(text_); + } + if (has_position) { + td::parse(position_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp index 75442e86..144ca7fd 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReaction.cpp @@ -7,7 +7,7 @@ #include "td/telegram/MessageReaction.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -17,6 +17,7 @@ #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/actor/actor.h" #include "td/actor/SleepActor.h" @@ -198,8 +199,8 @@ class GetMessageReactionsListQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetMessageReactionsListQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetMessageReactionsListQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetMessageReactionsListQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetMessageReactionsListQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetMessageReactionsListQuery"); int32 total_count = ptr->count_; auto received_reaction_count = static_cast(ptr->reactions_.size()); @@ -516,13 +517,13 @@ unique_ptr MessageReactions::get_message_reactions( auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::User) { auto user_id = dialog_id.get_user_id(); - if (!td->contacts_manager_->have_min_user(user_id)) { + if (!td->user_manager_->have_min_user(user_id)) { LOG(ERROR) << "Receive unknown " << user_id; continue; } } else if (dialog_type == DialogType::Channel) { auto channel_id = dialog_id.get_channel_id(); - auto min_channel = td->contacts_manager_->get_min_channel(channel_id); + auto min_channel = td->chat_manager_->get_min_channel(channel_id); if (min_channel == nullptr) { LOG(ERROR) << "Receive unknown reacted " << channel_id; continue; @@ -814,7 +815,7 @@ void MessageReactions::add_min_channels(Td *td) const { for (const auto &reaction : reactions_) { for (const auto &recent_chooser_min_channel : reaction.get_recent_chooser_min_channels()) { LOG(INFO) << "Add min reacted " << recent_chooser_min_channel.first; - td->contacts_manager_->add_min_channel(recent_chooser_min_channel.first, recent_chooser_min_channel.second); + td->chat_manager_->add_min_channel(recent_chooser_min_channel.first, recent_chooser_min_channel.second); } } } @@ -872,8 +873,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr &&message_ids) { - if (!td->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || - dialog_id.get_type() == DialogType::SecretChat || message_ids.empty()) { + if (!td->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) || message_ids.empty()) { create_actor( "RetryReloadMessageReactionsActor", 0.2, PromiseCreator::lambda([actor_id = G()->messages_manager(), dialog_id](Result result) mutable { @@ -937,17 +937,10 @@ void get_message_added_reactions(Td *td, MessageFullId message_full_id, Reaction void report_message_reactions(Td *td, MessageFullId message_full_id, DialogId chooser_dialog_id, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); - if (!td->dialog_manager_->have_dialog_force(dialog_id, "send_callback_query")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - if (dialog_id.get_type() == DialogType::SecretChat) { - return promise.set_error(Status::Error(400, "Reactions can't be reported in the chat")); - } + TRY_STATUS_PROMISE(promise, td->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "report_message_reactions")); - if (!td->messages_manager_->have_message_force(message_full_id, "report_user_reactions")) { + if (!td->messages_manager_->have_message_force(message_full_id, "report_message_reactions")) { return promise.set_error(Status::Error(400, "Message not found")); } auto message_id = message_full_id.get_message_id(); @@ -958,7 +951,7 @@ void report_message_reactions(Td *td, MessageFullId message_full_id, DialogId ch return promise.set_error(Status::Error(400, "Message reactions can't be reported")); } - if (!td->dialog_manager_->have_input_peer(chooser_dialog_id, AccessRights::Know)) { + if (!td->dialog_manager_->have_input_peer(chooser_dialog_id, false, AccessRights::Know)) { return promise.set_error(Status::Error(400, "Reaction sender not found")); } diff --git a/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp b/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp index 984d79b0..de0ff551 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageReplyInfo.cpp @@ -6,11 +6,12 @@ // #include "td/telegram/MessageReplyInfo.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/MessageSender.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/logging.h" @@ -59,13 +60,13 @@ MessageReplyInfo::MessageReplyInfo(Td *td, tl_object_ptrcontacts_manager_->have_min_user(replier_user_id)) { + if (!td->user_manager_->have_min_user(replier_user_id)) { LOG(ERROR) << "Receive unknown replied " << replier_user_id; continue; } } else if (dialog_type == DialogType::Channel) { auto replier_channel_id = dialog_id.get_channel_id(); - auto min_channel = td->contacts_manager_->get_min_channel(replier_channel_id); + auto min_channel = td->chat_manager_->get_min_channel(replier_channel_id); if (min_channel == nullptr) { LOG(ERROR) << "Receive unknown replied " << replier_channel_id; continue; @@ -187,7 +188,7 @@ bool MessageReplyInfo::need_reget(const Td *td) const { for (auto &dialog_id : recent_replier_dialog_ids_) { if (dialog_id.get_type() != DialogType::User && !td->dialog_manager_->have_dialog_info(dialog_id)) { if (dialog_id.get_type() == DialogType::Channel && - td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) { + td->chat_manager_->have_min_channel(dialog_id.get_channel_id())) { return false; } LOG(INFO) << "Reget a message because of replied " << dialog_id; diff --git a/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.cpp b/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.cpp new file mode 100644 index 00000000..9d08b8ae --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.cpp @@ -0,0 +1,69 @@ +// +// 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/MessageSearchOffset.h" + +#include "td/telegram/DialogManager.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/ServerMessageId.h" + +#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/StringBuilder.h" + +#include + +namespace td { + +void MessageSearchOffset::update_from_message(const telegram_api::object_ptr &message) { + auto message_date = MessagesManager::get_message_date(message); + auto message_id = MessageId::get_message_id(message, false); + auto dialog_id = DialogId::get_message_dialog_id(message); + if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) { + date_ = message_date; + message_id_ = message_id; + dialog_id_ = dialog_id; + } +} + +string MessageSearchOffset::to_string() const { + return PSTRING() << date_ << ',' << dialog_id_.get() << ',' << message_id_.get_server_message_id().get(); +} + +Result MessageSearchOffset::from_string(const string &offset) { + MessageSearchOffset result; + result.date_ = std::numeric_limits::max(); + bool is_offset_valid = [&] { + if (offset.empty()) { + return true; + } + + auto parts = full_split(offset, ','); + if (parts.size() != 3) { + return false; + } + auto r_offset_date = to_integer_safe(parts[0]); + auto r_offset_dialog_id = to_integer_safe(parts[1]); + auto r_offset_message_id = to_integer_safe(parts[2]); + if (r_offset_date.is_error() || r_offset_message_id.is_error() || r_offset_dialog_id.is_error()) { + return false; + } + result.date_ = r_offset_date.ok(); + result.message_id_ = MessageId(ServerMessageId(r_offset_message_id.ok())); + result.dialog_id_ = DialogId(r_offset_dialog_id.ok()); + if (result.date_ <= 0 || !result.message_id_.is_valid() || !result.dialog_id_.is_valid() || + DialogManager::get_input_peer_force(result.dialog_id_)->get_id() == telegram_api::inputPeerEmpty::ID) { + return false; + } + return true; + }(); + if (!is_offset_valid) { + return Status::Error(400, "Invalid offset specified"); + } + return std::move(result); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.h b/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.h new file mode 100644 index 00000000..91a88e64 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MessageSearchOffset.h @@ -0,0 +1,30 @@ +// +// 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/MessageId.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Status.h" + +namespace td { + +struct MessageSearchOffset { + int32 date_ = 0; + MessageId message_id_; + DialogId dialog_id_; + + void update_from_message(const telegram_api::object_ptr &message); + + string to_string() const; + + static Result from_string(const string &offset); +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MessageSender.cpp b/lib/tgchat/ext/td/td/telegram/MessageSender.cpp index a174d319..7b375554 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageSender.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageSender.cpp @@ -7,10 +7,11 @@ #include "td/telegram/MessageSender.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/logging.h" @@ -27,9 +28,9 @@ td_api::object_ptr get_message_sender_object_const(Td *td if (!user_id.is_valid()) { // can happen only if the server sends a message with wrong sender LOG(ERROR) << "Receive message with wrong sender " << user_id << '/' << dialog_id << " from " << source; - user_id = td->contacts_manager_->add_service_notifications_user(); + user_id = td->user_manager_->add_service_notifications_user(); } - return td_api::make_object(td->contacts_manager_->get_user_id_object(user_id, source)); + return td_api::make_object(td->user_manager_->get_user_id_object(user_id, source)); } td_api::object_ptr get_message_sender_object_const(Td *td, DialogId dialog_id, @@ -46,9 +47,9 @@ td_api::object_ptr get_message_sender_object(Td *td, User td->dialog_manager_->force_create_dialog(dialog_id, source, true); } if (!user_id.is_valid() && td->auth_manager_->is_bot()) { - td->contacts_manager_->add_anonymous_bot_user(); - td->contacts_manager_->add_channel_bot_user(); - td->contacts_manager_->add_service_notifications_user(); + td->user_manager_->add_anonymous_bot_user(); + td->user_manager_->add_channel_bot_user(); + td->user_manager_->add_service_notifications_user(); } return get_message_sender_object_const(td, user_id, dialog_id, source); } @@ -65,13 +66,13 @@ td_api::object_ptr get_min_message_sender_object(Td *td, auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::User) { auto user_id = dialog_id.get_user_id(); - if (td->contacts_manager_->have_min_user(user_id)) { - return td_api::make_object(td->contacts_manager_->get_user_id_object(user_id, source)); + if (td->user_manager_->have_min_user(user_id)) { + return td_api::make_object(td->user_manager_->get_user_id_object(user_id, source)); } } else { if (!td->messages_manager_->have_dialog(dialog_id) && (td->dialog_manager_->have_dialog_info(dialog_id) || - (dialog_type == DialogType::Channel && td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())))) { + (dialog_type == DialogType::Channel && td->chat_manager_->have_min_channel(dialog_id.get_channel_id())))) { LOG(INFO) << "Force creation of " << dialog_id; td->dialog_manager_->force_create_dialog(dialog_id, source, true); } @@ -95,7 +96,7 @@ vector get_message_sender_dialog_ids(Td *td, continue; } if (dialog_id.get_type() == DialogType::User) { - if (!td->contacts_manager_->have_user(dialog_id.get_user_id())) { + if (!td->user_manager_->have_user(dialog_id.get_user_id())) { LOG(ERROR) << "Receive unknown " << dialog_id.get_user_id(); continue; } @@ -140,7 +141,7 @@ Result get_message_sender_dialog_id(Td *td, } return Status::Error(400, "Invalid user identifier specified"); } - bool know_user = td->contacts_manager_->have_user_force(user_id, "get_message_sender_dialog_id"); + bool know_user = td->user_manager_->have_user_force(user_id, "get_message_sender_dialog_id"); if (check_access && !know_user) { return Status::Error(400, "Unknown user identifier specified"); } @@ -156,7 +157,7 @@ Result get_message_sender_dialog_id(Td *td, } bool know_dialog = dialog_id.get_type() == DialogType::User - ? td->contacts_manager_->have_user_force(dialog_id.get_user_id(), "get_message_sender_dialog_id 2") + ? td->user_manager_->have_user_force(dialog_id.get_user_id(), "get_message_sender_dialog_id 2") : td->dialog_manager_->have_dialog_force(dialog_id, "get_message_sender_dialog_id"); if (check_access && !know_dialog) { return Status::Error(400, "Unknown chat identifier specified"); diff --git a/lib/tgchat/ext/td/td/telegram/MessageViewer.cpp b/lib/tgchat/ext/td/td/telegram/MessageViewer.cpp index e8d46b7c..1cb01195 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageViewer.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessageViewer.cpp @@ -6,7 +6,7 @@ // #include "td/telegram/MessageViewer.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/logging.h" @@ -17,10 +17,9 @@ MessageViewer::MessageViewer(telegram_api::object_ptruser_id_), read_date->date_) { } -td_api::object_ptr MessageViewer::get_message_viewer_object( - ContactsManager *contacts_manager) const { +td_api::object_ptr MessageViewer::get_message_viewer_object(UserManager *user_manager) const { return td_api::make_object( - contacts_manager->get_user_id_object(user_id_, "get_message_viewer_object"), date_); + user_manager->get_user_id_object(user_id_, "get_message_viewer_object"), date_); } StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewer &viewer) { @@ -42,11 +41,10 @@ vector MessageViewers::get_user_ids() const { return transform(message_viewers_, [](auto &viewer) { return viewer.get_user_id(); }); } -td_api::object_ptr MessageViewers::get_message_viewers_object( - ContactsManager *contacts_manager) const { +td_api::object_ptr MessageViewers::get_message_viewers_object(UserManager *user_manager) const { return td_api::make_object( - transform(message_viewers_, [contacts_manager](const MessageViewer &message_viewer) { - return message_viewer.get_message_viewer_object(contacts_manager); + transform(message_viewers_, [user_manager](const MessageViewer &message_viewer) { + return message_viewer.get_message_viewer_object(user_manager); })); } diff --git a/lib/tgchat/ext/td/td/telegram/MessageViewer.h b/lib/tgchat/ext/td/td/telegram/MessageViewer.h index b09aafbe..f3ddaffb 100644 --- a/lib/tgchat/ext/td/td/telegram/MessageViewer.h +++ b/lib/tgchat/ext/td/td/telegram/MessageViewer.h @@ -15,7 +15,7 @@ namespace td { -class ContactsManager; +class UserManager; class MessageViewer { UserId user_id_; @@ -37,7 +37,7 @@ class MessageViewer { return user_id_ == UserId() && date_ == 0; } - td_api::object_ptr get_message_viewer_object(ContactsManager *contacts_manager) const; + td_api::object_ptr get_message_viewer_object(UserManager *user_manager) const; }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewer &viewer); @@ -54,7 +54,7 @@ class MessageViewers { vector get_user_ids() const; - td_api::object_ptr get_message_viewers_object(ContactsManager *contacts_manager) const; + td_api::object_ptr get_message_viewers_object(UserManager *user_manager) const; }; StringBuilder &operator<<(StringBuilder &string_builder, const MessageViewers &viewers); diff --git a/lib/tgchat/ext/td/td/telegram/MessagesInfo.cpp b/lib/tgchat/ext/td/td/telegram/MessagesInfo.cpp index 2e4b45b5..e7a1c4b5 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessagesInfo.cpp @@ -6,10 +6,11 @@ // #include "td/telegram/MessagesInfo.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/logging.h" #include "td/utils/misc.h" @@ -67,8 +68,8 @@ MessagesInfo get_messages_info(Td *td, DialogId dialog_id, break; } - td->contacts_manager_->on_get_users(std::move(users), source); - td->contacts_manager_->on_get_chats(std::move(chats), source); + td->user_manager_->on_get_users(std::move(users), source); + td->chat_manager_->on_get_chats(std::move(chats), source); td->forum_topic_manager_->on_get_forum_topic_infos(dialog_id, std::move(topics), source); return result; diff --git a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp index d749b79b..d5b18ab9 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/MessagesManager.cpp @@ -6,15 +6,15 @@ // #include "td/telegram/MessagesManager.h" -#include "td/telegram/AccessRights.h" #include "td/telegram/AccountManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundInfo.hpp" #include "td/telegram/BlockListId.h" +#include "td/telegram/BusinessBotManageBar.h" #include "td/telegram/ChainId.h" #include "td/telegram/ChannelType.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ChatReactions.hpp" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogActionBar.h" @@ -28,6 +28,8 @@ #include "td/telegram/DownloadManager.h" #include "td/telegram/DraftMessage.h" #include "td/telegram/DraftMessage.hpp" +#include "td/telegram/FactCheck.h" +#include "td/telegram/FactCheck.hpp" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileId.hpp" #include "td/telegram/files/FileLocation.h" @@ -36,6 +38,7 @@ #include "td/telegram/ForumTopicManager.h" #include "td/telegram/Global.h" #include "td/telegram/GroupCallManager.h" +#include "td/telegram/HashtagHints.h" #include "td/telegram/InlineQueriesManager.h" #include "td/telegram/InputDialogId.h" #include "td/telegram/InputMessageText.h" @@ -49,11 +52,14 @@ #include "td/telegram/MessageForwardInfo.h" #include "td/telegram/MessageForwardInfo.hpp" #include "td/telegram/MessageOrigin.hpp" +#include "td/telegram/MessageQuote.h" #include "td/telegram/MessageReaction.h" #include "td/telegram/MessageReaction.hpp" #include "td/telegram/MessageReplyInfo.hpp" +#include "td/telegram/MessageSearchOffset.h" #include "td/telegram/MessageSender.h" #include "td/telegram/misc.h" +#include "td/telegram/MissingInvitee.h" #include "td/telegram/net/DcId.h" #include "td/telegram/NotificationGroupInfo.hpp" #include "td/telegram/NotificationGroupType.h" @@ -84,6 +90,7 @@ #include "td/telegram/TopDialogManager.h" #include "td/telegram/TranslationManager.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Usernames.h" #include "td/telegram/Version.h" #include "td/telegram/WebPageId.h" @@ -136,8 +143,8 @@ class GetDialogQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetDialogQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetDialogQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetDialogQuery"); td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_](Result<> result) { @@ -179,8 +186,8 @@ class GetPinnedDialogsQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive pinned chats in " << folder_id_ << ": " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetPinnedDialogsQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetPinnedDialogsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetPinnedDialogsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetPinnedDialogsQuery"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(result->dialogs_), -2, std::move(result->messages_), std::move(promise_)); } @@ -352,7 +359,7 @@ class GetChannelMessagesQuery final : public Td::ResultHandler { promise_.set_value(Unit()); return; } - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery"); promise_.set_error(std::move(status)); } }; @@ -581,7 +588,7 @@ class ExportChannelMessageLinkQuery final : public Td::ResultHandler { message_id_ = message_id; for_group_ = for_group; ignore_result_ = ignore_result; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } @@ -652,8 +659,8 @@ class GetDialogListQuery final : public Td::ResultHandler { switch (ptr->get_id()) { case telegram_api::messages_dialogs::ID: { auto dialogs = move_tl_object_as(ptr); - td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery"); - td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery"); + td_->user_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery"); + td_->chat_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), narrow_cast(dialogs->dialogs_.size()), std::move(dialogs->messages_), std::move(promise_)); @@ -661,8 +668,8 @@ class GetDialogListQuery final : public Td::ResultHandler { } case telegram_api::messages_dialogsSlice::ID: { auto dialogs = move_tl_object_as(ptr); - td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery slice"); - td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery slice"); + td_->user_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery slice"); + td_->chat_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery slice"); td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), max(dialogs->count_, 0), std::move(dialogs->messages_), std::move(promise_)); break; @@ -698,8 +705,8 @@ class SearchPublicDialogsQuery final : public Td::ResultHandler { auto dialogs = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for SearchPublicDialogsQuery: " << to_string(dialogs); - td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "SearchPublicDialogsQuery"); - td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "SearchPublicDialogsQuery"); + td_->user_manager_->on_get_users(std::move(dialogs->users_), "SearchPublicDialogsQuery"); + td_->chat_manager_->on_get_chats(std::move(dialogs->chats_), "SearchPublicDialogsQuery"); td_->messages_manager_->on_get_public_dialogs_search_result(query_, std::move(dialogs->my_results_), std::move(dialogs->results_)); } @@ -751,8 +758,8 @@ class GetBlockedDialogsQuery final : public Td::ResultHandler { case telegram_api::contacts_blocked::ID: { auto blocked_peers = move_tl_object_as(ptr); - td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery"); - td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery"); + td_->user_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery"); + td_->chat_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery"); td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, narrow_cast(blocked_peers->blocked_.size()), std::move(blocked_peers->blocked_), std::move(promise_)); @@ -761,8 +768,8 @@ class GetBlockedDialogsQuery final : public Td::ResultHandler { case telegram_api::contacts_blockedSlice::ID: { auto blocked_peers = move_tl_object_as(ptr); - td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery slice"); - td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery slice"); + td_->user_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery slice"); + td_->chat_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery slice"); td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, blocked_peers->count_, std::move(blocked_peers->blocked_), std::move(promise_)); break; @@ -791,8 +798,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; + 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( - std::move(input_peer), available_reactions.get_input_chat_reactions()))); + flags, std::move(input_peer), available_reactions.get_input_chat_reactions(), + available_reactions.reactions_limit_))); } void on_result(BufferSlice packet) final { @@ -1002,7 +1014,7 @@ class ToggleViewForumAsMessagesQuery final : public Td::ResultHandler { view_as_messages_ = view_as_messages; CHECK(dialog_id.get_type() == DialogType::Channel); - auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id()); + auto input_channel = td_->chat_manager_->get_input_channel(dialog_id.get_channel_id()); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( telegram_api::channels_toggleViewForumAsMessages(std::move(input_channel), view_as_messages), {{dialog_id}})); @@ -1216,8 +1228,8 @@ class GetMessagesViewsQuery final : public Td::ResultHandler { if (message_ids_.size() != interaction_infos.size()) { return on_error(Status::Error(500, "Wrong number of message views returned")); } - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetMessagesViewsQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetMessagesViewsQuery"); for (size_t i = 0; i < message_ids_.size(); i++) { MessageFullId message_full_id{dialog_id_, message_ids_[i]}; @@ -1275,6 +1287,85 @@ class GetExtendedMediaQuery final : public Td::ResultHandler { } }; +class GetFactCheckQuery final : public Td::ResultHandler { + Promise>> promise_; + DialogId dialog_id_; + + public: + explicit GetFactCheckQuery(Promise>> &&promise) + : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, vector &&message_ids) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); + if (input_peer == nullptr) { + return promise_.set_error(Status::Error(400, "Can't access the chat")); + } + send_query(G()->net_query_creator().create( + telegram_api::messages_getFactCheck(std::move(input_peer), MessageId::get_server_message_ids(message_ids)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetFactCheckQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr)); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetFactCheckQuery"); + promise_.set_error(std::move(status)); + } +}; + +class EditMessageFactCheckQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit EditMessageFactCheckQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, MessageId message_id, const FormattedText &text) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); + CHECK(input_peer != nullptr); + auto server_message_id = message_id.get_server_message_id().get(); + if (text.text.empty()) { + send_query(G()->net_query_creator().create( + telegram_api::messages_deleteFactCheck(std::move(input_peer), server_message_id))); + } else { + send_query(G()->net_query_creator().create(telegram_api::messages_editFactCheck( + std::move(input_peer), server_message_id, + get_input_text_with_entities(td_->user_manager_.get(), text, "messages_editFactCheck")))); + } + } + + void on_result(BufferSlice packet) final { + static_assert(std::is_same::value, + ""); + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for EditMessageFactCheckQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "EditMessageFactCheckQuery"); + promise_.set_error(std::move(status)); + } +}; + class ReadMessagesContentsQuery final : public Td::ResultHandler { Promise promise_; @@ -1324,7 +1415,7 @@ class ReadChannelMessagesContentsQuery final : public Td::ResultHandler { void send(ChannelId channel_id, vector &&message_ids) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { LOG(ERROR) << "Have no input channel for " << channel_id; return on_error(Status::Error(400, "Can't access the chat")); @@ -1349,7 +1440,7 @@ class ReadChannelMessagesContentsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) { + if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) { LOG(ERROR) << "Receive error for read messages contents in " << channel_id_ << ": " << status; } promise_.set_error(std::move(status)); @@ -1357,16 +1448,16 @@ class ReadChannelMessagesContentsQuery final : public Td::ResultHandler { }; class GetDialogMessageByDateQuery final : public Td::ResultHandler { - Promise promise_; + Promise> promise_; DialogId dialog_id_; int32 date_; - int64 random_id_; public: - explicit GetDialogMessageByDateQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit GetDialogMessageByDateQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - void send(DialogId dialog_id, int32 date, int64 random_id) { + void send(DialogId dialog_id, int32 date) { auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); if (input_peer == nullptr) { return promise_.set_error(Status::Error(400, "Can't access the chat")); @@ -1374,7 +1465,6 @@ class GetDialogMessageByDateQuery final : public Td::ResultHandler { dialog_id_ = dialog_id; date_ = date; - random_id_ = random_id; send_query(G()->net_query_creator().create( telegram_api::messages_getHistory(std::move(input_peer), 0, date, -3, 5, 0, 0, 0))); @@ -1390,13 +1480,12 @@ class GetDialogMessageByDateQuery final : public Td::ResultHandler { td_->messages_manager_->get_channel_difference_if_needed( dialog_id_, std::move(info), PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, date = date_, - random_id = random_id_, promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); - send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_success, dialog_id, date, random_id, + send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date, dialog_id, date, std::move(info.messages), std::move(promise)); } }), @@ -1408,7 +1497,6 @@ class GetDialogMessageByDateQuery final : public Td::ResultHandler { LOG(ERROR) << "Receive error for GetDialogMessageByDateQuery in " << dialog_id_ << ": " << status; } promise_.set_error(std::move(status)); - td_->messages_manager_->on_get_dialog_message_by_date_fail(random_id_); } }; @@ -1545,7 +1633,7 @@ class ReadChannelHistoryQuery final : public Td::ResultHandler { void send(ChannelId channel_id, MessageId max_message_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } @@ -1565,7 +1653,7 @@ class ReadChannelHistoryQuery final : public Td::ResultHandler { } void on_error(Status status) final { - if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) { + if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) { LOG(ERROR) << "Receive error for ReadChannelHistoryQuery: " << status; } promise_.set_error(std::move(status)); @@ -1649,8 +1737,8 @@ class GetSearchResultCalendarQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetSearchResultCalendarQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "GetSearchResultCalendarQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetSearchResultCalendarQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "GetSearchResultCalendarQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetSearchResultCalendarQuery"); // unused: inexact:flags.0?true min_date:int min_msg_id:int offset_id_offset:flags.1?int @@ -1854,7 +1942,7 @@ class SearchMessagesQuery final : public Td::ResultHandler { CHECK(!saved_messages_topic_id.is_valid()); CHECK(tag_.is_empty()); handle_errors_ = dialog_id.get_type() != DialogType::Channel || - !td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()); + !td_->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id()); send_query(G()->net_query_creator().create(telegram_api::messages_getReplies( std::move(input_peer), top_msg_id, offset_id, 0, offset, limit, std::numeric_limits::max(), 0, 0))); } else { @@ -2044,7 +2132,7 @@ class GetSearchCountersQuery final : public Td::ResultHandler { }; class SearchMessagesGlobalQuery final : public Td::ResultHandler { - Promise promise_; + Promise> promise_; string query_; int32 offset_date_; DialogId offset_dialog_id_; @@ -2053,21 +2141,20 @@ class SearchMessagesGlobalQuery final : public Td::ResultHandler { MessageSearchFilter filter_; int32 min_date_; int32 max_date_; - int64 random_id_; public: - explicit SearchMessagesGlobalQuery(Promise &&promise) : promise_(std::move(promise)) { + explicit SearchMessagesGlobalQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - void send(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, + void send(FolderId folder_id, bool ignore_folder_id, bool broadcasts_only, const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, - int32 min_date, int32 max_date, int64 random_id) { + int32 min_date, int32 max_date) { query_ = query; offset_date_ = offset_date; offset_dialog_id_ = offset_dialog_id; offset_message_id_ = offset_message_id; limit_ = limit; - random_id_ = random_id; filter_ = filter; min_date_ = min_date; max_date_ = max_date; @@ -2079,9 +2166,12 @@ class SearchMessagesGlobalQuery final : public Td::ResultHandler { if (!ignore_folder_id) { flags |= telegram_api::messages_searchGlobal::FOLDER_ID_MASK; } + if (broadcasts_only) { + flags |= telegram_api::messages_searchGlobal::BROADCASTS_ONLY_MASK; + } send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal( - flags, folder_id.get(), query, get_input_messages_filter(filter), min_date_, max_date_, offset_date_, - std::move(input_peer), offset_message_id.get_server_message_id().get(), limit))); + flags, false /*ignored*/, folder_id.get(), query, get_input_messages_filter(filter), min_date_, max_date_, + offset_date_, std::move(input_peer), offset_message_id.get_server_message_id().get(), limit))); } void on_result(BufferSlice packet) final { @@ -2096,22 +2186,78 @@ class SearchMessagesGlobalQuery final : public Td::ResultHandler { PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), query = std::move(query_), offset_date = offset_date_, offset_dialog_id = offset_dialog_id_, offset_message_id = offset_message_id_, limit = limit_, filter = std::move(filter_), - min_date = min_date_, max_date = max_date_, random_id = random_id_, + min_date = min_date_, max_date = max_date_, promise = std::move(promise_)](Result &&result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { auto info = result.move_as_ok(); send_closure(actor_id, &MessagesManager::on_get_messages_search_result, query, offset_date, - offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, random_id, - info.total_count, std::move(info.messages), info.next_rate, std::move(promise)); + offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, info.total_count, + std::move(info.messages), info.next_rate, std::move(promise)); } }), "SearchMessagesGlobalQuery"); } void on_error(Status status) final { - td_->messages_manager_->on_failed_messages_search(random_id_); + if (status.message() == "SEARCH_QUERY_EMPTY") { + return promise_.set_value(td_->messages_manager_->get_found_messages_object({}, "SearchMessagesGlobalQuery")); + } + promise_.set_error(std::move(status)); + } +}; + +class SearchPostsQuery final : public Td::ResultHandler { + Promise> promise_; + string hashtag_; + MessageSearchOffset offset_; + int32 limit_; + + public: + explicit SearchPostsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &hashtag, MessageSearchOffset offset, int32 limit) { + hashtag_ = hashtag; + offset_ = offset; + limit_ = limit; + + auto input_peer = DialogManager::get_input_peer_force(offset.dialog_id_); + CHECK(input_peer != nullptr); + + send_query(G()->net_query_creator().create(telegram_api::channels_searchPosts( + hashtag, offset.date_, std::move(input_peer), offset.message_id_.get_server_message_id().get(), limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto info = get_messages_info(td_, DialogId(), result_ptr.move_as_ok(), "SearchPostsQuery"); + td_->messages_manager_->get_channel_differences_if_needed( + std::move(info), + PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), hashtag = std::move(hashtag_), + offset = offset_, limit = limit_, + promise = std::move(promise_)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + auto info = result.move_as_ok(); + send_closure(actor_id, &MessagesManager::on_get_hashtag_search_result, hashtag, offset, limit, + info.total_count, std::move(info.messages), info.next_rate, std::move(promise)); + } + }), + "SearchPostsQuery"); + } + + void on_error(Status status) final { + if (status.message() == "SEARCH_QUERY_EMPTY") { + return promise_.set_value(td_->messages_manager_->get_found_messages_object({}, "SearchPostsQuery")); + } promise_.set_error(std::move(status)); } }; @@ -2335,7 +2481,7 @@ class DeleteTopicHistoryQuery final : public Td::ResultHandler { channel_id_ = dialog_id.get_channel_id(); top_thread_message_id_ = top_thread_message_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id_); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id_); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } @@ -2374,7 +2520,7 @@ class DeleteChannelHistoryQuery final : public Td::ResultHandler { channel_id_ = channel_id; max_message_id_ = max_message_id; allow_error_ = allow_error; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } @@ -2400,7 +2546,7 @@ class DeleteChannelHistoryQuery final : public Td::ResultHandler { } void on_error(Status status) final { - if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) { + if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) { LOG(ERROR) << "Receive error for DeleteChannelHistoryQuery: " << status; } promise_.set_error(std::move(status)); @@ -2535,7 +2681,7 @@ class DeleteParticipantHistoryQuery final : public Td::ResultHandler { channel_id_ = channel_id; sender_dialog_id_ = sender_dialog_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Chat is not accessible")); } @@ -2559,7 +2705,7 @@ class DeleteParticipantHistoryQuery final : public Td::ResultHandler { void on_error(Status status) final { if (sender_dialog_id_.get_type() != DialogType::Channel) { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery"); } promise_.set_error(std::move(status)); } @@ -2691,7 +2837,7 @@ class SendMessageQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, - tl_object_ptr &&reply_markup, + int64 effect_id, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, bool is_copy, int64 random_id, NetQueryRef *send_query_ref) { random_id_ = random_id; @@ -2718,7 +2864,7 @@ class SendMessageQuery final : public Td::ResultHandler { telegram_api::messages_sendMessage( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr), + std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr, effect_id), {{dialog_id, MessageContentType::Text}, {dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { @@ -2891,8 +3037,8 @@ class SendMultiMediaQuery final : public Td::ResultHandler { public: void send(int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, MessageId top_thread_message_id, int32 schedule_date, - vector &&file_ids, vector> &&input_single_media, - bool is_copy) { + int64 effect_id, vector &&file_ids, + vector> &&input_single_media, bool is_copy) { for (auto &single_media : input_single_media) { random_ids_.push_back(single_media->random_id_); CHECK(FileManager::extract_was_uploaded(single_media->media_) == false); @@ -2921,7 +3067,7 @@ class SendMultiMediaQuery final : public Td::ResultHandler { telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_single_media), - schedule_date, std::move(as_input_peer), nullptr), + schedule_date, std::move(as_input_peer), nullptr, effect_id), {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo}, {dialog_id, MessageContentType::Photo}})); } @@ -2973,11 +3119,11 @@ class SendMultiMediaQuery final : public Td::ResultHandler { } void on_error(Status status) final { - LOG(INFO) << "Receive error for SendMultiMedia: " << status; if (G()->close_flag() && G()->use_message_database()) { // do not send error, message will be re-sent after restart return; } + LOG(INFO) << "Receive error for SendMultiMedia: " << status; if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { auto pos = FileReferenceManager::get_file_reference_error_pos(status); if (1 <= pos && pos <= file_ids_.size() && file_ids_[pos - 1].is_valid()) { @@ -3009,7 +3155,7 @@ class SendMediaQuery final : public Td::ResultHandler { public: void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, tl_object_ptr as_input_peer, const MessageInputReplyTo &input_reply_to, - MessageId top_thread_message_id, int32 schedule_date, + MessageId top_thread_message_id, int32 schedule_date, int64 effect_id, tl_object_ptr &&reply_markup, vector> &&entities, const string &text, tl_object_ptr &&input_media, MessageContentType content_type, bool is_copy, @@ -3043,7 +3189,7 @@ class SendMediaQuery final : public Td::ResultHandler { telegram_api::messages_sendMedia( flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(reply_to), std::move(input_media), text, random_id, - std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr), + std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer), nullptr, effect_id), {{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}}); if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) { query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result result) { @@ -3093,9 +3239,7 @@ class SendMediaQuery final : public Td::ResultHandler { td_->messages_manager_->on_send_message_file_parts_missing(random_id_, std::move(bad_parts)); return; } else { - if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); } } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { if (file_id_.is_valid() && !was_uploaded_) { @@ -3140,8 +3284,9 @@ class UploadMediaQuery final : public Td::ResultHandler { return on_error(Status::Error(400, "Have no write access to the chat")); } + int32 flags = 0; send_query(G()->net_query_creator().create( - telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))); + telegram_api::messages_uploadMedia(flags, string(), std::move(input_peer), std::move(input_media)))); } void on_result(BufferSlice packet) final { @@ -3183,9 +3328,7 @@ class UploadMediaQuery final : public Td::ResultHandler { std::move(bad_parts)); return; } else { - if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); } } else if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error for UploadMediaQuery"; @@ -3311,7 +3454,12 @@ class EditMessageQuery final : public Td::ResultHandler { } void on_error(Status status) final { - LOG(INFO) << "Receive error for EditMessageQuery: " << status; + if (status.code() != 403 && !(status.code() == 500 && G()->close_flag())) { + LOG(WARNING) << "Failed to edit " << MessageFullId{dialog_id_, message_id_} << " with the error " + << status.message(); + } else { + LOG(INFO) << "Receive error for EditMessageQuery: " << status; + } if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") { return promise_.set_value(0); } @@ -3509,6 +3657,7 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { Promise promise_; vector random_ids_; DialogId dialog_id_; + QuickReplyShortcutId shortcut_id_; public: explicit SendQuickReplyMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { @@ -3516,8 +3665,9 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { void send(DialogId dialog_id, QuickReplyShortcutId shortcut_id, const vector &message_ids, vector &&random_ids) { - random_ids_ = std::move(random_ids); + random_ids_ = random_ids; dialog_id_ = dialog_id; + shortcut_id_ = shortcut_id; auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Write); if (input_peer == nullptr) { @@ -3525,7 +3675,9 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { } auto query = G()->net_query_creator().create( - telegram_api::messages_sendQuickReplyMessages(std::move(input_peer), shortcut_id.get()), + telegram_api::messages_sendQuickReplyMessages(std::move(input_peer), shortcut_id.get(), + MessageId::get_server_message_ids(message_ids), + std::move(random_ids)), {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}); if (td_->option_manager_->get_option_boolean("use_quick_ack")) { query->quick_ack_promise_ = PromiseCreator::lambda([random_ids = random_ids_](Result result) { @@ -3549,10 +3701,17 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { LOG(INFO) << "Receive result for SendQuickReplyMessagesQuery for " << format::as_array(random_ids_) << ": " << to_string(ptr); auto sent_messages = UpdatesManager::get_new_messages(ptr.get()); + auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get()); bool is_result_wrong = false; - if (random_ids_.size() != sent_messages.size()) { + if (random_ids_.size() != sent_messages.size() || random_ids_.size() != sent_random_ids.size()) { is_result_wrong = true; } + for (auto &random_id : random_ids_) { + auto it = sent_random_ids.find(random_id); + if (it == sent_random_ids.end()) { + is_result_wrong = true; + } + } for (auto &sent_message : sent_messages) { if (DialogId::get_message_dialog_id(sent_message.first) != dialog_id_) { is_result_wrong = true; @@ -3565,12 +3724,6 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { for (auto &random_id : random_ids_) { td_->messages_manager_->on_send_message_fail(random_id, Status::Error(500, "Receive invalid response")); } - } else { - // generate fake updates - for (size_t i = 0; i < random_ids_.size(); i++) { - td_->messages_manager_->on_update_message_id( - random_ids_[i], MessageId::get_message_id(sent_messages[i].first, false), "SendQuickReplyMessagesQuery"); - } } td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); } @@ -3582,6 +3735,9 @@ class SendQuickReplyMessagesQuery final : public Td::ResultHandler { return; } td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SendQuickReplyMessagesQuery"); + if (status.code() == 400 && status.message() == CSlice("MESSAGE_IDS_MISMATCH")) { + td_->quick_reply_manager_->reload_quick_reply_messages(shortcut_id_, Auto()); + } for (auto &random_id : random_ids_) { td_->messages_manager_->on_send_message_fail(random_id, status.clone()); } @@ -3713,18 +3869,14 @@ class DeleteMessagesQuery final : public Td::ResultHandler { auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteMessagesQuery: " << to_string(affected_messages); - if (affected_messages->pts_count_ > 0) { - td_->updates_manager_->add_pending_pts_update(make_tl_object(), affected_messages->pts_, - affected_messages->pts_count_, Time::now(), std::move(promise_), - "delete messages query"); - } else { - promise_.set_value(Unit()); - } + td_->updates_manager_->add_pending_pts_update(make_tl_object(), affected_messages->pts_, + affected_messages->pts_count_, Time::now(), std::move(promise_), + "delete messages query"); } void on_error(Status status) final { if (!G()->is_expected_error(status)) { - // MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights was removed + // MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights were removed // MESSAGE_DELETE_FORBIDDEN can be returned in private chats for bots when revoke time limit exceeded if (status.message() != "MESSAGE_DELETE_FORBIDDEN" || (dialog_id_.get_type() == DialogType::User && !td_->auth_manager_->is_bot())) { @@ -3749,7 +3901,7 @@ class DeleteChannelMessagesQuery final : public Td::ResultHandler { channel_id_ = channel_id; server_message_ids_ = server_message_ids; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return on_error(Status::Error(400, "Can't access the chat")); } @@ -3765,17 +3917,13 @@ class DeleteChannelMessagesQuery final : public Td::ResultHandler { auto affected_messages = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for DeleteChannelMessagesQuery: " << to_string(affected_messages); - if (affected_messages->pts_count_ > 0) { - td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object(), - affected_messages->pts_, affected_messages->pts_count_, - std::move(promise_), "DeleteChannelMessagesQuery"); - } else { - promise_.set_value(Unit()); - } + td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object(), + affected_messages->pts_, affected_messages->pts_count_, + std::move(promise_), "DeleteChannelMessagesQuery"); } void on_error(Status status) final { - if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) { + if (!td_->chat_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) { if (status.message() != "MESSAGE_DELETE_FORBIDDEN") { LOG(ERROR) << "Receive error for delete channel messages: " << status; } @@ -3846,8 +3994,8 @@ class GetPeerSettingsQuery final : public Td::ResultHandler { } auto ptr = result_ptr.move_as_ok(); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetPeerSettingsQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetPeerSettingsQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetPeerSettingsQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetPeerSettingsQuery"); td_->messages_manager_->on_get_peer_settings(dialog_id_, std::move(ptr->settings_)); } @@ -3934,7 +4082,7 @@ class ReportEncryptedSpamQuery final : public Td::ResultHandler { LOG(INFO) << "Receive error for report encrypted spam: " << status; td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "ReportEncryptedSpamQuery"); td_->messages_manager_->reget_dialog_action_bar( - DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id_.get_secret_chat_id())), + DialogId(td_->user_manager_->get_secret_chat_user_id(dialog_id_.get_secret_chat_id())), "ReportEncryptedSpamQuery"); promise_.set_error(std::move(status)); } @@ -4102,6 +4250,9 @@ void MessagesManager::Message::store(StorerT &storer) const { bool has_saved_messages_topic_id = saved_messages_topic_id.is_valid(); bool has_initial_top_thread_message_id = !message_id.is_any_server() && initial_top_thread_message_id.is_valid(); bool has_sender_boost_count = sender_boost_count != 0; + bool has_via_business_bot_user_id = via_business_bot_user_id.is_valid(); + bool has_effect_id = effect_id != 0; + bool has_fact_check = fact_check != nullptr; BEGIN_STORE_FLAGS(); STORE_FLAG(is_channel_post); STORE_FLAG(is_outgoing); @@ -4186,6 +4337,10 @@ void MessagesManager::Message::store(StorerT &storer) const { STORE_FLAG(has_saved_messages_topic_id); STORE_FLAG(has_initial_top_thread_message_id); STORE_FLAG(has_sender_boost_count); + STORE_FLAG(has_via_business_bot_user_id); + STORE_FLAG(is_from_offline); + STORE_FLAG(has_effect_id); + STORE_FLAG(has_fact_check); END_STORE_FLAGS(); } @@ -4309,6 +4464,15 @@ void MessagesManager::Message::store(StorerT &storer) const { if (has_sender_boost_count) { store(sender_boost_count, storer); } + if (has_via_business_bot_user_id) { + store(via_business_bot_user_id, storer); + } + if (has_effect_id) { + store(effect_id, storer); + } + if (has_fact_check) { + store(fact_check, storer); + } } // do not forget to resolve message dependencies @@ -4365,6 +4529,9 @@ void MessagesManager::Message::parse(ParserT &parser) { bool has_saved_messages_topic_id = false; bool has_initial_top_thread_message_id = false; bool has_sender_boost_count = false; + bool has_via_business_bot_user_id = false; + bool has_effect_id = false; + bool has_fact_check = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_channel_post); PARSE_FLAG(is_outgoing); @@ -4449,6 +4616,10 @@ void MessagesManager::Message::parse(ParserT &parser) { PARSE_FLAG(has_saved_messages_topic_id); PARSE_FLAG(has_initial_top_thread_message_id); PARSE_FLAG(has_sender_boost_count); + PARSE_FLAG(has_via_business_bot_user_id); + PARSE_FLAG(is_from_offline); + PARSE_FLAG(has_effect_id); + PARSE_FLAG(has_fact_check); END_PARSE_FLAGS(); } @@ -4619,7 +4790,7 @@ void MessagesManager::Message::parse(ParserT &parser) { if (reply_to_story_full_id.is_valid()) { input_reply_to = MessageInputReplyTo(reply_to_story_full_id); } else if (legacy_reply_to_message_id.is_valid()) { - input_reply_to = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), FormattedText(), 0}; + input_reply_to = MessageInputReplyTo{legacy_reply_to_message_id, DialogId(), MessageQuote()}; } } if (has_replied_message_info) { @@ -4636,6 +4807,15 @@ void MessagesManager::Message::parse(ParserT &parser) { if (has_sender_boost_count) { parse(sender_boost_count, parser); } + if (has_via_business_bot_user_id) { + parse(via_business_bot_user_id, parser); + } + if (has_effect_id) { + parse(effect_id, parser); + } + if (has_fact_check) { + parse(fact_check, parser); + } CHECK(content != nullptr); is_content_secret |= ttl.is_secret_message_content(content->get_type()); // repair is_content_secret for old messages @@ -4696,6 +4876,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { bool has_available_reactions = !available_reactions.empty(); bool has_history_generation = history_generation != 0; bool has_background = background_info.is_valid(); + bool has_business_bot_manage_bar = business_bot_manage_bar != nullptr; BEGIN_STORE_FLAGS(); STORE_FLAG(has_draft_message); STORE_FLAG(has_last_database_message); @@ -4789,6 +4970,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const { STORE_FLAG(is_view_as_messages_inited); STORE_FLAG(is_forum); STORE_FLAG(is_saved_messages_view_as_messages_inited); + STORE_FLAG(has_business_bot_manage_bar); END_STORE_FLAGS(); } @@ -4907,6 +5089,9 @@ void MessagesManager::Dialog::store(StorerT &storer) const { if (has_background) { store(background_info, storer); } + if (has_business_bot_manage_bar) { + store(business_bot_manage_bar, storer); + } } // do not forget to resolve dialog dependencies including dependencies of last_message @@ -4958,6 +5143,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { bool has_available_reactions = false; bool has_history_generation = false; bool has_background = false; + bool has_business_bot_manage_bar = false; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_draft_message); PARSE_FLAG(has_last_database_message); @@ -5066,6 +5252,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { PARSE_FLAG(is_view_as_messages_inited); PARSE_FLAG(is_forum); PARSE_FLAG(is_saved_messages_view_as_messages_inited); + PARSE_FLAG(has_business_bot_manage_bar); END_PARSE_FLAGS(); } else { need_repair_action_bar = false; @@ -5223,7 +5410,7 @@ void MessagesManager::Dialog::parse(ParserT &parser) { } else if (has_legacy_available_reactions) { vector legacy_available_reaction_types; parse(legacy_available_reaction_types, parser); - available_reactions = ChatReactions(std::move(legacy_available_reaction_types)); + available_reactions = ChatReactions::legacy(std::move(legacy_available_reaction_types)); } if (has_available_reactions_generation) { parse(available_reactions_generation, parser); @@ -5237,6 +5424,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) { if (has_background) { parse(background_info, parser); } + if (has_business_bot_manage_bar) { + parse(business_bot_manage_bar, parser); + } (void)legacy_know_can_report_spam; if (know_action_bar && !has_action_bar) { @@ -5756,7 +5946,7 @@ void MessagesManager::skip_old_pending_pts_update(tl_object_ptrcontacts_manager_->add_service_notifications_user(); + UserId service_notifications_user_id = td_->user_manager_->add_service_notifications_user(); DialogId service_notifications_dialog_id(service_notifications_user_id); force_create_dialog(service_notifications_dialog_id, "get_service_notifications_dialog"); return get_dialog(service_notifications_dialog_id); @@ -5765,7 +5955,7 @@ MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() { void MessagesManager::extract_authentication_codes(DialogId dialog_id, const Message *m, vector &authentication_codes) { CHECK(m != nullptr); - if (dialog_id != DialogId(ContactsManager::get_service_notifications_user_id()) || !m->message_id.is_valid() || + if (dialog_id != DialogId(UserManager::get_service_notifications_user_id()) || !m->message_id.is_valid() || !m->message_id.is_server() || m->content->get_type() != MessageContentType::Text || m->is_outgoing) { return; } @@ -5850,8 +6040,8 @@ void MessagesManager::on_update_service_notification(tl_object_ptrauth_manager_->is_authorized(); bool is_user = is_authorized && !td_->auth_manager_->is_bot(); - auto contacts_manager = is_authorized ? td_->contacts_manager_.get() : nullptr; - auto message_text = get_message_text(contacts_manager, std::move(update->message_), std::move(update->entities_), + auto user_manager = is_authorized ? td_->user_manager_.get() : nullptr; + auto message_text = get_message_text(user_manager, std::move(update->message_), std::move(update->entities_), skip_new_entities, !is_user, date, false, "on_update_service_notification"); DialogId owner_dialog_id = is_user ? get_service_notifications_dialog()->dialog_id : DialogId(); MessageSelfDestructType ttl; @@ -5993,7 +6183,7 @@ void MessagesManager::on_update_channel_too_long(tl_object_ptrcontacts_manager_->have_channel_force(channel_id, "on_update_channel_too_long")) { + if (!td_->chat_manager_->have_channel_force(channel_id, "on_update_channel_too_long")) { LOG(INFO) << "Skip updateChannelTooLong about unknown " << channel_id; return; } @@ -6047,7 +6237,7 @@ void MessagesManager::on_update_message_reactions(MessageFullId message_full_id, auto new_reactions = MessageReactions::get_message_reactions(td_, std::move(reactions), td_->auth_manager_->is_bot()); if (!have_message_force(message_full_id, "on_update_message_reactions")) { auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { LOG(INFO) << "Ignore updateMessageReaction in inaccessible " << message_full_id; promise.set_value(Unit()); return; @@ -6232,15 +6422,15 @@ bool MessagesManager::is_active_message_reply_info(DialogId dialog_id, const Mes } auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) { + if (!td_->chat_manager_->get_channel_has_linked_channel(channel_id)) { return false; } auto linked_channel_id = - td_->contacts_manager_->get_channel_linked_channel_id(channel_id, "is_active_message_reply_info"); + td_->chat_manager_->get_channel_linked_channel_id(channel_id, "is_active_message_reply_info"); if (!linked_channel_id.is_valid()) { // keep the comment button while linked channel is unknown - send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, Promise(), + send_closure_later(G()->chat_manager(), &ChatManager::load_channel_full, channel_id, false, Promise(), "is_active_message_reply_info"); return true; } @@ -6268,8 +6458,8 @@ bool MessagesManager::is_visible_message_reply_info(DialogId dialog_id, const Me return false; } if (m->reply_info.is_comment_ && is_broadcast && - td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_, "is_visible_message_reply_info") && - !td_->contacts_manager_->have_input_peer_channel(m->reply_info.channel_id_, AccessRights::Read)) { + td_->chat_manager_->have_channel_force(m->reply_info.channel_id_, "is_visible_message_reply_info") && + !td_->chat_manager_->have_input_peer_channel(m->reply_info.channel_id_, AccessRights::Read)) { // keep the comment button while have no information about the linked channel return false; } @@ -6341,7 +6531,7 @@ td_api::object_ptr MessagesManager::get_message_ UserId my_user_id; UserId peer_user_id; if (dialog_id.get_type() == DialogType::User) { - my_user_id = td_->contacts_manager_->get_my_id(); + my_user_id = td_->user_manager_->get_my_id(); peer_user_id = dialog_id.get_user_id(); } reactions = m->reactions->get_message_reactions_object(td_, my_user_id, peer_user_id); @@ -6351,6 +6541,13 @@ td_api::object_ptr MessagesManager::get_message_ std::move(reactions)); } +td_api::object_ptr MessagesManager::get_message_fact_check_object(const Message *m) const { + if (m->fact_check == nullptr) { + return nullptr; + } + return m->fact_check->get_fact_check_object(); +} + vector> MessagesManager::get_unread_reactions_object( DialogId dialog_id, const Message *m) const { if (!has_unread_message_reactions(dialog_id, m)) { @@ -6367,6 +6564,32 @@ vector> MessagesManager::get_unread_r return unread_reactions; } +bool MessagesManager::update_message_fact_check(const Dialog *d, Message *m, unique_ptr &&fact_check, + bool need_save) { + CHECK(m != nullptr); + if (td_->auth_manager_->is_bot() || !m->message_id.is_valid() || !m->message_id.is_server()) { + return false; + } + if (fact_check != nullptr && m->fact_check != nullptr) { + fact_check->update_from(*m->fact_check); + } + bool need_update = false; + if (fact_check != m->fact_check) { + if ((fact_check != nullptr && !fact_check->need_check()) || + (m->fact_check != nullptr && !m->fact_check->need_check())) { + need_update = true; + } + m->fact_check = std::move(fact_check); + if (need_save) { + on_message_changed(d, m, false, "update_message_fact_check"); + } + } + if (need_update) { + send_update_message_fact_check(d->dialog_id, m); + } + return need_update; +} + bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int32 view_count, int32 forward_count, bool has_reply_info, MessageReplyInfo &&reply_info, bool has_reactions, unique_ptr &&reactions, @@ -6558,13 +6781,13 @@ void MessagesManager::on_update_some_live_location_viewed(Promise &&promis void MessagesManager::on_update_message_extended_media( MessageFullId message_full_id, telegram_api::object_ptr extended_media) { auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "on_update_message_extended_media"); + Dialog *d = get_dialog_force(dialog_id, "on_update_message_extended_media 1"); if (d == nullptr) { LOG(INFO) << "Ignore update of message extended media in unknown " << dialog_id; return; } - auto m = get_message_force(d, message_full_id.get_message_id(), "on_update_message_extended_media"); + auto m = get_message_force(d, message_full_id.get_message_id(), "on_update_message_extended_media 2"); if (m == nullptr) { LOG(INFO) << "Ignore update of message extended media in unknown " << message_full_id; return; @@ -6579,9 +6802,9 @@ void MessagesManager::on_update_message_extended_media( return; } if (update_message_content_extended_media(content, std::move(extended_media), dialog_id, td_)) { - send_update_message_content(d, m, true, "on_update_message_extended_media"); - on_message_changed(d, m, true, "on_update_message_extended_media"); - on_message_notification_changed(d, m, "on_update_message_extended_media"); // usually a no-op + send_update_message_content(d, m, true, "on_update_message_extended_media 3"); + on_message_changed(d, m, true, "on_update_message_extended_media 4"); + on_message_notification_changed(d, m, "on_update_message_extended_media 5"); // usually a no-op } } @@ -6595,19 +6818,19 @@ bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, const Message * } auto d = get_dialog(dialog_id); - CHECK(d != nullptr); - return (d->is_has_bots_inited && !d->has_bots) || td_->dialog_manager_->is_broadcast_channel(dialog_id); + return (d != nullptr && d->is_has_bots_inited && !d->has_bots) || + td_->dialog_manager_->is_broadcast_channel(dialog_id); } -void MessagesManager::on_external_update_message_content(MessageFullId message_full_id) { +void MessagesManager::on_external_update_message_content(MessageFullId message_full_id, const char *source) { Dialog *d = get_dialog(message_full_id.get_dialog_id()); CHECK(d != nullptr); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); - send_update_message_content(d, m, true, "on_external_update_message_content"); + send_update_message_content(d, m, true, source); // must not call on_message_changed, because the message itself wasn't changed - send_update_last_message_if_needed(d, m, "on_external_update_message_content"); - on_message_notification_changed(d, m, "on_external_update_message_content"); + send_update_last_message_if_needed(d, m, source); + on_message_notification_changed(d, m, source); } void MessagesManager::on_update_message_content(MessageFullId message_full_id) { @@ -6615,9 +6838,9 @@ void MessagesManager::on_update_message_content(MessageFullId message_full_id) { CHECK(d != nullptr); Message *m = get_message(d, message_full_id.get_message_id()); CHECK(m != nullptr); - send_update_message_content(d, m, true, "on_update_message_content"); - on_message_changed(d, m, true, "on_update_message_content"); - on_message_notification_changed(d, m, "on_update_message_content"); + send_update_message_content(d, m, true, "on_update_message_content 1"); + on_message_changed(d, m, true, "on_update_message_content 2"); + on_message_notification_changed(d, m, "on_update_message_content 3"); } bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, @@ -6779,25 +7002,6 @@ void MessagesManager::on_update_delete_scheduled_messages(DialogId dialog_id, send_update_chat_has_scheduled_messages(d, true); } -void MessagesManager::on_update_created_public_broadcasts(vector channel_ids) { - if (td_->auth_manager_->is_bot()) { - // just in case - return; - } - - if (created_public_broadcasts_inited_ && created_public_broadcasts_ == channel_ids) { - return; - } - - LOG(INFO) << "Update create public channels to " << channel_ids; - for (auto channel_id : channel_ids) { - force_create_dialog(DialogId(channel_id), "on_update_created_public_broadcasts"); - } - - created_public_broadcasts_inited_ = true; - created_public_broadcasts_ = std::move(channel_ids); -} - void MessagesManager::on_dialog_speaking_action(DialogId dialog_id, DialogId speaking_dialog_id, int32 date) { const Dialog *d = get_dialog_force(dialog_id, "on_dialog_speaking_action"); if (d != nullptr && d->active_group_call_id.is_valid()) { @@ -6834,7 +7038,6 @@ void MessagesManager::add_postponed_channel_update(DialogId dialog_id, tl_object void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_ptr &&update, int32 new_pts, int32 pts_count, Promise &&promise, const char *source, bool is_postponed_update) { - LOG(INFO) << "Receive from " << source << " pending " << to_string(update); CHECK(update != nullptr); if (dialog_id.get_type() != DialogType::Channel) { LOG(ERROR) << "Receive channel update in invalid " << dialog_id << " from " << source << ": " @@ -6850,7 +7053,7 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p } auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->have_channel(channel_id) && td_->contacts_manager_->have_min_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id) && td_->chat_manager_->have_min_channel(channel_id)) { td_->updates_manager_->schedule_get_difference("add_pending_channel_update 1"); promise.set_value(Unit()); return; @@ -6863,7 +7066,7 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p if (d == nullptr) { auto pts = load_channel_pts(dialog_id); if (pts > 0) { - if (!td_->contacts_manager_->have_channel(channel_id)) { + if (!td_->chat_manager_->have_channel(channel_id)) { // do not create dialog if there is no info about the channel LOG(INFO) << "There is no info about " << channel_id << ", so ignore " << oneline(to_string(update)); promise.set_value(Unit()); @@ -6878,7 +7081,8 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p } if (d == nullptr) { // if there is no dialog, it can be created by the update - LOG(INFO) << "Receive pending update from " << source << " about unknown " << dialog_id; + LOG(INFO) << "Receive from " << source << " pending update about unknown " << dialog_id << ": " + << to_string(update); if (running_get_channel_difference(dialog_id)) { add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); return; @@ -6915,11 +7119,13 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update)) << "Receive from " << source << " useless channel update " << oneline(to_string(update)); - LOG(INFO) << "Skip already applied channel update"; + LOG(INFO) << "Skip already applied channel update with PTS " << new_pts << " from " << source; + Scheduler::instance()->destroy_on_scheduler_unique_ptr(G()->get_gc_scheduler_id(), update); promise.set_value(Unit()); return; } + LOG(INFO) << "Receive from " << source << " pending " << to_string(update); if (running_get_channel_difference(dialog_id)) { LOG(INFO) << "Postpone channel update, because getChannelDifference is run"; add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); @@ -6932,8 +7138,7 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p } else if (old_pts != new_pts - pts_count) { LOG(INFO) << "Found a gap in the " << dialog_id << " with PTS = " << old_pts << ". new_pts = " << new_pts << ", pts_count = " << pts_count << " in update from " << source; - if (d->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() || - is_dialog_sponsored(d)) { + if (d->was_opened || td_->chat_manager_->get_channel_status(channel_id).is_member() || is_dialog_sponsored(d)) { add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise)); get_channel_difference(dialog_id, old_pts, new_pts, MessageId(), true, "add_pending_channel_update PTS mismatch"); @@ -7394,13 +7599,14 @@ void MessagesManager::on_update_dialog_notify_settings( } void MessagesManager::on_update_dialog_available_reactions( - DialogId dialog_id, telegram_api::object_ptr &&available_reactions) { + DialogId dialog_id, telegram_api::object_ptr &&available_reactions, + int32 reactions_limit) { 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))); + set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions), reactions_limit)); } void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) { @@ -7731,11 +7937,11 @@ void MessagesManager::reget_dialog_action_bar(DialogId dialog_id, const char *so LOG(INFO) << "Reget action bar in " << dialog_id << " from " << source; switch (dialog_id.get_type()) { case DialogType::User: - td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto(), source); + td_->user_manager_->reload_user_full(dialog_id.get_user_id(), Auto(), source); return; case DialogType::Chat: case DialogType::Channel: - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } @@ -7751,7 +7957,7 @@ void MessagesManager::repair_dialog_action_bar(Dialog *d, const char *source) { CHECK(d != nullptr); auto dialog_id = d->dialog_id; d->need_repair_action_bar = true; - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { create_actor( "RepairChatActionBarActor", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, source](Result result) { @@ -7789,24 +7995,12 @@ void MessagesManager::hide_dialog_action_bar(Dialog *d) { } void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "remove_dialog_action_bar"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "remove_dialog_action_bar")); if (dialog_id.get_type() == DialogType::SecretChat) { - dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); - d = get_dialog_force(dialog_id, "remove_dialog_action_bar 2"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat with the user not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + dialog_id = DialogId(td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); + TRY_RESULT_PROMISE_ASSIGN(promise, d, + check_dialog_access(dialog_id, false, AccessRights::Read, "remove_dialog_action_bar 2")); } if (!d->know_action_bar) { @@ -7826,8 +8020,18 @@ void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise toggle_dialog_report_spam_state_on_server(dialog_id, false, 0, std::move(promise)); } +void MessagesManager::hide_all_business_bot_manager_bars() { + dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr &dialog) { + Dialog *d = dialog.get(); + if (d->business_bot_manage_bar != nullptr) { + d->business_bot_manage_bar = nullptr; + send_update_chat_business_bot_manage_bar(d); + } + }); +} + void MessagesManager::repair_dialog_active_group_call_id(DialogId dialog_id) { - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { LOG(INFO) << "Repair active voice chat ID in " << dialog_id; create_actor("RepairChatActiveVoiceChatId", 1.0, PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result result) { @@ -7852,7 +8056,7 @@ void MessagesManager::do_repair_dialog_active_group_call_id(DialogId dialog_id) if (!need_repair_active_group_call_id && !need_repair_expected_group_call_id) { return; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } @@ -7902,7 +8106,7 @@ void MessagesManager::toggle_dialog_report_spam_state_on_server(DialogId dialog_ if (is_spam_dialog) { return td_->create_handler(std::move(promise))->send(dialog_id); } else { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "Peer user not found")); } @@ -7924,8 +8128,8 @@ void MessagesManager::on_get_peer_settings(DialogId dialog_id, } if (dialog_id.get_type() == DialogType::User && !ignore_privacy_exception) { - td_->contacts_manager_->on_update_user_need_phone_number_privacy_exception(dialog_id.get_user_id(), - peer_settings->need_contacts_exception_); + td_->user_manager_->on_update_user_need_phone_number_privacy_exception(dialog_id.get_user_id(), + peer_settings->need_contacts_exception_); } Dialog *d = get_dialog_force(dialog_id, "on_get_peer_settings"); @@ -7933,6 +8137,15 @@ void MessagesManager::on_get_peer_settings(DialogId dialog_id, return; } + auto business_bot_manage_bar = BusinessBotManageBar::create( + peer_settings->business_bot_paused_, peer_settings->business_bot_can_reply_, + UserId(peer_settings->business_bot_id_), std::move(peer_settings->business_bot_manage_url_)); + fix_dialog_business_bot_manage_bar(dialog_id, business_bot_manage_bar.get()); + if (d->business_bot_manage_bar != business_bot_manage_bar) { + d->business_bot_manage_bar = std::move(business_bot_manage_bar); + send_update_chat_business_bot_manage_bar(d); + } + auto distance = (peer_settings->flags_ & telegram_api::peerSettings::GEO_DISTANCE_MASK) != 0 ? peer_settings->geo_distance_ : -1; if (distance < -1 || d->has_outgoing_messages) { @@ -7971,15 +8184,19 @@ void MessagesManager::fix_dialog_action_bar(const Dialog *d, DialogActionBar *ac action_bar->fix(td_, d->dialog_id, d->is_blocked, d->folder_id); } -Result MessagesManager::get_login_button_url(MessageFullId message_full_id, int64 button_id) { - Dialog *d = get_dialog_force(message_full_id.get_dialog_id(), "get_login_button_url"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); +void MessagesManager::fix_dialog_business_bot_manage_bar(DialogId dialog_id, + BusinessBotManageBar *business_bot_manage_bar) { + if (business_bot_manage_bar == nullptr) { + return; } + business_bot_manage_bar->fix(dialog_id); +} + +Result MessagesManager::get_login_button_url(MessageFullId message_full_id, int64 button_id) { + TRY_RESULT(d, + check_dialog_access(message_full_id.get_dialog_id(), false, AccessRights::Read, "get_login_button_url")); + auto m = get_message_force(d, message_full_id.get_message_id(), "get_login_button_url"); if (m == nullptr) { return Status::Error(400, "Message not found"); @@ -7994,10 +8211,6 @@ Result MessagesManager::get_login_button_url(MessageFullId message_full_ // it shouldn't have UrlAuth buttons anyway return Status::Error(400, "Message is not server"); } - if (d->dialog_id.get_type() == DialogType::SecretChat) { - // secret chat messages can't have reply markup, so this shouldn't happen now - return Status::Error(400, "Message is in a secret chat"); - } if (button_id < std::numeric_limits::min() || button_id > std::numeric_limits::max()) { return Status::Error(400, "Invalid button identifier specified"); } @@ -8136,7 +8349,7 @@ void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_ << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail << ", self-destruct time = " << m->ttl; - MessageContent *content = nullptr; + const MessageContent *content = nullptr; if (m->message_id.is_any_server()) { content = m->edited_content.get(); if (content == nullptr) { @@ -8168,7 +8381,7 @@ void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileI LOG(INFO) << "Do send secret media file " << file_id << " with thumbnail " << thumbnail_file_id << ", have_input_file = " << have_input_file; - auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto layer = td_->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); on_secret_message_media_uploaded( dialog_id, m, get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail), layer), @@ -8364,7 +8577,7 @@ void MessagesManager::after_get_difference() { // add the message or delete it from the server. In this case we forget updateMessageId for such messages in // order to not check them over and over. const Dialog *d = get_dialog(dialog_id); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) || (d != nullptr && message_id <= td::max(d->last_clear_history_message_id, d->max_unavailable_message_id))) { update_message_ids_to_delete.push_back(message_full_id); @@ -8445,6 +8658,14 @@ void MessagesManager::on_get_empty_messages(DialogId dialog_id, const vectorpts, 0, message_id, true, source); +} + void MessagesManager::get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info, Promise &&promise, const char *source) { if (td_->auth_manager_->is_bot()) { @@ -8627,7 +8848,7 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_ } // be aware that returned messages are guaranteed to be consecutive messages, but if !from_the_end they - // may be older (if some messages was deleted) or newer (if some messages were added) than an expected answer + // may be older (if some messages were deleted) or newer (if some messages were added) than an expected answer // be aware that any subset of the returned messages may be already deleted and returned as MessageEmpty bool is_channel_message = dialog_id.get_type() == DialogType::Channel; MessageId first_added_message_id; @@ -9198,34 +9419,23 @@ void MessagesManager::on_get_dialog_message_count(DialogId dialog_id, SavedMessa void MessagesManager::on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date, - int64 random_id, int32 total_count, + int32 total_count, vector> &&messages, - int32 next_rate, Promise &&promise) { + int32 next_rate, + Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); LOG(INFO) << "Receive " << messages.size() << " found messages"; - auto it = found_messages_.find(random_id); - CHECK(it != found_messages_.end()); - auto &result = it->second.message_full_ids; - CHECK(result.empty()); - int32 last_message_date = 0; - MessageId last_message_id; - DialogId last_dialog_id; + FoundMessages found_messages; + auto &result = found_messages.message_full_ids; + MessageSearchOffset next_offset; for (auto &message : messages) { - auto message_date = get_message_date(message); - auto message_id = MessageId::get_message_id(message, false); - auto dialog_id = DialogId::get_message_dialog_id(message); - if (message_date > 0 && message_id.is_valid() && dialog_id.is_valid()) { - last_message_date = message_date; - last_message_id = message_id; - last_dialog_id = dialog_id; - } + next_offset.update_from_message(message); - auto new_message_full_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, - false, "search messages"); + bool is_channel_message = DialogId::get_message_dialog_id(message).get_type() == DialogType::Channel; + auto new_message_full_id = on_get_message(std::move(message), false, is_channel_message, false, "search messages"); if (new_message_full_id != MessageFullId()) { - CHECK(dialog_id == new_message_full_id.get_dialog_id()); result.push_back(new_message_full_id); } else { total_count--; @@ -9236,21 +9446,49 @@ void MessagesManager::on_get_messages_search_result(const string &query, int32 o << " messages"; total_count = static_cast(result.size()); } - it->second.total_count = total_count; + found_messages.total_count = total_count; if (!result.empty()) { if (next_rate > 0) { - last_message_date = next_rate; + next_offset.date_ = next_rate; } - it->second.next_offset = PSTRING() << last_message_date << ',' << last_dialog_id.get() << ',' - << last_message_id.get_server_message_id().get(); + found_messages.next_offset = next_offset.to_string(); } - promise.set_value(Unit()); + promise.set_value(get_found_messages_object(found_messages, "on_get_messages_search_result")); } -void MessagesManager::on_failed_messages_search(int64 random_id) { - auto it = found_messages_.find(random_id); - CHECK(it != found_messages_.end()); - found_messages_.erase(it); +void MessagesManager::on_get_hashtag_search_result(const string &hashtag, const MessageSearchOffset &old_offset, + int32 limit, int32 total_count, + vector> &&messages, + int32 next_rate, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + FoundMessages found_messages; + auto &result = found_messages.message_full_ids; + MessageSearchOffset next_offset; + for (auto &message : messages) { + next_offset.update_from_message(message); + + auto new_message_full_id = on_get_message(std::move(message), false, true, false, "search hashtag"); + if (new_message_full_id != MessageFullId()) { + result.push_back(new_message_full_id); + } else { + total_count--; + } + } + if (total_count < static_cast(result.size())) { + LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size() + << " messages"; + total_count = static_cast(result.size()); + } + found_messages.total_count = total_count; + if (!result.empty()) { + if (next_rate > 0) { + next_offset.date_ = next_rate; + } + found_messages.next_offset = next_offset.to_string(); + } + promise.set_value(get_found_messages_object(found_messages, "on_get_hashtag_search_result")); } void MessagesManager::on_get_outgoing_document_messages(vector> &&messages, @@ -9570,14 +9808,14 @@ bool MessagesManager::can_get_message_statistics(MessageFullId message_full_id) } bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Message *m) const { - if (td_->auth_manager_->is_bot()) { + if (td_->auth_manager_->is_bot() || dialog_id.get_type() != DialogType::Channel) { return false; } if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 || m->had_forward_info || (m->forward_info != nullptr && m->forward_info->get_origin().is_channel_post())) { return false; } - return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id); + return td_->chat_manager_->can_get_channel_message_statistics(dialog_id.get_channel_id()); } bool MessagesManager::can_delete_channel_message(const DialogParticipantStatus &status, const Message *m, bool is_bot) { @@ -9637,7 +9875,7 @@ bool MessagesManager::can_delete_message(DialogId dialog_id, const Message *m) c case DialogType::Chat: return true; case DialogType::Channel: { - auto dialog_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto dialog_status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); return can_delete_channel_message(dialog_status, m, td_->auth_manager_->is_bot()); } case DialogType::SecretChat: @@ -9683,8 +9921,7 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c G()->unix_time() - m->date <= revoke_time_limit; } case DialogType::Chat: { - bool is_appointed_administrator = - td_->contacts_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id()); + bool is_appointed_administrator = td_->chat_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id()); int64 revoke_time_limit = td_->option_manager_->get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT); @@ -9695,7 +9932,7 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c return true; // any server message that can be deleted will be deleted for all participants case DialogType::SecretChat: // all non-service messages will be deleted for everyone if secret chat is active - return td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Active && + return td_->user_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Active && !is_service_message_content(content_type); case DialogType::None: default: @@ -9781,7 +10018,7 @@ void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageI // being sent message was deleted by the user or is in an inaccessible channel // don't need to send an update to the user, because the message has already been deleted - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { LOG(INFO) << "Ignore sent " << message_id << " in inaccessible " << dialog_id; return; } @@ -9960,7 +10197,7 @@ void MessagesManager::on_failed_message_deletion(DialogId dialog_id, const vecto d->deleted_message_ids.erase(message_id); message_full_ids.emplace_back(dialog_id, message_id); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } get_messages_from_server(std::move(message_full_ids), Promise(), "on_failed_message_deletion"); @@ -9977,12 +10214,11 @@ void MessagesManager::on_failed_scheduled_message_deletion(DialogId dialog_id, c d->scheduled_messages->deleted_scheduled_server_message_ids_.erase(message_id.get_scheduled_server_message_id()); } } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || - dialog_id.get_type() == DialogType::SecretChat) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } if (td_->dialog_manager_->is_broadcast_channel(dialog_id) && - !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { + !td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { return; } load_dialog_scheduled_messages(dialog_id, false, 0, Promise()); @@ -10001,32 +10237,32 @@ MessagesManager::CanDeleteDialog MessagesManager::can_delete_dialog(const Dialog } } } - if (td_->auth_manager_->is_bot() || !td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { + if (td_->auth_manager_->is_bot() || !td_->dialog_manager_->have_input_peer(d->dialog_id, true, AccessRights::Read)) { return {false, false}; } switch (d->dialog_id.get_type()) { case DialogType::User: if (d->dialog_id == td_->dialog_manager_->get_my_dialog_id() || - td_->contacts_manager_->is_user_deleted(d->dialog_id.get_user_id()) || - td_->contacts_manager_->is_user_bot(d->dialog_id.get_user_id())) { + td_->user_manager_->is_user_deleted(d->dialog_id.get_user_id()) || + td_->user_manager_->is_user_bot(d->dialog_id.get_user_id())) { return {true, false}; } return {true, td_->option_manager_->get_option_boolean("revoke_pm_inbox", true)}; case DialogType::Chat: // chats can be deleted only for self and can be deleted for everyone by their creator - return {true, td_->contacts_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()}; + return {true, td_->chat_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()}; case DialogType::Channel: { // private non-forum joined supergroups can be deleted for self auto channel_id = d->dialog_id.get_channel_id(); - return {!td_->contacts_manager_->is_broadcast_channel(channel_id) && - !td_->contacts_manager_->is_channel_public(channel_id) && - !td_->contacts_manager_->is_forum_channel(channel_id) && - td_->contacts_manager_->get_channel_status(channel_id).is_member(), - td_->contacts_manager_->get_channel_can_be_deleted(channel_id)}; + return {!td_->chat_manager_->is_broadcast_channel(channel_id) && + !td_->chat_manager_->is_channel_public(channel_id) && + !td_->chat_manager_->is_forum_channel(channel_id) && + td_->chat_manager_->get_channel_status(channel_id).is_member(), + td_->chat_manager_->get_channel_can_be_deleted(channel_id)}; } case DialogType::SecretChat: - if (td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { + if (td_->user_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { // in a closed secret chats there is no way to delete messages for both users return {true, false}; } @@ -10044,14 +10280,7 @@ void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from LOG(INFO) << "Receive deleteChatHistory request to delete all messages in " << dialog_id << ", remove_from_chat_list is " << remove_from_dialog_list << ", revoke is " << revoke; - Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "delete_dialog_history")); if (is_dialog_sponsored(d)) { auto chat_source = sponsored_dialog_source_.get_chat_source_object(); @@ -10190,14 +10419,8 @@ void MessagesManager::delete_dialog_history_on_server(DialogId dialog_id, Messag void MessagesManager::delete_topic_history(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Chat info not found")); - } + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "delete_topic_history")); // auto old_order = d->order; // delete_all_dialog_topic_messages(d, top_thread_message_id); @@ -10332,16 +10555,10 @@ void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, Dialo bool is_bot = td_->auth_manager_->is_bot(); CHECK(!is_bot); - Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_sender"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Not enough rights")); - } + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Write, "delete_dialog_messages_by_sender")); - if (!td_->dialog_manager_->have_input_peer(sender_dialog_id, AccessRights::Know)) { + if (!td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { return promise.set_error(Status::Error(400, "Message sender not found")); } @@ -10355,10 +10572,10 @@ void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, Dialo Status::Error(400, "All messages from a sender can be deleted only in supergroup chats")); case DialogType::Channel: { channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->is_megagroup_channel(channel_id)) { + if (!td_->chat_manager_->is_megagroup_channel(channel_id)) { return promise.set_error(Status::Error(400, "The method is available only in supergroup chats")); } - channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); + channel_status = td_->chat_manager_->get_channel_permissions(channel_id); if (!channel_status.can_delete_messages()) { return promise.set_error(Status::Error(400, "Need delete messages administator right in the supergroup chat")); } @@ -10470,15 +10687,8 @@ void MessagesManager::delete_dialog_messages_by_date(DialogId dialog_id, int32 m Promise &&promise) { CHECK(!td_->auth_manager_->is_bot()); - Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_date"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } - + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Read, "delete_dialog_messages_by_date")); TRY_STATUS_PROMISE(promise, fix_delete_message_min_max_dates(min_date, max_date)); if (max_date == 0) { return promise.set_value(Unit()); @@ -10792,17 +11002,9 @@ void MessagesManager::on_update_dialog_group_call_rights(DialogId dialog_id) { void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_mentions"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "read_all_dialog_mentions")); TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Chat is not accessible")); - } - if (top_thread_message_id.is_valid()) { LOG(INFO) << "Receive readAllChatMentions request in thread of " << top_thread_message_id << " in " << dialog_id; AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, @@ -10895,17 +11097,9 @@ void MessagesManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uin void MessagesManager::read_all_dialog_reactions(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_reactions"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "read_all_dialog_reactions")); TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageInputReplyTo())); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Chat is not accessible")); - } - if (top_thread_message_id.is_valid()) { LOG(INFO) << "Receive readAllChatReactions request in thread of " << top_thread_message_id << " in " << dialog_id; AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id, @@ -11019,7 +11213,7 @@ void MessagesManager::read_channel_message_content_from_updates(Dialog *d, Messa if (m != nullptr) { read_message_content(d, m, false, 0, "read_channel_message_content_from_updates"); } else { - if (!td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(d->dialog_id, false, AccessRights::Read)) { LOG(INFO) << "Ignore updateChannelReadMessagesContents in inaccessible " << d->dialog_id; if (d->unread_mention_count != 0) { set_dialog_unread_mention_count(d, 0); @@ -11160,7 +11354,7 @@ int32 MessagesManager::calc_new_unread_count(Dialog *d, MessageId max_message_id } void MessagesManager::repair_server_unread_count(DialogId dialog_id, int32 unread_count, const char *source) { - if (td_->auth_manager_->is_bot() || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->auth_manager_->is_bot() || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } if (pending_read_history_timeout_.has_timeout(dialog_id.get())) { @@ -11313,8 +11507,7 @@ void MessagesManager::read_history_inbox(Dialog *d, MessageId max_message_id, in if (server_unread_count < 0) { server_unread_count = unread_count >= 0 ? unread_count : d->server_unread_count; - if (dialog_id.get_type() != DialogType::SecretChat && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) && need_unread_counter(d->order)) { d->need_repair_server_unread_count = true; on_dialog_updated(dialog_id, "read_history_inbox"); repair_server_unread_count(dialog_id, server_unread_count, "read_history_inbox"); @@ -11882,6 +12075,9 @@ vector MessagesManager::get_message_user_ids(const Message *m) const { if (m->via_bot_user_id.is_valid()) { user_ids.push_back(m->via_bot_user_id); } + if (m->via_business_bot_user_id.is_valid()) { + user_ids.push_back(m->via_business_bot_user_id); + } if (m->forward_info != nullptr) { m->forward_info->add_min_user_ids(user_ids); } @@ -12253,7 +12449,7 @@ void MessagesManager::init() { is_inited_ = true; td_->notification_settings_manager_->init(); // load scope notification settings - td_->reaction_manager_->init(); // load available reactions + td_->reaction_manager_->init(); // load active reactions start_time_ = Time::now(); last_channel_pts_jump_warning_time_ = start_time_ - 3600; @@ -12555,9 +12751,7 @@ void MessagesManager::on_send_secret_message_error(int64 random_id, Status error return; } - if (error.code() != 429 && error.code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id, error); } } } @@ -12684,9 +12878,9 @@ void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32 } if (read_date > 0) { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); + auto user_id = td_->user_manager_->get_secret_chat_user_id(secret_chat_id); if (user_id.is_valid()) { - td_->contacts_manager_->on_update_user_local_was_online(user_id, read_date); + td_->user_manager_->on_update_user_local_was_online(user_id, read_date); } } @@ -12836,7 +13030,7 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId message_info.content = get_secret_message_content( td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_), message_info.dialog_id, pending_secret_message->load_data_multipromise, - td_->contacts_manager_->is_user_premium(user_id)); + td_->user_manager_->is_user_premium(user_id)); add_secret_message(std::move(pending_secret_message), std::move(lock_promise)); } @@ -12848,7 +13042,7 @@ void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const stri auto dialog_id = td_->dialog_manager_->get_resolved_dialog_by_username(via_bot_username); if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) { auto user_id = dialog_id.get_user_id(); - auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id); + auto r_bot_data = td_->user_manager_->get_bot_data(user_id); if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) { message_info_ptr->via_bot_user_id = user_id; } @@ -12983,7 +13177,7 @@ void MessagesManager::finish_add_secret_message(unique_ptr << " received earlier with " << message_id << " and random_id " << random_id; } } else { - if (!td_->contacts_manager_->is_user_premium(pending_secret_message->message_info.sender_user_id)) { + if (!td_->user_manager_->is_user_premium(pending_secret_message->message_info.sender_user_id)) { auto message_text = get_message_content_text_mutable(pending_secret_message->message_info.content.get()); if (message_text != nullptr) { remove_premium_custom_emoji_entities(td_, message_text->entities, true); @@ -13007,7 +13201,7 @@ void MessagesManager::finish_add_secret_message(unique_ptr } MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( - tl_object_ptr message_ptr, bool is_scheduled, const char *source) const { + Td *td, tl_object_ptr message_ptr, bool is_scheduled, const char *source) { LOG(DEBUG) << "Receive from " << source << " " << to_string(message_ptr); LOG_CHECK(message_ptr != nullptr) << source; @@ -13034,8 +13228,8 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.date = message->date_; message_info.forward_header = std::move(message->fwd_from_); bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && - !td_->dialog_manager_->is_broadcast_channel(message_info.dialog_id); - message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, + !td->dialog_manager_->is_broadcast_channel(message_info.dialog_id); + message_info.reply_header = MessageReplyHeader(td, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); if (message->flags_ & telegram_api::message::VIA_BOT_ID_MASK) { message_info.via_bot_user_id = UserId(message->via_bot_id_); @@ -13044,10 +13238,18 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.via_bot_user_id = UserId(); } } + if (message->flags2_ & telegram_api::message::VIA_BUSINESS_BOT_ID_MASK) { + message_info.via_business_bot_user_id = UserId(message->via_business_bot_id_); + if (!message_info.via_business_bot_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << message_info.via_business_bot_user_id << " from " << source; + message_info.via_business_bot_user_id = UserId(); + } + } message_info.view_count = message->views_; message_info.forward_count = message->forwards_; message_info.reply_info = std::move(message->replies_); message_info.reactions = std::move(message->reactions_); + message_info.fact_check = std::move(message->factcheck_); message_info.edit_date = message->edit_date_; message_info.media_album_id = message->grouped_id_; message_info.ttl_period = message->ttl_period_; @@ -13057,17 +13259,19 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.is_legacy = message->legacy_; message_info.hide_edit_date = message->edit_hide_; message_info.is_from_scheduled = message->from_scheduled_; + message_info.is_from_offline = message->offline_; message_info.is_pinned = message->pinned_; message_info.noforwards = message->noforwards_; message_info.has_mention = message->mentioned_; message_info.has_unread_content = message->media_unread_; message_info.invert_media = message->invert_media_; + message_info.effect_id = message->effect_; bool is_content_read = true; - if (!td_->auth_manager_->is_bot()) { + if (!td->auth_manager_->is_bot()) { if (is_scheduled) { is_content_read = false; - } else if (is_message_auto_read(message_info.dialog_id, message_info.is_outgoing)) { + } else if (td->messages_manager_->is_message_auto_read(message_info.dialog_id, message_info.is_outgoing)) { is_content_read = true; } else { is_content_read = !message_info.has_unread_content; @@ -13076,9 +13280,9 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( auto new_source = PSTRING() << MessageFullId(message_info.dialog_id, message_info.message_id) << " sent by " << message_info.sender_dialog_id << " from " << source; message_info.content = get_message_content( - td_, - get_message_text(td_->contacts_manager_.get(), std::move(message->message_), std::move(message->entities_), - true, td_->auth_manager_->is_bot(), + td, + get_message_text(td->user_manager_.get(), std::move(message->message_), std::move(message->entities_), true, + td->auth_manager_->is_bot(), message_info.forward_header ? message_info.forward_header->date_ : message_info.date, message_info.media_album_id != 0, new_source.c_str()), std::move(message->media_), message_info.dialog_id, message_info.date, is_content_read, @@ -13110,15 +13314,15 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( message_info.has_mention = message->mentioned_; message_info.has_unread_content = message->media_unread_; bool can_have_thread = message_info.dialog_id.get_type() == DialogType::Channel && - !td_->dialog_manager_->is_broadcast_channel(message_info.dialog_id); - message_info.reply_header = MessageReplyHeader(td_, std::move(message->reply_to_), message_info.dialog_id, + !td->dialog_manager_->is_broadcast_channel(message_info.dialog_id); + message_info.reply_header = MessageReplyHeader(td, std::move(message->reply_to_), message_info.dialog_id, message_info.message_id, message_info.date, can_have_thread); message_info.content = - get_action_message_content(td_, std::move(message->action_), message_info.dialog_id, message_info.date, + get_action_message_content(td, std::move(message->action_), message_info.dialog_id, message_info.date, message_info.reply_header.replied_message_info_); message_info.reply_header.replied_message_info_ = RepliedMessageInfo(); message_info.reply_header.story_full_id_ = StoryFullId(); - if (message_info.dialog_id == td_->dialog_manager_->get_my_dialog_id()) { + if (message_info.dialog_id == td->dialog_manager_->get_my_dialog_id()) { message_info.saved_messages_topic_id = SavedMessagesTopicId(message_info.dialog_id); } break; @@ -13134,9 +13338,8 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message( return message_info; } -std::pair> MessagesManager::create_message(MessageInfo &&message_info, - bool is_channel_message, - const char *source) { +std::pair> MessagesManager::create_message( + Td *td, MessageInfo &&message_info, bool is_channel_message, bool is_business_message, const char *source) { DialogId dialog_id = message_info.dialog_id; MessageId message_id = message_info.message_id; if ((!message_id.is_valid() && !message_id.is_valid_scheduled()) || !dialog_id.is_valid()) { @@ -13152,6 +13355,7 @@ std::pair> MessagesManager::creat CHECK(message_info.content != nullptr); + auto is_bot = td->auth_manager_->is_bot(); auto dialog_type = dialog_id.get_type(); UserId sender_user_id = message_info.sender_user_id; DialogId sender_dialog_id = message_info.sender_dialog_id; @@ -13160,12 +13364,12 @@ std::pair> MessagesManager::creat LOG(ERROR) << "Receive invalid " << sender_user_id; sender_user_id = UserId(); } - if (!td_->dialog_manager_->is_broadcast_channel(dialog_id) && td_->auth_manager_->is_bot()) { + if (!td->dialog_manager_->is_broadcast_channel(dialog_id) && is_bot) { if (dialog_id == sender_dialog_id) { - td_->contacts_manager_->add_anonymous_bot_user(); + td->user_manager_->add_anonymous_bot_user(); } else { - td_->contacts_manager_->add_service_notifications_user(); - td_->contacts_manager_->add_channel_bot_user(); + td->user_manager_->add_service_notifications_user(); + td->user_manager_->add_channel_bot_user(); } } } @@ -13186,12 +13390,12 @@ std::pair> MessagesManager::creat << "Receive wrong is_channel_message for " << message_id << " in " << dialog_id; bool is_channel_post = message_info.is_channel_post; - if (is_channel_post && !td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + if (is_channel_post && !td->dialog_manager_->is_broadcast_channel(dialog_id)) { LOG(ERROR) << "Receive is_channel_post for " << message_id << " in " << dialog_id; is_channel_post = false; } - UserId my_id = td_->contacts_manager_->get_my_id(); + UserId my_id = td->user_manager_->get_my_id(); DialogId my_dialog_id = DialogId(my_id); if (dialog_id == my_dialog_id && (sender_user_id != my_id || sender_dialog_id.is_valid())) { LOG(ERROR) << "Receive " << sender_user_id << "/" << sender_dialog_id << " as a sender of " << message_id @@ -13202,7 +13406,7 @@ std::pair> MessagesManager::creat bool is_outgoing = message_info.is_outgoing; bool supposed_to_be_outgoing = sender_user_id == my_id && !(dialog_id == my_dialog_id && !message_id.is_scheduled()); - if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing) { + if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing && !is_business_message) { LOG(ERROR) << "Receive wrong out flag for " << message_id << " in " << dialog_id << ": me is " << my_id << ", but message is from " << sender_user_id; is_outgoing = supposed_to_be_outgoing; @@ -13230,10 +13434,13 @@ std::pair> MessagesManager::creat MessageId top_thread_message_id = message_info.reply_header.top_thread_message_id_; bool is_topic_message = message_info.reply_header.is_topic_message_; auto reply_to_story_full_id = message_info.reply_header.story_full_id_; - if (reply_to_story_full_id != StoryFullId() && - (reply_to_story_full_id.get_dialog_id() != my_dialog_id && reply_to_story_full_id.get_dialog_id() != dialog_id)) { - LOG(ERROR) << "Receive reply to " << reply_to_story_full_id << " in " << dialog_id; - reply_to_story_full_id = {}; + if (reply_to_story_full_id != StoryFullId()) { + auto story_dialog_id = reply_to_story_full_id.get_dialog_id(); + if (story_dialog_id != my_dialog_id && story_dialog_id != dialog_id && + story_dialog_id != DialogId(sender_user_id)) { + LOG(ERROR) << "Receive reply to " << reply_to_story_full_id << " in " << dialog_id; + reply_to_story_full_id = {}; + } } UserId via_bot_user_id = message_info.via_bot_user_id; @@ -13249,7 +13456,7 @@ std::pair> MessagesManager::creat auto content_type = message_info.content->get_type(); if (content_type == MessageContentType::Sticker && - get_message_content_sticker_type(td_, message_info.content.get()) == StickerType::CustomEmoji) { + get_message_content_sticker_type(td, message_info.content.get()) == StickerType::CustomEmoji) { LOG(INFO) << "Replace emoji sticker with an empty message"; message_info.content = create_text_message_content("Invalid sticker", {}, WebPageId(), false, false, false, string()); @@ -13258,7 +13465,7 @@ std::pair> MessagesManager::creat } bool hide_edit_date = message_info.hide_edit_date; - if (hide_edit_date && td_->auth_manager_->is_bot()) { + if (hide_edit_date && is_bot) { hide_edit_date = false; } if (hide_edit_date && content_type == MessageContentType::LiveLocation) { @@ -13278,7 +13485,7 @@ std::pair> MessagesManager::creat LOG(ERROR) << "Wrong " << ttl << " received in " << message_id << " in " << dialog_id; ttl = {}; } else { - ttl.ensure_at_least(get_message_content_duration(message_info.content.get(), td_) + 1); + ttl.ensure_at_least(get_message_content_duration(message_info.content.get(), td) + 1); } } @@ -13291,6 +13498,10 @@ std::pair> MessagesManager::creat LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.reactions); message_info.reactions = nullptr; } + if (message_info.fact_check != nullptr) { + LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with " << to_string(message_info.fact_check); + message_info.fact_check = nullptr; + } } int32 view_count = message_info.view_count; if (view_count < 0) { @@ -13302,8 +13513,9 @@ std::pair> MessagesManager::creat LOG(ERROR) << "Wrong forward_count = " << forward_count << " received in " << message_id << " in " << dialog_id; forward_count = 0; } - MessageReplyInfo reply_info(td_, std::move(message_info.reply_info), td_->auth_manager_->is_bot()); - if (!top_thread_message_id.is_valid() && is_thread_message(dialog_id, message_id, reply_info, content_type)) { + MessageReplyInfo reply_info(td, std::move(message_info.reply_info), is_bot); + if (!top_thread_message_id.is_valid() && + td->messages_manager_->is_thread_message(dialog_id, message_id, reply_info, content_type)) { top_thread_message_id = message_id; is_topic_message = (content_type == MessageContentType::TopicCreate); } @@ -13315,21 +13527,15 @@ std::pair> MessagesManager::creat // just in case is_topic_message = false; } - auto reactions = - MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot()); + auto reactions = MessageReactions::get_message_reactions(td, std::move(message_info.reactions), is_bot); if (reactions != nullptr) { - reactions->sort_reactions(active_reaction_pos_); + reactions->sort_reactions(td->messages_manager_->active_reaction_pos_); reactions->fix_chosen_reaction(); - reactions->fix_my_recent_chooser_dialog_id(td_->dialog_manager_->get_my_dialog_id()); + reactions->fix_my_recent_chooser_dialog_id(my_dialog_id); } + auto fact_check = FactCheck::get_fact_check(td, std::move(message_info.fact_check), is_bot); bool has_forward_info = message_info.forward_header != nullptr; - - if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id) { - CHECK(sender_dialog_id.get_type() != DialogType::User); - force_create_dialog(sender_dialog_id, "create_message", true); - } - bool noforwards = message_info.noforwards; bool is_expired = is_expired_message_content(content_type); if (is_expired) { @@ -13349,7 +13555,7 @@ std::pair> MessagesManager::creat bool has_mention = message_info.has_mention || (content_type == MessageContentType::PinMessage && - td_->option_manager_->get_option_boolean("process_pinned_messages_as_mentions")); + td->option_manager_->get_option_boolean("process_pinned_messages_as_mentions")); LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id; @@ -13363,11 +13569,12 @@ std::pair> MessagesManager::creat message->disable_web_page_preview = message_info.disable_web_page_preview; message->edit_date = edit_date; message->random_id = message_info.random_id; - message->forward_info = MessageForwardInfo::get_message_forward_info(td_, std::move(message_info.forward_header)); + message->forward_info = MessageForwardInfo::get_message_forward_info(td, std::move(message_info.forward_header)); message->replied_message_info = std::move(message_info.reply_header.replied_message_info_); message->top_thread_message_id = top_thread_message_id; message->is_topic_message = is_topic_message; message->via_bot_user_id = via_bot_user_id; + message->via_business_bot_user_id = message_info.via_business_bot_user_id; message->reply_to_story_full_id = reply_to_story_full_id; message->restriction_reasons = std::move(message_info.restriction_reasons); message->author_signature = std::move(message_info.author_signature); @@ -13375,17 +13582,17 @@ std::pair> MessagesManager::creat message->saved_messages_topic_id = message_info.saved_messages_topic_id; message->is_outgoing = is_outgoing; message->is_channel_post = is_channel_post; - message->contains_mention = - !is_outgoing && dialog_type != DialogType::User && !is_expired && has_mention && !td_->auth_manager_->is_bot(); + message->contains_mention = !is_outgoing && dialog_type != DialogType::User && !is_expired && has_mention && !is_bot; message->contains_unread_mention = !message_id.is_scheduled() && message_id.is_server() && message->contains_mention && message_info.has_unread_content && (dialog_type == DialogType::Chat || - (dialog_type == DialogType::Channel && !td_->dialog_manager_->is_broadcast_channel(dialog_id))); + (dialog_type == DialogType::Channel && !td->dialog_manager_->is_broadcast_channel(dialog_id))); message->disable_notification = message_info.is_silent; message->is_content_secret = is_content_secret; message->hide_edit_date = hide_edit_date; message->is_from_scheduled = message_info.is_from_scheduled; + message->is_from_offline = message_info.is_from_offline; message->is_pinned = is_pinned; message->noforwards = noforwards; message->interaction_info_update_date = G()->unix_time(); @@ -13393,10 +13600,12 @@ std::pair> MessagesManager::creat message->forward_count = forward_count; message->reply_info = std::move(reply_info); message->reactions = std::move(reactions); + message->fact_check = std::move(fact_check); + message->effect_id = message_info.effect_id; message->legacy_layer = (message_info.is_legacy ? MTPROTO_LAYER : 0); message->invert_media = message_info.invert_media; message->content = std::move(message_info.content); - message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false, + message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), is_bot, false, message->contains_mention || dialog_type == DialogType::User); if (message->reply_markup != nullptr && is_expired) { // just in case @@ -13411,7 +13620,8 @@ std::pair> MessagesManager::creat if (content_type != MessageContentType::Unsupported) { LOG(ERROR) << "Receive media group identifier " << message_info.media_album_id << " in " << message_id << " from " << dialog_id << " with content " - << oneline(to_string(get_message_message_content_object(dialog_id, message.get()))); + << oneline(to_string( + td->messages_manager_->get_message_message_content_object(dialog_id, message.get()))); } } else { message->media_album_id = message_info.media_album_id; @@ -13436,9 +13646,11 @@ std::pair> MessagesManager::creat } Dependencies dependencies; - add_message_dependencies(dependencies, message.get()); + td->messages_manager_->add_message_dependencies(dependencies, message.get()); for (auto dependent_dialog_id : dependencies.get_dialog_ids()) { - force_create_dialog(dependent_dialog_id, source, true); + if (dependent_dialog_id != dialog_id) { + td->dialog_manager_->force_create_dialog(dependent_dialog_id, source, true); + } } return {dialog_id, std::move(message)}; @@ -13483,7 +13695,7 @@ void MessagesManager::delete_update_message_id(DialogId dialog_id, MessageId mes MessageFullId MessagesManager::on_get_message(tl_object_ptr message_ptr, bool from_update, bool is_channel_message, bool is_scheduled, const char *source) { - return on_get_message(parse_telegram_api_message(std::move(message_ptr), is_scheduled, source), from_update, + return on_get_message(parse_telegram_api_message(td_, std::move(message_ptr), is_scheduled, source), from_update, is_channel_message, source); } @@ -13491,7 +13703,7 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const const bool is_channel_message, const char *source) { DialogId dialog_id; unique_ptr new_message; - std::tie(dialog_id, new_message) = create_message(std::move(message_info), is_channel_message, source); + std::tie(dialog_id, new_message) = create_message(td_, std::move(message_info), is_channel_message, false, source); if (new_message == nullptr) { return MessageFullId(); } @@ -13525,7 +13737,7 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const LOG(INFO) << "Ignore " << old_message_id << "/" << message_id << " received not through update from " << source << ": " << oneline(to_string(get_message_object(dialog_id, new_message.get(), "on_get_message"))); if (dialog_id.get_type() == DialogType::Channel && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { schedule_get_channel_difference(dialog_id, 0, message_id, 0.001, "on_get_message"); } return MessageFullId(); @@ -13612,7 +13824,7 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const } if (dialog_id.get_type() == DialogType::Channel && - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { auto message = delete_message(d, message_id, false, &need_update_dialog_pos, "get a message in inaccessible chat"); CHECK(message.get() == m); send_update_delete_messages(dialog_id, {m->message_id.get()}, false); @@ -13641,11 +13853,11 @@ MessageFullId MessagesManager::on_get_message(MessageInfo &&message_info, const auto pending_created_dialog = std::move(it->second); pending_created_dialogs_.erase(it); - pending_created_dialog.promise_.set_value(get_chat_object(d)); - if (!pending_created_dialog.group_invite_privacy_forbidden_user_ids_.empty()) { - send_closure(G()->dialog_participant_manager(), - &DialogParticipantManager::send_update_add_chat_members_privacy_forbidden, dialog_id, - std::move(pending_created_dialog.group_invite_privacy_forbidden_user_ids_), "on_get_message"); + if (pending_created_dialog.chat_promise_) { + pending_created_dialog.chat_promise_.set_value(td_api::make_object( + get_chat_id_object(dialog_id, "on_get_message"), std::move(pending_created_dialog.failed_to_add_members_))); + } else { + pending_created_dialog.channel_promise_.set_value(get_chat_object(d, "on_get_message")); } } } @@ -14203,7 +14415,7 @@ void MessagesManager::on_update_sent_text_message(int64 random_id, const FormattedText *old_message_text = get_message_content_text(m->content.get()); CHECK(old_message_text != nullptr); FormattedText new_message_text = get_message_text( - td_->contacts_manager_.get(), old_message_text->text, std::move(entities), true, td_->auth_manager_->is_bot(), + td_->user_manager_.get(), old_message_text->text, std::move(entities), true, td_->auth_manager_->is_bot(), get_message_original_date(m), m->media_album_id != 0, "on_update_sent_text_message"); auto new_content = get_message_content(td_, std::move(new_message_text), std::move(message_media), dialog_id, m->date, true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/, @@ -14541,7 +14753,7 @@ void MessagesManager::on_get_dialogs(FolderId folder_id, vectorneed_repair_server_unread_count && (d->last_read_inbox_message_id <= read_inbox_max_message_id || !need_unread_counter(d->order) || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read))) { + !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read))) { LOG(INFO) << "Repaired server unread count in " << dialog_id << " from " << d->last_read_inbox_message_id << "/" << d->server_unread_count << " to " << read_inbox_max_message_id << "/" << dialog->unread_count_; d->need_repair_server_unread_count = false; @@ -15059,7 +15271,7 @@ unique_ptr MessagesManager::do_delete_message(Dialog * if (*it != nullptr) { if ((*it)->get_message_id() < d->first_database_message_id && d->dialog_id.get_type() == DialogType::Channel) { - // possible if messages was deleted from database, but not from memory after updateChannelTooLong + // possible if messages were deleted from database, but not from memory after updateChannelTooLong set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 1"); } else { set_dialog_last_database_message_id(d, (*it)->get_message_id(), "do_delete_message 2"); @@ -15223,8 +15435,8 @@ void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanen break; case DialogType::Channel: if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { - td_->contacts_manager_->unregister_message_users({d->dialog_id, m->message_id}, get_message_user_ids(m)); - td_->contacts_manager_->unregister_message_channels({d->dialog_id, m->message_id}, get_message_channel_ids(m)); + td_->user_manager_->unregister_message_users({d->dialog_id, m->message_id}, get_message_user_ids(m)); + td_->chat_manager_->unregister_message_channels({d->dialog_id, m->message_id}, get_message_channel_ids(m)); } break; case DialogType::SecretChat: @@ -15362,7 +15574,12 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promiseauth_manager_->is_bot() && dialog_id.get_type() == DialogType::User && + !td_->user_manager_->have_user(dialog_id.get_user_id())) { + need_load = true; + } + if (need_load) { if (G()->use_message_database()) { // TODO load dialog from database, DialogLoader // send_closure_later(actor_id(this), &MessagesManager::load_dialog_from_database, dialog_id, @@ -15373,14 +15590,14 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promisecontacts_manager_->get_user(user_id, left_tries, std::move(promise)); + auto have_user = td_->user_manager_->get_user(user_id, left_tries, std::move(promise)); if (!have_user) { return false; } break; } case DialogType::Chat: { - auto have_chat = td_->contacts_manager_->get_chat(dialog_id.get_chat_id(), left_tries, std::move(promise)); + auto have_chat = td_->chat_manager_->get_chat(dialog_id.get_chat_id(), left_tries, std::move(promise)); if (!have_chat) { return false; } @@ -15388,7 +15605,7 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promisecontacts_manager_->get_channel(dialog_id.get_channel_id(), left_tries, std::move(promise)); + td_->chat_manager_->get_channel(dialog_id.get_channel_id(), left_tries, std::move(promise)); if (!have_channel) { return false; } @@ -15401,7 +15618,7 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promisedialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return false; } @@ -15841,7 +16058,8 @@ void MessagesManager::get_dialogs_from_list(DialogListId dialog_list_id, int32 l Promise> &&promise) { CHECK(!td_->auth_manager_->is_bot()); - if (get_dialog_list(dialog_list_id) == nullptr) { + auto *list = get_dialog_list(dialog_list_id); + if (list == nullptr) { return promise.set_error(Status::Error(400, "Chat list not found")); } @@ -15852,6 +16070,7 @@ void MessagesManager::get_dialogs_from_list(DialogListId dialog_list_id, int32 l auto task_id = ++current_get_dialogs_task_id_; auto &task = get_dialogs_tasks_[task_id]; task.dialog_list_id = dialog_list_id; + task.dialog_list_unique_id = list->unique_id_; task.retry_count = 5; task.limit = limit; task.promise = std::move(promise); @@ -15889,11 +16108,15 @@ void MessagesManager::get_dialogs_from_list_impl(int64 task_id) { void MessagesManager::on_get_dialogs_from_list(int64 task_id, Result &&result) { auto task_it = get_dialogs_tasks_.find(task_id); if (task_it == get_dialogs_tasks_.end()) { - // the task has already been completed LOG(INFO) << "Chat list load task " << task_id << " has already been completed"; return; } auto &task = task_it->second; + auto list_ptr = get_dialog_list(task.dialog_list_id); + if (result.is_ok() && (list_ptr == nullptr || list_ptr->unique_id_ != task.dialog_list_unique_id)) { + CHECK(!task.dialog_list_id.is_folder()); + result = Status::Error(400, "Chat list not found"); + } if (result.is_error()) { LOG(INFO) << "Chat list load task " << task_id << " failed with the error " << result.error(); auto task_promise = std::move(task.promise); @@ -15901,7 +16124,6 @@ void MessagesManager::on_get_dialogs_from_list(int64 task_id, Result &&res return task_promise.set_error(result.move_as_error()); } - auto list_ptr = get_dialog_list(task.dialog_list_id); CHECK(list_ptr != nullptr); auto &list = *list_ptr; if (task.last_dialog_date == list.list_last_dialog_date_) { @@ -16014,7 +16236,7 @@ vector MessagesManager::search_public_dialogs(const string &query, Pro auto d = get_dialog(dialog_id); if (d == nullptr || d->order != DEFAULT_ORDER || (dialog_id.get_type() == DialogType::User && - td_->contacts_manager_->is_user_contact(dialog_id.get_user_id()))) { + td_->user_manager_->is_user_contact(dialog_id.get_user_id()))) { continue; } @@ -16150,14 +16372,9 @@ vector MessagesManager::search_dialogs_on_server(const string &query, void MessagesManager::block_message_sender_from_replies(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam, Promise &&promise) { - auto dialog_id = DialogId(ContactsManager::get_replies_bot_user_id()); - Dialog *d = get_dialog_force(dialog_id, "block_message_sender_from_replies"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Not enough rights")); - } + auto dialog_id = DialogId(UserManager::get_replies_bot_user_id()); + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, false, AccessRights::Read, "block_message_sender_from_replies")); auto *m = get_message_force(d, message_id, "block_message_sender_from_replies"); if (m == nullptr) { @@ -16406,7 +16623,7 @@ MessageFullId MessagesManager::get_replied_message(DialogId dialog_id, MessageId promise.set_value(Unit()); return {}; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { promise.set_value(Unit()); return {}; } @@ -16447,7 +16664,7 @@ Result MessagesManager::get_top_thread_message_full_id(DialogId d return Status::Error(400, "Message has no thread"); } if (!allow_non_root && m->top_thread_message_id != m->message_id && - !td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { + !td_->chat_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { return Status::Error(400, "Root message must be used to get the message thread"); } return MessageFullId{dialog_id, m->top_thread_message_id}; @@ -16457,13 +16674,7 @@ Result MessagesManager::get_top_thread_message_full_id(DialogId d void MessagesManager::get_message_thread(DialogId dialog_id, MessageId message_id, Promise &&promise) { LOG(INFO) << "Get message thread from " << message_id << " in " << dialog_id; - Dialog *d = get_dialog_force(dialog_id, "get_message_thread"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, false, AccessRights::Read, "get_message_thread")); if (dialog_id.get_type() != DialogType::Channel) { return promise.set_error(Status::Error(400, "Chat is not a supergroup or a channel")); } @@ -16511,8 +16722,8 @@ void MessagesManager::process_discussion_message( Promise promise) { LOG(INFO) << "Receive discussion message for " << message_id << " in " << dialog_id << " with expected " << expected_message_id << " in " << expected_dialog_id << ": " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "process_discussion_message"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message"); + td_->user_manager_->on_get_users(std::move(result->users_), "process_discussion_message"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message"); for (auto &message : result->messages_) { auto message_dialog_id = DialogId::get_message_dialog_id(message); @@ -16592,7 +16803,7 @@ void MessagesManager::on_get_discussion_message(DialogId dialog_id, MessageId me } if (message_thread_info.message_ids.empty()) { if (message_thread_info.dialog_id != dialog_id && - !td_->dialog_manager_->have_input_peer(message_thread_info.dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(message_thread_info.dialog_id, false, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access message comments")); } return promise.set_error(Status::Error(400, "Message has no thread")); @@ -16706,14 +16917,14 @@ Status MessagesManager::can_get_message_read_date(DialogId dialog_id, const Mess if (dialog_id.get_type() != DialogType::User) { return Status::Error(400, "Read date can be received only in private chats"); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } auto user_id = dialog_id.get_user_id(); - if (td_->contacts_manager_->is_user_bot(user_id)) { + if (td_->user_manager_->is_user_bot(user_id)) { return Status::Error(400, "The user is a bot"); } - if (td_->contacts_manager_->is_user_support(user_id)) { + if (td_->user_manager_->is_user_support(user_id)) { return Status::Error(400, "The user is a Telegram support account"); } @@ -16749,7 +16960,7 @@ void MessagesManager::get_message_read_date(MessageFullId message_full_id, if (d->last_read_outbox_message_id < m->message_id) { return promise.set_value(td_api::make_object()); } - if (td_->contacts_manager_->get_user_read_dates_private(dialog_id.get_user_id())) { + if (td_->user_manager_->get_user_read_dates_private(dialog_id.get_user_id())) { return promise.set_value(td_api::make_object()); } @@ -16790,20 +17001,20 @@ Status MessagesManager::can_get_message_viewers(DialogId dialog_id, const Messag case DialogType::User: return Status::Error(400, "Can't get message viewers in private chats"); case DialogType::Chat: - if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) { + if (!td_->chat_manager_->get_chat_is_active(dialog_id.get_chat_id())) { return Status::Error(400, "Chat is deactivated"); } - participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id()); + participant_count = td_->chat_manager_->get_chat_participant_count(dialog_id.get_chat_id()); break; case DialogType::Channel: if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { return Status::Error(400, "Can't get message viewers in channel chats"); } - if (td_->contacts_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id(), - "can_get_message_viewers")) { + if (td_->chat_manager_->get_channel_effective_has_hidden_participants(dialog_id.get_channel_id(), + "can_get_message_viewers")) { return Status::Error(400, "Participant list is hidden in the chat"); } - participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id()); + participant_count = td_->chat_manager_->get_channel_participant_count(dialog_id.get_channel_id()); break; case DialogType::SecretChat: return Status::Error(400, "Can't get message viewers in secret chats"); @@ -16812,7 +17023,7 @@ Status MessagesManager::can_get_message_viewers(DialogId dialog_id, const Messag UNREACHABLE(); return Status::OK(); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return Status::Error(400, "Can't access the chat"); } if (participant_count == 0) { @@ -16863,7 +17074,7 @@ void MessagesManager::on_get_message_viewers(DialogId dialog_id, MessageViewers if (!is_recursive) { bool need_participant_list = false; for (auto user_id : message_viewers.get_user_ids()) { - if (!td_->contacts_manager_->have_user_force(user_id, "on_get_message_viewers")) { + if (!td_->user_manager_->have_user_force(user_id, "on_get_message_viewers")) { need_participant_list = true; } } @@ -16877,8 +17088,8 @@ void MessagesManager::on_get_message_viewers(DialogId dialog_id, MessageViewers switch (dialog_id.get_type()) { case DialogType::Chat: - return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise), - "on_get_message_viewers"); + return td_->chat_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise), + "on_get_message_viewers"); case DialogType::Channel: return td_->dialog_participant_manager_->get_channel_participants( dialog_id.get_channel_id(), td_api::make_object(), string(), 0, @@ -16891,7 +17102,7 @@ void MessagesManager::on_get_message_viewers(DialogId dialog_id, MessageViewers } } } - promise.set_value(message_viewers.get_message_viewers_object(td_->contacts_manager_.get())); + promise.set_value(message_viewers.get_message_viewers_object(td_->user_manager_.get())); } void MessagesManager::translate_message_text(MessageFullId message_full_id, const string &to_language_code, @@ -17080,8 +17291,8 @@ void MessagesManager::get_messages_from_server(vector &&message_i } for (auto &it : channel_message_ids) { - td_->contacts_manager_->have_channel_force(it.first, "get_messages_from_server"); - auto input_channel = td_->contacts_manager_->get_input_channel(it.first); + td_->chat_manager_->have_channel_force(it.first, "get_messages_from_server"); + auto input_channel = td_->chat_manager_->get_input_channel(it.first); if (input_channel == nullptr) { LOG(ERROR) << "Can't find info about " << it.first << " to get a message from it from " << source; mpas.get_promise().set_error(Status::Error(400, "Can't access the chat")); @@ -17116,7 +17327,7 @@ MessagesManager::ReportDialogFromActionBar MessagesManager::report_dialog_from_a ReportDialogFromActionBar result; Dialog *d = nullptr; if (dialog_id.get_type() == DialogType::SecretChat) { - auto user_dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); + auto user_dialog_id = DialogId(td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); d = get_dialog_force(user_dialog_id, "report_dialog_from_action_bar"); if (d == nullptr) { promise.set_error(Status::Error(400, "Chat with the user not found")); @@ -17170,7 +17381,7 @@ Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const M bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Message *m) const { CHECK(m != nullptr); if (dialog_id.get_type() != DialogType::Channel || td_->dialog_manager_->is_broadcast_channel(dialog_id) || - !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id())) { + !td_->chat_manager_->is_channel_public(dialog_id.get_channel_id())) { return false; } if (m->message_id.is_scheduled() || !m->message_id.is_server()) { @@ -17186,13 +17397,7 @@ bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Mes Result> MessagesManager::get_message_link(MessageFullId message_full_id, int32 media_timestamp, bool for_group, bool in_message_thread) { auto dialog_id = message_full_id.get_dialog_id(); - auto d = get_dialog_force(dialog_id, "get_message_link"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "get_message_link")); auto *m = get_message_force(d, message_full_id.get_message_id(), "get_message_link"); TRY_STATUS(can_get_media_timestamp_link(dialog_id, m)); @@ -17254,10 +17459,11 @@ Result> MessagesManager::get_message_link(MessageFullId CHECK(linked_d != nullptr); CHECK(linked_dialog_id.get_type() == DialogType::Channel); auto *linked_m = get_message_force(linked_d, linked_message_id, "get_public_message_link"); - auto channel_username = td_->contacts_manager_->get_channel_first_username(linked_dialog_id.get_channel_id()); + auto channel_username = td_->chat_manager_->get_channel_first_username(linked_dialog_id.get_channel_id()); if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info) && linked_message_id.is_server() && - td_->dialog_manager_->have_input_peer(linked_dialog_id, AccessRights::Read) && !channel_username.empty()) { + td_->dialog_manager_->have_input_peer(linked_dialog_id, false, AccessRights::Read) && + !channel_username.empty()) { sb << channel_username << '/' << linked_message_id.get_server_message_id().get() << "?comment=" << message_id.get_server_message_id().get(); if (!for_group) { @@ -17271,7 +17477,7 @@ Result> MessagesManager::get_message_link(MessageFullId } } - auto dialog_username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); + auto dialog_username = td_->chat_manager_->get_channel_first_username(dialog_id.get_channel_id()); bool is_public = !dialog_username.empty(); if (m->content->get_type() == MessageContentType::VideoNote && td_->dialog_manager_->is_broadcast_channel(dialog_id) && is_public) { @@ -17314,17 +17520,14 @@ Result> MessagesManager::get_message_link(MessageFullId string MessagesManager::get_message_embedding_code(MessageFullId message_full_id, bool for_group, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); - auto d = get_dialog_force(dialog_id, "get_message_embedding_code"); - if (d == nullptr) { - promise.set_error(Status::Error(400, "Chat not found")); - return {}; - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - promise.set_error(Status::Error(400, "Can't access the chat")); + auto r_d = check_dialog_access(dialog_id, false, AccessRights::Read, "get_message_embedding_code"); + if (r_d.is_error()) { + promise.set_error(r_d.move_as_error()); return {}; } + auto d = r_d.move_as_ok(); if (dialog_id.get_type() != DialogType::Channel || - td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) { + td_->chat_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) { promise.set_error(Status::Error( 400, "Message embedding code is available only for messages in public supergroups and channel chats")); return {}; @@ -17419,7 +17622,7 @@ void MessagesManager::on_get_message_link_message(MessageLinkInfo &&info, Dialog return promise.set_value(std::move(info)); } - if (td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_, "on_get_message_link_message")) { + if (td_->chat_manager_->have_channel_force(m->reply_info.channel_id_, "on_get_message_link_message")) { force_create_dialog(DialogId(m->reply_info.channel_id_), "on_get_message_link_message"); on_get_message_link_discussion_message(std::move(info), DialogId(m->reply_info.channel_id_), std::move(promise)); return; @@ -17517,13 +17720,7 @@ Status MessagesManager::can_add_dialog_to_filter(DialogId dialog_id) { if (!dialog_id.is_valid()) { return Status::Error(400, "Invalid chat identifier specified"); } - const Dialog *d = get_dialog_force(dialog_id, "can_add_dialog_to_filter"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "can_add_dialog_to_filter")); if (d->order == DEFAULT_ORDER) { return Status::Error(400, "Chat is not in the chat list"); } @@ -17701,13 +17898,7 @@ Status MessagesManager::toggle_dialog_is_pinned(DialogListId dialog_list_id, Dia return Status::Error(400, "Bots can't change chat pin state"); } - Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_pinned"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "toggle_dialog_is_pinned")); if (d->order == DEFAULT_ORDER && is_pinned) { return Status::Error(400, "The chat can't be pinned"); } @@ -17857,13 +18048,7 @@ Status MessagesManager::set_pinned_dialogs(DialogListId dialog_list_id, vector new_pinned_dialog_ids; for (auto dialog_id : dialog_ids) { - Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "set_pinned_dialogs")); if (d->order == DEFAULT_ORDER) { return Status::Error(400, "The chat can't be pinned"); } @@ -17959,13 +18144,7 @@ void MessagesManager::reorder_pinned_dialogs_on_server(FolderId folder_id, const } Status MessagesManager::toggle_dialog_view_as_messages(DialogId dialog_id, bool view_as_messages) { - Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_view_as_messages"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, false, AccessRights::Read, "toggle_dialog_view_as_messages")); bool is_saved_messages = dialog_id == td_->dialog_manager_->get_my_dialog_id(); if (!is_saved_messages && !td_->dialog_manager_->is_forum_channel(dialog_id)) { return Status::Error(400, "The method is available only in forum channels"); @@ -18025,14 +18204,7 @@ void MessagesManager::toggle_dialog_view_as_messages_on_server(DialogId dialog_i } Status MessagesManager::toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) { - Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_marked_as_unread"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } - + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "toggle_dialog_is_marked_as_unread")); if (is_marked_as_unread == d->is_marked_as_unread) { return Status::OK(); } @@ -18090,14 +18262,7 @@ void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialo } Status MessagesManager::toggle_dialog_is_translatable(DialogId dialog_id, bool is_translatable) { - Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_translatable"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } - + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "toggle_dialog_is_translatable")); if (is_translatable == d->is_translatable) { return Status::OK(); } @@ -18173,8 +18338,8 @@ Status MessagesManager::set_message_sender_block_list(const td_api::object_ptrcontacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); - if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id, "set_message_sender_block_list")) { + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + if (!user_id.is_valid() || !td_->user_manager_->have_user_force(user_id, "set_message_sender_block_list")) { return Status::Error(400, "The secret chat can't be blocked"); } dialog_id = DialogId(user_id); @@ -18186,7 +18351,7 @@ Status MessagesManager::set_message_sender_block_list(const td_api::object_ptrdialog_manager_->have_input_peer(dialog_id, AccessRights::Know)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Know)) { return Status::Error(400, "Message sender isn't accessible"); } if (d != nullptr) { @@ -18196,7 +18361,7 @@ Status MessagesManager::set_message_sender_block_list(const td_api::object_ptrcontacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); + td_->user_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, is_blocked_for_stories, 0); @@ -18250,13 +18415,7 @@ void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, boo Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) { CHECK(!td_->auth_manager_->is_bot()); - Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_silent_send_message"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "toggle_dialog_silent_send_message")); if (update_dialog_silent_send_message(d, silent_send_message)) { update_dialog_notification_settings_on_server(dialog_id, false); @@ -18385,11 +18544,11 @@ bool MessagesManager::is_dialog_mention_notifications_disabled(const Dialog *d) } void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise &&promise) { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { if (!td_->dialog_manager_->have_dialog_info_force(dialog_id, "create dialog")) { return promise.set_error(Status::Error(400, "Chat info not found")); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } } @@ -18435,13 +18594,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess bool force_read) { CHECK(!td_->auth_manager_->is_bot()); - Dialog *d = get_dialog_force(dialog_id, "view_messages"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "view_messages")); if (source == MessageSource::Auto) { if (d->open_count > 0) { @@ -18458,8 +18611,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess bool need_mark_download_as_viewed = is_dialog_history || source == MessageSource::HistoryPreview || source == MessageSource::Search || source == MessageSource::Other; bool need_invalidate_authentication_code = - dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && - source == MessageSource::Screenshot; + dialog_id == DialogId(UserManager::get_service_notifications_user_id()) && source == MessageSource::Screenshot; auto dialog_type = dialog_id.get_type(); bool need_screenshot_notification = source == MessageSource::Screenshot && (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) && @@ -18595,6 +18747,7 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess vector read_reaction_message_ids; vector new_viewed_message_ids; vector viewed_reaction_message_ids; + vector viewed_fact_check_message_ids; vector authentication_codes; vector screenshotted_secret_message_ids; for (auto message_id : message_ids) { @@ -18662,6 +18815,12 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess info->recently_viewed_messages[view_id] = message_id; } + if (m->message_id.is_server() && m->fact_check != nullptr && m->fact_check->need_check() && + being_reloaded_fact_checks_.insert({dialog_id, message_id}).second) { + CHECK(message_id.is_server()); + viewed_fact_check_message_ids.push_back(message_id); + } + if (need_invalidate_authentication_code) { extract_authentication_codes(dialog_id, m, authentication_codes); } @@ -18712,6 +18871,17 @@ Status MessagesManager::view_messages(DialogId dialog_id, vector mess queue_message_reactions_reload(dialog_id, viewed_reaction_message_ids); } } + if (!viewed_fact_check_message_ids.empty()) { + CHECK(dialog_id.get_type() != DialogType::SecretChat); + auto promise = + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_ids = viewed_fact_check_message_ids]( + Result>> r_fact_checks) { + send_closure(actor_id, &MessagesManager::on_get_message_fact_checks, dialog_id, message_ids, + std::move(r_fact_checks)); + }); + 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) { update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD); } @@ -18848,6 +19018,34 @@ void MessagesManager::finish_get_message_extended_media(DialogId dialog_id, cons } } +void MessagesManager::on_get_message_fact_checks( + DialogId dialog_id, const vector &message_ids, + Result>> r_fact_checks) { + G()->ignore_result_if_closing(r_fact_checks); + for (auto message_id : message_ids) { + auto erased_count = being_reloaded_fact_checks_.erase({dialog_id, message_id}); + CHECK(erased_count > 0); + } + if (r_fact_checks.is_error() || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { + return; + } + auto fact_checks = r_fact_checks.move_as_ok(); + if (fact_checks.size() != message_ids.size()) { + LOG(ERROR) << "Receive " << fact_checks.size() << " fact checks instead of " << message_ids.size(); + return; + } + auto *d = get_dialog(dialog_id); + CHECK(d != nullptr); + for (size_t i = 0; i < message_ids.size(); i++) { + auto *m = get_message_force(d, message_ids[i], "on_get_message_fact_checks"); + if (m == nullptr) { + continue; + } + auto fact_check = FactCheck::get_fact_check(td_, std::move(fact_checks[i]), false); + update_message_fact_check(d, m, std::move(fact_check), true); + } +} + Status MessagesManager::open_message_content(MessageFullId message_full_id) { auto dialog_id = message_full_id.get_dialog_id(); Dialog *d = get_dialog_force(dialog_id, "open_message_content"); @@ -18970,7 +19168,7 @@ void MessagesManager::click_animated_emoji_message(MessageFullId message_full_id void MessagesManager::open_dialog(Dialog *d) { CHECK(!td_->auth_manager_->is_bot()); DialogId dialog_id = d->dialog_id; - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return; } recently_opened_dialogs_.add_dialog(dialog_id); @@ -19020,14 +19218,14 @@ void MessagesManager::open_dialog(Dialog *d) { td_->story_manager_->on_view_dialog_active_stories({dialog_id}); break; case DialogType::Chat: - td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id()); + td_->chat_manager_->repair_chat_participants(dialog_id.get_chat_id()); reget_dialog_action_bar(dialog_id, "open_dialog", false); break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { - auto participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id); - auto has_hidden_participants = td_->contacts_manager_->get_channel_effective_has_hidden_participants( + auto participant_count = td_->chat_manager_->get_channel_participant_count(channel_id); + auto has_hidden_participants = td_->chat_manager_->get_channel_effective_has_hidden_participants( dialog_id.get_channel_id(), "open_dialog"); if (participant_count < 195 && !has_hidden_participants) { // include unknown participant_count td_->dialog_participant_manager_->get_channel_participants( @@ -19039,12 +19237,12 @@ void MessagesManager::open_dialog(Dialog *d) { get_channel_difference(dialog_id, d->pts, 0, MessageId(), true, "open_dialog"); reget_dialog_action_bar(dialog_id, "open_dialog", false); - if (td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) { - auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id, "open_dialog"); + if (td_->chat_manager_->get_channel_has_linked_channel(channel_id)) { + auto linked_channel_id = td_->chat_manager_->get_channel_linked_channel_id(channel_id, "open_dialog"); if (!linked_channel_id.is_valid()) { // load linked_channel_id - send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, - Promise(), "open_dialog"); + send_closure_later(G()->chat_manager(), &ChatManager::load_channel_full, channel_id, false, Promise(), + "open_dialog"); } else { td_->dialog_manager_->get_dialog_info_full(DialogId(linked_channel_id), Auto(), "open_dialog"); } @@ -19053,9 +19251,9 @@ void MessagesManager::open_dialog(Dialog *d) { } case DialogType::SecretChat: { // to repair dialog action bar - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { - td_->contacts_manager_->reload_user_full(user_id, Promise(), "open_dialog"); + td_->user_manager_->reload_user_full(user_id, Promise(), "open_dialog"); } break; } @@ -19093,7 +19291,7 @@ void MessagesManager::close_dialog(Dialog *d) { } auto dialog_id = d->dialog_id; - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Write)) { if (pending_draft_message_timeout_.has_timeout(dialog_id.get())) { pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), 0.0); } @@ -19101,7 +19299,7 @@ void MessagesManager::close_dialog(Dialog *d) { pending_draft_message_timeout_.cancel_timeout(dialog_id.get()); } - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { if (pending_message_views_timeout_.has_timeout(dialog_id.get())) { pending_message_views_timeout_.set_timeout_in(dialog_id.get(), 0.0); } @@ -19178,7 +19376,7 @@ td_api::object_ptr MessagesManager::get_chat_action_bar_o CHECK(d != nullptr); auto dialog_type = d->dialog_id.get_type(); if (dialog_type == DialogType::SecretChat) { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return nullptr; } @@ -19195,10 +19393,19 @@ td_api::object_ptr MessagesManager::get_chat_action_bar_o return d->action_bar->get_chat_action_bar_object(dialog_type, false); } +td_api::object_ptr MessagesManager::get_business_bot_manage_bar_object( + const Dialog *d) const { + CHECK(d != nullptr); + if (d->business_bot_manage_bar == nullptr) { + return nullptr; + } + return d->business_bot_manage_bar->get_business_bot_manage_bar_object(td_); +} + td_api::object_ptr MessagesManager::get_chat_background_object(const Dialog *d) const { CHECK(d != nullptr); if (d->dialog_id.get_type() == DialogType::SecretChat) { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return nullptr; } @@ -19213,7 +19420,7 @@ td_api::object_ptr MessagesManager::get_chat_background_ string MessagesManager::get_dialog_theme_name(const Dialog *d) const { CHECK(d != nullptr); if (d->dialog_id.get_type() == DialogType::SecretChat) { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return string(); } @@ -19231,8 +19438,8 @@ td_api::object_ptr MessagesManager::get_chat_join_ return nullptr; } return td_api::make_object( - d->pending_join_request_count, td_->contacts_manager_->get_user_ids_object(d->pending_join_request_user_ids, - "get_chat_join_requests_info_object")); + d->pending_join_request_count, + td_->user_manager_->get_user_ids_object(d->pending_join_request_user_ids, "get_chat_join_requests_info_object")); } td_api::object_ptr MessagesManager::get_video_chat_object(const Dialog *d) const { @@ -19253,7 +19460,7 @@ td_api::object_ptr MessagesManager::get_default_message_s : nullptr; } -td_api::object_ptr MessagesManager::get_chat_object(const Dialog *d) const { +td_api::object_ptr MessagesManager::get_chat_object(const Dialog *d, const char *source) const { CHECK(d != nullptr); bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); @@ -19261,13 +19468,13 @@ td_api::object_ptr MessagesManager::get_chat_object(const Dialog * auto can_delete = can_delete_dialog(d); // TODO hide/show draft message when need_hide_dialog_draft_message changes auto draft_message = !need_hide_dialog_draft_message(d) ? get_draft_message_object(td_, d->draft_message) : nullptr; - auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); + auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(td_); auto is_translatable = d->is_translatable && is_premium; auto block_list_id = BlockListId(d->is_blocked, d->is_blocked_for_stories); auto chat_lists = transform(get_dialog_list_ids(d), [](DialogListId dialog_list_id) { return dialog_list_id.get_chat_list_object(); }); return make_tl_object( - d->dialog_id.get(), td_->dialog_manager_->get_chat_type_object(d->dialog_id), + d->dialog_id.get(), td_->dialog_manager_->get_chat_type_object(d->dialog_id, source), td_->dialog_manager_->get_dialog_title(d->dialog_id), get_chat_photo_info_object(td_->file_manager_.get(), td_->dialog_manager_->get_dialog_photo(d->dialog_id)), td_->dialog_manager_->get_dialog_accent_color_id_object(d->dialog_id), @@ -19275,27 +19482,27 @@ 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), "get_chat_object"), - 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), + 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(), + 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), d->notification_settings.silent_send_message, d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(), d->last_read_outbox_message_id.get(), d->unread_mention_count, d->unread_reaction_count, get_chat_notification_settings_object(&d->notification_settings), std::move(available_reactions), d->message_ttl.get_message_auto_delete_time_object(), td_->dialog_manager_->get_dialog_emoji_status_object(d->dialog_id), get_chat_background_object(d), - get_dialog_theme_name(d), get_chat_action_bar_object(d), get_video_chat_object(d), - get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(), std::move(draft_message), - d->client_data); + get_dialog_theme_name(d), get_chat_action_bar_object(d), get_business_bot_manage_bar_object(d), + get_video_chat_object(d), get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(), + std::move(draft_message), d->client_data); } -td_api::object_ptr MessagesManager::get_chat_object(DialogId dialog_id) { +td_api::object_ptr MessagesManager::get_chat_object(DialogId dialog_id, const char *source) { const Dialog *d = get_dialog(dialog_id); if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) { - send_update_chat_read_inbox(d, true, "get_chat_object"); + send_update_chat_read_inbox(d, true, source); } - return get_chat_object(d); + return get_chat_object(d, source); } td_api::object_ptr MessagesManager::get_my_dialog_draft_message_object() const { @@ -19433,7 +19640,7 @@ DialogNotificationSettings *MessagesManager::get_dialog_notification_settings(Di if (d == nullptr) { return nullptr; } - if (!force && !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!force && !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return nullptr; } return &d->notification_settings; @@ -19527,7 +19734,7 @@ tl_object_ptr MessagesManager::get_dialog_history(DialogId dia promise.set_error(Status::Error(400, "Chat not found")); return nullptr; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return nullptr; } @@ -19747,7 +19954,7 @@ void MessagesManager::read_history_on_server_impl(Dialog *d, MessageId max_messa repair_server_unread_count(dialog_id, d->server_unread_count, "read_history_on_server_impl"); } - if (!max_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!max_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return promise.set_value(Unit()); } @@ -19811,7 +20018,7 @@ void MessagesManager::read_message_thread_history_on_server_impl(Dialog *d, Mess }); } - if (!max_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!max_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return promise.set_value(Unit()); } @@ -19893,7 +20100,7 @@ std::pair> MessagesManager::get_message_thread_histo promise.set_error(Status::Error(400, "Chat not found")); return {}; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } @@ -20067,7 +20274,7 @@ td_api::object_ptr MessagesManager::get_dialog_message_ promise.set_error(Status::Error(400, "Chat not found")); return {}; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } @@ -20254,7 +20461,7 @@ MessagesManager::FoundDialogMessages MessagesManager::search_dialog_messages( promise.set_error(Status::Error(400, "Chat not found")); return result; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return result; } @@ -20265,7 +20472,8 @@ MessagesManager::FoundDialogMessages MessagesManager::search_dialog_messages( return result; } auto sender_dialog_id = r_sender_dialog_id.move_as_ok(); - if (sender_dialog_id != DialogId() && !td_->dialog_manager_->have_input_peer(sender_dialog_id, AccessRights::Know)) { + if (sender_dialog_id != DialogId() && + !td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { promise.set_error(Status::Error(400, "Invalid message sender specified")); return result; } @@ -20481,6 +20689,28 @@ void MessagesManager::search_outgoing_document_messages(const string &query, int td_->create_handler(std::move(promise))->send(query, limit); } +void MessagesManager::search_hashtag_posts(string hashtag, string offset_str, int32 limit, + Promise> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + if (limit > MAX_SEARCH_MESSAGES) { + limit = MAX_SEARCH_MESSAGES; + } + + TRY_RESULT_PROMISE(promise, offset, MessageSearchOffset::from_string(offset_str)); + + if (hashtag[0] == '#') { + hashtag = hashtag.substr(1); + } + if (hashtag.empty()) { + return promise.set_value(get_found_messages_object({}, "search_hashtag_posts")); + } + send_closure(td_->hashtag_search_hints_, &HashtagHints::hashtag_used, hashtag); + + td_->create_handler(std::move(promise))->send(hashtag, offset, limit); +} + void MessagesManager::search_dialog_recent_location_messages(DialogId dialog_id, int32 limit, Promise> &&promise) { LOG(INFO) << "Search recent location messages in " << dialog_id << " with limit " << limit; @@ -20604,7 +20834,8 @@ void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Mes 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->forward_info != nullptr) { + 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; } @@ -20681,8 +20912,9 @@ void MessagesManager::on_message_live_location_viewed(Dialog *d, const Message * return; } - if (m->is_outgoing || !m->message_id.is_server() || m->via_bot_user_id.is_valid() || !m->sender_user_id.is_valid() || - td_->contacts_manager_->is_user_bot(m->sender_user_id) || m->forward_info != nullptr) { + if (m->is_outgoing || !m->message_id.is_server() || m->via_bot_user_id.is_valid() || + m->via_business_bot_user_id.is_valid() || !m->sender_user_id.is_valid() || + td_->user_manager_->is_user_bot(m->sender_user_id) || m->forward_info != nullptr) { return; } @@ -21000,36 +21232,20 @@ td_api::object_ptr MessagesManager::get_found_messages_ob found_messages.next_offset); } -MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId dialog_id, const string &query, - string offset, int32 limit, - MessageSearchFilter filter, int64 &random_id, - Promise &&promise) { +void MessagesManager::offline_search_messages(DialogId dialog_id, const string &query, string offset, int32 limit, + MessageSearchFilter filter, + Promise> &&promise) { if (!G()->use_message_database()) { - promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats")); - return {}; - } - - if (random_id != 0) { - // request has already been sent before - auto it = found_fts_messages_.find(random_id); - CHECK(it != found_fts_messages_.end()); - auto result = std::move(it->second); - found_fts_messages_.erase(it); - promise.set_value(Unit()); - return result; + return promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats")); } - if (query.empty()) { - promise.set_value(Unit()); - return {}; + return promise.set_value(get_found_messages_object({}, "offline_search_messages")); } if (dialog_id != DialogId() && !have_dialog_force(dialog_id, "offline_search_messages")) { - promise.set_error(Status::Error(400, "Chat not found")); - return {}; + return promise.set_error(Status::Error(400, "Chat not found")); } if (limit <= 0) { - promise.set_error(Status::Error(400, "Limit must be positive")); - return {}; + return promise.set_error(Status::Error(400, "Limit must be positive")); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; @@ -21042,56 +21258,43 @@ MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId if (!offset.empty()) { auto r_from_search_id = to_integer_safe(offset); if (r_from_search_id.is_error()) { - promise.set_error(Status::Error(400, "Invalid offset specified")); - return {}; + return promise.set_error(Status::Error(400, "Invalid offset specified")); } fts_query.from_search_id = r_from_search_id.ok(); } fts_query.limit = limit; - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || found_fts_messages_.count(random_id) > 0); - found_fts_messages_[random_id]; // reserve place for result - G()->td_db()->get_message_db_async()->get_messages_fts( - std::move(fts_query), - PromiseCreator::lambda([random_id, offset = std::move(offset), limit, - promise = std::move(promise)](Result fts_result) mutable { + std::move(fts_query), PromiseCreator::lambda([offset = std::move(offset), limit, promise = std::move(promise)]( + Result fts_result) mutable { send_closure(G()->messages_manager(), &MessagesManager::on_message_db_fts_result, std::move(fts_result), - std::move(offset), limit, random_id, std::move(promise)); + std::move(offset), limit, std::move(promise)); })); - - return {}; } void MessagesManager::on_message_db_fts_result(Result result, string offset, int32 limit, - int64 random_id, Promise &&promise) { + Promise> &&promise) { G()->ignore_result_if_closing(result); if (result.is_error()) { - found_fts_messages_.erase(random_id); return promise.set_error(result.move_as_error()); } auto fts_result = result.move_as_ok(); - auto it = found_fts_messages_.find(random_id); - CHECK(it != found_fts_messages_.end()); - auto &res = it->second.message_full_ids; - - res.reserve(fts_result.messages.size()); + FoundMessages found_messages; + found_messages.message_full_ids.reserve(fts_result.messages.size()); for (auto &message : fts_result.messages) { auto m = on_get_message_from_database(message, false, "on_message_db_fts_result"); if (m != nullptr) { - res.emplace_back(message.dialog_id, m->message_id); + found_messages.message_full_ids.emplace_back(message.dialog_id, m->message_id); } } - it->second.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id); - it->second.total_count = offset.empty() && fts_result.messages.size() < static_cast(limit) - ? static_cast(fts_result.messages.size()) - : -1; + found_messages.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id); + found_messages.total_count = offset.empty() && fts_result.messages.size() < static_cast(limit) + ? static_cast(fts_result.messages.size()) + : -1; - promise.set_value(Unit()); + promise.set_value(get_found_messages_object(found_messages, "on_message_db_fts_result")); } void MessagesManager::on_message_db_calls_result(Result result, int64 random_id, @@ -21133,132 +21336,65 @@ void MessagesManager::on_message_db_calls_result(Result re promise.set_value(Unit()); } -MessagesManager::FoundMessages MessagesManager::search_messages(FolderId folder_id, bool ignore_folder_id, - const string &query, const string &offset, int32 limit, - MessageSearchFilter filter, int32 min_date, - int32 max_date, int64 &random_id, - Promise &&promise) { - if (random_id != 0) { - // request has already been sent before - auto it = found_messages_.find(random_id); - CHECK(it != found_messages_.end()); - auto result = std::move(it->second); - found_messages_.erase(it); - promise.set_value(Unit()); - return result; +void MessagesManager::search_messages(DialogListId dialog_list_id, bool ignore_folder_id, bool broadcasts_only, + const string &query, const string &offset_str, int32 limit, + MessageSearchFilter filter, int32 min_date, int32 max_date, + Promise> &&promise) { + if (!dialog_list_id.is_folder()) { + return promise.set_error(Status::Error(400, "Wrong chat list specified")); } - if (limit <= 0) { - promise.set_error(Status::Error(400, "Parameter limit must be positive")); - return {}; + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } if (limit > MAX_SEARCH_MESSAGES) { limit = MAX_SEARCH_MESSAGES; } - int32 offset_date = std::numeric_limits::max(); - DialogId offset_dialog_id; - MessageId offset_message_id; - bool is_offset_valid = [&] { - if (offset.empty()) { - return true; - } - - auto parts = full_split(offset, ','); - if (parts.size() != 3) { - return false; - } - auto r_offset_date = to_integer_safe(parts[0]); - auto r_offset_dialog_id = to_integer_safe(parts[1]); - auto r_offset_message_id = to_integer_safe(parts[2]); - if (r_offset_date.is_error() || r_offset_date.ok() <= 0 || r_offset_message_id.is_error() || - r_offset_dialog_id.is_error()) { - return false; - } - offset_date = r_offset_date.ok(); - offset_message_id = MessageId(ServerMessageId(r_offset_message_id.ok())); - offset_dialog_id = DialogId(r_offset_dialog_id.ok()); - if (!offset_message_id.is_valid() || !offset_dialog_id.is_valid() || - DialogManager::get_input_peer_force(offset_dialog_id)->get_id() == telegram_api::inputPeerEmpty::ID) { - return false; - } - return true; - }(); - if (!is_offset_valid) { - promise.set_error(Status::Error(400, "Invalid offset specified")); - return {}; - } + TRY_RESULT_PROMISE(promise, offset, MessageSearchOffset::from_string(offset_str)); CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall); if (filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::FailedToSend || filter == MessageSearchFilter::Pinned) { - promise.set_error(Status::Error(400, "The filter is not supported")); - return {}; + return promise.set_error(Status::Error(400, "The filter is not supported")); } if (query.empty() && filter == MessageSearchFilter::Empty) { - promise.set_value(Unit()); - return {}; + return promise.set_value(get_found_messages_object({}, "search_messages")); } - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || found_messages_.count(random_id) > 0); - found_messages_[random_id]; // reserve place for result - - LOG(DEBUG) << "Search all messages filtered by " << filter << " with query = \"" << query << "\" from offset " - << offset << " and limit " << limit; - td_->create_handler(std::move(promise)) - ->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, filter, - min_date, max_date, random_id); - return {}; + ->send(dialog_list_id.get_folder_id(), ignore_folder_id, broadcasts_only, query, offset.date_, offset.dialog_id_, + offset.message_id_, limit, filter, min_date, max_date); } -int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_by_date"); - if (d == nullptr) { - promise.set_error(Status::Error(400, "Chat not found")); - return 0; - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - promise.set_error(Status::Error(400, "Can't access the chat")); - return 0; - } +void MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, + Promise> &&promise) { + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Read, "get_dialog_message_by_date")); if (date <= 0) { date = 1; } - int64 random_id = 0; - do { - random_id = Random::secure_int64(); - } while (random_id == 0 || get_dialog_message_by_date_results_.count(random_id) > 0); - get_dialog_message_by_date_results_[random_id]; // reserve place for result - 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())) { - get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; - promise.set_value(Unit()); - return random_id; + return promise.set_value(get_message_object(dialog_id, get_message(d, message_id), "get_dialog_message_by_date")); } if (G()->use_message_database() && d->last_database_message_id != MessageId()) { CHECK(d->first_database_message_id != MessageId()); G()->td_db()->get_message_db_async()->get_dialog_message_by_date( dialog_id, d->first_database_message_id, d->last_database_message_id, date, - PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, date, random_id, + PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, date, promise = std::move(promise)](Result result) mutable { send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_from_database, dialog_id, date, - random_id, std::move(result), std::move(promise)); + std::move(result), std::move(promise)); })); } else { - get_dialog_message_by_date_from_server(d, date, random_id, false, std::move(promise)); + get_dialog_message_by_date_from_server(d, date, false, std::move(promise)); } - return random_id; } void MessagesManager::run_affected_history_query_until_complete(DialogId dialog_id, AffectedHistoryQuery query, @@ -21314,9 +21450,9 @@ std::function MessagesManager::get_get_message_date(const Dial }; } -void MessagesManager::on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id, - Result result, - Promise promise) { +void MessagesManager::on_get_dialog_message_by_date_from_database( + DialogId dialog_id, int32 date, Result result, + Promise> promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); Dialog *d = get_dialog(dialog_id); @@ -21329,50 +21465,44 @@ void MessagesManager::on_get_dialog_message_by_date_from_database(DialogId dialo LOG(ERROR) << "Failed to find " << m->message_id << " in " << dialog_id << " by date " << date; message_id = m->message_id; } - get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; - promise.set_value(Unit()); + promise.set_value( + get_message_object(dialog_id, get_message(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 } - return get_dialog_message_by_date_from_server(d, date, random_id, true, std::move(promise)); + return get_dialog_message_by_date_from_server(d, date, true, std::move(promise)); } -void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id, - bool after_database_search, Promise &&promise) { +void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, int32 date, bool after_database_search, + Promise> &&promise) { CHECK(d != nullptr); if (d->have_full_history) { // request can always be done locally/in memory. There is no need to send request to the server if (after_database_search) { - return promise.set_value(Unit()); + return promise.set_value(nullptr); } auto message_id = d->ordered_messages.find_message_by_date(date, get_get_message_date(d)); if (message_id.is_valid()) { - get_dialog_message_by_date_results_[random_id] = {d->dialog_id, message_id}; + promise.set_value( + get_message_object(d->dialog_id, get_message(d, message_id), "get_dialog_message_by_date_from_server")); + } else { + promise.set_value(nullptr); } - promise.set_value(Unit()); return; } - if (d->dialog_id.get_type() == DialogType::SecretChat) { - // there is no way to send request to the server - return promise.set_value(Unit()); - } + CHECK(d->dialog_id.get_type() != DialogType::SecretChat); - td_->create_handler(std::move(promise))->send(d->dialog_id, date, random_id); + td_->create_handler(std::move(promise))->send(d->dialog_id, date); } -void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id, - vector> &&messages, - Promise &&promise) { +void MessagesManager::on_get_dialog_message_by_date(DialogId dialog_id, int32 date, + vector> &&messages, + Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - auto it = get_dialog_message_by_date_results_.find(random_id); - CHECK(it != get_dialog_message_by_date_results_.end()); - auto &result = it->second; - CHECK(result == MessageFullId()); - for (auto &message : messages) { auto message_date = get_message_date(message); auto message_dialog_id = DialogId::get_message_dialog_id(message); @@ -21381,8 +21511,8 @@ void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, continue; } if (message_date != 0 && message_date <= date) { - result = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, - "on_get_dialog_message_by_date_success"); + auto result = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, + "on_get_dialog_message_by_date_success"); if (result != MessageFullId()) { const Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); @@ -21391,27 +21521,12 @@ void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, LOG(ERROR) << "Failed to find " << result.get_message_id() << " in " << dialog_id << " by date " << date; message_id = result.get_message_id(); } - get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id}; - // TODO result must be adjusted by local messages - promise.set_value(Unit()); - return; + return promise.set_value( + get_message_object(dialog_id, get_message(d, message_id), "on_get_dialog_message_by_date")); } } } - promise.set_value(Unit()); -} - -void MessagesManager::on_get_dialog_message_by_date_fail(int64 random_id) { - auto erased_count = get_dialog_message_by_date_results_.erase(random_id); - CHECK(erased_count > 0); -} - -tl_object_ptr MessagesManager::get_dialog_message_by_date_object(int64 random_id) { - auto it = get_dialog_message_by_date_results_.find(random_id); - CHECK(it != get_dialog_message_by_date_results_.end()); - auto message_full_id = std::move(it->second); - get_dialog_message_by_date_results_.erase(it); - return get_message_object(message_full_id, "get_dialog_message_by_date_object"); + promise.set_value(nullptr); } void MessagesManager::get_dialog_sparse_message_positions( @@ -21570,13 +21685,8 @@ void MessagesManager::get_dialog_message_position(MessageFullId message_full_id, SavedMessagesTopicId saved_messages_topic_id, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_position"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Read, "get_dialog_message_position")); auto message_id = message_full_id.get_message_id(); const Message *m = get_message_force(d, message_id, "get_dialog_message_position"); @@ -22018,7 +22128,7 @@ void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_i } auto dialog_id = d->dialog_id; - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { // can't get history in dialogs without read access return promise.set_value(Unit()); } @@ -22154,12 +22264,12 @@ vector MessagesManager::get_dialog_scheduled_messages(DialogId dialog promise.set_error(Status::Error(400, "Chat not found")); return {}; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { promise.set_error(Status::Error(400, "Can't access the chat")); return {}; } if (td_->dialog_manager_->is_broadcast_channel(dialog_id) && - !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { + !td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) { promise.set_error(Status::Error(400, "Not enough rights to get scheduled messages")); return {}; } @@ -22338,9 +22448,8 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, bool can_use_reactions = true; if (d->dialog_id.get_type() == DialogType::Channel) { auto channel_id = d->dialog_id.get_channel_id(); - if (td_->contacts_manager_->is_megagroup_channel(channel_id) && - !td_->contacts_manager_->get_channel_status(channel_id).is_member() && - can_send_message(d->dialog_id).is_error()) { + if (td_->chat_manager_->is_megagroup_channel(channel_id) && + !td_->chat_manager_->get_channel_status(channel_id).is_member() && can_send_message(d->dialog_id).is_error()) { // can't use reactions if can't send messages to the group without joining if (unavailability_reason != nullptr) { *unavailability_reason = ReactionUnavailabilityReason::Guest; @@ -22348,7 +22457,7 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, can_use_reactions = false; } else if (td_->dialog_manager_->is_anonymous_administrator(d->dialog_id, nullptr) && !td_->dialog_manager_->is_broadcast_channel(d->dialog_id) && - !td_->contacts_manager_->get_channel_status(channel_id).is_creator()) { + !td_->chat_manager_->get_channel_status(channel_id).is_creator()) { // only creator can react as the chat if (unavailability_reason != nullptr) { *unavailability_reason = ReactionUnavailabilityReason::AnonymousAdministrator; @@ -22358,6 +22467,9 @@ ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, } int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11); + if (0 < active_reactions.reactions_limit_ && active_reactions.reactions_limit_ < reactions_uniq_max) { + reactions_uniq_max = active_reactions.reactions_limit_; + } bool can_add_new_reactions = m->reactions == nullptr || static_cast(m->reactions->reactions_.size()) < reactions_uniq_max; @@ -22624,9 +22736,9 @@ td_api::object_ptr MessagesManager::get_message_message_ td_api::object_ptr MessagesManager::get_dialog_event_log_message_object( DialogId dialog_id, tl_object_ptr &&message, DialogId &sender_dialog_id) { - auto dialog_message = - create_message(parse_telegram_api_message(std::move(message), false, "get_dialog_event_log_message_object"), - dialog_id.get_type() == DialogType::Channel, "get_dialog_event_log_message_object"); + auto dialog_message = create_message( + td_, parse_telegram_api_message(td_, std::move(message), false, "get_dialog_event_log_message_object"), + dialog_id.get_type() == DialogType::Channel, false, "get_dialog_event_log_message_object"); const Message *m = dialog_message.second.get(); if (m == nullptr || dialog_message.first != dialog_id) { LOG(ERROR) << "Failed to create event log message in " << dialog_id; @@ -22641,27 +22753,98 @@ td_api::object_ptr MessagesManager::get_dialog_event_log_messag auto import_info = m->forward_info == nullptr ? nullptr : m->forward_info->get_message_import_info_object(); auto interaction_info = get_message_interaction_info_object(dialog_id, m); auto can_be_saved = can_save_message(dialog_id, m); - auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); + auto via_bot_user_id = + td_->user_manager_->get_user_id_object(m->via_bot_user_id, "get_dialog_event_log_message_object via_bot_user_id"); auto edit_date = m->hide_edit_date ? 0 : m->edit_date; - auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); + auto reply_markup = get_reply_markup_object(td_->user_manager_.get(), m->reply_markup); auto content = get_message_content_object(m->content.get(), td_, dialog_id, 0, false, true, get_message_own_max_media_timestamp(m), m->invert_media, m->disable_web_page_preview); return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_dialog_event_log_message_object"), - nullptr, nullptr, m->is_outgoing, m->is_pinned, false, false, false, can_be_saved, false, false, false, false, - false, false, false, false, false, true, m->is_channel_post, m->is_topic_message, false, m->date, edit_date, - std::move(forward_info), std::move(import_info), std::move(interaction_info), Auto(), nullptr, 0, 0, nullptr, 0.0, - 0.0, via_bot_user_id, m->sender_boost_count, m->author_signature, 0, + nullptr, nullptr, m->is_outgoing, m->is_pinned, m->is_from_offline, false, false, false, can_be_saved, false, + false, false, false, false, false, false, false, false, true, m->is_channel_post, m->is_topic_message, false, + m->date, edit_date, std::move(forward_info), std::move(import_info), std::move(interaction_info), Auto(), nullptr, + 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)); } -tl_object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, const char *source) { +td_api::object_ptr MessagesManager::get_business_message_object( + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message) { + auto message_object = get_business_message_message_object(std::move(message)); + if (message_object == nullptr) { + LOG(ERROR) << "Failed to create a business message"; + return nullptr; + } + return td_api::make_object(std::move(message_object), + get_business_message_message_object(std::move(reply_to_message))); +} + +td_api::object_ptr MessagesManager::get_business_message_message_object( + telegram_api::object_ptr &&message) { + CHECK(td_->auth_manager_->is_bot()); + if (message == nullptr) { + return nullptr; + } + auto dialog_message = create_message( + td_, parse_telegram_api_message(td_, std::move(message), false, "get_business_message_message_object"), false, + true, "get_business_message_message_object"); + const Message *m = dialog_message.second.get(); + if (m == nullptr) { + return nullptr; + } + + auto dialog_id = dialog_message.first; + if (dialog_id.get_type() != DialogType::User) { + LOG(ERROR) << "Receive a business message in " << dialog_id; + return nullptr; + } + force_create_dialog(dialog_id, "get_business_message_message_object chat", true); + + auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, + "get_business_message_message_object"); + auto forward_info = + m->forward_info == nullptr ? nullptr : m->forward_info->get_message_forward_info_object(td_, false); + auto import_info = m->forward_info == nullptr ? nullptr : m->forward_info->get_message_import_info_object(); + auto can_be_saved = can_save_message(dialog_id, m); + auto via_bot_user_id = + td_->user_manager_->get_user_id_object(m->via_bot_user_id, "get_business_message_message_object via_bot_user_id"); + auto via_business_bot_user_id = td_->user_manager_->get_user_id_object( + m->via_business_bot_user_id, "get_business_message_message_object via_business_bot_user_id"); + auto reply_to = [&]() -> td_api::object_ptr { + if (!m->replied_message_info.is_empty()) { + return m->replied_message_info.get_message_reply_to_message_object(td_, dialog_id); + } + if (m->reply_to_story_full_id.is_valid()) { + return td_api::make_object( + get_chat_id_object(m->reply_to_story_full_id.get_dialog_id(), + "get_business_message_message_object messageReplyToStory"), + m->reply_to_story_full_id.get_story_id().get()); + } + return nullptr; + }(); + auto reply_markup = get_reply_markup_object(td_->user_manager_.get(), m->reply_markup); + auto content = get_message_message_content_object(dialog_id, m); + auto self_destruct_type = m->ttl.get_message_self_destruct_type_object(); + + return td_api::make_object( + m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_business_message_message_object"), + nullptr, nullptr, m->is_outgoing, m->is_from_offline, false, false, false, false, can_be_saved, false, false, + false, false, false, false, false, false, false, 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_restriction_reason_description(m->restriction_reasons), std::move(content), + std::move(reply_markup)); +} + +td_api::object_ptr MessagesManager::get_message_object(MessageFullId message_full_id, + const char *source) { return get_message_object(message_full_id.get_dialog_id(), get_message_force(message_full_id, source), source); } -tl_object_ptr MessagesManager::get_message_object(DialogId dialog_id, const Message *m, - const char *source) const { +td_api::object_ptr MessagesManager::get_message_object(DialogId dialog_id, const Message *m, + const char *source) const { if (m == nullptr) { return nullptr; } @@ -22720,6 +22903,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto import_info = m->forward_info == nullptr ? nullptr : m->forward_info->get_message_import_info_object(); auto interaction_info = is_bot ? nullptr : get_message_interaction_info_object(dialog_id, m); auto unread_reactions = get_unread_reactions_object(dialog_id, m); + auto fact_check = get_message_fact_check_object(m); 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); @@ -22731,7 +22915,10 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto can_get_viewers = can_get_message_viewers(dialog_id, m).is_ok(); auto can_get_media_timestamp_links = can_get_media_timestamp_link(dialog_id, m).is_ok(); auto can_report_reactions = can_report_message_reactions(dialog_id, m); - auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"); + auto via_bot_user_id = + td_->user_manager_->get_user_id_object(m->via_bot_user_id, "get_message_object via_bot_user_id"); + auto via_business_bot_user_id = td_->user_manager_->get_user_id_object(m->via_business_bot_user_id, + "get_message_object via_business_bot_user_id"); auto reply_to = [&]() -> td_api::object_ptr { if (!m->replied_message_info.is_empty()) { if (!is_bot && m->is_topic_message && @@ -22742,7 +22929,7 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial } if (m->reply_to_story_full_id.is_valid()) { return td_api::make_object( - get_chat_id_object(m->reply_to_story_full_id.get_dialog_id(), "messageReplyToStory"), + get_chat_id_object(m->reply_to_story_full_id.get_dialog_id(), "get_message_object messageReplyToStory"), m->reply_to_story_full_id.get_story_id().get()); } return nullptr; @@ -22751,27 +22938,28 @@ tl_object_ptr MessagesManager::get_message_object(DialogId dial auto date = is_scheduled ? 0 : m->date; auto edit_date = m->hide_edit_date ? 0 : m->edit_date; auto has_timestamped_media = reply_to == nullptr || m->max_own_media_timestamp >= 0; - auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup); + auto reply_markup = get_reply_markup_object(td_->user_manager_.get(), m->reply_markup); auto content = get_message_message_content_object(dialog_id, m); auto self_destruct_type = m->ttl.get_message_self_destruct_type_object(); return td_api::make_object( m->message_id.get(), std::move(sender), get_chat_id_object(dialog_id, "get_message_object"), - std::move(sending_state), std::move(scheduling_state), is_outgoing, m->is_pinned, can_be_edited, can_be_forwarded, - can_be_replied_in_another_chat, can_be_saved, can_delete_for_self, can_delete_for_all_users, - can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_read_date, can_get_viewers, - can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post, + std::move(sending_state), std::move(scheduling_state), is_outgoing, m->is_pinned, m->is_from_offline, + can_be_edited, can_be_forwarded, can_be_replied_in_another_chat, can_be_saved, can_delete_for_self, + can_delete_for_all_users, can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_read_date, + can_get_viewers, can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post, m->is_topic_message, m->contains_unread_mention, date, edit_date, std::move(forward_info), std::move(import_info), - std::move(interaction_info), std::move(unread_reactions), std::move(reply_to), top_thread_message_id, + std::move(interaction_info), std::move(unread_reactions), std::move(fact_check), std::move(reply_to), + top_thread_message_id, 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, m->sender_boost_count, - m->author_signature, m->media_album_id, get_restriction_reason_description(m->restriction_reasons), - std::move(content), std::move(reply_markup)); + 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_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup)); } -tl_object_ptr MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id, - const vector &message_ids, - bool skip_not_found, const char *source) { +td_api::object_ptr MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id, + const vector &message_ids, + 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) { @@ -22780,18 +22968,17 @@ tl_object_ptr MessagesManager::get_messages_object(int32 total return get_messages_object(total_count, std::move(message_objects), skip_not_found); } -tl_object_ptr MessagesManager::get_messages_object(int32 total_count, - const vector &message_full_ids, - bool skip_not_found, const char *source) { +td_api::object_ptr MessagesManager::get_messages_object(int32 total_count, + const vector &message_full_ids, + bool skip_not_found, const char *source) { auto message_objects = transform(message_full_ids, [this, source](MessageFullId message_full_id) { return get_message_object(message_full_id, source); }); return get_messages_object(total_count, std::move(message_objects), skip_not_found); } -tl_object_ptr MessagesManager::get_messages_object(int32 total_count, - vector> &&messages, - bool skip_not_found) { +td_api::object_ptr MessagesManager::get_messages_object( + int32 total_count, vector> &&messages, bool skip_not_found) { auto message_count = narrow_cast(messages.size()); if (total_count < message_count) { if (total_count != -1) { @@ -22822,10 +23009,10 @@ DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId return d->default_send_message_as_dialog_id; } -MessageInputReplyTo MessagesManager::get_message_input_reply_to( +MessageInputReplyTo MessagesManager::create_message_input_reply_to( DialogId dialog_id, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, bool for_draft) { - return get_message_input_reply_to(get_dialog(dialog_id), top_thread_message_id, std::move(reply_to), for_draft); + return create_message_input_reply_to(get_dialog(dialog_id), top_thread_message_id, std::move(reply_to), for_draft); } int64 MessagesManager::generate_new_random_id(const Dialog *d) { @@ -22849,14 +23036,14 @@ unique_ptr MessagesManager::create_message_to_send( DialogId dialog_id = d->dialog_id; auto dialog_type = dialog_id.get_type(); - auto my_id = td_->contacts_manager_->get_my_id(); + auto my_id = td_->user_manager_->get_my_id(); int64 reply_to_random_id = 0; bool is_topic_message = false; auto initial_top_thread_message_id = top_thread_message_id; auto same_chat_reply_to_message_id = input_reply_to.get_same_chat_reply_to_message_id(); if (same_chat_reply_to_message_id.is_valid() || same_chat_reply_to_message_id.is_valid_scheduled()) { - // the message was forcely preloaded in get_message_input_reply_to + // the message was forcely preloaded in create_message_input_reply_to // it can be missing, only if it is unknown message from a push notification, or an unknown top thread message const Message *reply_m = get_message(d, same_chat_reply_to_message_id); if (reply_m != nullptr && !same_chat_reply_to_message_id.is_scheduled()) { @@ -22887,8 +23074,8 @@ unique_ptr MessagesManager::create_message_to_send( 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_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { - m->author_signature = td_->contacts_manager_->get_user_title(my_id); + 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 { @@ -22945,14 +23132,14 @@ unique_ptr MessagesManager::create_message_to_send( return false; } if (is_channel_post) { - return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id()); + return td_->chat_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id()); } return !m->input_reply_to.is_valid(); }()) { m->reply_info.reply_count_ = 0; if (is_channel_post) { auto linked_channel_id = - td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id(), "create_message_to_send"); + td_->chat_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id(), "create_message_to_send"); if (linked_channel_id.is_valid()) { m->reply_info.is_comment_ = true; m->reply_info.channel_id_ = linked_channel_id; @@ -22960,8 +23147,9 @@ unique_ptr MessagesManager::create_message_to_send( } } if (m->sender_user_id == my_id && dialog_type == DialogType::Channel) { - m->sender_boost_count = td_->contacts_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); + m->sender_boost_count = td_->chat_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); } + m->effect_id = options.effect_id; m->content = std::move(content); m->invert_media = invert_media; m->forward_info = std::move(forward_info); @@ -22981,8 +23169,7 @@ unique_ptr MessagesManager::create_message_to_send( if (is_service_message_content(m->content->get_type())) { m->ttl = {}; } else { - m->ttl = - MessageSelfDestructType(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), false); + m->ttl = MessageSelfDestructType(td_->user_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), false); } m->is_content_secret = m->ttl.is_secret_message_content(m->content->get_type()); } @@ -23008,7 +23195,7 @@ MessagesManager::Message *MessagesManager::get_message_to_send( message->random_id = generate_new_random_id(d); bool need_update = false; - CHECK(td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)); + CHECK(td_->dialog_manager_->have_input_peer(d->dialog_id, true, AccessRights::Read)); auto result = add_message_to_dialog(d, std::move(message), false, true, &need_update, need_update_dialog_pos, "send message"); LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_; @@ -23031,14 +23218,14 @@ int64 MessagesManager::begin_send_message(DialogId dialog_id, const Message *m) } Status MessagesManager::can_send_message(DialogId dialog_id) const { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Write)) { return Status::Error(400, "Have no write access to the chat"); } if (dialog_id.get_type() == DialogType::Channel) { auto channel_id = dialog_id.get_channel_id(); - auto channel_type = td_->contacts_manager_->get_channel_type(channel_id); - auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); + auto channel_type = td_->chat_manager_->get_channel_type(channel_id); + auto channel_status = td_->chat_manager_->get_channel_permissions(channel_id); switch (channel_type) { case ChannelType::Unknown: @@ -23073,17 +23260,17 @@ MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId return message_id; } -MessageInputReplyTo MessagesManager::get_message_input_reply_to( +MessageInputReplyTo MessagesManager::create_message_input_reply_to( Dialog *d, MessageId top_thread_message_id, td_api::object_ptr &&reply_to, bool for_draft) { CHECK(d != nullptr); if (top_thread_message_id.is_valid() && - !have_message_force(d, top_thread_message_id, "get_message_input_reply_to 1")) { + !have_message_force(d, top_thread_message_id, "create_message_input_reply_to 1")) { LOG(INFO) << "Have reply in the thread of unknown " << top_thread_message_id; } if (reply_to == nullptr) { if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { - return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; + return MessageInputReplyTo{top_thread_message_id, DialogId(), MessageQuote()}; } return {}; } @@ -23111,14 +23298,14 @@ MessageInputReplyTo MessagesManager::get_message_input_reply_to( if (!message_id.is_valid()) { if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { - return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; + return MessageInputReplyTo{top_thread_message_id, DialogId(), MessageQuote()}; } return {}; } auto *reply_d = d; auto reply_dialog_id = DialogId(reply_to_message->chat_id_); if (reply_dialog_id != DialogId()) { - reply_d = get_dialog_force(reply_dialog_id, "get_message_input_reply_to"); + reply_d = get_dialog_force(reply_dialog_id, "create_message_input_reply_to"); if (reply_d == nullptr) { return {}; } @@ -23130,24 +23317,7 @@ MessageInputReplyTo MessagesManager::get_message_input_reply_to( if (message_id == MessageId(ServerMessageId(1)) && reply_d->dialog_id.get_type() == DialogType::Channel) { return {}; } - FormattedText quote; - int32 quote_position = 0; - if (reply_to_message->quote_ != nullptr) { - int32 ltrim_count = 0; - auto r_quote = get_formatted_text(td_, td_->dialog_manager_->get_my_dialog_id(), - std::move(reply_to_message->quote_->text_), td_->auth_manager_->is_bot(), - true, true, false, <rim_count); - if (r_quote.is_ok() && d->dialog_id.get_type() != DialogType::SecretChat && !r_quote.ok().text.empty()) { - quote = r_quote.move_as_ok(); - quote_position = reply_to_message->quote_->position_; - if (0 <= quote_position && quote_position <= 1000000) { // some unreasonably big bound - quote_position += ltrim_count; - } else { - quote_position = 0; - } - } - } - const Message *m = get_message_force(reply_d, message_id, "get_message_input_reply_to 2"); + const Message *m = get_message_force(reply_d, message_id, "create_message_input_reply_to 2"); if (m == nullptr || m->message_id.is_yet_unsent() || (m->message_id.is_local() && reply_d->dialog_id.get_type() != DialogType::SecretChat)) { if (message_id.is_server() && reply_d->dialog_id.get_type() != DialogType::SecretChat && @@ -23155,10 +23325,11 @@ MessageInputReplyTo MessagesManager::get_message_input_reply_to( (reply_d->notification_info != nullptr && message_id <= reply_d->notification_info->max_push_notification_message_id_)) { // allow to reply yet unreceived server message in the same chat - return MessageInputReplyTo{message_id, reply_dialog_id, std::move(quote), quote_position}; + return MessageInputReplyTo{message_id, reply_dialog_id, + MessageQuote{td_, std::move(reply_to_message->quote_)}}; } if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) { - return MessageInputReplyTo{top_thread_message_id, DialogId(), FormattedText(), 0}; + return MessageInputReplyTo{top_thread_message_id, DialogId(), MessageQuote()}; } LOG(INFO) << "Can't find " << message_id << " in " << reply_d->dialog_id; @@ -23170,7 +23341,8 @@ MessageInputReplyTo MessagesManager::get_message_input_reply_to( LOG(INFO) << "Can't reply in another chat " << m->message_id << " in " << reply_d->dialog_id; return {}; } - return MessageInputReplyTo{m->message_id, reply_dialog_id, std::move(quote), quote_position}; + return MessageInputReplyTo{m->message_id, reply_dialog_id, + MessageQuote{td_, std::move(reply_to_message->quote_)}}; } default: UNREACHABLE(); @@ -23262,7 +23434,7 @@ void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) CHECK(input_reply_to != nullptr); CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, m->message_id)); set_message_reply(reply_d, replied_m, - MessageInputReplyTo{replied_m->top_thread_message_id, DialogId(), FormattedText(), 0}, true); + MessageInputReplyTo{replied_m->top_thread_message_id, DialogId(), MessageQuote()}, true); } replied_yet_unsent_messages_.erase(it); } @@ -23306,11 +23478,10 @@ bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing) switch (dialog_id.get_type()) { case DialogType::User: { auto user_id = dialog_id.get_user_id(); - if (user_id == td_->contacts_manager_->get_my_id()) { + if (user_id == td_->user_manager_->get_my_id()) { return true; } - if (is_outgoing && td_->contacts_manager_->is_user_bot(user_id) && - !td_->contacts_manager_->is_user_support(user_id)) { + if (is_outgoing && td_->user_manager_->is_user_bot(user_id) && !td_->user_manager_->is_user_support(user_id)) { return true; } return false; @@ -23339,12 +23510,13 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, const dependencies.add_dialog_and_dependencies(m->reply_to_story_full_id.get_dialog_id()); dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id); dependencies.add(m->via_bot_user_id); + dependencies.add(m->via_business_bot_user_id); if (m->forward_info != nullptr) { m->forward_info->add_dependencies(dependencies); } for (const auto &replier_min_channel : m->reply_info.replier_min_channels_) { LOG(INFO) << "Add min replied " << replier_min_channel.first; - td_->contacts_manager_->add_min_channel(replier_min_channel.first, replier_min_channel.second); + td_->chat_manager_->add_min_channel(replier_min_channel.first, replier_min_channel.second); } for (auto recent_replier_dialog_id : m->reply_info.recent_replier_dialog_ids_) { dependencies.add_message_sender_dependencies(recent_replier_dialog_id); @@ -23353,6 +23525,9 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, const m->reactions->add_min_channels(td_); m->reactions->add_dependencies(dependencies); } + if (m->fact_check != nullptr) { + m->fact_check->add_dependencies(dependencies); + } add_message_content_dependencies(dependencies, m->content.get(), is_bot); add_reply_markup_dependencies(dependencies, m->reply_markup.get()); add_draft_message_dependencies(dependencies, m->thread_draft_message); @@ -23361,22 +23536,17 @@ void MessagesManager::add_message_dependencies(Dependencies &dependencies, const void MessagesManager::get_dialog_send_message_as_dialog_ids( DialogId dialog_id, Promise> &&promise, bool is_recursive) { TRY_STATUS_PROMISE(promise, G()->close_status()); - - const Dialog *d = get_dialog_force(dialog_id, "get_group_call_join_as"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access chat")); - } + 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()) { return promise.set_value(td_api::make_object()); } CHECK(dialog_id.get_type() == DialogType::Channel); - if (created_public_broadcasts_inited_) { + if (td_->chat_manager_->are_created_public_broadcasts_inited()) { auto senders = td_api::make_object(); - if (!created_public_broadcasts_.empty()) { + const auto &created_public_broadcasts = td_->chat_manager_->get_created_public_broadcasts(); + if (!created_public_broadcasts.empty()) { auto add_sender = [&senders, td = td_](DialogId dialog_id, bool needs_premium) { auto sender = get_message_sender_object(td, dialog_id, "add_sender"); senders->senders_.push_back(td_api::make_object(std::move(sender), needs_premium)); @@ -23394,12 +23564,12 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( std::multimap sorted_senders; bool is_premium = td_->option_manager_->get_option_boolean("is_premium"); - auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id( + 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_) { - int64 score = td_->contacts_manager_->get_channel_participant_count(channel_id); - bool needs_premium = !is_premium && channel_id != linked_channel_id && - !td_->contacts_manager_->get_channel_is_verified(channel_id); + for (auto channel_id : created_public_broadcasts) { + 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); if (needs_premium) { score -= static_cast(1) << 40; } @@ -23426,24 +23596,20 @@ void MessagesManager::get_dialog_send_message_as_dialog_ids( std::move(promise), true); } }); - td_->contacts_manager_->get_created_public_dialogs(PublicDialogType::HasUsername, std::move(new_promise), true); + td_->chat_manager_->get_created_public_dialogs(PublicDialogType::ForPersonalDialog, std::move(new_promise), true); } void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dialog_id, DialogId message_sender_dialog_id, Promise &&promise) { - Dialog *d = get_dialog_force(dialog_id, "set_dialog_default_send_message_as_dialog_id"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } + 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()) { 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)); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } bool is_anonymous = td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr); switch (message_sender_dialog_id.get_type()) { @@ -23462,14 +23628,14 @@ void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dial break; } if (!td_->dialog_manager_->is_broadcast_channel(message_sender_dialog_id) || - td_->contacts_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) { + td_->chat_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) { return promise.set_error(Status::Error(400, "Message sender chat must be a public channel")); } break; default: return promise.set_error(Status::Error(400, "Invalid message sender specified")); } - if (!td_->dialog_manager_->have_input_peer(message_sender_dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(message_sender_dialog_id, true, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access specified message sender chat")); } @@ -23519,7 +23685,7 @@ Result> MessagesManager::send_message( return Status::Error(400, "Chat not found"); } - auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); + auto input_reply_to = create_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { auto input_message = td_api::move_object_as(input_message_content); @@ -23534,7 +23700,7 @@ Result> MessagesManager::send_message( TRY_STATUS(can_send_message(dialog_id)); TRY_RESULT(message_reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup))); TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true, true)); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); @@ -23599,12 +23765,9 @@ Result MessagesManager::process_input_message_content( if (from_dialog == nullptr) { return Status::Error(400, "Chat to copy message from not found"); } - if (!td_->dialog_manager_->have_input_peer(from_dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(from_dialog_id, false, AccessRights::Read)) { return Status::Error(400, "Can't access the chat to copy message from"); } - if (from_dialog_id.get_type() == DialogType::SecretChat) { - return Status::Error(400, "Can't copy message from secret chats"); - } MessageId message_id = get_persistent_message_id(from_dialog, MessageId(input_message->message_id_)); const Message *copied_message = get_message_force(from_dialog, message_id, "process_input_message_content"); @@ -23618,6 +23781,10 @@ Result MessagesManager::process_input_message_content( return Status::Error(400, "Message copying is restricted"); } + auto new_invert_media = + copy_options.replace_caption && is_allowed_invert_caption_message_content(copied_message->content->get_type()) + ? copy_options.new_invert_media + : copied_message->invert_media; unique_ptr content = dup_message_content(td_, dialog_id, copied_message->content.get(), MessageContentDupType::Copy, std::move(copy_options)); if (content == nullptr) { @@ -23625,7 +23792,7 @@ Result MessagesManager::process_input_message_content( } return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), - copied_message->invert_media, false, MessageSelfDestructType(), UserId(), + new_invert_media, false, MessageSelfDestructType(), UserId(), copied_message->send_emoji); } @@ -23650,49 +23817,61 @@ Result MessagesManager::process_message_copy_options( if (result.replace_caption) { TRY_RESULT_ASSIGN(result.new_caption, get_formatted_text(td_, dialog_id, std::move(options->new_caption_), td_->auth_manager_->is_bot(), true, false, false)); + result.new_invert_media = options->new_show_caption_above_media_; } return std::move(result); } Result MessagesManager::process_message_send_options( - DialogId dialog_id, tl_object_ptr &&options, - bool allow_update_stickersets_order) const { + DialogId dialog_id, tl_object_ptr &&options, bool allow_update_stickersets_order, + bool allow_effect) const { MessageSendOptions result; - if (options != nullptr) { - result.disable_notification = options->disable_notification_; - result.from_background = options->from_background_; - if (allow_update_stickersets_order) { - result.update_stickersets_order = options->update_order_of_installed_sticker_sets_; - } + if (options == nullptr) { + return std::move(result); + } + + result.disable_notification = options->disable_notification_; + result.from_background = options->from_background_; + if (allow_update_stickersets_order) { + result.update_stickersets_order = options->update_order_of_installed_sticker_sets_; + } + if (td_->auth_manager_->is_bot()) { result.protect_content = options->protect_content_; - result.only_preview = options->only_preview_; - TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_))); - result.sending_id = options->sending_id_; } + result.only_preview = options->only_preview_; + TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_))); + result.sending_id = options->sending_id_; - auto dialog_type = dialog_id.get_type(); if (result.schedule_date != 0) { + auto dialog_type = dialog_id.get_type(); if (dialog_type == DialogType::SecretChat) { return Status::Error(400, "Can't schedule messages in secret chats"); } if (td_->auth_manager_->is_bot()) { return Status::Error(400, "Bots can't send scheduled messages"); } + + if (result.schedule_date == SCHEDULE_WHEN_ONLINE_DATE) { + if (dialog_type != DialogType::User) { + return Status::Error(400, "Messages can be scheduled till online only in private chats"); + } + if (dialog_id == td_->dialog_manager_->get_my_dialog_id()) { + return Status::Error(400, "Can't scheduled till online messages in chat with self"); + } + } } - if (result.schedule_date == SCHEDULE_WHEN_ONLINE_DATE) { + if (options->effect_id_ != 0) { + auto dialog_type = dialog_id.get_type(); if (dialog_type != DialogType::User) { - return Status::Error(400, "Messages can be scheduled till online only in private chats"); + return Status::Error(400, "Can't use message effects in the chat"); } - if (dialog_id == td_->dialog_manager_->get_my_dialog_id()) { - return Status::Error(400, "Can't scheduled till online messages in chat with self"); + if (!allow_effect) { + return Status::Error(400, "Can't use message effects in the method"); } + result.effect_id = options->effect_id_; } - if (result.protect_content && !td_->auth_manager_->is_bot()) { - result.protect_content = false; - } - - return result; + return std::move(result); } Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options, @@ -23762,43 +23941,23 @@ Result> MessagesManager::send_message_group DialogId dialog_id, const MessageId top_thread_message_id, td_api::object_ptr &&reply_to, tl_object_ptr &&options, vector> &&input_message_contents) { - if (input_message_contents.size() > MAX_GROUPED_MESSAGES) { - return Status::Error(400, "Too many messages to send as an album"); - } - if (input_message_contents.empty()) { - return Status::Error(400, "There are no messages to send"); - } - Dialog *d = get_dialog_force(dialog_id, "send_message_group"); if (d == nullptr) { return Status::Error(400, "Chat not found"); } TRY_STATUS(can_send_message(dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true)); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true, true)); vector message_contents; - std::unordered_set message_content_types; for (auto &input_message_content : input_message_contents) { TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content))); TRY_STATUS(can_use_message_send_options(message_send_options, message_content)); - auto message_content_type = message_content.content->get_type(); - if (!is_allowed_media_group_content(message_content_type)) { - return Status::Error(400, "Invalid message content type"); - } - message_content_types.insert(message_content_type); - message_contents.push_back(std::move(message_content)); } - if (message_content_types.size() > 1) { - for (auto message_content_type : message_content_types) { - if (is_homogenous_media_group_content(message_content_type)) { - return Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types"); - } - } - } + TRY_STATUS(check_message_group_message_contents(message_contents)); - auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); + auto input_reply_to = create_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); int64 media_album_id = 0; @@ -23815,12 +23974,12 @@ Result> MessagesManager::send_message_group unique_ptr message; Message *m; if (message_send_options.only_preview) { - message = create_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + message = create_message_to_send(d, top_thread_message_id, input_reply_to.clone(), message_send_options, std::move(message_content.content), message_content.invert_media, i != 0, nullptr, DialogId(), false, DialogId()); m = message.get(); } else { - m = get_message_to_send(d, top_thread_message_id, std::move(input_reply_to), message_send_options, + m = get_message_to_send(d, top_thread_message_id, input_reply_to.clone(), message_send_options, dup_message_content(td_, dialog_id, message_content.content.get(), MessageContentDupType::Send, MessageCopyOptions()), message_content.invert_media, &need_update_dialog_pos, i != 0); @@ -23890,17 +24049,17 @@ void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vect } FileId file_id = get_message_content_any_file_id(content); // any_file_id, because it could be a photo sent by ID - FileView file_view = td_->file_manager_->get_file_view(file_id); FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content, td_); LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id; if (is_secret) { CHECK(!is_edit); - auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto layer = td_->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice(), layer); if (secret_input_media.empty()) { LOG(INFO) << "Ask to upload encrypted file " << file_id; - CHECK(file_view.is_encrypted_secret()); CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + CHECK(file_view.is_encrypted_secret()); bool is_inserted = being_uploaded_files_ .emplace(file_id, std::make_pair(MessageFullId(dialog_id, m->message_id), thumbnail_file_id)) @@ -23919,6 +24078,8 @@ void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vect content_type == MessageContentType::Story) { return; } + CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); if (get_main_file_type(file_view.get_type()) == FileType::Photo) { thumbnail_file_id = FileId(); } @@ -23951,7 +24112,7 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Messag auto message_id = m->message_id; if (message_id.is_any_server()) { const FormattedText *caption = get_message_content_caption(m->edited_content.get()); - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), m->edited_reply_markup); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), m->edited_reply_markup); bool was_uploaded = FileManager::extract_was_uploaded(input_media); bool was_thumbnail_uploaded = FileManager::extract_was_thumbnail_uploaded(input_media); @@ -23967,35 +24128,35 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Messag }); td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, message_id, caption == nullptr ? "" : caption->text, - get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_message_media"), + get_input_message_entities(td_->user_manager_.get(), caption, "edit_message_media"), std::move(input_media), m->edited_invert_media, std::move(input_reply_markup), schedule_date); return; } if (m->media_album_id == 0) { - send_closure_later( - actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, message_id, - PromiseCreator::lambda([this, dialog_id, input_media = std::move(input_media), file_id, - thumbnail_file_id](Result result) mutable { - if (G()->close_flag() || result.is_error()) { - return; - } - - auto m = result.move_as_ok(); - CHECK(m != nullptr); - CHECK(input_media != nullptr); - - const FormattedText *caption = get_message_content_caption(m->content.get()); - LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id; - int64 random_id = begin_send_message(dialog_id, m); - td_->create_handler()->send( - file_id, thumbnail_file_id, get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), - *get_message_input_reply_to(m), m->initial_top_thread_message_id, get_message_schedule_date(m), - get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), - get_input_message_entities(td_->contacts_manager_.get(), caption, "on_message_media_uploaded"), - caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), m->is_copy, - random_id, &m->send_query_ref); - })); + send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, message_id, + PromiseCreator::lambda([this, dialog_id, input_media = std::move(input_media), file_id, + thumbnail_file_id](Result result) mutable { + if (G()->close_flag() || result.is_error()) { + return; + } + + auto m = result.move_as_ok(); + CHECK(m != nullptr); + CHECK(input_media != nullptr); + + const FormattedText *caption = get_message_content_caption(m->content.get()); + LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id; + int64 random_id = begin_send_message(dialog_id, m); + td_->create_handler()->send( + file_id, thumbnail_file_id, get_message_flags(m), dialog_id, + get_send_message_as_input_peer(m), *get_message_input_reply_to(m), + m->initial_top_thread_message_id, get_message_schedule_date(m), m->effect_id, + get_input_reply_markup(td_->user_manager_.get(), m->reply_markup), + get_input_message_entities(td_->user_manager_.get(), caption, "on_message_media_uploaded"), + caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), + m->is_copy, random_id, &m->send_query_ref); + })); } else { switch (input_media->get_id()) { case telegram_api::inputMediaUploadedDocument::ID: @@ -24078,7 +24239,7 @@ void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, auto text = get_message_content_text(m->content.get()); vector> entities; if (text != nullptr && !text->entities.empty()) { - auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto layer = td_->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); entities = get_input_secret_message_entities(text->entities, layer); } @@ -24108,8 +24269,8 @@ void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, make_tl_object( flags, false /*ignored*/, random_id, m->ttl.get_input_ttl(), m->content->get_type() == MessageContentType::Text ? text->text : string(), std::move(media.decrypted_media_), - std::move(entities), td_->contacts_manager_->get_user_first_username(m->via_bot_user_id), - m->reply_to_random_id, -m->media_album_id), + std::move(entities), td_->user_manager_->get_user_first_username(m->via_bot_user_id), m->reply_to_random_id, + -m->media_album_id), std::move(media.input_file_), Promise()); } @@ -24130,7 +24291,7 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag return; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return; // the message should be deleted soon } @@ -24174,7 +24335,7 @@ void MessagesManager::on_upload_message_media_file_parts_missing(DialogId dialog return; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return; // the message should be deleted soon } @@ -24197,7 +24358,7 @@ void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId return; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return; // the message should be deleted soon } @@ -24295,6 +24456,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { MessageId top_thread_message_id; int32 flags = 0; int32 schedule_date = 0; + int64 effect_id = 0; bool is_copy = false; for (size_t i = 0; i < request.message_ids.size(); i++) { auto *m = get_message(d, request.message_ids[i]); @@ -24308,6 +24470,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { top_thread_message_id = m->initial_top_thread_message_id; flags = get_message_flags(m); schedule_date = get_message_schedule_date(m); + effect_id = m->effect_id; is_copy = m->is_copy; as_input_peer = get_send_message_as_input_peer(m); @@ -24336,7 +24499,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { << file_view.has_active_download_remote_location() << " " << file_view.is_encrypted() << " " << is_web << " " << file_view.has_url() << " " << to_string(get_message_message_content_object(dialog_id, m)); } - auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group"); + auto entities = get_input_message_entities(td_->user_manager_.get(), caption, "do_send_message_group"); int32 input_single_media_flags = 0; if (!entities.empty()) { input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK; @@ -24371,7 +24534,7 @@ void MessagesManager::do_send_message_group(int64 media_album_id) { } CHECK(input_reply_to != nullptr); td_->create_handler()->send(flags, dialog_id, std::move(as_input_peer), *input_reply_to, - top_thread_message_id, schedule_date, std::move(file_ids), + top_thread_message_id, schedule_date, effect_id, std::move(file_ids), std::move(input_single_media), is_copy); } @@ -24394,7 +24557,7 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI if (dialog_id.get_type() == DialogType::SecretChat) { CHECK(!message_id.is_scheduled()); - auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); + auto layer = td_->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()); send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice(), layer)); } else { const FormattedText *message_text = get_message_content_text(content); @@ -24404,17 +24567,18 @@ void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageI if (input_media == nullptr) { td_->create_handler()->send( get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), *get_message_input_reply_to(m), - m->initial_top_thread_message_id, get_message_schedule_date(m), - get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), - get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, - m->is_copy, random_id, &m->send_query_ref); + m->initial_top_thread_message_id, get_message_schedule_date(m), m->effect_id, + get_input_reply_markup(td_->user_manager_.get(), m->reply_markup), + get_input_message_entities(td_->user_manager_.get(), message_text, "on_text_message_ready_to_send"), + message_text->text, m->is_copy, random_id, &m->send_query_ref); } else { td_->create_handler()->send( FileId(), FileId(), get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), - *get_message_input_reply_to(m), m->initial_top_thread_message_id, get_message_schedule_date(m), - get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup), - get_input_message_entities(td_->contacts_manager_.get(), message_text, "do_send_message"), message_text->text, - std::move(input_media), MessageContentType::Text, m->is_copy, random_id, &m->send_query_ref); + *get_message_input_reply_to(m), m->initial_top_thread_message_id, get_message_schedule_date(m), m->effect_id, + get_input_reply_markup(td_->user_manager_.get(), m->reply_markup), + get_input_message_entities(td_->user_manager_.get(), message_text, "on_text_message_ready_to_send"), + message_text->text, std::move(input_media), MessageContentType::Text, m->is_copy, random_id, + &m->send_query_ref); } } } @@ -24494,7 +24658,7 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di const string ¶meter) { CHECK(!td_->auth_manager_->is_bot()); - TRY_RESULT(bot_data, td_->contacts_manager_->get_bot_data(bot_user_id)); + TRY_RESULT(bot_data, td_->user_manager_->get_bot_data(bot_user_id)); Dialog *d = get_dialog_force(dialog_id, "send_bot_start_message"); if (d == nullptr) { @@ -24515,10 +24679,10 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di } auto chat_id = dialog_id.get_chat_id(); - if (!td_->contacts_manager_->have_input_peer_chat(chat_id, AccessRights::Write)) { + if (!td_->chat_manager_->have_input_peer_chat(chat_id, AccessRights::Write)) { return Status::Error(400, "Can't access the chat"); } - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_invite_users()) { return Status::Error(400, "Need administrator rights to invite a bot to the group chat"); } @@ -24526,10 +24690,10 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (!td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Write)) { + if (!td_->chat_manager_->have_input_peer_channel(channel_id, AccessRights::Write)) { return Status::Error(400, "Can't access the chat"); } - switch (td_->contacts_manager_->get_channel_type(channel_id)) { + switch (td_->chat_manager_->get_channel_type(channel_id)) { case ChannelType::Megagroup: if (!bot_data.can_join_groups) { return Status::Error(400, "The bot can't join groups"); @@ -24541,7 +24705,7 @@ Result MessagesManager::send_bot_start_message(UserId bot_user_id, Di default: UNREACHABLE(); } - auto status = td_->contacts_manager_->get_channel_permissions(channel_id); + auto status = td_->chat_manager_->get_channel_permissions(channel_id); if (!status.can_invite_users()) { return Status::Error(400, "Need administrator rights to invite a bot to the supergroup chat"); } @@ -24647,7 +24811,7 @@ void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dia if (input_peer == nullptr) { return on_send_message_fail(random_id, Status::Error(400, "Chat is not accessible")); } - auto r_bot_input_user = td_->contacts_manager_->get_input_user(bot_user_id); + auto r_bot_input_user = td_->user_manager_->get_input_user(bot_user_id); if (r_bot_input_user.is_error()) { return on_send_message_fail(random_id, r_bot_input_user.move_as_error()); } @@ -24666,7 +24830,7 @@ Result> MessagesManager::send_inline_query_r } TRY_STATUS(can_send_message(dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false)); + TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false, false)); bool to_secret = false; switch (dialog_id.get_type()) { case DialogType::User: @@ -24674,7 +24838,7 @@ Result> MessagesManager::send_inline_query_r // ok break; case DialogType::Channel: { - auto channel_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto channel_status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!channel_status.can_use_inline_bots()) { return Status::Error(400, "Can't use inline bots in the chat"); } @@ -24694,7 +24858,7 @@ Result> MessagesManager::send_inline_query_r return Status::Error(400, "Inline query result not found"); } - auto input_reply_to = get_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); + auto input_reply_to = create_message_input_reply_to(d, top_thread_message_id, std::move(reply_to), false); TRY_STATUS(can_use_message_send_options(message_send_options, content->message_content, MessageSelfDestructType())); TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, true, td_)); TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, input_reply_to)); @@ -24835,8 +24999,9 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo return false; } - auto my_id = td_->contacts_manager_->get_my_id(); - if (m->via_bot_user_id.is_valid() && (m->via_bot_user_id != my_id || m->message_id.is_scheduled())) { + auto my_id = td_->user_manager_->get_my_id(); + bool is_inline_message = m->via_bot_user_id.is_valid(); + if (is_inline_message && (m->via_bot_user_id != my_id || m->message_id.is_scheduled())) { return false; } @@ -24848,23 +25013,23 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo content_type != MessageContentType::LiveLocation && !m->message_id.is_scheduled(); switch (dialog_id.get_type()) { case DialogType::User: - if (!m->is_outgoing && dialog_id != my_dialog_id && !m->via_bot_user_id.is_valid()) { + if (!m->is_outgoing && dialog_id != my_dialog_id && !is_inline_message) { return false; } break; case DialogType::Chat: - if (!m->is_outgoing && !m->via_bot_user_id.is_valid()) { + if (!m->is_outgoing && !is_inline_message) { return false; } break; case DialogType::Channel: { - if (m->via_bot_user_id.is_valid()) { + if (is_inline_message) { // outgoing via_bot messages can always be edited break; } auto channel_id = dialog_id.get_channel_id(); - auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); + auto channel_status = td_->chat_manager_->get_channel_permissions(channel_id); if (m->is_channel_post) { if (m->message_id.is_scheduled()) { if (!channel_status.can_post_messages()) { @@ -24973,7 +25138,7 @@ bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const { return false; } - auto state = td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()); + auto state = td_->user_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()); if (state != SecretChatState::Closed) { return false; } @@ -25029,20 +25194,12 @@ void MessagesManager::edit_message_text(MessageFullId message_full_id, } auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_text"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_text")); const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_text"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } @@ -25052,51 +25209,38 @@ void MessagesManager::edit_message_text(MessageFullId message_full_id, return promise.set_error(Status::Error(400, "There is no text in the message to edit")); } - auto r_input_message_text = - process_input_message_text(td_, dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot()); - if (r_input_message_text.is_error()) { - return promise.set_error(r_input_message_text.move_as_error()); - } - const InputMessageText input_message_text = r_input_message_text.move_as_ok(); - - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(dialog_id, m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); + TRY_RESULT_PROMISE( + promise, input_message_text, + process_input_message_text(td_, dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot())); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(dialog_id, m))); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); int32 flags = 0; if (input_message_text.disable_web_page_preview) { flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW; } td_->create_handler(std::move(promise)) - ->send(flags, dialog_id, m->message_id, input_message_text.text.text, - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, - "edit_message_text"), - input_message_text.get_input_media_web_page(), input_message_text.show_above_text, - std::move(input_reply_markup), get_message_schedule_date(m)); + ->send( + flags, dialog_id, m->message_id, input_message_text.text.text, + get_input_message_entities(td_->user_manager_.get(), input_message_text.text.entities, "edit_message_text"), + input_message_text.get_input_media_web_page(), input_message_text.show_above_text, + std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_live_location(MessageFullId message_full_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_location, int32 heading, - int32 proximity_alert_radius, Promise &&promise) { + tl_object_ptr &&input_location, int32 live_period, + int32 heading, int32 proximity_alert_radius, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_live_location"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_live_location")); const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_live_location"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } @@ -25115,26 +25259,27 @@ void MessagesManager::edit_message_live_location(MessageFullId message_full_id, return promise.set_error(Status::Error(400, "Invalid location specified")); } - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(dialog_id, m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(dialog_id, m))); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); int32 flags = 0; if (location.empty()) { flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK; } + if (live_period != 0) { + flags |= telegram_api::inputMediaGeoLive::PERIOD_MASK; + } if (heading != 0) { flags |= telegram_api::inputMediaGeoLive::HEADING_MASK; } flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK; auto input_media = telegram_api::make_object( - flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); + flags, false /*ignored*/, location.get_input_geo_point(), heading, live_period, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), - std::move(input_media), false, std::move(input_reply_markup), get_message_schedule_date(m)); + std::move(input_media), false /*ignored*/, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message) { @@ -25157,12 +25302,21 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess Result &&result) { // must not run getDifference + if (was_thumbnail_uploaded) { + CHECK(thumbnail_file_id.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id); + } + CHECK(message_id.is_any_server()); Dialog *d = get_dialog(dialog_id); CHECK(d != nullptr); auto m = get_message(d, message_id); if (m == nullptr || m->edit_generation != generation) { // message is already deleted or was edited again + if (was_uploaded) { + cancel_upload_file(file_id, "on_message_media_edited"); + } return; } @@ -25205,9 +25359,7 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess return; } - if (result.error().code() != 429 && result.error().code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id, result.error()); } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(result.error())) { if (file_id.is_valid()) { VLOG(file_references) << "Receive " << result.error() << " for " << file_id; @@ -25225,6 +25377,9 @@ void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId mess get_message_from_server({dialog_id, m->message_id}, Auto(), "on_message_media_edited"); } } + if (was_uploaded) { + cancel_upload_file(file_id, "on_message_media_edited"); + } if (m->edited_schedule_date == schedule_date) { m->edited_schedule_date = 0; @@ -25257,20 +25412,12 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, } auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_media"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_media")); Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_media"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } @@ -25287,11 +25434,7 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, return promise.set_error(Status::Error(400, "Can't edit media in self-destructing message")); } - auto r_input_message_content = process_input_message_content(dialog_id, std::move(input_message_content)); - if (r_input_message_content.is_error()) { - return promise.set_error(r_input_message_content.move_as_error()); - } - InputMessageContent content = r_input_message_content.move_as_ok(); + TRY_RESULT_PROMISE(promise, content, process_input_message_content(dialog_id, std::move(input_message_content))); if (!content.ttl.is_empty()) { return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); } @@ -25309,11 +25452,9 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, } } - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(dialog_id, m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(dialog_id, m))); cancel_edit_message_media(dialog_id, m, "Canceled by new editMessageMedia request"); @@ -25321,7 +25462,7 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions()); CHECK(m->edited_content != nullptr); m->edited_invert_media = content.invert_media; - m->edited_reply_markup = r_new_reply_markup.move_as_ok(); + m->edited_reply_markup = std::move(new_reply_markup); m->edit_generation = ++current_message_edit_generation_; m->edit_promise = std::move(promise); @@ -25330,23 +25471,15 @@ void MessagesManager::edit_message_media(MessageFullId message_full_id, void MessagesManager::edit_message_caption(MessageFullId message_full_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_caption, + tl_object_ptr &&input_caption, bool invert_media, Promise &&promise) { auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_caption"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_caption")); const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_caption"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!can_edit_message(dialog_id, m, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } @@ -25354,25 +25487,22 @@ void MessagesManager::edit_message_caption(MessageFullId message_full_id, if (!can_have_message_content_caption(m->content->get_type())) { return promise.set_error(Status::Error(400, "There is no caption in the message to edit")); } - - auto r_caption = - get_formatted_text(td_, dialog_id, std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false); - if (r_caption.is_error()) { - return promise.set_error(r_caption.move_as_error()); + if (invert_media && !is_allowed_invert_caption_message_content(m->content->get_type())) { + invert_media = false; } - auto caption = r_caption.move_as_ok(); - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(dialog_id, m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); + TRY_RESULT_PROMISE( + promise, caption, + get_formatted_text(td_, dialog_id, std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false)); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(dialog_id, m))); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); td_->create_handler(std::move(promise)) ->send(1 << 11, dialog_id, m->message_id, caption.text, - get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_message_caption"), - nullptr, m->invert_media, std::move(input_reply_markup), get_message_schedule_date(m)); + get_input_message_entities(td_->user_manager_.get(), caption.entities, "edit_message_caption"), nullptr, + invert_media, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_message_reply_markup(MessageFullId message_full_id, @@ -25381,33 +25511,23 @@ void MessagesManager::edit_message_reply_markup(MessageFullId message_full_id, CHECK(td_->auth_manager_->is_bot()); auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_reply_markup"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_reply_markup")); const Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_reply_markup"); if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!can_edit_message(dialog_id, m, true, true)) { return promise.set_error(Status::Error(400, "Message can't be edited")); } - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(dialog_id, m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(dialog_id, m))); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), new_reply_markup); td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, - false, std::move(input_reply_markup), get_message_schedule_date(m)); + m->invert_media /*ignored*/, std::move(input_reply_markup), get_message_schedule_date(m)); } void MessagesManager::edit_inline_message_text(const string &inline_message_id, @@ -25424,17 +25544,11 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id, return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText")); } - auto r_input_message_text = - process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot()); - if (r_input_message_text.is_error()) { - return promise.set_error(r_input_message_text.move_as_error()); - } - const InputMessageText input_message_text = r_input_message_text.move_as_ok(); - - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE( + promise, input_message_text, + process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot())); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { @@ -25447,22 +25561,21 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id, } td_->create_handler(std::move(promise)) ->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text, - get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities, + get_input_message_entities(td_->user_manager_.get(), input_message_text.text.entities, "edit_inline_message_text"), input_message_text.get_input_media_web_page(), input_message_text.show_above_text, - get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); } void MessagesManager::edit_inline_message_live_location(const string &inline_message_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_location, int32 heading, - int32 proximity_alert_radius, Promise &&promise) { + tl_object_ptr &&input_location, + int32 live_period, int32 heading, int32 proximity_alert_radius, + Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { @@ -25478,16 +25591,19 @@ void MessagesManager::edit_inline_message_live_location(const string &inline_mes if (location.empty()) { flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK; } + if (live_period != 0) { + flags |= telegram_api::inputMediaGeoLive::PERIOD_MASK; + } if (heading != 0) { flags |= telegram_api::inputMediaGeoLive::HEADING_MASK; } flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK; auto input_media = telegram_api::make_object( - flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius); + flags, false /*ignored*/, location.get_input_geo_point(), heading, live_period, proximity_alert_radius); td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), "", vector>(), - std::move(input_media), false, - get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + std::move(input_media), false /*ignored*/, + get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); } void MessagesManager::edit_inline_message_media(const string &inline_message_id, @@ -25508,19 +25624,13 @@ void MessagesManager::edit_inline_message_media(const string &inline_message_id, return promise.set_error(Status::Error(400, "Unsupported input message content type")); } - auto r_input_message_content = process_input_message_content(DialogId(), std::move(input_message_content)); - if (r_input_message_content.is_error()) { - return promise.set_error(r_input_message_content.move_as_error()); - } - InputMessageContent content = r_input_message_content.move_as_ok(); + TRY_RESULT_PROMISE(promise, content, process_input_message_content(DialogId(), std::move(input_message_content))); if (!content.ttl.is_empty()) { return promise.set_error(Status::Error(400, "Can't enable self-destruction for media")); } - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { @@ -25535,28 +25645,22 @@ void MessagesManager::edit_inline_message_media(const string &inline_message_id, const FormattedText *caption = get_message_content_caption(content.content.get()); td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption == nullptr ? "" : caption->text, - get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_inline_message_media"), + get_input_message_entities(td_->user_manager_.get(), caption, "edit_inline_message_media"), std::move(input_media), content.invert_media, - get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); } void MessagesManager::edit_inline_message_caption(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_caption, - Promise &&promise) { + bool invert_media, Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); - auto r_caption = - get_formatted_text(td_, DialogId(), std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false); - if (r_caption.is_error()) { - return promise.set_error(r_caption.move_as_error()); - } - auto caption = r_caption.move_as_ok(); - - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, caption, + get_formatted_text(td_, td_->dialog_manager_->get_my_dialog_id(), std::move(input_caption), + td_->auth_manager_->is_bot(), true, false, false)); + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { @@ -25565,8 +25669,8 @@ void MessagesManager::edit_inline_message_caption(const string &inline_message_i td_->create_handler(std::move(promise)) ->send(1 << 11, std::move(input_bot_inline_message_id), caption.text, - get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_inline_message_caption"), - nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + get_input_message_entities(td_->user_manager_.get(), caption.entities, "edit_inline_message_caption"), + nullptr, invert_media, get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); } void MessagesManager::edit_inline_message_reply_markup(const string &inline_message_id, @@ -25574,10 +25678,8 @@ void MessagesManager::edit_inline_message_reply_markup(const string &inline_mess Promise &&promise) { CHECK(td_->auth_manager_->is_bot()); - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true)); auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id); if (input_bot_inline_message_id == nullptr) { @@ -25586,27 +25688,17 @@ void MessagesManager::edit_inline_message_reply_markup(const string &inline_mess td_->create_handler(std::move(promise)) ->send(0, std::move(input_bot_inline_message_id), string(), vector>(), - nullptr, false, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok())); + nullptr, false /*ignored*/, get_input_reply_markup(td_->user_manager_.get(), new_reply_markup)); } void MessagesManager::edit_message_scheduling_state( MessageFullId message_full_id, td_api::object_ptr &&scheduling_state, Promise &&promise) { - auto r_schedule_date = get_message_schedule_date(std::move(scheduling_state)); - if (r_schedule_date.is_error()) { - return promise.set_error(r_schedule_date.move_as_error()); - } - auto schedule_date = r_schedule_date.move_as_ok(); + TRY_RESULT_PROMISE(promise, schedule_date, get_message_schedule_date(std::move(scheduling_state))); auto dialog_id = message_full_id.get_dialog_id(); - Dialog *d = get_dialog_force(dialog_id, "edit_message_scheduling_state"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Edit)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, + check_dialog_access(dialog_id, true, AccessRights::Edit, "edit_message_scheduling_state")); Message *m = get_message_force(d, message_full_id.get_message_id(), "edit_message_scheduling_state"); if (m == nullptr) { @@ -25628,18 +25720,39 @@ void MessagesManager::edit_message_scheduling_state( if (schedule_date > 0) { td_->create_handler(std::move(promise)) ->send(0, dialog_id, m->message_id, string(), vector>(), nullptr, - false, nullptr, schedule_date); + m->invert_media /*ignored*/, nullptr, schedule_date); } else { td_->create_handler(std::move(promise))->send(dialog_id, m->message_id); } } +void MessagesManager::set_message_fact_check(MessageFullId message_full_id, + td_api::object_ptr &&text, + Promise &&promise) { + auto dialog_id = message_full_id.get_dialog_id(); + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, false, AccessRights::Read, "set_message_fact_check")); + + const Message *m = get_message_force(d, message_full_id.get_message_id(), "set_message_fact_check"); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (!td_->dialog_manager_->is_broadcast_channel(dialog_id) || !m->message_id.is_valid() || + !m->message_id.is_server()) { + return promise.set_error(Status::Error(400, "Message fact-check can't be changed for the message")); + } + + TRY_RESULT_PROMISE(promise, fact_check_text, + get_formatted_text(td_, dialog_id, std::move(text), false, true, true, false)); + + td_->create_handler(std::move(promise))->send(dialog_id, m->message_id, fact_check_text); +} + bool MessagesManager::is_discussion_message(DialogId dialog_id, const Message *m) const { if (m == nullptr || m->forward_info == nullptr) { return false; } if (m->sender_user_id.is_valid()) { - if (!td_->auth_manager_->is_bot() || m->sender_user_id != ContactsManager::get_service_notifications_user_id()) { + if (!td_->auth_manager_->is_bot() || m->sender_user_id != UserManager::get_service_notifications_user_id()) { return false; } } @@ -25940,6 +26053,9 @@ int32 MessagesManager::get_message_flags(const Message *m) { if (m->invert_media) { flags |= SEND_MESSAGE_FLAG_INVERT_MEDIA; } + if (m->effect_id != 0) { + flags |= SEND_MESSAGE_FLAG_EFFECT; + } return flags; } @@ -25970,7 +26086,8 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c if (m->message_id.is_local()) { return false; } - if (m->via_bot_user_id.is_valid() && m->via_bot_user_id != td_->contacts_manager_->get_my_id()) { + auto is_inline_message = m->via_bot_user_id.is_valid(); + if (is_inline_message && m->via_bot_user_id != td_->user_manager_->get_my_id()) { return false; } @@ -25994,12 +26111,12 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c } break; case DialogType::Channel: { - if (m->via_bot_user_id.is_valid()) { + if (is_inline_message) { // outgoing via_bot messages can always be edited break; } auto channel_id = dialog_id.get_channel_id(); - auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id); + auto channel_status = td_->chat_manager_->get_channel_permissions(channel_id); if (m->is_channel_post) { if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) { return false; @@ -26024,7 +26141,7 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c Result> MessagesManager::get_dialog_reply_markup( DialogId dialog_id, tl_object_ptr &&reply_markup) const { - return get_reply_markup(std::move(reply_markup), dialog_id, td_->auth_manager_->is_bot(), + return get_reply_markup(std::move(reply_markup), dialog_id.get_type(), td_->auth_manager_->is_bot(), td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr)); } @@ -26164,8 +26281,8 @@ MessageOrigin MessagesManager::get_forwarded_message_origin(DialogId dialog_id, origin = m->forward_info->get_origin(); } else if (m->is_channel_post) { if (td_->dialog_manager_->is_broadcast_channel(dialog_id)) { - auto author_signature = m->sender_user_id.is_valid() ? td_->contacts_manager_->get_user_title(m->sender_user_id) - : m->author_signature; + auto author_signature = + m->sender_user_id.is_valid() ? td_->user_manager_->get_user_title(m->sender_user_id) : m->author_signature; origin = MessageOrigin{UserId(), dialog_id, m->message_id, std::move(author_signature), string()}; } else { LOG(ERROR) << "Don't know how to forward a channel post not from a channel"; @@ -26239,7 +26356,7 @@ void MessagesManager::fix_forwarded_message(Message *m, DialogId to_dialog_id, c // if there is no via_bot_user_id, then the original message was sent by the game owner m->via_bot_user_id = forwarded_message->sender_user_id; } - if (m->via_bot_user_id == td_->contacts_manager_->get_my_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 m->via_bot_user_id = UserId(); } @@ -26296,12 +26413,9 @@ Result MessagesManager::get_forwarded_messag if (from_dialog == nullptr) { return Status::Error(400, "Chat to forward messages from not found"); } - if (!td_->dialog_manager_->have_input_peer(from_dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(from_dialog_id, false, AccessRights::Read)) { return Status::Error(400, "Can't access the chat to forward messages from"); } - if (from_dialog_id.get_type() == DialogType::SecretChat) { - return Status::Error(400, "Can't forward messages from secret chats"); - } if (td_->dialog_manager_->get_dialog_has_protected_content(from_dialog_id)) { for (const auto ©_option : copy_options) { if (!copy_option.send_copy || !td_->auth_manager_->is_bot()) { @@ -26316,7 +26430,7 @@ Result MessagesManager::get_forwarded_messag } TRY_STATUS(can_send_message(to_dialog_id)); - TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false)); + TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false, false)); TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageInputReplyTo())); { @@ -26398,6 +26512,10 @@ Result MessagesManager::get_forwarded_messag : MessageContentDupType::Forward; auto input_reply_to = std::move(copy_options[i].input_reply_to); auto reply_markup = std::move(copy_options[i].reply_markup); + auto new_invert_media = is_local_copy && copy_options[i].replace_caption && + is_allowed_invert_caption_message_content(forwarded_message->content->get_type()) + ? copy_options[i].new_invert_media + : forwarded_message->invert_media; unique_ptr content = dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type, std::move(copy_options[i])); if (content == nullptr) { @@ -26440,13 +26558,13 @@ Result MessagesManager::get_forwarded_messag if (is_local_copy) { auto original_reply_to_message_id = forwarded_message->replied_message_info.get_same_chat_reply_to_message_id(true); - copied_messages.push_back( - {std::move(content), std::move(input_reply_to), forwarded_message->message_id, original_reply_to_message_id, - std::move(reply_markup), forwarded_message->media_album_id, - get_message_disable_web_page_preview(forwarded_message), forwarded_message->invert_media, i}); + copied_messages.push_back({std::move(content), std::move(input_reply_to), forwarded_message->message_id, + original_reply_to_message_id, std::move(reply_markup), + forwarded_message->media_album_id, + get_message_disable_web_page_preview(forwarded_message), new_invert_media, i}); } else { forwarded_message_contents.push_back( - {std::move(content), forwarded_message->invert_media, forwarded_message->media_album_id, i}); + {std::move(content), new_invert_media, forwarded_message->media_album_id, i}); } } result.top_thread_message_id = top_thread_message_id; @@ -26577,7 +26695,7 @@ Result> MessagesManager::forward_messages( bool is_secret = to_dialog_id.get_type() == DialogType::SecretChat; bool is_copy = !is_secret; bool need_invalidate_authentication_code = - from_dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()) && !td_->auth_manager_->is_bot(); + from_dialog_id == DialogId(UserManager::get_service_notifications_user_id()) && !td_->auth_manager_->is_bot(); vector authentication_codes; for (const auto &copied_message : copied_messages) { if (forwarded_message_id_to_new_message_id.count(copied_message.original_reply_to_message_id) > 0) { @@ -26591,7 +26709,7 @@ Result> MessagesManager::forward_messages( if (!input_reply_to.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) { auto it = forwarded_message_id_to_new_message_id.find(copied_message.original_reply_to_message_id); if (it != forwarded_message_id_to_new_message_id.end()) { - input_reply_to = MessageInputReplyTo{it->second, DialogId(), FormattedText(), 0}; + input_reply_to = MessageInputReplyTo{it->second, DialogId(), MessageQuote()}; } } @@ -26672,7 +26790,7 @@ Result> MessagesManager::send_quick_reply_s auto *d = get_dialog(dialog_id); CHECK(d != nullptr); - MessageSendOptions message_send_options(false, false, false, false, false, 0, sending_id); + MessageSendOptions message_send_options(false, false, false, false, false, 0, sending_id, 0); FlatHashMap original_message_id_to_new_message_id; vector> result; vector sent_messages; @@ -26683,13 +26801,14 @@ Result> MessagesManager::send_quick_reply_s if (content.original_reply_to_message_id_.is_valid()) { auto it = original_message_id_to_new_message_id.find(content.original_reply_to_message_id_); if (it != original_message_id_to_new_message_id.end()) { - input_reply_to = MessageInputReplyTo{it->second, DialogId(), FormattedText(), 0}; + input_reply_to = MessageInputReplyTo{it->second, DialogId(), MessageQuote()}; } } Message *m = get_message_to_send(d, MessageId(), std::move(input_reply_to), message_send_options, std::move(content.content_), content.invert_media_, &need_update_dialog_pos, false, nullptr, DialogId(), true); + m->via_bot_user_id = content.via_bot_user_id_; m->reply_markup = std::move(content.reply_markup_); m->disable_web_page_preview = content.disable_web_page_preview_; m->media_album_id = content.media_album_id_; @@ -26699,7 +26818,7 @@ Result> MessagesManager::send_quick_reply_s send_update_new_message(d, m); } sent_messages.push_back(m); - sent_message_ids.push_back(m->message_id); + sent_message_ids.push_back(content.original_message_id_); result.push_back(get_message_object(dialog_id, m, "send_quick_reply_shortcut_messages")); } @@ -26713,6 +26832,44 @@ Result> MessagesManager::send_quick_reply_s return get_messages_object(-1, std::move(result), false); } +class MessagesManager::SendQuickReplyShortcutMessagesLogEvent { + public: + DialogId dialog_id; + QuickReplyShortcutId shortcut_id; + vector message_ids; + vector messages_in; + vector> messages_out; + + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + td::store(dialog_id, storer); + td::store(shortcut_id, storer); + td::store(message_ids, storer); + td::store(messages_in, storer); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + td::parse(dialog_id, parser); + td::parse(shortcut_id, parser); + td::parse(message_ids, parser); + td::parse(messages_out, parser); + } +}; + +uint64 MessagesManager::save_send_quick_reply_shortcut_messages_log_event(DialogId dialog_id, + QuickReplyShortcutId shortcut_id, + const vector &messages, + const vector &message_ids) { + SendQuickReplyShortcutMessagesLogEvent log_event{dialog_id, shortcut_id, message_ids, messages, Auto()}; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendQuickReplyShortcutMessages, + get_log_event_storer(log_event)); +} + void MessagesManager::do_send_quick_reply_shortcut_messages(DialogId dialog_id, QuickReplyShortcutId shortcut_id, const vector &messages, const vector &message_ids, uint64 log_event_id) { @@ -26726,7 +26883,7 @@ void MessagesManager::do_send_quick_reply_shortcut_messages(DialogId dialog_id, } if (log_event_id == 0 && G()->use_message_database()) { - // log_event_id = save_send_quick_reply_shortcut_messages_log_event(dialog_id, shprtcut_id, messages, message_ids); + log_event_id = save_send_quick_reply_shortcut_messages_log_event(dialog_id, shortcut_id, messages, message_ids); } vector random_ids = @@ -26839,17 +26996,13 @@ Result> MessagesManager::resend_messages(DialogId dialog_id, v if (need_another_reply_quote && message_ids.size() == 1 && quote != nullptr) { CHECK(message->input_reply_to.is_valid()); CHECK(message->input_reply_to.has_quote()); // checked in on_send_message_fail - auto r_quote = get_formatted_text(td_, td_->dialog_manager_->get_my_dialog_id(), std::move(quote->text_), - td_->auth_manager_->is_bot(), true, true, true); - if (r_quote.is_ok()) { - message->input_reply_to.set_quote(r_quote.move_as_ok(), quote->position_); - } + message->input_reply_to.set_quote(MessageQuote{td_, std::move(quote)}); } else if (need_drop_reply) { message->input_reply_to = {}; } MessageSendOptions options(message->disable_notification, message->from_background, message->update_stickersets_order, message->noforwards, false, - get_message_schedule_date(message.get()), message->sending_id); + get_message_schedule_date(message.get()), message->sending_id, message->effect_id); Message *m = get_message_to_send(d, message->top_thread_message_id, std::move(message->input_reply_to), options, std::move(new_contents[i]), message->invert_media, &need_update_dialog_pos, false, nullptr, DialogId(), message->is_copy, @@ -26970,7 +27123,7 @@ void MessagesManager::share_dialogs_with_bot(MessageFullId message_full_id, int3 if (!expect_user) { return promise.set_error(Status::Error(400, "Wrong chat type")); } - if (!td_->contacts_manager_->have_user(shared_dialog_id.get_user_id())) { + if (!td_->user_manager_->have_user(shared_dialog_id.get_user_id())) { return promise.set_error(Status::Error(400, "Shared user not found")); } } @@ -26993,14 +27146,7 @@ Result MessagesManager::add_local_message( return Status::Error(400, "Can't add local message without content"); } - Dialog *d = get_dialog_force(dialog_id, "add_local_message"); - if (d == nullptr) { - return Status::Error(400, "Chat not found"); - } - - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return Status::Error(400, "Can't access the chat"); - } + TRY_RESULT(d, check_dialog_access(dialog_id, true, AccessRights::Read, "add_local_message")); TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content), false)); if (message_content.content->get_type() == MessageContentType::Poll) { return Status::Error(400, "Can't add local poll message"); @@ -27037,20 +27183,20 @@ Result MessagesManager::add_local_message( } auto dialog_type = dialog_id.get_type(); - auto my_id = td_->contacts_manager_->get_my_id(); + auto my_id = td_->user_manager_->get_my_id(); if (sender_user_id != my_id) { if (dialog_type == DialogType::User && DialogId(sender_user_id) != dialog_id) { return Status::Error(400, "Wrong sender user"); } if (dialog_type == DialogType::SecretChat) { - auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto peer_user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!peer_user_id.is_valid() || sender_user_id != peer_user_id) { return Status::Error(400, "Wrong sender user"); } } } - auto input_reply_to = get_message_input_reply_to(d, MessageId(), std::move(reply_to), false); + auto input_reply_to = create_message_input_reply_to(d, MessageId(), std::move(reply_to), false); MessageId message_id = get_next_local_message_id(d); @@ -27059,8 +27205,8 @@ Result MessagesManager::add_local_message( m->message_id = message_id; if (is_channel_post) { // sender of the post can be hidden - if (td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) { - m->author_signature = td_->contacts_manager_->get_user_title(sender_user_id); + 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); } m->sender_dialog_id = sender_dialog_id; } else { @@ -27088,7 +27234,7 @@ Result MessagesManager::add_local_message( m->view_count = 0; m->forward_count = 0; if (m->sender_user_id == my_id && dialog_type == DialogType::Channel) { - m->sender_boost_count = td_->contacts_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); + m->sender_boost_count = td_->chat_manager_->get_channel_my_boost_count(dialog_id.get_channel_id()); } m->content = std::move(message_content.content); m->invert_media = message_content.invert_media; @@ -27096,8 +27242,7 @@ Result MessagesManager::add_local_message( m->clear_draft = message_content.clear_draft; if (dialog_type == DialogType::SecretChat) { if (!is_service_message_content(m->content->get_type())) { - m->ttl = - MessageSelfDestructType(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), false); + m->ttl = MessageSelfDestructType(td_->user_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()), false); } } else if (message_content.ttl.is_valid()) { m->ttl = message_content.ttl; @@ -27438,7 +27583,7 @@ NotificationGroupFromDatabase MessagesManager::get_message_notification_group_fo CHECK(d->dialog_id.get_type() == DialogType::SecretChat); result.type = NotificationGroupType::SecretChat; result.notifications.emplace_back(d->notification_info->new_secret_chat_notification_id_, - td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()), + td_->user_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()), false, create_new_secret_chat_notification()); } else { result.type = from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages; @@ -27758,7 +27903,7 @@ void MessagesManager::get_message_notifications_from_database(DialogId dialog_id CHECK(dialog_id.get_type() == DialogType::SecretChat); vector notifications; if (!from_mentions && d->notification_info->new_secret_chat_notification_id_.get() < from_notification_id.get()) { - auto date = td_->contacts_manager_->get_secret_chat_date(dialog_id.get_secret_chat_id()); + auto date = td_->user_manager_->get_secret_chat_date(dialog_id.get_secret_chat_id()); if (date <= 0) { remove_new_secret_chat_notification(d, true); } else { @@ -28137,18 +28282,18 @@ bool MessagesManager::is_dialog_message_notification_disabled(DialogId dialog_id case DialogType::User: break; case DialogType::Chat: - if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) { + if (!td_->chat_manager_->get_chat_is_active(dialog_id.get_chat_id())) { return true; } break; case DialogType::Channel: - if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_member() || - message_date < td_->contacts_manager_->get_channel_date(dialog_id.get_channel_id())) { + if (!td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()).is_member() || + message_date < td_->chat_manager_->get_channel_date(dialog_id.get_channel_id())) { return true; } break; case DialogType::SecretChat: - if (td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { + if (td_->user_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Closed) { return true; } break; @@ -28327,7 +28472,7 @@ bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool f VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " << group_info.get_group_id() << '/' << d->dialog_id; int32 min_delay_ms = 0; - if (need_delay_message_content_notification(m->content.get(), td_->contacts_manager_->get_my_id())) { + 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) { min_delay_ms = 1000; // 1 second @@ -28513,7 +28658,7 @@ void MessagesManager::send_update_message_edited(DialogId dialog_id, const Messa send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(dialog_id, "updateMessageEdited"), m->message_id.get(), edit_date, - get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup))); + get_reply_markup_object(td_->user_manager_.get(), m->reply_markup))); } void MessagesManager::send_update_message_interaction_info(DialogId dialog_id, const Message *m) const { @@ -28548,6 +28693,18 @@ void MessagesManager::send_update_message_unread_reactions(DialogId dialog_id, c get_unread_reactions_object(dialog_id, m), unread_reaction_count)); } +void MessagesManager::send_update_message_fact_check(DialogId dialog_id, const Message *m) const { + CHECK(m != nullptr); + if (td_->auth_manager_->is_bot() || !m->is_update_sent) { + return; + } + + send_closure( + G()->td(), &Td::send_update, + td_api::make_object(get_chat_id_object(dialog_id, "updateMessageFactCheck"), + m->message_id.get(), get_message_fact_check_object(m))); +} + void MessagesManager::send_update_message_live_location_viewed(MessageFullId message_full_id) { CHECK(get_message(message_full_id) != nullptr); send_closure(G()->td(), &Td::send_update, @@ -28567,7 +28724,7 @@ void MessagesManager::send_update_delete_messages(DialogId dialog_id, vectormessages.empty()); if ((d->dialog_id.get_type() == DialogType::User || d->dialog_id.get_type() == DialogType::SecretChat) && @@ -28575,7 +28732,7 @@ void MessagesManager::send_update_new_chat(Dialog *d) { (void)td_->dialog_manager_->get_dialog_photo(d->dialog_id); // to apply pending user photo } d->is_update_new_chat_being_sent = true; - auto chat_object = get_chat_object(d); + auto chat_object = get_chat_object(d, source); bool has_action_bar = chat_object->action_bar_ != nullptr; bool has_background = chat_object->background_ != nullptr; bool has_theme = !chat_object->theme_name_.empty(); @@ -28863,7 +29020,7 @@ void MessagesManager::send_update_secret_chats_with_user_action_bar(const Dialog return; } - td_->contacts_manager_->for_each_secret_chat_with_user( + td_->user_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog @@ -28893,10 +29050,27 @@ void MessagesManager::send_update_chat_action_bar(Dialog *d) { send_update_secret_chats_with_user_action_bar(d); } +void MessagesManager::send_update_chat_business_bot_manage_bar(Dialog *d) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (d->business_bot_manage_bar != nullptr && d->business_bot_manage_bar->is_empty()) { + d->business_bot_manage_bar = nullptr; + } + + CHECK(d != nullptr); + LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_business_bot_manage_bar"; + on_dialog_updated(d->dialog_id, "send_update_chat_business_bot_manage_bar"); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object( + get_chat_id_object(d->dialog_id, "updateChatBusinessBotManageBar"), get_business_bot_manage_bar_object(d))); +} + void MessagesManager::send_update_chat_available_reactions(const Dialog *d) { CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_available_reactions"; - auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(); + auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object(td_); send_closure(G()->td(), &Td::send_update, td_api::make_object( get_chat_id_object(d->dialog_id, "updateChatAvailableReactions"), std::move(available_reactions))); @@ -28911,7 +29085,7 @@ void MessagesManager::send_update_secret_chats_with_user_background(const Dialog return; } - td_->contacts_manager_->for_each_secret_chat_with_user( + td_->user_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog @@ -28948,7 +29122,7 @@ void MessagesManager::send_update_secret_chats_with_user_theme(const Dialog *d) return; } - td_->contacts_manager_->for_each_secret_chat_with_user( + td_->user_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog @@ -29122,7 +29296,9 @@ void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId o return; } CHECK(old_message_id.is_yet_unsent()); + CHECK(new_message_id == MessageId() || new_message_id.is_valid() || new_message_id.is_valid_scheduled()); + MessageFullId old_message_full_id(dialog_id, old_message_id); for (auto message_full_id : it->second) { auto reply_d = get_dialog(message_full_id.get_dialog_id()); CHECK(reply_d != nullptr); @@ -29130,14 +29306,18 @@ void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId o CHECK(replied_m != nullptr); const auto *input_reply_to = get_message_input_reply_to(replied_m); CHECK(input_reply_to != nullptr); - CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == MessageFullId(dialog_id, old_message_id)); + CHECK(input_reply_to->get_reply_message_full_id(reply_d->dialog_id) == old_message_full_id); if (new_message_id != MessageId()) { + LOG_CHECK(replied_m->replied_message_info.get_reply_message_full_id(reply_d->dialog_id, true) == + old_message_full_id) + << old_message_full_id << ' ' << replied_m->replied_message_info << ' ' << *input_reply_to; update_message_reply_to_message_id(reply_d, replied_m, new_message_id, true); } else { set_message_reply(reply_d, replied_m, MessageInputReplyTo(), true); } } if (have_new_message) { + CHECK(new_message_id != MessageId()); CHECK(!new_message_id.is_yet_unsent()); replied_by_yet_unsent_messages_[MessageFullId{dialog_id, new_message_id}] = static_cast(it->second.size()); } else { @@ -29382,7 +29562,7 @@ void MessagesManager::on_send_media_group_file_reference_error(DialogId dialog_i CHECK(dialog_id.get_type() != DialogType::SecretChat); if (message_ids.empty()) { - // all messages was deleted, nothing to do + // all messages were deleted, nothing to do return; } @@ -29510,8 +29690,7 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) { if (error.message() == "CHAT_GUEST_SEND_FORBIDDEN") { error_code = 400; if (dialog_id.get_type() == DialogType::Channel) { - td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), Promise(), - "CHAT_GUEST_SEND_FORBIDDEN"); + td_->chat_manager_->reload_channel(dialog_id.get_channel_id(), Promise(), "CHAT_GUEST_SEND_FORBIDDEN"); } } else if (error.message() != "CHANNEL_PUBLIC_GROUP_NA" && error.message() != "USER_IS_BLOCKED" && error.message() != "USER_BOT_INVALID" && error.message() != "USER_DELETED") { @@ -29626,7 +29805,11 @@ void MessagesManager::fail_send_message(MessageFullId message_full_id, int32 err if (get_message_force(d, new_message_id, "fail_send_message") != nullptr || is_deleted_message(d, new_message_id) || new_message_id <= d->last_clear_history_message_id) { new_message_id = get_next_local_message_id(d); - } else if (new_message_id > d->last_assigned_message_id) { + while (get_message_force(d, new_message_id, "fail_send_message") != nullptr) { + new_message_id = new_message_id.get_next_message_id(MessageType::Local); + } + } + if (new_message_id > d->last_assigned_message_id) { d->last_assigned_message_id = new_message_id; } } else { @@ -29722,7 +29905,7 @@ void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, Message if (d == nullptr) { LOG(INFO) << "Ignore update chat draft in unknown " << dialog_id; if (draft_message != nullptr && draft_message->get_id() != telegram_api::draftMessageEmpty::ID) { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message"; } else { send_get_dialog_query(dialog_id, Auto(), 0, "on_update_dialog_draft_message"); @@ -30078,7 +30261,7 @@ void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_bl return; } if (dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); + td_->user_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); } auto d = get_dialog_force(dialog_id, "on_update_dialog_is_blocked"); @@ -30117,7 +30300,7 @@ void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_ block_list_id.get_block_list_object())); if (d->dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); + td_->user_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked, is_blocked_for_stories); if (d->know_action_bar) { if (is_blocked) { @@ -30130,7 +30313,7 @@ void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_ } } - td_->contacts_manager_->for_each_secret_chat_with_user( + td_->user_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this, is_blocked, is_blocked_for_stories](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog @@ -30142,6 +30325,23 @@ void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked, bool is_ } } +void MessagesManager::on_update_dialog_business_bot_is_paused(DialogId dialog_id, bool is_paused) { + auto d = get_dialog_force(dialog_id, "on_update_dialog_business_bot_is_paused"); + CHECK(d != nullptr); + if (d->business_bot_manage_bar != nullptr && d->business_bot_manage_bar->set_business_bot_is_paused(is_paused)) { + send_update_chat_business_bot_manage_bar(d); + } +} + +void MessagesManager::on_update_dialog_business_bot_removed(DialogId dialog_id) { + auto d = get_dialog_force(dialog_id, "on_update_dialog_business_bot_removed"); + CHECK(d != nullptr); + if (d->business_bot_manage_bar != nullptr) { + d->business_bot_manage_bar = nullptr; + send_update_chat_business_bot_manage_bar(d); + } +} + void MessagesManager::on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId pinned_message_id) { if (!dialog_id.is_valid()) { LOG(ERROR) << "Receive pinned message in invalid " << dialog_id; @@ -30317,7 +30517,7 @@ void MessagesManager::fix_pending_join_requests(DialogId dialog_id, int32 &pendi return true; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_status(chat_id); + auto status = td_->chat_manager_->get_chat_status(chat_id); if (!status.can_manage_invite_links()) { return true; } @@ -30325,7 +30525,7 @@ void MessagesManager::fix_pending_join_requests(DialogId dialog_id, int32 &pendi } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - auto status = td_->contacts_manager_->get_channel_permissions(channel_id); + auto status = td_->chat_manager_->get_channel_permissions(channel_id); if (!status.can_manage_invite_links()) { return true; } @@ -30507,7 +30707,7 @@ void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) { d->folder_id = folder_id; if (d->dialog_id.get_type() == DialogType::SecretChat) { // need to change action bar only for the secret chat and keep unarchive for the main chat - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id()); if (d->is_update_new_chat_sent && user_id.is_valid()) { const Dialog *user_d = get_dialog(DialogId(user_id)); if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) { @@ -30612,8 +30812,8 @@ void MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id(Dial if (default_join_as_dialog_id.is_valid()) { if (default_join_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_join_as_dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id"); - } else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id(), - "on_update_dialog_default_join_group_call_as_dialog_id") || + } else if (!td_->user_manager_->have_user_force(default_join_as_dialog_id.get_user_id(), + "on_update_dialog_default_join_group_call_as_dialog_id") || default_join_as_dialog_id != td_->dialog_manager_->get_my_dialog_id()) { default_join_as_dialog_id = DialogId(); } @@ -30653,8 +30853,8 @@ void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogI if (default_send_as_dialog_id.is_valid()) { if (default_send_as_dialog_id.get_type() != DialogType::User) { force_create_dialog(default_send_as_dialog_id, "on_update_dialog_default_send_message_as_dialog_id"); - } else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id(), - "on_update_dialog_default_send_message_as_dialog_id") || + } else if (!td_->user_manager_->have_user_force(default_send_as_dialog_id.get_user_id(), + "on_update_dialog_default_send_message_as_dialog_id") || default_send_as_dialog_id != td_->dialog_manager_->get_my_dialog_id()) { default_send_as_dialog_id = DialogId(); } @@ -30662,7 +30862,8 @@ void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogI if (d->default_send_message_as_dialog_id != default_send_as_dialog_id) { if (force || default_send_as_dialog_id.is_valid() || - (created_public_broadcasts_inited_ && created_public_broadcasts_.empty())) { + (td_->chat_manager_->are_created_public_broadcasts_inited() && + td_->chat_manager_->get_created_public_broadcasts().empty())) { LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_as_dialog_id; d->need_drop_default_send_message_as_dialog_id = false; d->default_send_message_as_dialog_id = default_send_as_dialog_id; @@ -30702,15 +30903,20 @@ void MessagesManager::set_dialog_message_ttl(Dialog *d, MessageTtl message_ttl) } void MessagesManager::on_create_new_dialog(telegram_api::object_ptr &&updates, - DialogType expected_type, - Promise> &&promise) { + MissingInvitees &&missing_invitees, + Promise> &&chat_promise, + Promise> &&channel_promise) { LOG(INFO) << "Receive result for creation of a chat: " << to_string(updates); + auto fail = [&](Slice message) { + chat_promise.set_error(Status::Error(500, message)); + channel_promise.set_error(Status::Error(500, message)); + }; auto sent_messages = UpdatesManager::get_new_messages(updates.get()); auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates.get()); if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u) { LOG(ERROR) << "Receive wrong result for create group or channel chat " << oneline(to_string(updates)); - return promise.set_error(Status::Error(500, "Unsupported server response")); + return fail("Unsupported server response"); } auto *message = sent_messages.begin()->first; @@ -30718,20 +30924,21 @@ void MessagesManager::on_create_new_dialog(telegram_api::object_ptrsecond) { - return promise.set_error(Status::Error(500, "Scheduled message received")); + return fail("Scheduled message received"); } + auto expected_type = chat_promise ? DialogType::Chat : DialogType::Channel; auto dialog_id = DialogId::get_message_dialog_id(message); if (dialog_id.get_type() != expected_type) { - return promise.set_error(Status::Error(500, "Chat of wrong type has been created")); + return fail("Chat of wrong type has been created"); } if (message->get_id() != telegram_api::messageService::ID) { - return promise.set_error(Status::Error(500, "Invalid message received")); + return fail("Invalid message received"); } auto action_id = static_cast(message)->action_->get_id(); if (action_id != telegram_api::messageActionChatCreate::ID && action_id != telegram_api::messageActionChannelCreate::ID) { - return promise.set_error(Status::Error(500, "Invalid service message received")); + return fail("Invalid service message received"); } const Dialog *d = get_dialog(dialog_id); @@ -30739,18 +30946,25 @@ void MessagesManager::on_create_new_dialog(telegram_api::object_ptr( + get_chat_id_object(dialog_id, "on_create_new_dialog"), + missing_invitees.get_failed_to_add_members_object(td_->user_manager_.get()))); + } else { + return channel_promise.set_value(get_chat_object(d, "on_create_new_dialog")); + } } if (pending_created_dialogs_.count(dialog_id) == 0) { PendingCreatedDialog pending_created_dialog; - pending_created_dialog.promise_ = std::move(promise); - pending_created_dialog.group_invite_privacy_forbidden_user_ids_ = - td_->updates_manager_->extract_group_invite_privacy_forbidden_updates(updates); + pending_created_dialog.failed_to_add_members_ = + missing_invitees.get_failed_to_add_members_object(td_->user_manager_.get()); + pending_created_dialog.chat_promise_ = std::move(chat_promise); + pending_created_dialog.channel_promise_ = std::move(channel_promise); pending_created_dialogs_.emplace(dialog_id, std::move(pending_created_dialog)); } else { LOG(ERROR) << "Receive twice " << dialog_id << " as result of chat creation"; - return promise.set_error(Status::Error(500, "Chat was created earlier")); + return fail("Chat was created earlier"); } td_->updates_manager_->on_get_updates(std::move(updates), Promise()); @@ -30783,6 +30997,7 @@ void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector } void MessagesManager::set_dialog_has_bots(Dialog *d, bool has_bots) { + CHECK(!td_->auth_manager_->is_bot()); CHECK(d != nullptr); LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_has_bots"; @@ -30892,7 +31107,7 @@ void MessagesManager::on_dialog_user_is_contact_updated(DialogId dialog_id, bool if (td_->dialog_filter_manager_->have_dialog_filters() && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated"); - td_->contacts_manager_->for_each_secret_chat_with_user( + td_->user_manager_->for_each_secret_chat_with_user( d->dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { DialogId dialog_id(secret_chat_id); auto d = get_dialog(dialog_id); // must not create the dialog @@ -30913,6 +31128,9 @@ void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool if (d->action_bar != nullptr && d->action_bar->on_user_deleted()) { send_update_chat_action_bar(d); } + if (d->business_bot_manage_bar != nullptr && d->business_bot_manage_bar->on_user_deleted()) { + send_update_chat_business_bot_manage_bar(d); + } } else { repair_dialog_action_bar(d, "on_dialog_user_is_deleted_updated"); } @@ -30920,26 +31138,24 @@ void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool if (td_->dialog_filter_manager_->have_dialog_filters() && d->order != DEFAULT_ORDER) { update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); - td_->contacts_manager_->for_each_secret_chat_with_user( - dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { - DialogId dialog_id(secret_chat_id); - auto d = get_dialog(dialog_id); // must not create the dialog - if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) { - update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); - } - }); + td_->user_manager_->for_each_secret_chat_with_user(dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { + DialogId dialog_id(secret_chat_id); + auto d = get_dialog(dialog_id); // must not create the dialog + if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) { + update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated"); + } + }); } if (is_deleted && d->has_bots) { set_dialog_has_bots(d, false); - td_->contacts_manager_->for_each_secret_chat_with_user( - dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { - DialogId dialog_id(secret_chat_id); - auto d = get_dialog(dialog_id); // must not create the dialog - if (d != nullptr && d->is_update_new_chat_sent && d->has_bots) { - set_dialog_has_bots(d, false); - } - }); + td_->user_manager_->for_each_secret_chat_with_user(dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) { + DialogId dialog_id(secret_chat_id); + auto d = get_dialog(dialog_id); // must not create the dialog + if (d != nullptr && d->is_update_new_chat_sent && d->has_bots) { + set_dialog_has_bots(d, false); + } + }); } } } @@ -30997,7 +31213,7 @@ void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise && } return promise.set_error(Status::Error(500, "Wrong getDialog query")); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { if (log_event_id != 0) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); } @@ -31080,11 +31296,11 @@ bool MessagesManager::get_dialog_view_as_topics(const Dialog *d) const { } bool MessagesManager::get_dialog_has_scheduled_messages(const Dialog *d) const { - if (!td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(d->dialog_id, true, AccessRights::Read)) { return false; } if (td_->dialog_manager_->is_broadcast_channel(d->dialog_id) && - !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).can_post_messages()) { + !td_->chat_manager_->get_channel_status(d->dialog_id.get_channel_id()).can_post_messages()) { return false; } // TODO send updateChatHasScheduledMessage when can_post_messages changes @@ -31162,7 +31378,7 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) { return; } LOG(INFO) << "Send " << action << " in " << dialog_id; - td_->dialog_action_manager_->send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), + td_->dialog_action_manager_->send_dialog_action(dialog_id, m->top_thread_message_id, {}, std::move(action), Promise()); } @@ -31487,7 +31703,7 @@ vector MessagesManager::get_dialog_lists_to_add_dialog(DialogId di } if (dialog_id != td_->dialog_manager_->get_my_dialog_id() && - dialog_id != DialogId(ContactsManager::get_service_notifications_user_id())) { + dialog_id != DialogId(UserManager::get_service_notifications_user_id())) { result.push_back( DialogListId(get_dialog(dialog_id)->folder_id == FolderId::archive() ? FolderId::main() : FolderId::archive())); } @@ -31502,13 +31718,7 @@ void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog LOG(INFO) << "Receive addChatToList request to add " << dialog_id << " to " << dialog_list_id; CHECK(!td_->auth_manager_->is_bot()); - Dialog *d = get_dialog_force(dialog_id, "add_dialog_to_list"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Read, "add_dialog_to_list")); if (d->order == DEFAULT_ORDER) { return promise.set_error(Status::Error(400, "Chat is not in a chat list")); @@ -31529,9 +31739,8 @@ void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog return promise.set_value(Unit()); } - if (folder_id == FolderId::archive() && - (dialog_id == td_->dialog_manager_->get_my_dialog_id() || - dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()))) { + if (folder_id == FolderId::archive() && (dialog_id == td_->dialog_manager_->get_my_dialog_id() || + dialog_id == DialogId(UserManager::get_service_notifications_user_id()))) { return promise.set_error(Status::Error(400, "Chat can't be archived")); } @@ -31615,15 +31824,15 @@ void MessagesManager::set_dialog_available_reactions( return promise.set_error(Status::Error(400, "Can't change private chat available reactions")); case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings() || - (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) { + (td_->auth_manager_->is_bot() && !td_->chat_manager_->is_appointed_chat_administrator(chat_id))) { return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions")); } break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions")); } @@ -31654,24 +31863,18 @@ void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Prom return promise.set_error(Status::Error(400, "Message auto-delete time can't be negative")); } - Dialog *d = get_dialog_force(dialog_id, "set_dialog_message_ttl"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Have no write access to the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Write, "set_dialog_message_ttl")); switch (dialog_id.get_type()) { case DialogType::User: if (dialog_id == td_->dialog_manager_->get_my_dialog_id() || - dialog_id == DialogId(ContactsManager::get_service_notifications_user_id())) { + dialog_id == DialogId(UserManager::get_service_notifications_user_id())) { return promise.set_error(Status::Error(400, "Message auto-delete time in the chat can't be changed")); } break; case DialogType::Chat: { auto chat_id = dialog_id.get_chat_id(); - auto status = td_->contacts_manager_->get_chat_permissions(chat_id); + auto status = td_->chat_manager_->get_chat_permissions(chat_id); if (!status.can_change_info_and_settings()) { return promise.set_error( Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); @@ -31679,7 +31882,7 @@ void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Prom break; } case DialogType::Channel: { - auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()); + auto status = td_->chat_manager_->get_channel_permissions(dialog_id.get_channel_id()); if (!status.can_change_info_and_settings()) { return promise.set_error( Status::Error(400, "Not enough rights to change message auto-delete time in the chat")); @@ -31715,13 +31918,7 @@ void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Prom } void MessagesManager::set_dialog_theme(DialogId dialog_id, const string &theme_name, Promise &&promise) { - auto d = get_dialog_force(dialog_id, "set_dialog_theme"); - if (d == nullptr) { - return promise.set_error(Status::Error(400, "Chat not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_RESULT_PROMISE(promise, d, check_dialog_access(dialog_id, true, AccessRights::Write, "set_dialog_theme")); switch (dialog_id.get_type()) { case DialogType::User: @@ -31730,7 +31927,7 @@ void MessagesManager::set_dialog_theme(DialogId dialog_id, const string &theme_n case DialogType::Channel: return promise.set_error(Status::Error(400, "Can't change theme in the chat")); case DialogType::SecretChat: { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (!user_id.is_valid()) { return promise.set_error(Status::Error(400, "Can't access the user")); } @@ -31985,7 +32182,7 @@ MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog * CHECK(d != nullptr); auto dialog_id = d->dialog_id; - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { return nullptr; } @@ -32031,7 +32228,7 @@ vector MessagesManager::on_get_messages_from_database(Dialog *d, vect MessageId first_message_id, bool &have_error, const char *source) { vector result; - if (!first_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { + if (!first_message_id.is_valid() || !td_->dialog_manager_->have_input_peer(d->dialog_id, true, AccessRights::Read)) { return result; } bool need_update = false; @@ -32092,7 +32289,7 @@ void MessagesManager::fix_new_message(const Dialog *d, Message *m, bool from_dat } auto dialog_type = dialog_id.get_type(); - if (m->sender_user_id == ContactsManager::get_anonymous_bot_user_id() && !m->sender_dialog_id.is_valid() && + if (m->sender_user_id == UserManager::get_anonymous_bot_user_id() && !m->sender_dialog_id.is_valid() && dialog_type == DialogType::Channel && !td_->dialog_manager_->is_broadcast_channel(dialog_id)) { m->sender_user_id = UserId(); m->sender_dialog_id = dialog_id; @@ -32721,14 +32918,14 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } if (m->date + 3600 >= now && m->is_outgoing) { auto channel_id = dialog_id.get_channel_id(); - auto slow_mode_delay = td_->contacts_manager_->get_channel_slow_mode_delay(channel_id, "add_message_to_dialog"); - auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()); + auto slow_mode_delay = td_->chat_manager_->get_channel_slow_mode_delay(channel_id, "add_message_to_dialog"); + auto status = td_->chat_manager_->get_channel_status(dialog_id.get_channel_id()); if (m->date + slow_mode_delay > now && !status.is_administrator()) { - td_->contacts_manager_->on_update_channel_slow_mode_next_send_date(channel_id, m->date + slow_mode_delay); + td_->chat_manager_->on_update_channel_slow_mode_next_send_date(channel_id, m->date + slow_mode_delay); } } if (m->date > now - 14 * 86400) { - td_->contacts_manager_->remove_inactive_channel(dialog_id.get_channel_id()); + td_->chat_manager_->remove_inactive_channel(dialog_id.get_channel_id()); } } @@ -32797,18 +32994,18 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq dialog_id != td_->dialog_manager_->get_my_dialog_id()) { switch (dialog_type) { case DialogType::User: - td_->contacts_manager_->invalidate_user_full(dialog_id.get_user_id()); - td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Promise(), "add_message_to_dialog"); + td_->user_manager_->invalidate_user_full(dialog_id.get_user_id()); + td_->user_manager_->reload_user_full(dialog_id.get_user_id(), Promise(), "add_message_to_dialog"); break; case DialogType::Chat: case DialogType::Channel: // nothing to do break; case DialogType::SecretChat: { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { - td_->contacts_manager_->invalidate_user_full(user_id); - td_->contacts_manager_->reload_user_full(user_id, Promise(), "add_message_to_dialog"); + td_->user_manager_->invalidate_user_full(user_id); + td_->user_manager_->reload_user_full(user_id, Promise(), "add_message_to_dialog"); } break; } @@ -32828,17 +33025,6 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq d->message_lru_list.put_back(result_message); - if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid() && - !td_->auth_manager_->is_bot()) { - auto is_inserted = - yet_unsent_thread_message_ids_[MessageFullId{dialog_id, m->top_thread_message_id}].insert(m->message_id).second; - CHECK(is_inserted); - } - - if (!td_->auth_manager_->is_bot() && dialog_type == DialogType::User && !m->is_outgoing) { - td_->contacts_manager_->allow_send_message_to_user(dialog_id.get_user_id()); - } - switch (dialog_type) { case DialogType::User: case DialogType::Chat: @@ -32847,9 +33033,9 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq } break; case DialogType::Channel: - if (m->message_id.is_server() && !td_->auth_manager_->is_bot()) { - td_->contacts_manager_->register_message_users({dialog_id, m->message_id}, get_message_user_ids(m)); - td_->contacts_manager_->register_message_channels({dialog_id, m->message_id}, get_message_channel_ids(m)); + if (!td_->auth_manager_->is_bot() && m->message_id.is_server()) { + td_->user_manager_->register_message_users({dialog_id, m->message_id}, get_message_user_ids(m)); + td_->chat_manager_->register_message_channels({dialog_id, m->message_id}, get_message_channel_ids(m)); } break; case DialogType::SecretChat: @@ -32859,23 +33045,37 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq UNREACHABLE(); } - if (m->notification_id.is_valid()) { - auto notification_info = add_dialog_notification_info(d); - add_notification_id_to_message_id_correspondence(notification_info, m->notification_id, m->message_id); - } if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) { add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id); } - try_add_bot_command_message_id(dialog_id, m); + if (!td_->auth_manager_->is_bot()) { + if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid()) { + auto is_inserted = yet_unsent_thread_message_ids_[MessageFullId{dialog_id, m->top_thread_message_id}] + .insert(m->message_id) + .second; + CHECK(is_inserted); + } + + if (dialog_type == DialogType::User && !m->is_outgoing) { + td_->user_manager_->allow_send_message_to_user(dialog_id.get_user_id()); + } + + if (m->notification_id.is_valid()) { + auto notification_info = add_dialog_notification_info(d); + add_notification_id_to_message_id_correspondence(notification_info, m->notification_id, m->message_id); + } - // must be called after the message is added to correctly update replies - update_message_max_reply_media_timestamp(d, result_message, false); - update_message_max_own_media_timestamp(d, result_message); + try_add_bot_command_message_id(dialog_id, m); - if (!td_->auth_manager_->is_bot() && from_update && m->saved_messages_topic_id.is_valid()) { - CHECK(dialog_id == td_->dialog_manager_->get_my_dialog_id()); - td_->saved_messages_manager_->set_topic_last_message_id(m->saved_messages_topic_id, m->message_id, m->date); + // must be called after the message is added to correctly update replies + update_message_max_reply_media_timestamp(d, result_message, false); + update_message_max_own_media_timestamp(d, result_message); + + if (from_update && m->saved_messages_topic_id.is_valid()) { + CHECK(dialog_id == td_->dialog_manager_->get_my_dialog_id()); + td_->saved_messages_manager_->set_topic_last_message_id(m->saved_messages_topic_id, m->message_id, m->date); + } } result_message->debug_source = source; @@ -33078,8 +33278,7 @@ void MessagesManager::on_message_notification_changed(Dialog *d, const Message * if (m->is_pinned && d->notification_info != nullptr && d->notification_info->pinned_message_notification_message_id_.is_valid() && d->notification_info->mention_notification_group_.is_valid()) { - auto pinned_message = - get_message_force(d, d->notification_info->pinned_message_notification_message_id_, "after update_message"); + auto pinned_message = get_message_force(d, d->notification_info->pinned_message_notification_message_id_, source); if (pinned_message != nullptr && pinned_message->notification_id.is_valid() && is_message_notification_active(d, pinned_message) && get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) { @@ -33253,8 +33452,8 @@ bool MessagesManager::need_delete_file(MessageFullId message_full_id, FileId fil auto message_full_ids = td_->file_reference_manager_->get_some_message_file_sources(main_file_id); LOG(INFO) << "Receive " << message_full_ids << " as sources for file " << main_file_id << "/" << file_id << " from " << message_full_id; - for (const auto &other_full_messsage_id : message_full_ids) { - if (other_full_messsage_id != message_full_id) { + for (const auto &other_full_message_id : message_full_ids) { + if (other_full_message_id != message_full_id) { return false; } } @@ -33721,6 +33920,10 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr old_message->hide_via_bot = false; } } + if (old_message->via_business_bot_user_id != new_message->via_business_bot_user_id) { + old_message->via_business_bot_user_id = new_message->via_business_bot_user_id; + need_send_update = true; + } if (old_message->is_outgoing != new_message->is_outgoing && is_new_available) { if (!replace_legacy && !(message_id.is_scheduled() && dialog_id == td_->dialog_manager_->get_my_dialog_id())) { LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing @@ -33787,6 +33990,10 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr LOG(DEBUG) << "Update message media_album_id"; need_send_update = true; } + if (old_message->effect_id != new_message->effect_id) { + old_message->effect_id = new_message->effect_id; + need_send_update = true; + } if (old_message->hide_edit_date != new_message->hide_edit_date) { old_message->hide_edit_date = new_message->hide_edit_date; } @@ -33801,6 +34008,9 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr // is_from_scheduled flag shouldn't be changed, because we are unable to show/hide message notification // old_message->is_from_scheduled = new_message->is_from_scheduled; } + if (old_message->is_from_offline != new_message->is_from_offline) { + old_message->is_from_offline = new_message->is_from_offline; + } if (old_message->edit_date > 0) { // inline keyboard can be edited @@ -33886,6 +34096,9 @@ bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr "update_message")) { need_send_update = true; } + if (update_message_fact_check(d, old_message, std::move(new_message->fact_check), false)) { + need_send_update = true; + } bool is_preview_changed = false; if (old_message->invert_media != new_message->invert_media) { @@ -33977,28 +34190,7 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me } if (need_merge_files && old_file_id.is_valid()) { - auto new_file_id = get_message_content_any_file_id(new_content.get()); - if (new_file_id.is_valid()) { - FileView old_file_view = td_->file_manager_->get_file_view(old_file_id); - FileView new_file_view = td_->file_manager_->get_file_view(new_file_id); - // if file type has changed, but file size remains the same, we are trying to update local location of the new - // file with the old local location - if (old_file_view.has_local_location() && !new_file_view.has_local_location() && old_file_view.size() != 0 && - old_file_view.size() == new_file_view.size()) { - auto old_file_type = old_file_view.get_type(); - auto new_file_type = new_file_view.get_type(); - - if (is_document_file_type(old_file_type) && is_document_file_type(new_file_type)) { - auto &old_location = old_file_view.local_location(); - auto r_file_id = td_->file_manager_->register_local( - FullLocalFileLocation(new_file_type, old_location.path_, old_location.mtime_nsec_), dialog_id, - old_file_view.size()); - if (r_file_id.is_ok()) { - LOG_STATUS(td_->file_manager_->merge(new_file_id, r_file_id.ok())); - } - } - } - } + td_->file_manager_->try_merge_documents(old_file_id, get_message_content_any_file_id(new_content.get())); } } else { merge_message_contents(td_, old_content.get(), new_content.get(), need_message_changed_warning(old_message), @@ -34136,13 +34328,13 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source update_dialog_pos(d, source); if (dialog_id.get_type() == DialogType::SecretChat && !d->notification_settings.is_synchronized && - td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) { + td_->user_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) { // secret chat is being created // let's copy notification settings from main chat if available VLOG(notifications) << "Create new secret " << dialog_id << " from " << source; auto secret_chat_id = dialog_id.get_secret_chat_id(); { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); + auto user_id = td_->user_manager_->get_secret_chat_user_id(secret_chat_id); Dialog *user_d = get_dialog_force(DialogId(user_id), source); if (user_d != nullptr && user_d->notification_settings.is_synchronized) { VLOG(notifications) << "Copy notification settings from " << user_d->dialog_id << " to " << dialog_id; @@ -34162,7 +34354,7 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source } if (G()->use_message_database() && !td_->auth_manager_->is_bot() && - !td_->contacts_manager_->get_secret_chat_is_outbound(secret_chat_id)) { + !td_->user_manager_->get_secret_chat_is_outbound(secret_chat_id)) { auto notification_info = add_dialog_notification_info(d); auto notification_group_id = get_dialog_notification_group_id(dialog_id, notification_info->message_notification_group_); @@ -34174,7 +34366,7 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source notification_info->new_secret_chat_notification_id_ = get_next_notification_id(notification_info, notification_group_id, MessageId()); if (notification_info->new_secret_chat_notification_id_.is_valid()) { - auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id); + auto date = td_->user_manager_->get_secret_chat_date(secret_chat_id); set_dialog_last_notification_checked(dialog_id, notification_info->message_notification_group_, date, notification_info->new_secret_chat_notification_id_, "add_new_secret_chat"); @@ -34190,10 +34382,10 @@ void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source } } } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { if (!td_->dialog_manager_->have_dialog_info(dialog_id)) { if (expect_no_access && dialog_id.get_type() == DialogType::Channel && - td_->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) { + td_->chat_manager_->have_min_channel(dialog_id.get_channel_id())) { LOG(INFO) << "Created " << dialog_id << " for min-channel from " << source; } else { LOG(ERROR) << "Forced to create unknown " << dialog_id << " from " << source; @@ -34253,8 +34445,9 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di d->last_read_inbox_message_id = d->last_new_message_id; d->last_read_outbox_message_id = d->last_new_message_id; } - d->has_bots = dialog_id.get_user_id() != ContactsManager::get_replies_bot_user_id() && - td_->contacts_manager_->is_user_bot(dialog_id.get_user_id()); + d->has_bots = !td_->auth_manager_->is_bot() && + dialog_id.get_user_id() != UserManager::get_replies_bot_user_id() && + td_->user_manager_->is_user_bot(dialog_id.get_user_id()); d->is_has_bots_inited = true; d->is_available_reactions_inited = true; d->is_view_as_messages_inited = true; @@ -34270,7 +34463,7 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di d->is_view_as_messages_inited = true; break; case DialogType::Channel: { - if (td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { + if (td_->chat_manager_->is_broadcast_channel(dialog_id.get_channel_id())) { d->last_read_outbox_message_id = MessageId::max(); d->is_last_read_outbox_message_id_inited = true; } @@ -34306,13 +34499,13 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di d->is_is_blocked_for_stories_inited = true; d->is_view_as_messages_inited = true; if (!d->is_folder_id_inited && !td_->auth_manager_->is_bot()) { - do_set_dialog_folder_id( - d, td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id())); + do_set_dialog_folder_id(d, + td_->user_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id())); } - d->message_ttl = MessageTtl(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id())); + d->message_ttl = MessageTtl(td_->user_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id())); d->is_message_ttl_inited = true; - d->has_bots = td_->contacts_manager_->is_user_bot( - td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); + d->has_bots = + td_->user_manager_->is_user_bot(td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id())); d->is_has_bots_inited = true; d->is_available_reactions_inited = true; d->has_loaded_scheduled_messages_from_database = true; @@ -34420,8 +34613,9 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr &&di failed_to_load_dialogs_.erase(dialog_id); fix_dialog_action_bar(d, d->action_bar.get()); + fix_dialog_business_bot_manage_bar(dialog_id, d->business_bot_manage_bar.get()); - send_update_new_chat(d); + send_update_new_chat(d, source); being_added_new_dialog_id_ = DialogId(); @@ -34448,7 +34642,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft auto dialog_type = dialog_id.get_type(); if (!td_->auth_manager_->is_bot() && dialog_type == DialogType::SecretChat) { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { force_create_dialog(DialogId(user_id), "add chat with user to load/store action_bar and is_blocked"); @@ -34466,7 +34660,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft } if (being_added_dialog_id_ != dialog_id && !td_->auth_manager_->is_bot() && !is_dialog_inited(d) && - dialog_type != DialogType::SecretChat && td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { // asynchronously get dialog from the server send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20"); } @@ -34485,7 +34679,8 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft reload_source = "fix_new_dialog init available_reactions"; } else if (!d->is_folder_id_inited && order != DEFAULT_ORDER) { reload_source = "fix_new_dialog init folder_id"; - } else if (!d->is_message_ttl_inited && td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + } else if (!d->is_message_ttl_inited && + td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Write)) { reload_source = "fix_new_dialog init message_auto_delete_time"; } else if (!d->is_view_as_messages_inited && td_->dialog_manager_->is_forum_channel(dialog_id)) { reload_source = "fix_new_dialog init view_as_messages"; @@ -34498,8 +34693,8 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft } } if ((!d->know_action_bar || d->need_repair_action_bar) && !td_->auth_manager_->is_bot() && - dialog_type != DialogType::SecretChat && dialog_id != td_->dialog_manager_->get_my_dialog_id() && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + dialog_id != td_->dialog_manager_->get_my_dialog_id() && + td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { // asynchronously get action bar from the server reget_dialog_action_bar(dialog_id, "fix_new_dialog", false); } @@ -34509,7 +34704,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft set_dialog_is_forum(d, td_->dialog_manager_->is_forum_channel(dialog_id)); if (d->notification_settings.is_synchronized && !d->notification_settings.is_use_default_fixed && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) && !td_->auth_manager_->is_bot()) { + td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read) && !td_->auth_manager_->is_bot()) { LOG(INFO) << "Reget notification settings of " << dialog_id; if (dialog_type == DialogType::SecretChat) { d->notification_settings.is_use_default_fixed = true; @@ -34600,6 +34795,9 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft set_dialog_last_clear_history_date(d, last_clear_history_date, last_clear_history_message_id, "fix_new_dialog 8", is_loaded_from_database); + if (td_->dialog_manager_->is_dialog_removed_from_dialog_list(dialog_id)) { + order = DEFAULT_ORDER; + } set_dialog_order(d, order, false, is_loaded_from_database, "fix_new_dialog 9"); } @@ -34804,7 +35002,7 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr &&draft // must be after checks that dialog has at most one message, because read_history_inbox can load // pinned message to remove notification about it if (d->pending_read_channel_inbox_pts != 0 && !td_->auth_manager_->is_bot() && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) { + td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read) && need_unread_counter(d->order)) { if (d->pts == d->pending_read_channel_inbox_pts) { d->pending_read_channel_inbox_pts = 0; read_history_inbox(d, d->pending_read_channel_inbox_max_message_id, @@ -34859,7 +35057,7 @@ bool MessagesManager::add_pending_dialog_data(Dialog *d, unique_ptr &&l << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id; const Message *m = nullptr; - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { bool need_update = false; m = add_message_to_dialog(d, std::move(last_database_message), true, false, &need_update, &need_update_dialog_pos, "add_pending_dialog_data 1"); @@ -35053,16 +35251,16 @@ void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need switch (d->dialog_id.get_type()) { case DialogType::Chat: { auto chat_id = d->dialog_id.get_chat_id(); - auto date = td_->contacts_manager_->get_chat_date(chat_id); + auto date = td_->chat_manager_->get_chat_date(chat_id); LOG(INFO) << "Creation at " << date << " found"; int64 join_order = get_dialog_order(MessageId(), date); - if (join_order > new_order && td_->contacts_manager_->get_chat_status(chat_id).is_member()) { + if (join_order > new_order && td_->chat_manager_->get_chat_status(chat_id).is_member()) { new_order = join_order; } break; } case DialogType::Channel: { - auto date = td_->contacts_manager_->get_channel_date(d->dialog_id.get_channel_id()); + auto date = td_->chat_manager_->get_channel_date(d->dialog_id.get_channel_id()); LOG(INFO) << "Join at " << date << " found"; int64 join_order = get_dialog_order(MessageId(), date); if (join_order > new_order) { @@ -35071,7 +35269,7 @@ void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need break; } case DialogType::SecretChat: { - auto date = td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()); + auto date = td_->user_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()); if (date != 0 && !is_deleted_secret_chat(d)) { LOG(INFO) << "Creation at " << date << " found"; int64 creation_order = get_dialog_order(MessageId(), date); @@ -35500,7 +35698,7 @@ unique_ptr MessagesManager::parse_dialog(DialogId dialo // and try to reget it from the server if possible td_->dialog_manager_->have_dialog_info_force(dialog_id, "parse_dialog"); - if (td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { if (dialog_id.get_type() != DialogType::SecretChat) { send_get_dialog_query(dialog_id, Auto(), 0, source); } @@ -35522,6 +35720,9 @@ unique_ptr MessagesManager::parse_dialog(DialogId dialo add_message_dependencies(dependencies, message.get()); }); add_draft_message_dependencies(dependencies, d->draft_message); + if (d->business_bot_manage_bar != nullptr) { + d->business_bot_manage_bar->add_dependencies(dependencies); + } for (auto user_id : d->pending_join_request_user_ids) { dependencies.add(user_id); } @@ -35552,8 +35753,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_->contacts_manager_->is_channel_public(dialog_id.get_channel_id()) && - !td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) { + dialog_type == DialogType::Channel && !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; } @@ -35587,6 +35788,19 @@ MessagesManager::Dialog *MessagesManager::on_load_dialog_from_database(DialogId return add_new_dialog(parse_dialog(dialog_id, value, source), true, source); } +Result MessagesManager::check_dialog_access(DialogId dialog_id, bool allow_secret_chats, + AccessRights access_rights, const char *source) { + auto *d = get_dialog_force(dialog_id, source); + if (d == nullptr) { + if (!dialog_id.is_valid()) { + return Status::Error(400, "Invalid chat identifier specified"); + } + return Status::Error(400, "Chat not found"); + } + TRY_STATUS(td_->dialog_manager_->check_dialog_access_in_memory(d->dialog_id, allow_secret_chats, access_rights)); + return d; +} + vector MessagesManager::get_dialog_list_folder_ids(const DialogList &list) const { CHECK(!td_->auth_manager_->is_bot()); if (list.dialog_list_id.is_folder()) { @@ -35724,11 +35938,13 @@ MessagesManager::DialogListView MessagesManager::get_dialog_lists(const Dialog * MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialog_list_id) { CHECK(!td_->auth_manager_->is_bot()); - if (dialog_lists_.count(dialog_list_id) == 0) { - LOG(INFO) << "Create " << dialog_list_id; - } + bool is_new = dialog_lists_.count(dialog_list_id) == 0; auto &list = dialog_lists_[dialog_list_id]; list.dialog_list_id = dialog_list_id; + if (is_new) { + LOG(INFO) << "Create " << dialog_list_id; + list.unique_id_ = ++current_dialog_list_unique_id_; + } return list; } @@ -35775,7 +35991,8 @@ string MessagesManager::get_channel_pts_key(DialogId dialog_id) { } int32 MessagesManager::load_channel_pts(DialogId dialog_id) const { - if (td_->ignore_background_updates() || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (td_->ignore_background_updates() || + !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case return 0; } @@ -35822,7 +36039,8 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour repair_channel_server_unread_count(d); } } - if (!td_->ignore_background_updates() && td_->dialog_manager_->have_input_peer(d->dialog_id, AccessRights::Read)) { + if (!td_->ignore_background_updates() && + td_->dialog_manager_->have_input_peer(d->dialog_id, false, AccessRights::Read)) { G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts)); } } else if (new_pts < d->pts) { @@ -35833,15 +36051,17 @@ void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *sour bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, const tl_object_ptr &message_ptr) { - if (dialog_id.get_type() != DialogType::Channel || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) || - dialog_id == debug_channel_difference_dialog_ || td_->auth_manager_->is_bot()) { + if (message_ptr == nullptr || DialogId::get_message_dialog_id(message_ptr) != dialog_id) { return false; } - if (message_ptr == nullptr) { - return true; - } - if (DialogId::get_message_dialog_id(message_ptr) != dialog_id) { + + return need_channel_difference_to_add_message(dialog_id, MessageId::get_message_id(message_ptr, false)); +} + +bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, MessageId message_id) { + if (td_->auth_manager_->is_bot() || dialog_id.get_type() != DialogType::Channel || + !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) || + dialog_id == debug_channel_difference_dialog_) { return false; } @@ -35855,7 +36075,6 @@ bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, return d->pts > 0 && !d->is_channel_difference_finished; } - auto message_id = MessageId::get_message_id(message_ptr, false); LOG(DEBUG) << "Check ability to add " << message_id << " to " << dialog_id; return message_id > d->last_new_message_id; } @@ -35863,7 +36082,7 @@ bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id, void MessagesManager::run_after_channel_difference(DialogId dialog_id, MessageId expected_max_message_id, Promise &&promise, const char *source) { CHECK(dialog_id.get_type() == DialogType::Channel); - CHECK(td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)); + CHECK(td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)); run_after_get_channel_difference_[dialog_id].push_back(std::move(promise)); @@ -35965,14 +36184,14 @@ void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, int3 debug_last_get_channel_difference_dialog_id_ = dialog_id; debug_last_get_channel_difference_source_ = source; - auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id()); + auto input_channel = td_->chat_manager_->get_input_channel(dialog_id.get_channel_id()); if (input_channel == nullptr) { LOG(ERROR) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because the channel is unknown"; after_get_channel_difference(dialog_id, false); return; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source << " because have no read access to it"; after_get_channel_difference(dialog_id, false); @@ -36366,8 +36585,9 @@ void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 reques if (difference_ptr == nullptr) { CHECK(status.is_error()); - bool have_access = - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read) && status.message() != "CHANNEL_INVALID"; + bool have_access = td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) && + td_->chat_manager_->have_channel(dialog_id.get_channel_id()) && + status.message() != "CHANNEL_INVALID"; if (have_access) { if (d == nullptr) { force_create_dialog(dialog_id, "on_get_channel_difference failed"); @@ -36400,8 +36620,8 @@ void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 reques case telegram_api::updates_channelDifference::ID: { auto difference = static_cast(difference_ptr.get()); have_new_messages = !difference->new_messages_.empty(); - td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference"); - td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference"); + td_->user_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference"); + td_->chat_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference"); for (const auto &message : difference->new_messages_) { if (is_invalid_poll_message(message.get())) { LOG(ERROR) << "Receive invalid poll message in updates.channelDifference: " << oneline(to_string(message)); @@ -36415,8 +36635,8 @@ void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 reques case telegram_api::updates_channelDifferenceTooLong::ID: { auto difference = static_cast(difference_ptr.get()); have_new_messages = difference->dialog_->get_id() == telegram_api::dialog::ID && !difference->messages_.empty(); - td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifferenceTooLong"); - td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifferenceTooLong"); + td_->user_manager_->on_get_users(std::move(difference->users_), "updates.channelDifferenceTooLong"); + td_->chat_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifferenceTooLong"); break; } default: @@ -36452,7 +36672,7 @@ void MessagesManager::on_get_channel_difference(DialogId dialog_id, int32 reques // bots can receive channelDifferenceEmpty with PTS bigger than known PTS // also, this can happen for deleted channels if (request_pts != difference->pts_ && !td_->auth_manager_->is_bot() && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference from " << source << " with PTS = " << request_pts << " and limit = " << request_limit << " in " << dialog_id << ", but PTS has changed to " << difference->pts_; @@ -36609,7 +36829,8 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ } auto d = get_dialog(dialog_id); - bool have_access = td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read); + bool have_access = td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read) && + td_->chat_manager_->have_channel(dialog_id.get_channel_id()); auto pts = d != nullptr ? d->pts : load_channel_pts(dialog_id); auto postponed_updates_it = postponed_channel_updates_.find(dialog_id); if (postponed_updates_it != postponed_channel_updates_.end()) { @@ -36763,19 +36984,19 @@ void MessagesManager::speculatively_update_channel_participants(DialogId dialog_ } auto channel_id = dialog_id.get_channel_id(); - UserId my_user_id(td_->contacts_manager_->get_my_id()); + UserId my_user_id(td_->user_manager_->get_my_id()); bool by_me = m->sender_user_id == my_user_id; switch (m->content->get_type()) { case MessageContentType::ChatAddUsers: - send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id, + send_closure_later(G()->chat_manager(), &ChatManager::speculative_add_channel_participants, channel_id, get_message_content_added_user_ids(m->content.get()), m->sender_user_id, m->date, by_me); break; case MessageContentType::ChatJoinedByLink: - send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id, + send_closure_later(G()->chat_manager(), &ChatManager::speculative_add_channel_participants, channel_id, vector{m->sender_user_id}, m->sender_user_id, m->date, by_me); break; case MessageContentType::ChatDeleteUser: - send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_delete_channel_participant, channel_id, + send_closure_later(G()->chat_manager(), &ChatManager::speculative_delete_channel_participant, channel_id, get_message_content_deleted_user_id(m->content.get()), by_me); break; default: @@ -36797,7 +37018,8 @@ void MessagesManager::update_sent_message_contents(DialogId dialog_id, const Mes void MessagesManager::update_used_hashtags(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != td_->dialog_manager_->get_my_dialog_id()) || - m->via_bot_user_id.is_valid() || m->hide_via_bot || m->forward_info != nullptr || m->had_forward_info) { + m->via_bot_user_id.is_valid() || m->via_business_bot_user_id.is_valid() || m->hide_via_bot || + m->forward_info != nullptr || m->had_forward_info) { return; } @@ -36808,7 +37030,8 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { CHECK(m != nullptr); auto dialog_type = dialog_id.get_type(); if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != td_->dialog_manager_->get_my_dialog_id()) || - dialog_type == DialogType::SecretChat || !m->message_id.is_any_server()) { + dialog_type == DialogType::SecretChat || !m->message_id.is_any_server() || + m->via_business_bot_user_id.is_valid()) { return; } @@ -36831,7 +37054,7 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { TopDialogCategory category = TopDialogCategory::Size; switch (dialog_type) { case DialogType::User: { - if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { + if (td_->user_manager_->is_user_bot(dialog_id.get_user_id())) { category = TopDialogCategory::BotPM; } else { category = TopDialogCategory::Correspondent; @@ -36842,7 +37065,7 @@ void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) { category = TopDialogCategory::Group; break; case DialogType::Channel: - switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) { + switch (td_->chat_manager_->get_channel_type(dialog_id.get_channel_id())) { case ChannelType::Broadcast: category = TopDialogCategory::Channel; break; @@ -36914,7 +37137,7 @@ void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Mes case DialogType::Channel: break; case DialogType::SecretChat: { - auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); + auto user_id = td_->user_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()); if (user_id.is_valid()) { d = get_dialog_force(DialogId(user_id), "update_has_outgoing_messages"); } @@ -36982,12 +37205,14 @@ void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) return; } CHECK(replied_message_full_id.get_dialog_id() == d->dialog_id); + LOG_CHECK(m->replied_message_info.get_reply_message_full_id(d->dialog_id, true) == replied_message_full_id) + << replied_message_full_id << ' ' << m->replied_message_info << ' ' << *input_reply_to; auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id"); if (message_id.is_valid() || message_id.is_valid_scheduled()) { update_message_reply_to_message_id(d, m, message_id, false); } else { - set_message_reply(d, m, MessageInputReplyTo{m->top_thread_message_id, DialogId(), FormattedText(), 0}, false); + set_message_reply(d, m, MessageInputReplyTo{m->top_thread_message_id, DialogId(), MessageQuote()}, false); } } @@ -37003,7 +37228,7 @@ MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog binlog_erase(G()->td_db()->get_binlog(), log_event_id); return nullptr; } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), log_event_id); return nullptr; } @@ -37241,7 +37466,7 @@ void MessagesManager::on_binlog_events(vector &&events) { to_dialog->was_opened = true; auto now = G()->unix_time(); - if (!td_->dialog_manager_->have_input_peer(from_dialog_id, AccessRights::Read) || + if (!td_->dialog_manager_->have_input_peer(from_dialog_id, false, AccessRights::Read) || can_send_message(to_dialog_id).is_error() || messages.empty() || (messages[0]->send_date < now - MAX_RESEND_DELAY && to_dialog_id != td_->dialog_manager_->get_my_dialog_id())) { @@ -37283,6 +37508,71 @@ void MessagesManager::on_binlog_events(vector &&events) { log_event.drop_author, log_event.drop_media_captions, event.id_); break; } + case LogEvent::HandlerType::SendQuickReplyShortcutMessages: { + if (!have_old_message_database) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + continue; + } + + SendQuickReplyShortcutMessagesLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + auto dialog_id = log_event.dialog_id; + auto messages = std::move(log_event.messages_out); + + Dependencies dependencies; + dependencies.add_dialog_and_dependencies(dialog_id); + for (auto &message : messages) { + add_message_dependencies(dependencies, message.get()); + } + if (!dependencies.resolve_force(td_, "SendQuickReplyShortcutMessagesLogEvent")) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + continue; + } + + Dialog *d = get_dialog(dialog_id); + CHECK(d != nullptr); + d->was_opened = true; + + auto now = G()->unix_time(); + if (can_send_message(dialog_id).is_error() || messages.empty() || + (messages[0]->send_date < now - MAX_RESEND_DELAY && + dialog_id != td_->dialog_manager_->get_my_dialog_id())) { + LOG(WARNING) << "Can't continue sending quick reply shortcut " << messages.size() << " message(s) to " + << dialog_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + continue; + } + + LOG(INFO) << "Continue to send quick reply shortcut " << messages.size() << " message(s) to " << dialog_id + << " from binlog"; + + bool need_update = false; + bool need_update_dialog_pos = false; + vector sent_messages; + for (auto &message : messages) { + message->message_id = get_next_yet_unsent_message_id(d); + message->date = now; + message->content = dup_message_content(td_, dialog_id, message->content.get(), + MessageContentDupType::ServerCopy, MessageCopyOptions(true, false)); + CHECK(message->content != nullptr); + + restore_message_reply_to_message_id(d, message.get()); + + sent_messages.push_back(add_message_to_dialog(d, std::move(message), false, true, &need_update, + &need_update_dialog_pos, + "send quick reply shortcut message again")); + send_update_new_message(d, sent_messages.back()); + } + + if (need_update_dialog_pos) { + send_update_chat_last_message(d, "SendQuickReplyShortcutMessagesLogEvent"); + } + + do_send_quick_reply_shortcut_messages(dialog_id, log_event.shortcut_id, sent_messages, log_event.message_ids, + event.id_); + break; + } case LogEvent::HandlerType::DeleteMessage: { if (!have_old_message_database) { binlog_erase(G()->td_db()->get_binlog(), event.id_); @@ -37318,7 +37608,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteMessagesOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37342,7 +37632,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteScheduledMessagesOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37367,7 +37657,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteDialogHistoryOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37387,7 +37677,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteTopicHistoryOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37426,7 +37716,7 @@ void MessagesManager::on_binlog_events(vector &&events) { dependencies.add(channel_id); dependencies.add_dialog_dependencies(sender_dialog_id); if (!dependencies.resolve_force(td_, "DeleteAllChannelMessagesFromSenderOnServer") || - !td_->dialog_manager_->have_input_peer(sender_dialog_id, AccessRights::Know)) { + !td_->dialog_manager_->have_input_peer(sender_dialog_id, false, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); continue; } @@ -37445,7 +37735,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "DeleteDialogMessagesByDateOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37465,7 +37755,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadHistoryOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37488,7 +37778,7 @@ void MessagesManager::on_binlog_events(vector &&events) { Dependencies dependencies; dependencies.add_dialog_and_dependencies(dialog_id); if (!dependencies.resolve_force(td_, "ReadHistoryInSecretChat") || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37518,7 +37808,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadMessageThreadHistoryOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37544,7 +37834,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadMessageContentsOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37563,7 +37853,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogMentionsOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37582,7 +37872,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogReactionsOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37601,7 +37891,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ToggleDialogIsPinnedOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37621,7 +37911,7 @@ void MessagesManager::on_binlog_events(vector &&events) { vector dialog_ids; for (auto &dialog_id : log_event.dialog_ids_) { Dialog *d = get_dialog_force(dialog_id, "ReorderPinnedDialogsOnServerLogEvent"); - if (d != nullptr && td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d != nullptr && td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { dialog_ids.push_back(dialog_id); } } @@ -37644,7 +37934,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogViewAsMessagesOnServerLogEvent") || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37663,7 +37953,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogIsTranslatableOnServerLogEvent") || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37682,7 +37972,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; if (!have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent") || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37702,7 +37992,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; if (dialog_id.get_type() == DialogType::SecretChat || !td_->dialog_manager_->have_dialog_info_force(dialog_id, "ToggleDialogIsBlockedOnServerLogEvent") || - !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Know)) { + !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Know)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37722,7 +38012,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "SaveDialogDraftMessageOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Write)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37742,7 +38032,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "UpdateDialogNotificationSettingsOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37769,7 +38059,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "ToggleDialogReportSpamStateOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37788,7 +38078,7 @@ void MessagesManager::on_binlog_events(vector &&events) { auto dialog_id = log_event.dialog_id_; Dialog *d = get_dialog_force(dialog_id, "SetDialogFolderIdOnServerLogEvent"); - if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (d == nullptr || !td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37813,7 +38103,7 @@ void MessagesManager::on_binlog_events(vector &&events) { dependencies.add_dialog_and_dependencies(dialog_id); dependencies.resolve_force(td_, "RegetDialogLogEvent", true); // dialog itself may not exist - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { binlog_erase(G()->td_db()->get_binlog(), event.id_); break; } @@ -37992,7 +38282,7 @@ void MessagesManager::set_poll_answer(MessageFullId message_full_id, vectordialog_manager_->have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(message_full_id.get_dialog_id(), true, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { @@ -38014,7 +38304,7 @@ void MessagesManager::get_poll_voters(MessageFullId message_full_id, int32 optio if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!td_->dialog_manager_->have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(message_full_id.get_dialog_id(), true, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { @@ -38036,7 +38326,7 @@ void MessagesManager::stop_poll(MessageFullId message_full_id, td_api::object_pt if (m == nullptr) { return promise.set_error(Status::Error(400, "Message not found")); } - if (!td_->dialog_manager_->have_input_peer(message_full_id.get_dialog_id(), AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(message_full_id.get_dialog_id(), true, AccessRights::Read)) { return promise.set_error(Status::Error(400, "Can't access the chat")); } if (m->content->get_type() != MessageContentType::Poll) { @@ -38055,14 +38345,11 @@ void MessagesManager::stop_poll(MessageFullId message_full_id, td_api::object_pt return promise.set_error(Status::Error(400, "Poll can't be stopped")); } - auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, - has_message_sender_user_id(message_full_id.get_dialog_id(), m)); - if (r_new_reply_markup.is_error()) { - return promise.set_error(r_new_reply_markup.move_as_error()); - } + TRY_RESULT_PROMISE(promise, new_reply_markup, + get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, + has_message_sender_user_id(message_full_id.get_dialog_id(), m))); - stop_message_content_poll(td_, m->content.get(), message_full_id, r_new_reply_markup.move_as_ok(), - std::move(promise)); + 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) { @@ -38138,8 +38425,8 @@ void MessagesManager::on_get_sponsored_dialog(tl_object_ptr vector> chats) { CHECK(peer != nullptr); - td_->contacts_manager_->on_get_users(std::move(users), "on_get_sponsored_dialog"); - td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_sponsored_dialog"); + td_->user_manager_->on_get_users(std::move(users), "on_get_sponsored_dialog"); + td_->chat_manager_->on_get_chats(std::move(chats), "on_get_sponsored_dialog"); set_sponsored_dialog(DialogId(peer), std::move(source)); } @@ -38287,7 +38574,7 @@ void MessagesManager::get_current_state(vector> last_message_updates; dialogs_.foreach([&](const DialogId &dialog_id, const unique_ptr &dialog) { const Dialog *d = dialog.get(); - auto update = td_api::make_object(get_chat_object(d)); + auto update = td_api::make_object(get_chat_object(d, "get_current_state")); if (update->chat_->last_message_ != nullptr) { last_message_updates.push_back(td_api::make_object( get_chat_id_object(dialog_id, "updateChatLastMessage"), std::move(update->chat_->last_message_), diff --git a/lib/tgchat/ext/td/td/telegram/MessagesManager.h b/lib/tgchat/ext/td/td/telegram/MessagesManager.h index b410b07e..40588791 100644 --- a/lib/tgchat/ext/td/td/telegram/MessagesManager.h +++ b/lib/tgchat/ext/td/td/telegram/MessagesManager.h @@ -6,6 +6,7 @@ // #pragma once +#include "td/telegram/AccessRights.h" #include "td/telegram/AffectedHistory.h" #include "td/telegram/BackgroundInfo.h" #include "td/telegram/ChannelId.h" @@ -105,14 +106,18 @@ namespace td { struct BinlogEvent; +class BusinessBotManageBar; class Dependencies; class DialogActionBar; class DialogFilter; class DraftMessage; +class FactCheck; struct InputMessageContent; class MessageContent; class MessageForwardInfo; struct MessageReactions; +struct MessageSearchOffset; +class MissingInvitees; class Td; class Usernames; @@ -133,6 +138,7 @@ class MessagesManager final : public Actor { static constexpr int32 SEND_MESSAGE_FLAG_NOFORWARDS = 1 << 14; static constexpr int32 SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER = 1 << 15; static constexpr int32 SEND_MESSAGE_FLAG_INVERT_MEDIA = 1 << 16; + static constexpr int32 SEND_MESSAGE_FLAG_EFFECT = 1 << 18; MessagesManager(Td *td, ActorShared<> parent); MessagesManager(const MessagesManager &) = delete; @@ -147,6 +153,8 @@ class MessagesManager final : public Actor { void on_get_empty_messages(DialogId dialog_id, const vector &empty_message_ids); + void get_channel_difference_if_needed(DialogId dialog_id, MessageId message_id, const char *source); + void get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info, Promise &&promise, const char *source); @@ -188,10 +196,13 @@ class MessagesManager final : public Actor { void on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter, - int32 min_date, int32 max_date, int64 random_id, int32 total_count, + int32 min_date, int32 max_date, int32 total_count, vector> &&messages, int32 next_rate, - Promise &&promise); - void on_failed_messages_search(int64 random_id); + Promise> &&promise); + + void on_get_hashtag_search_result(const string &hashtag, const MessageSearchOffset &old_offset, int32 limit, + int32 total_count, vector> &&messages, + int32 next_rate, Promise> &&promise); void on_get_outgoing_document_messages(vector> &&messages, Promise> &&promise); @@ -261,6 +272,10 @@ class MessagesManager final : public Actor { void on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked, bool is_blocked_for_stories); + void on_update_dialog_business_bot_is_paused(DialogId dialog_id, bool is_paused); + + void on_update_dialog_business_bot_removed(DialogId dialog_id); + void on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId last_pinned_message_id); void on_update_dialog_background(DialogId dialog_id, telegram_api::object_ptr &&wallpaper); @@ -329,7 +344,7 @@ class MessagesManager final : public Actor { void on_update_message_extended_media(MessageFullId message_full_id, telegram_api::object_ptr extended_media); - void on_external_update_message_content(MessageFullId message_full_id); + void on_external_update_message_content(MessageFullId message_full_id, const char *source); void on_update_message_content(MessageFullId message_full_id); @@ -343,8 +358,6 @@ class MessagesManager final : public Actor { void on_update_delete_scheduled_messages(DialogId dialog_id, vector &&server_message_ids); - void on_update_created_public_broadcasts(vector channel_ids); - void on_dialog_speaking_action(DialogId dialog_id, DialogId speaking_dialog_id, int32 date); void on_message_animated_emoji_clicked(MessageFullId message_full_id, string &&emoji, string &&data); @@ -409,9 +422,9 @@ class MessagesManager final : public Actor { }; ForwardedMessageInfo get_forwarded_message_info(MessageFullId message_full_id); - MessageInputReplyTo get_message_input_reply_to(DialogId dialog_id, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, - bool for_draft); + MessageInputReplyTo create_message_input_reply_to(DialogId dialog_id, MessageId top_thread_message_id, + td_api::object_ptr &&reply_to, + bool for_draft); Result> send_message( DialogId dialog_id, const MessageId top_thread_message_id, @@ -457,14 +470,15 @@ class MessagesManager final : public Actor { tl_object_ptr &&input_message_content, Promise &&promise); void edit_message_live_location(MessageFullId message_full_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_location, int32 heading, + tl_object_ptr &&input_location, int32 live_period, int32 heading, int32 proximity_alert_radius, Promise &&promise); void edit_message_media(MessageFullId message_full_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise); void edit_message_caption(MessageFullId message_full_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_caption, Promise &&promise); + tl_object_ptr &&input_caption, bool invert_media, + Promise &&promise); void edit_message_reply_markup(MessageFullId message_full_id, tl_object_ptr &&reply_markup, Promise &&promise); @@ -475,15 +489,16 @@ class MessagesManager final : public Actor { void edit_inline_message_live_location(const string &inline_message_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_location, int32 heading, - int32 proximity_alert_radius, Promise &&promise); + tl_object_ptr &&input_location, int32 live_period, + int32 heading, int32 proximity_alert_radius, Promise &&promise); void edit_inline_message_media(const string &inline_message_id, tl_object_ptr &&reply_markup, tl_object_ptr &&input_message_content, Promise &&promise); void edit_inline_message_caption(const string &inline_message_id, tl_object_ptr &&reply_markup, - tl_object_ptr &&input_caption, Promise &&promise); + tl_object_ptr &&input_caption, bool invert_media, + Promise &&promise); void edit_inline_message_reply_markup(const string &inline_message_id, tl_object_ptr &&reply_markup, Promise &&promise); @@ -492,6 +507,9 @@ class MessagesManager final : public Actor { td_api::object_ptr &&scheduling_state, Promise &&promise); + void set_message_fact_check(MessageFullId message_full_id, td_api::object_ptr &&text, + Promise &&promise); + void get_dialog_filter_dialog_count(td_api::object_ptr filter, Promise &&promise); void add_dialog_list_for_dialog_filter(DialogFilterId dialog_filter_id); @@ -680,7 +698,7 @@ class MessagesManager final : public Actor { int64 get_chat_id_object(DialogId dialog_id, const char *source) const; - td_api::object_ptr get_chat_object(DialogId dialog_id); + td_api::object_ptr get_chat_object(DialogId dialog_id, const char *source); td_api::object_ptr get_my_dialog_draft_message_object() const; @@ -724,12 +742,13 @@ class MessagesManager final : public Actor { td_api::object_ptr get_found_messages_object(const FoundMessages &found_messages, const char *source); - FoundMessages offline_search_messages(DialogId dialog_id, const string &query, string offset, int32 limit, - MessageSearchFilter filter, int64 &random_id, Promise &&promise); + void offline_search_messages(DialogId dialog_id, const string &query, string offset, int32 limit, + MessageSearchFilter filter, + Promise> &&promise); - FoundMessages search_messages(FolderId folder_id, bool ignore_folder_id, const string &query, const string &offset, - int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date, - int64 &random_id, Promise &&promise); + void search_messages(DialogListId dialog_list_id, bool ignore_folder_id, bool broadcasts_only, const string &query, + const string &offset_str, int32 limit, MessageSearchFilter filter, int32 min_date, + int32 max_date, Promise> &&promise); FoundMessages search_call_messages(const string &offset, int32 limit, bool only_missed, int64 &random_id, bool use_db, Promise &&promise); @@ -737,18 +756,20 @@ class MessagesManager final : public Actor { void search_outgoing_document_messages(const string &query, int32 limit, Promise> &&promise); + void search_hashtag_posts(string hashtag, string offset_str, int32 limit, + Promise> &&promise); + void search_dialog_recent_location_messages(DialogId dialog_id, int32 limit, Promise> &&promise); vector get_active_live_location_messages(Promise &&promise); - int64 get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise &&promise); - - void on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id, - vector> &&messages, - Promise &&promise); + void get_dialog_message_by_date(DialogId dialog_id, int32 date, + Promise> &&promise); - void on_get_dialog_message_by_date_fail(int64 random_id); + void on_get_dialog_message_by_date(DialogId dialog_id, int32 date, + vector> &&messages, + Promise> &&promise); void get_dialog_sparse_message_positions(DialogId dialog_id, SavedMessagesTopicId saved_messages_topic_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit, @@ -782,19 +803,22 @@ class MessagesManager final : public Actor { void remove_message_reaction(MessageFullId message_full_id, ReactionType reaction_type, Promise &&promise); - tl_object_ptr get_dialog_message_by_date_object(int64 random_id); - td_api::object_ptr get_dialog_event_log_message_object( DialogId dialog_id, tl_object_ptr &&message, DialogId &sender_dialog_id); - tl_object_ptr get_message_object(MessageFullId message_full_id, const char *source); + td_api::object_ptr get_business_message_object( + telegram_api::object_ptr &&message, + telegram_api::object_ptr &&reply_to_message); - tl_object_ptr get_messages_object(int32 total_count, DialogId dialog_id, - const vector &message_ids, bool skip_not_found, - const char *source); + td_api::object_ptr get_message_object(MessageFullId message_full_id, const char *source); - tl_object_ptr get_messages_object(int32 total_count, const vector &message_full_ids, - bool skip_not_found, const char *source); + td_api::object_ptr get_messages_object(int32 total_count, DialogId dialog_id, + const vector &message_ids, bool skip_not_found, + const char *source); + + td_api::object_ptr get_messages_object(int32 total_count, + const vector &message_full_ids, + bool skip_not_found, const char *source); void process_pts_update(tl_object_ptr &&update_ptr); @@ -831,8 +855,9 @@ class MessagesManager final : public Actor { tl_object_ptr &&peer_notify_settings, const char *source); - void on_update_dialog_available_reactions( - DialogId dialog_id, telegram_api::object_ptr &&available_reactions); + void on_update_dialog_available_reactions(DialogId dialog_id, + telegram_api::object_ptr &&available_reactions, + int32 reactions_limit); void hide_dialog_action_bar(DialogId dialog_id); @@ -840,6 +865,8 @@ class MessagesManager final : public Actor { void reget_dialog_action_bar(DialogId dialog_id, const char *source, bool is_repair = true); + void hide_all_business_bot_manager_bars(); + void on_get_peer_settings(DialogId dialog_id, tl_object_ptr &&peer_settings, bool ignore_privacy_exception = false); @@ -874,8 +901,10 @@ class MessagesManager final : public Actor { void on_upload_message_media_fail(DialogId dialog_id, MessageId message_id, Status error); - void on_create_new_dialog(telegram_api::object_ptr &&updates, DialogType expected_type, - Promise> &&promise); + void on_create_new_dialog(telegram_api::object_ptr &&updates, + MissingInvitees &&missing_invitees, + Promise> &&chat_promise, + Promise> &&channel_promise); void on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit, tl_object_ptr &&difference_ptr, @@ -981,21 +1010,25 @@ class MessagesManager final : public Actor { tl_object_ptr forward_header; MessageReplyHeader reply_header; UserId via_bot_user_id; + UserId via_business_bot_user_id; int32 view_count = 0; int32 forward_count = 0; tl_object_ptr reply_info; tl_object_ptr reactions; + tl_object_ptr fact_check; int32 sender_boost_count = 0; int32 edit_date = 0; vector restriction_reasons; string author_signature; int64 media_album_id = 0; + int64 effect_id = 0; bool is_outgoing = false; bool is_silent = false; bool is_channel_post = false; bool is_legacy = false; bool hide_edit_date = false; bool is_from_scheduled = false; + bool is_from_offline = false; bool is_pinned = false; bool noforwards = false; bool has_mention = false; @@ -1035,6 +1068,7 @@ class MessagesManager final : public Actor { string send_emoji; // for send_message UserId via_bot_user_id; + UserId via_business_bot_user_id; vector restriction_reasons; @@ -1053,6 +1087,7 @@ class MessagesManager final : public Actor { bool is_content_secret = false; // must be shown only while tapped bool is_mention_notification_disabled = false; bool is_from_scheduled = false; + bool is_from_offline = false; bool is_pinned = false; bool are_media_timestamp_entities_found = false; bool noforwards = false; @@ -1086,6 +1121,7 @@ class MessagesManager final : public Actor { int32 forward_count = 0; MessageReplyInfo reply_info; unique_ptr reactions; + unique_ptr fact_check; unique_ptr thread_draft_message; uint32 available_reactions_generation = 0; int32 interaction_info_update_date = 0; @@ -1102,6 +1138,7 @@ class MessagesManager final : public Actor { double ttl_expires_at = 0; // only for TTL int64 media_album_id = 0; + int64 effect_id = 0; unique_ptr content; @@ -1193,6 +1230,7 @@ class MessagesManager final : public Actor { MessageTtl message_ttl; unique_ptr draft_message; unique_ptr action_bar; + unique_ptr business_bot_manage_bar; LogEventIdWithGeneration save_draft_message_log_event_id; LogEventIdWithGeneration save_notification_settings_log_event_id; LogEventIdWithGeneration set_folder_id_log_event_id; @@ -1351,6 +1389,7 @@ class MessagesManager final : public Actor { int32 in_memory_dialog_total_count_ = 0; int32 server_dialog_total_count_ = -1; int32 secret_chat_total_count_ = -1; + int64 unique_id_ = 0; vector> load_list_queries_; @@ -1468,17 +1507,19 @@ class MessagesManager final : public Actor { bool only_preview = false; int32 schedule_date = 0; int32 sending_id = 0; + int64 effect_id = 0; MessageSendOptions() = default; MessageSendOptions(bool disable_notification, bool from_background, bool update_stickersets_order, - bool protect_content, bool only_preview, int32 schedule_date, int32 sending_id) + bool protect_content, bool only_preview, int32 schedule_date, int32 sending_id, int64 effect_id) : disable_notification(disable_notification) , from_background(from_background) , update_stickersets_order(update_stickersets_order) , protect_content(protect_content) , only_preview(only_preview) , schedule_date(schedule_date) - , sending_id(sending_id) { + , sending_id(sending_id) + , effect_id(effect_id) { } }; @@ -1544,6 +1585,7 @@ class MessagesManager final : public Actor { class SendBotStartMessageLogEvent; class SendInlineQueryResultMessageLogEvent; class SendMessageLogEvent; + class SendQuickReplyShortcutMessagesLogEvent; class SendScreenshotTakenNotificationMessageLogEvent; class SetDialogFolderIdOnServerLogEvent; class ToggleDialogIsBlockedOnServerLogEvent; @@ -1639,11 +1681,12 @@ class MessagesManager final : public Actor { void finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list, MessageId last_message_id, Promise promise); - MessageInfo parse_telegram_api_message(tl_object_ptr message_ptr, bool is_scheduled, - const char *source) const; + static MessageInfo parse_telegram_api_message(Td *td, tl_object_ptr message_ptr, + bool is_scheduled, const char *source); - std::pair> create_message(MessageInfo &&message_info, bool is_channel_message, - const char *source); + static std::pair> create_message(Td *td, MessageInfo &&message_info, + bool is_channel_message, bool is_business_message, + const char *source); MessageId find_old_message_id(DialogId dialog_id, MessageId message_id) const; @@ -1664,7 +1707,7 @@ class MessagesManager final : public Actor { Result process_message_send_options(DialogId dialog_id, tl_object_ptr &&options, - bool allow_update_stickersets_order) const; + bool allow_update_stickersets_order, bool allow_effect) const; static Status can_use_message_send_options(const MessageSendOptions &options, const unique_ptr &content, MessageSelfDestructType ttl); @@ -1718,9 +1761,9 @@ class MessagesManager final : public Actor { static MessageFullId get_replied_message_id(DialogId dialog_id, const Message *m); - MessageInputReplyTo get_message_input_reply_to(Dialog *d, MessageId top_thread_message_id, - td_api::object_ptr &&reply_to, - bool for_draft); + MessageInputReplyTo create_message_input_reply_to(Dialog *d, MessageId top_thread_message_id, + td_api::object_ptr &&reply_to, + bool for_draft); static const MessageInputReplyTo *get_message_input_reply_to(const Message *m); @@ -1750,6 +1793,10 @@ class MessagesManager final : public Actor { const vector &message_ids, bool drop_author, bool drop_media_captions, uint64 log_event_id); + uint64 save_send_quick_reply_shortcut_messages_log_event(DialogId dialog_id, QuickReplyShortcutId shortcut_id, + const vector &messages, + const vector &message_ids); + void do_send_quick_reply_shortcut_messages(DialogId dialog_id, QuickReplyShortcutId shortcut_id, const vector &messages, const vector &message_ids, uint64 log_event_id); @@ -1992,6 +2039,8 @@ class MessagesManager final : public Actor { td_api::object_ptr get_message_interaction_info_object(DialogId dialog_id, const Message *m) const; + td_api::object_ptr get_message_fact_check_object(const Message *m) const; + vector> get_unread_reactions_object(DialogId dialog_id, const Message *m) const; @@ -1999,6 +2048,8 @@ class MessagesManager final : public Actor { bool has_reply_info, MessageReplyInfo &&reply_info, bool has_reactions, unique_ptr &&reactions, const char *source); + bool update_message_fact_check(const Dialog *d, Message *m, unique_ptr &&fact_check, bool need_save); + bool update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source); bool remove_message_unread_reactions(Dialog *d, Message *m, const char *source); @@ -2305,13 +2356,15 @@ class MessagesManager final : public Actor { void send_update_message_interaction_info(DialogId dialog_id, const Message *m) const; + void send_update_message_fact_check(DialogId dialog_id, const Message *m) const; + void send_update_message_unread_reactions(DialogId dialog_id, const Message *m, int32 unread_reaction_count) const; void send_update_message_live_location_viewed(MessageFullId message_full_id); void send_update_delete_messages(DialogId dialog_id, vector &&message_ids, bool is_permanent) const; - void send_update_new_chat(Dialog *d); + void send_update_new_chat(Dialog *d, const char *source); bool need_hide_dialog_draft_message(const Dialog *d) const; @@ -2343,6 +2396,8 @@ class MessagesManager final : public Actor { void send_update_chat_action_bar(Dialog *d); + void send_update_chat_business_bot_manage_bar(Dialog *d); + void send_update_chat_available_reactions(const Dialog *d); void send_update_secret_chats_with_user_background(const Dialog *d) const; @@ -2380,11 +2435,15 @@ class MessagesManager final : public Actor { td_api::object_ptr get_message_message_content_object(DialogId dialog_id, const Message *m) const; - tl_object_ptr get_message_object(DialogId dialog_id, const Message *m, const char *source) const; + td_api::object_ptr get_message_object(DialogId dialog_id, const Message *m, + const char *source) const; - static tl_object_ptr get_messages_object(int32 total_count, - vector> &&messages, - bool skip_not_found); + static td_api::object_ptr get_messages_object(int32 total_count, + vector> &&messages, + bool skip_not_found); + + td_api::object_ptr get_business_message_message_object( + telegram_api::object_ptr &&message); vector sort_dialogs_by_order(const vector &dialog_ids, int32 limit) const; @@ -2558,6 +2617,9 @@ class MessagesManager final : public Actor { void queue_message_reactions_reload(DialogId dialog_id, const vector &message_ids); + void on_get_message_fact_checks(DialogId dialog_id, const vector &message_ids, + Result>> r_fact_checks); + void on_send_dialog_action_timeout(DialogId dialog_id); void cancel_dialog_action(DialogId dialog_id, const Message *m); @@ -2584,8 +2646,12 @@ class MessagesManager final : public Actor { void fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar); + void fix_dialog_business_bot_manage_bar(DialogId dialog_id, BusinessBotManageBar *business_bot_manage_bar); + td_api::object_ptr get_chat_action_bar_object(const Dialog *d) const; + td_api::object_ptr get_business_bot_manage_bar_object(const Dialog *d) const; + td_api::object_ptr get_chat_background_object(const Dialog *d) const; string get_dialog_theme_name(const Dialog *d) const; @@ -2596,7 +2662,7 @@ class MessagesManager final : public Actor { td_api::object_ptr get_default_message_sender_object(const Dialog *d) const; - td_api::object_ptr get_chat_object(const Dialog *d) const; + td_api::object_ptr get_chat_object(const Dialog *d, const char *source) const; Dialog *get_dialog(DialogId dialog_id); const Dialog *get_dialog(DialogId dialog_id) const; @@ -2608,6 +2674,9 @@ class MessagesManager final : public Actor { void on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs, Promise &&promise); + Result check_dialog_access(DialogId dialog_id, bool allow_secret_chats, AccessRights access_rights, + const char *source); + void send_get_dialog_query(DialogId dialog_id, Promise &&promise, uint64 log_event_id, const char *source); void send_search_public_dialogs_query(const string &query, Promise &&promise); @@ -2681,11 +2750,12 @@ class MessagesManager final : public Actor { vector on_get_messages_from_database(Dialog *d, vector &&messages, MessageId first_message_id, bool &have_error, const char *source); - void get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id, bool after_database_search, - Promise &&promise); + void get_dialog_message_by_date_from_server(const Dialog *d, int32 date, bool after_database_search, + Promise> &&promise); - void on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id, - Result result, Promise promise); + void on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, + Result result, + Promise> promise); std::pair get_dialog_mute_until(DialogId dialog_id, const Dialog *d) const; @@ -2781,8 +2851,8 @@ class MessagesManager final : public Actor { int32 limit, Result> r_messages, Promise promise); - void on_message_db_fts_result(Result result, string offset, int32 limit, int64 random_id, - Promise &&promise); + void on_message_db_fts_result(Result result, string offset, int32 limit, + Promise> &&promise); void on_message_db_calls_result(Result result, int64 random_id, MessageId first_db_message_id, MessageSearchFilter filter, Promise &&promise); @@ -2895,6 +2965,8 @@ class MessagesManager final : public Actor { bool need_channel_difference_to_add_message(DialogId dialog_id, const tl_object_ptr &message_ptr); + bool need_channel_difference_to_add_message(DialogId dialog_id, MessageId message_id); + void run_after_channel_difference(DialogId dialog_id, MessageId expected_max_message_id, Promise &&promise, const char *source); @@ -3135,7 +3207,7 @@ class MessagesManager final : public Actor { } bool operator==(const TtlNode &other) const { - return message_full_id_ == other.message_full_id_; + return message_full_id_ == other.message_full_id_ && by_ttl_period_ == other.by_ttl_period_; } }; struct TtlNodeHash { @@ -3174,12 +3246,10 @@ class MessagesManager final : public Actor { WaitFreeHashMap message_id_to_dialog_id_; FlatHashMap last_clear_history_message_id_to_dialog_id_; - bool created_public_broadcasts_inited_ = false; - vector created_public_broadcasts_; - struct PendingCreatedDialog { - Promise> promise_; - vector group_invite_privacy_forbidden_user_ids_; + td_api::object_ptr failed_to_add_members_; + Promise> chat_promise_; + Promise> channel_promise_; }; FlatHashMap pending_created_dialogs_; @@ -3197,16 +3267,11 @@ class MessagesManager final : public Actor { FlatHashMap> found_public_dialogs_; // TODO time bound cache FlatHashMap> found_on_server_dialogs_; // TODO time bound cache - FlatHashMap get_dialog_message_by_date_results_; - FlatHashMap> found_dialog_message_calendars_; FlatHashMap found_dialog_messages_; // random_id -> FoundDialogMessages FlatHashMap found_dialog_messages_dialog_id_; // random_id -> dialog_id - FlatHashMap found_messages_; // random_id -> FoundMessages FlatHashMap found_call_messages_; // random_id -> FoundMessages - FlatHashMap found_fts_messages_; // random_id -> FoundMessages - struct MessageEmbeddingCodes { FlatHashMap embedding_codes_; }; @@ -3236,6 +3301,7 @@ class MessagesManager final : public Actor { int64 current_pinned_dialog_order_ = static_cast(MIN_PINNED_DIALOG_DATE) << 32; + int64 current_dialog_list_unique_id_ = 0; std::unordered_map dialog_lists_; std::unordered_map dialog_folders_; @@ -3293,6 +3359,7 @@ class MessagesManager final : public Actor { struct GetDialogsTask { DialogListId dialog_list_id; + int64 dialog_list_unique_id; int32 limit; int32 retry_count; DialogDate last_dialog_date = MIN_DIALOG_DATE; @@ -3384,6 +3451,8 @@ class MessagesManager final : public Actor { }; FlatHashMap being_reloaded_reactions_; + FlatHashSet being_reloaded_fact_checks_; + FlatHashMap, DialogIdHash> pending_dialog_group_call_updates_; FlatHashMap auth_notification_id_date_; diff --git a/lib/tgchat/ext/td/td/telegram/MissingInvitee.cpp b/lib/tgchat/ext/td/td/telegram/MissingInvitee.cpp new file mode 100644 index 00000000..2d83021d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MissingInvitee.cpp @@ -0,0 +1,57 @@ +// +// 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/MissingInvitee.h" + +#include "td/telegram/UserManager.h" + +#include "td/utils/algorithm.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" + +namespace td { + +MissingInvitee::MissingInvitee(telegram_api::object_ptr &&invitee) + : user_id_(invitee->user_id_) + , premium_would_allow_invite_(invitee->premium_would_allow_invite_) + , premium_required_for_pm_(invitee->premium_required_for_pm_) { +} + +td_api::object_ptr MissingInvitee::get_failed_to_add_member_object( + UserManager *user_manager) const { + return td_api::make_object( + user_manager->get_user_id_object(user_id_, "get_failed_to_add_member_object"), premium_would_allow_invite_, + premium_required_for_pm_); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitee &invitee) { + return string_builder << '[' << invitee.user_id_ << ' ' << invitee.premium_would_allow_invite_ << ' ' + << invitee.premium_required_for_pm_ << ']'; +} + +MissingInvitees::MissingInvitees(vector> &&invitees) { + for (auto &invitee : invitees) { + missing_invitees_.emplace_back(std::move(invitee)); + if (!missing_invitees_.back().is_valid()) { + LOG(ERROR) << "Receive invalid " << missing_invitees_.back() << " as a missing invitee"; + missing_invitees_.pop_back(); + } + } +} + +td_api::object_ptr MissingInvitees::get_failed_to_add_members_object( + UserManager *user_manager) const { + return td_api::make_object( + transform(missing_invitees_, [user_manager](const MissingInvitee &message_invitee) { + return message_invitee.get_failed_to_add_member_object(user_manager); + })); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitees &invitees) { + return string_builder << invitees.missing_invitees_; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/MissingInvitee.h b/lib/tgchat/ext/td/td/telegram/MissingInvitee.h new file mode 100644 index 00000000..73ff7a75 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/MissingInvitee.h @@ -0,0 +1,54 @@ +// +// 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/telegram/UserId.h" + +#include "td/utils/common.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +class UserManager; + +class MissingInvitee { + UserId user_id_; + bool premium_would_allow_invite_ = false; + bool premium_required_for_pm_ = false; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitee &invitee); + + public: + explicit MissingInvitee(telegram_api::object_ptr &&invitee); + + bool is_valid() const { + return user_id_.is_valid(); + } + + td_api::object_ptr get_failed_to_add_member_object(UserManager *user_manager) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitee &invitee); + +class MissingInvitees { + vector missing_invitees_; + + friend StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitees &invitees); + + public: + MissingInvitees() = default; + + explicit MissingInvitees(vector> &&invitees); + + td_api::object_ptr get_failed_to_add_members_object(UserManager *user_manager) const; +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const MissingInvitees &invitees); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp index bf786c18..b20aba29 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationManager.cpp @@ -9,7 +9,6 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChatId.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DeviceTokenManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" @@ -33,9 +32,9 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/mtproto/AuthKey.h" -#include "td/mtproto/mtproto_api.h" #include "td/mtproto/PacketInfo.h" #include "td/mtproto/Transport.h" @@ -620,14 +619,14 @@ void NotificationManager::on_get_notifications_from_database(NotificationGroupId auto first_notification_id = get_first_notification_id(group); if (first_notification_id.is_valid()) { while (!notifications.empty() && notifications.back().notification_id.get() >= first_notification_id.get()) { - // possible if notifications was added after the database request was sent + // possible if notifications were added after the database request was sent notifications.pop_back(); } } auto first_object_id = get_first_object_id(group); if (first_object_id.is_valid()) { while (!notifications.empty() && notifications.back().type->get_object_id() >= first_object_id) { - // possible if notifications was added after the database request was sent + // possible if notifications were added after the database request was sent notifications.pop_back(); } } @@ -846,7 +845,7 @@ int32 NotificationManager::get_notification_delay_ms(DialogId dialog_id, const P auto server_time = G()->server_time(); auto delay_ms = [&] { - auto online_info = td_->contacts_manager_->get_my_online_status(); + auto online_info = td_->user_manager_->get_my_online_status(); if (!online_info.is_online_local && online_info.is_online_remote) { // If we are offline, but online from some other client, then delay notification // for 'notification_cloud_delay' seconds. @@ -3080,10 +3079,10 @@ void NotificationManager::add_push_notification_user( false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, sender_user_id.get(), sender_access_hash, user_name, string(), string(), string(), - std::move(sender_photo), nullptr, 0, Auto(), string(), string(), nullptr, + false /*ignored*/, false /*ignored*/, sender_user_id.get(), sender_access_hash, user_name, string(), string(), + string(), std::move(sender_photo), nullptr, 0, Auto(), string(), string(), nullptr, vector>(), 0, nullptr, nullptr); - td_->contacts_manager_->on_get_user(std::move(user), "add_push_notification_user"); + td_->user_manager_->on_get_user(std::move(user), "add_push_notification_user"); } Status NotificationManager::parse_push_notification_attach(DialogId dialog_id, string &loc_key, JsonObject &custom, @@ -3100,14 +3099,14 @@ Status NotificationManager::parse_push_notification_attach(DialogId dialog_id, s return Status::Error(PSLICE() << "Failed to parse attach: " << gzip_parser.get_error()); } BufferSlice buffer; - if (id == mtproto_api::gzip_packed::ID) { - mtproto_api::gzip_packed gzip(gzip_parser); + static constexpr int32 GZIP_PACKED_ID = 812830625; + if (id == GZIP_PACKED_ID) { + auto packed_data = gzip_parser.template fetch_string(); gzip_parser.fetch_end(); if (gzip_parser.get_error()) { - return Status::Error(PSLICE() << "Failed to parse mtproto_api::gzip_packed in attach: " - << gzip_parser.get_error()); + return Status::Error(PSLICE() << "Failed to parse gzip_packed in attach: " << gzip_parser.get_error()); } - buffer = gzdecode(gzip.packed_data_); + buffer = gzdecode(packed_data); if (buffer.empty()) { return Status::Error("Failed to uncompress attach"); } @@ -3573,7 +3572,7 @@ Status NotificationManager::process_push_notification_payload(string payload, bo } if (sender_user_id.is_valid() && - !td_->contacts_manager_->have_user_force(sender_user_id, "process_push_notification_payload")) { + !td_->user_manager_->have_user_force(sender_user_id, "process_push_notification_payload")) { int64 sender_access_hash = -1; telegram_api::object_ptr sender_photo; TRY_RESULT(mtpeer, custom.extract_optional_field("mtpeer", JsonValue::Type::Object)); @@ -3847,7 +3846,7 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess } if (sender_user_id.is_valid() && - !td_->contacts_manager_->have_user_force(sender_user_id, "add_message_push_notification")) { + !td_->user_manager_->have_user_force(sender_user_id, "add_message_push_notification")) { add_push_notification_user(sender_user_id, -1, sender_name, nullptr); } @@ -3875,8 +3874,7 @@ void NotificationManager::add_message_push_notification(DialogId dialog_id, Mess auto group_id = info.group_id; CHECK(group_id.is_valid()); - bool is_outgoing = - sender_user_id.is_valid() ? td_->contacts_manager_->get_my_id() == sender_user_id : is_from_scheduled; + bool is_outgoing = sender_user_id.is_valid() ? td_->user_manager_->get_my_id() == sender_user_id : is_from_scheduled; if (log_event_id != 0) { VLOG(notifications) << "Register temporary " << notification_id << " with log event " << log_event_id; NotificationObjectFullId object_full_id(dialog_id, message_id); diff --git a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp index ef1d526e..b965f9ae 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationSettingsManager.cpp @@ -10,7 +10,7 @@ #include "td/telegram/AudiosManager.h" #include "td/telegram/AudiosManager.hpp" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" @@ -26,11 +26,13 @@ #include "td/telegram/NotificationManager.h" #include "td/telegram/NotificationSound.h" #include "td/telegram/OptionManager.h" +#include "td/telegram/ReactionNotificationSettings.hpp" #include "td/telegram/ScopeNotificationSettings.hpp" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/VoiceNotesManager.h" #include "td/db/binlog/BinlogEvent.h" @@ -271,8 +273,8 @@ class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler { break; } } - td_->contacts_manager_->on_get_users(std::move(users), "GetNotifySettingsExceptionsQuery"); - td_->contacts_manager_->on_get_chats(std::move(chats), "GetNotifySettingsExceptionsQuery"); + td_->user_manager_->on_get_users(std::move(users), "GetNotifySettingsExceptionsQuery"); + td_->chat_manager_->on_get_chats(std::move(chats), "GetNotifySettingsExceptionsQuery"); for (auto &dialog_id : dialog_ids) { td_->dialog_manager_->force_create_dialog(dialog_id, "GetNotifySettingsExceptionsQuery"); } @@ -326,8 +328,8 @@ class GetStoryNotifySettingsExceptionsQuery final : public Td::ResultHandler { break; } } - td_->contacts_manager_->on_get_users(std::move(users), "GetStoryNotifySettingsExceptionsQuery"); - td_->contacts_manager_->on_get_chats(std::move(chats), "GetStoryNotifySettingsExceptionsQuery"); + td_->user_manager_->on_get_users(std::move(users), "GetStoryNotifySettingsExceptionsQuery"); + td_->chat_manager_->on_get_chats(std::move(chats), "GetStoryNotifySettingsExceptionsQuery"); for (auto &dialog_id : dialog_ids) { td_->dialog_manager_->force_create_dialog(dialog_id, "GetStoryNotifySettingsExceptionsQuery"); } @@ -374,6 +376,34 @@ class GetScopeNotifySettingsQuery final : public Td::ResultHandler { } }; +class GetReactionsNotifySettingsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetReactionsNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::account_getReactionsNotifySettings())); + } + + 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(); + td_->notification_settings_manager_->on_update_reaction_notification_settings( + ReactionNotificationSettings(std::move(ptr))); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class UpdateDialogNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; DialogId dialog_id_; @@ -469,6 +499,41 @@ class UpdateScopeNotifySettingsQuery final : public Td::ResultHandler { } }; +class SetReactionsNotifySettingsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit SetReactionsNotifySettingsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const ReactionNotificationSettings &settings) { + send_query(G()->net_query_creator().create( + telegram_api::account_setReactionsNotifySettings(settings.get_input_reactions_notify_settings()))); + } + + 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 SetReactionsNotifySettingsQuery: " << to_string(ptr); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + LOG(INFO) << "Receive error for set reaction notification settings: " << status; + + if (!td_->auth_manager_->is_bot()) { + // trying to repair notification settings + td_->notification_settings_manager_->send_get_reaction_notification_settings_query(Promise<>()); + } + + promise_.set_error(std::move(status)); + } +}; + class ResetNotifySettingsQuery final : public Td::ResultHandler { Promise promise_; @@ -604,6 +669,17 @@ void NotificationSettingsManager::init() { channels_notification_settings_.is_synchronized = false; send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>()); } + auto reaction_notification_settings_string = + G()->td_db()->get_binlog_pmc()->get(get_reaction_notification_settings_database_key()); + if (!reaction_notification_settings_string.empty()) { + log_event_parse(reaction_notification_settings_, reaction_notification_settings_string).ensure(); + have_reaction_notification_settings_ = true; + + VLOG(notifications) << "Loaded reaction notification settings: " << reaction_notification_settings_; + } else { + send_get_reaction_notification_settings_query(Promise()); + } + send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); } G()->td_db()->get_binlog_pmc()->erase("nsfac"); } @@ -716,6 +792,12 @@ NotificationSettingsManager::get_update_scope_notification_settings_object(Notif get_notification_settings_scope_object(scope), get_scope_notification_settings_object(notification_settings)); } +td_api::object_ptr +NotificationSettingsManager::get_update_reaction_notification_settings_object() const { + return td_api::make_object( + reaction_notification_settings_.get_reaction_notification_settings_object()); +} + void NotificationSettingsManager::on_scope_unmute(NotificationSettingsScope scope) { if (td_->auth_manager_->is_bot()) { // just in case @@ -825,6 +907,46 @@ bool NotificationSettingsManager::update_scope_notification_settings(Notificatio return need_update_server; } +void NotificationSettingsManager::send_get_reaction_notification_settings_query(Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Can't get reaction notification settings"; + return promise.set_error(Status::Error(500, "Wrong getReactionNotificationSettings query")); + } + + td_->create_handler(std::move(promise))->send(); +} + +void NotificationSettingsManager::on_update_reaction_notification_settings( + ReactionNotificationSettings reaction_notification_settings) { + CHECK(!td_->auth_manager_->is_bot()); + if (reaction_notification_settings == reaction_notification_settings_) { + if (!have_reaction_notification_settings_) { + have_reaction_notification_settings_ = true; + save_reaction_notification_settings(); + } + return; + } + + VLOG(notifications) << "Update reaction notification settings from " << reaction_notification_settings_ << " to " + << reaction_notification_settings; + + reaction_notification_settings_ = std::move(reaction_notification_settings); + have_reaction_notification_settings_ = true; + + save_reaction_notification_settings(); + + send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); +} + +string NotificationSettingsManager::get_reaction_notification_settings_database_key() { + return "rns"; +} + +void NotificationSettingsManager::save_reaction_notification_settings() const { + string key = get_reaction_notification_settings_database_key(); + G()->td_db()->get_binlog_pmc()->set(key, log_event_store(reaction_notification_settings_).as_slice().str()); +} + void NotificationSettingsManager::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until, int32 unix_time) { if (mute_until >= unix_time && mute_until < unix_time + 366 * 86400) { @@ -1404,14 +1526,12 @@ FileSourceId NotificationSettingsManager::get_saved_ringtones_file_source_id() { void NotificationSettingsManager::send_get_dialog_notification_settings_query(DialogId dialog_id, MessageId top_thread_message_id, Promise &&promise) { - if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) { - LOG(WARNING) << "Can't get notification settings for " << dialog_id; + if (td_->auth_manager_->is_bot()) { + LOG(ERROR) << "Can't get notification settings for " << dialog_id; return promise.set_error(Status::Error(500, "Wrong getDialogNotificationSettings query")); } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - LOG(WARNING) << "Have no access to " << dialog_id << " to get notification settings"; - return promise.set_error(Status::Error(400, "Can't access the chat")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access_in_memory(dialog_id, false, AccessRights::Read)); auto &promises = get_dialog_notification_settings_queries_[{dialog_id, top_thread_message_id}]; promises.push_back(std::move(promise)); @@ -1523,6 +1643,61 @@ void NotificationSettingsManager::reset_notify_settings(Promise &&promise) td_->create_handler(std::move(promise))->send(); } +Status NotificationSettingsManager::set_reaction_notification_settings( + ReactionNotificationSettings &¬ification_settings) { + CHECK(!td_->auth_manager_->is_bot()); + notification_settings.update_default_notification_sound(reaction_notification_settings_); + if (notification_settings == reaction_notification_settings_) { + have_reaction_notification_settings_ = true; + return Status::OK(); + } + + VLOG(notifications) << "Update reaction notification settings from " << reaction_notification_settings_ << " to " + << notification_settings; + + reaction_notification_settings_ = std::move(notification_settings); + have_reaction_notification_settings_ = true; + + save_reaction_notification_settings(); + + send_closure(G()->td(), &Td::send_update, get_update_reaction_notification_settings_object()); + + update_reaction_notification_settings_on_server(0); + return Status::OK(); +} + +class NotificationSettingsManager::UpdateReactionNotificationSettingsOnServerLogEvent { + public: + template + void store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + } + + template + void parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + } +}; + +uint64 NotificationSettingsManager::save_update_reaction_notification_settings_on_server_log_event() { + UpdateReactionNotificationSettingsOnServerLogEvent log_event; + return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UpdateReactionNotificationSettingsOnServer, + get_log_event_storer(log_event)); +} + +void NotificationSettingsManager::update_reaction_notification_settings_on_server(uint64 log_event_id) { + CHECK(!td_->auth_manager_->is_bot()); + if (log_event_id == 0) { + log_event_id = save_update_reaction_notification_settings_on_server_log_event(); + } + + LOG(INFO) << "Update reaction notification settings on server with log_event " << log_event_id; + td_->create_handler(get_erase_log_event_promise(log_event_id)) + ->send(reaction_notification_settings_); +} + void NotificationSettingsManager::get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound, Promise &&promise) { td_->create_handler(std::move(promise))->send(scope, filter_scope, compare_sound); @@ -1547,6 +1722,13 @@ void NotificationSettingsManager::on_binlog_events(vector &&events) update_scope_notification_settings_on_server(log_event.scope_, event.id_); break; } + case LogEvent::HandlerType::UpdateReactionNotificationSettingsOnServer: { + UpdateReactionNotificationSettingsOnServerLogEvent log_event; + log_event_parse(log_event, event.get_data()).ensure(); + + update_reaction_notification_settings_on_server(event.id_); + break; + } default: LOG(FATAL) << "Unsupported log event type " << event.type_; } @@ -1567,6 +1749,8 @@ void NotificationSettingsManager::get_current_state(vector &&peer_notify_settings); + void on_update_reaction_notification_settings(ReactionNotificationSettings reaction_notification_settings); + void add_saved_ringtone(td_api::object_ptr &&input_file, Promise> &&promise); @@ -92,6 +95,8 @@ class NotificationSettingsManager final : public Actor { Promise &&promise); void send_get_scope_notification_settings_query(NotificationSettingsScope scope, Promise &&promise); + void send_get_reaction_notification_settings_query(Promise &&promise); + void on_get_dialog_notification_settings_query_finished(DialogId dialog_id, MessageId top_thread_message_id, Status &&status); @@ -102,6 +107,8 @@ class NotificationSettingsManager final : public Actor { td_api::object_ptr &¬ification_settings) TD_WARN_UNUSED_RESULT; + Status set_reaction_notification_settings(ReactionNotificationSettings &¬ification_settings) TD_WARN_UNUSED_RESULT; + void reset_scope_notification_settings(); void reset_notify_settings(Promise &&promise); @@ -119,6 +126,7 @@ class NotificationSettingsManager final : public Actor { private: class UpdateScopeNotificationSettingsOnServerLogEvent; + class UpdateReactionNotificationSettingsOnServerLogEvent; class RingtoneListLogEvent; @@ -172,6 +180,9 @@ class NotificationSettingsManager final : public Actor { td_api::object_ptr get_update_scope_notification_settings_object( NotificationSettingsScope scope) const; + td_api::object_ptr get_update_reaction_notification_settings_object() + const; + td_api::object_ptr get_update_saved_notification_sounds_object() const; void on_scope_unmute(NotificationSettingsScope scope); @@ -192,6 +203,14 @@ class NotificationSettingsManager final : public Actor { void update_scope_unmute_timeout(NotificationSettingsScope scope, int32 &old_mute_until, int32 new_mute_until); + static string get_reaction_notification_settings_database_key(); + + void save_reaction_notification_settings() const; + + uint64 save_update_reaction_notification_settings_on_server_log_event(); + + void update_reaction_notification_settings_on_server(uint64 log_event_id); + Td *td_; ActorShared<> parent_; @@ -203,6 +222,9 @@ class NotificationSettingsManager final : public Actor { ScopeNotificationSettings chats_notification_settings_; ScopeNotificationSettings channels_notification_settings_; + ReactionNotificationSettings reaction_notification_settings_; + bool have_reaction_notification_settings_ = false; + MultiTimeout scope_unmute_timeout_{"ScopeUnmuteTimeout"}; int64 saved_ringtone_hash_ = 0; diff --git a/lib/tgchat/ext/td/td/telegram/NotificationSound.cpp b/lib/tgchat/ext/td/td/telegram/NotificationSound.cpp index dfe1e520..395fc1f6 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationSound.cpp +++ b/lib/tgchat/ext/td/td/telegram/NotificationSound.cpp @@ -223,7 +223,7 @@ unique_ptr get_notification_sound(bool use_default_sound, int return td::make_unique(ringtone_id); } -static unique_ptr get_notification_sound(telegram_api::NotificationSound *notification_sound) { +unique_ptr get_notification_sound(telegram_api::NotificationSound *notification_sound) { if (notification_sound == nullptr) { return nullptr; } @@ -256,7 +256,7 @@ unique_ptr get_notification_sound(telegram_api::peerNotifySet telegram_api::NotificationSound *sound = #if TD_ANDROID for_stories ? settings->stories_android_sound_.get() : settings->android_sound_.get(); -#elif TD_DARWIN_IOS || TD_DARWIN_TV_OS || TD_DARWIN_WATCH_OS +#elif TD_DARWIN_IOS || TD_DARWIN_TV_OS || TD_DARWIN_VISION_OS || TD_DARWIN_WATCH_OS || TD_DARWIN_UNKNOWN for_stories ? settings->stories_ios_sound_.get() : settings->ios_sound_.get(); #else for_stories ? settings->stories_other_sound_.get() : settings->other_sound_.get(); @@ -265,8 +265,11 @@ unique_ptr get_notification_sound(telegram_api::peerNotifySet } telegram_api::object_ptr get_input_notification_sound( - const unique_ptr ¬ification_sound) { + const unique_ptr ¬ification_sound, bool return_non_null) { if (notification_sound == nullptr) { + if (return_non_null) { + return telegram_api::make_object(); + } return nullptr; } diff --git a/lib/tgchat/ext/td/td/telegram/NotificationSound.h b/lib/tgchat/ext/td/td/telegram/NotificationSound.h index abfb9ea1..19ca2538 100644 --- a/lib/tgchat/ext/td/td/telegram/NotificationSound.h +++ b/lib/tgchat/ext/td/td/telegram/NotificationSound.h @@ -55,12 +55,14 @@ int64 get_notification_sound_ringtone_id(const unique_ptr &no unique_ptr get_legacy_notification_sound(const string &sound); +unique_ptr get_notification_sound(telegram_api::NotificationSound *notification_sound); + unique_ptr get_notification_sound(bool use_default_sound, int64 ringtone_id); unique_ptr get_notification_sound(telegram_api::peerNotifySettings *settings, bool for_stories); telegram_api::object_ptr get_input_notification_sound( - const unique_ptr ¬ification_sound); + const unique_ptr ¬ification_sound, bool return_non_null = false); unique_ptr dup_notification_sound(const unique_ptr ¬ification_sound); diff --git a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp index 87f96ed6..cf5dd0d4 100644 --- a/lib/tgchat/ext/td/td/telegram/OptionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/OptionManager.cpp @@ -10,8 +10,8 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AttachMenuManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/CountryInfoManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/GitCommitHash.h" @@ -21,6 +21,7 @@ #include "td/telegram/net/MtprotoHeader.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/NotificationManager.h" +#include "td/telegram/PeopleNearbyManager.h" #include "td/telegram/ReactionType.h" #include "td/telegram/StateManager.h" #include "td/telegram/StickersManager.h" @@ -30,6 +31,7 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/TopDialogManager.h" +#include "td/telegram/UserManager.h" #include "td/db/KeyValueSyncInterface.h" #include "td/db/TsSeqKeyValue.h" @@ -97,11 +99,11 @@ OptionManager::OptionManager(Td *td) } }; set_default_integer_option("telegram_service_notifications_chat_id", - DialogId(ContactsManager::get_service_notifications_user_id()).get()); - set_default_integer_option("replies_bot_chat_id", DialogId(ContactsManager::get_replies_bot_user_id()).get()); - set_default_integer_option("group_anonymous_bot_user_id", ContactsManager::get_anonymous_bot_user_id().get()); - set_default_integer_option("channel_bot_user_id", ContactsManager::get_channel_bot_user_id().get()); - set_default_integer_option("anti_spam_bot_user_id", ContactsManager::get_anti_spam_bot_user_id().get()); + DialogId(UserManager::get_service_notifications_user_id()).get()); + set_default_integer_option("replies_bot_chat_id", DialogId(UserManager::get_replies_bot_user_id()).get()); + set_default_integer_option("group_anonymous_bot_user_id", UserManager::get_anonymous_bot_user_id().get()); + set_default_integer_option("channel_bot_user_id", UserManager::get_channel_bot_user_id().get()); + set_default_integer_option("anti_spam_bot_user_id", UserManager::get_anti_spam_bot_user_id().get()); set_default_integer_option("message_caption_length_max", 1024); set_default_integer_option("message_reply_quote_length_max", 1024); set_default_integer_option("story_caption_length_max", 200); @@ -130,6 +132,7 @@ OptionManager::OptionManager(Td *td) set_default_integer_option("channel_custom_wallpaper_level_min", is_test_dc ? 4 : 10); set_default_integer_option("channel_emoji_status_level_min", is_test_dc ? 2 : 8); set_default_integer_option("channel_profile_bg_icon_level_min", is_test_dc ? 1 : 7); + set_default_integer_option("channel_restrict_sponsored_level_min", is_test_dc ? 5 : 50); set_default_integer_option("channel_wallpaper_level_min", is_test_dc ? 3 : 9); set_default_integer_option("pm_read_date_expire_period", 604800); set_default_integer_option("group_transcribe_level_min", is_test_dc ? 4 : 6); @@ -140,12 +143,22 @@ OptionManager::OptionManager(Td *td) set_default_integer_option("group_custom_wallpaper_level_min", is_test_dc ? 4 : 10); set_default_integer_option("quick_reply_shortcut_count_max", is_test_dc ? 10 : 100); set_default_integer_option("quick_reply_shortcut_message_count_max", 20); + set_default_integer_option("business_start_page_title_length_max", 32); + set_default_integer_option("business_start_page_message_length_max", 70); + set_default_integer_option("premium_download_speedup", 10); + set_default_integer_option("premium_upload_speedup", 10); + set_default_integer_option("upload_premium_speedup_notify_period", 3600); + set_default_integer_option("business_chat_link_count_max", is_test_dc ? 5 : 100); + set_default_integer_option("pinned_story_count_max", 3); + set_default_integer_option("fact_check_length_max", 1024); if (options.isset("my_phone_number") || !options.isset("my_id")) { update_premium_options(); } set_option_empty("archive_and_mute_new_chats_from_unknown_users"); + set_option_empty("business_intro_title_length_max"); + set_option_empty("business_intro_message_length_max"); set_option_empty("channel_custom_accent_color_boost_level_min"); set_option_empty("chat_filter_count_max"); set_option_empty("chat_filter_chosen_chat_count_max"); @@ -179,6 +192,9 @@ void OptionManager::update_premium_options() { set_option_integer("monthly_sent_story_count_max", get_option_integer("stories_sent_monthly_limit_premium", 3000)); set_option_integer("story_suggested_reaction_area_count_max", get_option_integer("stories_suggested_reactions_limit_premium", 5)); + + set_option_boolean("can_set_new_chat_privacy_settings", true); + set_option_boolean("can_use_text_entities_in_story_caption", true); } else { set_option_integer("saved_animations_limit", get_option_integer("saved_gifs_limit_default", 200)); set_option_integer("favorite_stickers_limit", get_option_integer("stickers_faved_limit_default", 5)); @@ -200,6 +216,10 @@ void OptionManager::update_premium_options() { set_option_integer("monthly_sent_story_count_max", get_option_integer("stories_sent_monthly_limit_default", 30)); set_option_integer("story_suggested_reaction_area_count_max", get_option_integer("stories_suggested_reactions_limit_default", 1)); + + set_option_boolean("can_set_new_chat_privacy_settings", !get_option_boolean("need_premium_for_new_chat_privacy")); + set_option_boolean("can_use_text_entities_in_story_caption", + !get_option_boolean("need_premium_for_story_caption_entities")); } } @@ -332,6 +352,7 @@ bool OptionManager::is_internal_option(Slice name) { "animation_search_provider", "authorization_autoconfirm_period", "base_language_pack_version", + "business_features", "call_receive_timeout_ms", "call_ring_timeout_ms", "caption_length_limit_default", @@ -340,6 +361,7 @@ bool OptionManager::is_internal_option(Slice name) { "channel_custom_wallpaper_level_min", "channel_emoji_status_level_min", "channel_profile_bg_icon_level_min", + "channel_restrict_sponsored_level_min", "channel_wallpaper_level_min", "channels_limit_default", "channels_limit_premium", @@ -365,6 +387,7 @@ bool OptionManager::is_internal_option(Slice name) { "dialogs_pinned_limit_premium", "dice_emojis", "dice_success_values", + "dismiss_birthday_contact_today", "edit_time_limit", "emoji_sounds", "fragment_prefixes", @@ -378,6 +401,7 @@ bool OptionManager::is_internal_option(Slice name) { "ignored_restriction_reasons", "language_pack_version", "my_phone_number", + "need_premium_for_new_chat_privacy", "need_premium_for_story_caption_entities", "need_synchronize_archive_all_stories", "notification_cloud_delay_ms", @@ -389,6 +413,7 @@ bool OptionManager::is_internal_option(Slice name) { "premium_bot_username", "premium_features", "premium_invoice_slug", + "premium_manage_subscription_url", "rating_e_decay", "reactions_uniq_max", "reactions_user_max_default", @@ -422,6 +447,7 @@ bool OptionManager::is_internal_option(Slice name) { "story_caption_length_limit_premium", "story_expiring_limit_default", "story_expiring_limit_premium", + "upload_premium_speedup_notify_period", "video_note_size_max", "webfile_dc_id"}; return internal_options.count(name) > 0; @@ -495,6 +521,9 @@ void OptionManager::on_option_updated(Slice name) { if (name == "disable_top_chats") { send_closure(td_->top_dialog_manager_actor_, &TopDialogManager::update_is_enabled, !get_option_boolean(name)); } + if (name == "dismiss_birthday_contact_today") { + send_closure(td_->user_manager_actor_, &UserManager::reload_contact_birthdates, true); + } break; case 'e': if (name == "emoji_sounds") { @@ -511,7 +540,8 @@ void OptionManager::on_option_updated(Slice name) { break; case 'i': if (name == "ignored_restriction_reasons") { - send_closure(td_->contacts_manager_actor_, &ContactsManager::on_ignored_restriction_reasons_changed); + send_closure(td_->chat_manager_actor_, &ChatManager::on_ignored_restriction_reasons_changed); + send_closure(td_->user_manager_actor_, &UserManager::on_ignored_restriction_reasons_changed); } if (name == "is_emulator") { if (G()->mtproto_header().set_is_emulator(get_option_boolean(name))) { @@ -519,9 +549,7 @@ void OptionManager::on_option_updated(Slice name) { } } if (name == "is_premium") { - set_option_boolean( - "can_use_text_entities_in_story_caption", - !get_option_boolean("need_premium_for_story_caption_entities") || get_option_boolean("is_premium")); + update_premium_options(); } break; case 'l': @@ -542,11 +570,17 @@ void OptionManager::on_option_updated(Slice name) { } } break; + case 'm': + if (name == "my_phone_number") { + send_closure(G()->config_manager(), &ConfigManager::reget_config, Promise()); + } + break; case 'n': + if (name == "need_premium_for_new_chat_privacy") { + update_premium_options(); + } if (name == "need_premium_for_story_caption_entities") { - set_option_boolean( - "can_use_text_entities_in_story_caption", - !get_option_boolean("need_premium_for_story_caption_entities") || get_option_boolean("is_premium")); + update_premium_options(); } if (name == "need_synchronize_archive_all_stories") { send_closure(td_->story_manager_actor_, &StoryManager::try_synchronize_archive_all_stories); @@ -636,7 +670,8 @@ void OptionManager::get_option(const string &name, Promisecontacts_manager_actor_, &ContactsManager::get_is_location_visible, wrap_promise()); + send_closure_later(td_->people_nearby_manager_actor_, &PeopleNearbyManager::get_is_location_visible, + wrap_promise()); } else { pending_get_options_.emplace_back(name, std::move(promise)); } @@ -667,7 +702,7 @@ td_api::object_ptr OptionManager::get_option_synchronously( break; case 'v': if (name == "version") { - return td_api::make_object("1.8.26"); + return td_api::make_object("1.8.30"); } break; } @@ -842,7 +877,7 @@ void OptionManager::set_option(const string &name, td_api::object_ptr get_input_invoice_info(Td *td, td_api::object_ptrpurpose_->get_id()) { case td_api::telegramPaymentPurposePremiumGiftCodes::ID: { - auto p = static_cast(invoice->purpose_.get()); + auto p = static_cast(invoice->purpose_.get()); vector> input_users; for (auto user_id : p->user_ids_) { - TRY_RESULT(input_user, td->contacts_manager_->get_input_user(UserId(user_id))); + TRY_RESULT(input_user, td->user_manager_->get_input_user(UserId(user_id))); input_users.push_back(std::move(input_user)); } if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } DialogId boosted_dialog_id(p->boosted_chat_id_); TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id)); int32 flags = 0; @@ -105,10 +108,13 @@ Result get_input_invoice_info(Td *td, td_api::object_ptr(invoice->purpose_.get()); + auto p = static_cast(invoice->purpose_.get()); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get())); auto option = telegram_api::make_object( 0, p->winner_count_, p->month_count_, string(), 0, p->currency_, p->amount_); @@ -116,6 +122,19 @@ Result get_input_invoice_info(Td *td, td_api::object_ptrcurrency_, p->amount_), std::move(option)); break; } + case td_api::telegramPaymentPurposeStars::ID: { + auto p = static_cast(invoice->purpose_.get()); + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } + auto option = telegram_api::make_object(0, false /*ignored*/, p->star_count_, + string(), p->currency_, p->amount_); + result.input_invoice_ = telegram_api::make_object(std::move(option)); + break; + } default: UNREACHABLE(); } @@ -430,42 +449,75 @@ class GetPaymentFormQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - auto payment_form = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetPaymentFormQuery: " << to_string(payment_form); - - td_->contacts_manager_->on_get_users(std::move(payment_form->users_), "GetPaymentFormQuery"); - - UserId payments_provider_user_id(payment_form->provider_id_); - if (!payments_provider_user_id.is_valid()) { - LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id; - return on_error(Status::Error(500, "Receive invalid payments provider identifier")); - } - UserId seller_bot_user_id(payment_form->bot_id_); - if (!seller_bot_user_id.is_valid()) { - LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; - return on_error(Status::Error(500, "Receive invalid seller identifier")); - } - bool can_save_credentials = payment_form->can_save_credentials_; - bool need_password = payment_form->password_missing_; - auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_); - auto payment_provider = convert_payment_provider( - payment_form->native_provider_, std::move(payment_form->native_params_), payment_form->invoice_->test_); - if (payment_provider == nullptr) { - payment_provider = td_api::make_object(std::move(payment_form->url_)); - } - auto additional_payment_options = transform( - payment_form->additional_methods_, [](const telegram_api::object_ptr &method) { - return td_api::make_object(method->title_, method->url_); - }); - promise_.set_value(make_tl_object( - payment_form->form_id_, convert_invoice(std::move(payment_form->invoice_)), - td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"), - td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentForm provider"), - std::move(payment_provider), std::move(additional_payment_options), - convert_order_info(std::move(payment_form->saved_info_)), - convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, need_password, - payment_form->title_, get_product_description_object(payment_form->description_), - get_photo_object(td_->file_manager_.get(), photo))); + auto payment_form_ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetPaymentFormQuery: " << to_string(payment_form_ptr); + switch (payment_form_ptr->get_id()) { + case telegram_api::payments_paymentForm::ID: { + auto payment_form = telegram_api::move_object_as(payment_form_ptr); + + td_->user_manager_->on_get_users(std::move(payment_form->users_), "GetPaymentFormQuery 1"); + + UserId payments_provider_user_id(payment_form->provider_id_); + if (!payments_provider_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id; + return on_error(Status::Error(500, "Receive invalid payments provider identifier")); + } + UserId seller_bot_user_id(payment_form->bot_id_); + if (!seller_bot_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; + return on_error(Status::Error(500, "Receive invalid seller identifier")); + } + bool can_save_credentials = payment_form->can_save_credentials_; + bool need_password = payment_form->password_missing_; + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_); + auto payment_provider = convert_payment_provider( + payment_form->native_provider_, std::move(payment_form->native_params_), payment_form->invoice_->test_); + if (payment_provider == nullptr) { + payment_provider = td_api::make_object(std::move(payment_form->url_)); + } + auto additional_payment_options = + transform(payment_form->additional_methods_, + [](const telegram_api::object_ptr &method) { + return td_api::make_object(method->title_, method->url_); + }); + auto type = td_api::make_object( + convert_invoice(std::move(payment_form->invoice_)), + td_->user_manager_->get_user_id_object(payments_provider_user_id, "paymentForm provider"), + std::move(payment_provider), std::move(additional_payment_options), + convert_order_info(std::move(payment_form->saved_info_)), + convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, + need_password); + promise_.set_value(td_api::make_object( + payment_form->form_id_, std::move(type), + td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"), + get_product_info_object(td_, payment_form->title_, payment_form->description_, photo))); + break; + } + case telegram_api::payments_paymentFormStars::ID: { + auto payment_form = telegram_api::move_object_as(payment_form_ptr); + + td_->user_manager_->on_get_users(std::move(payment_form->users_), "GetPaymentFormQuery 2"); + + UserId seller_bot_user_id(payment_form->bot_id_); + if (!seller_bot_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; + return on_error(Status::Error(500, "Receive invalid seller identifier")); + } + if (payment_form->invoice_->prices_.size() != 1u) { + LOG(ERROR) << "Receive invalid prices " << to_string(payment_form->invoice_->prices_); + return on_error(Status::Error(500, "Receive invalid price")); + } + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_); + auto type = td_api::make_object(payment_form->invoice_->prices_[0]->amount_); + promise_.set_value(td_api::make_object( + payment_form->form_id_, std::move(type), + td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"), + get_product_info_object(td_, payment_form->title_, payment_form->description_, photo))); + break; + } + default: + UNREACHABLE(); + } } void on_error(Status status) final { @@ -584,6 +636,56 @@ class SendPaymentFormQuery final : public Td::ResultHandler { } }; +class SendStarPaymentFormQuery final : public Td::ResultHandler { + Promise> promise_; + DialogId dialog_id_; + + public: + explicit SendStarPaymentFormQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(InputInvoiceInfo &&input_invoice_info, int64 payment_form_id) { + dialog_id_ = input_invoice_info.dialog_id_; + + 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); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto payment_result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendStarPaymentFormQuery: " << to_string(payment_result); + + switch (payment_result->get_id()) { + case telegram_api::payments_paymentResult::ID: { + auto result = move_tl_object_as(payment_result); + td_->updates_manager_->on_get_updates( + std::move(result->updates_), PromiseCreator::lambda([promise = std::move(promise_)](Unit) mutable { + promise.set_value(make_tl_object(true, string())); + })); + return; + } + case telegram_api::payments_paymentVerificationNeeded::ID: { + auto result = move_tl_object_as(payment_result); + promise_.set_value(make_tl_object(false, std::move(result->url_))); + return; + } + default: + UNREACHABLE(); + } + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "SendStarPaymentFormQuery"); + promise_.set_error(std::move(status)); + } +}; + class GetPaymentReceiptQuery final : public Td::ResultHandler { Promise> promise_; DialogId dialog_id_; @@ -610,35 +712,67 @@ class GetPaymentReceiptQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - auto payment_receipt = result_ptr.move_as_ok(); - LOG(INFO) << "Receive result for GetPaymentReceiptQuery: " << to_string(payment_receipt); + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetPaymentReceiptQuery: " << to_string(ptr); + switch (ptr->get_id()) { + case telegram_api::payments_paymentReceiptStars::ID: { + auto payment_receipt = telegram_api::move_object_as(ptr); + + td_->user_manager_->on_get_users(std::move(payment_receipt->users_), "GetPaymentReceiptQuery 1"); + UserId seller_bot_user_id(payment_receipt->bot_id_); + if (!seller_bot_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; + return on_error(Status::Error(500, "Receive invalid seller identifier")); + } + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_receipt->photo_), dialog_id_); + if (payment_receipt->invoice_->prices_.size() != 1u) { + LOG(ERROR) << "Receive invalid prices " << to_string(payment_receipt->invoice_->prices_); + return on_error(Status::Error(500, "Receive invalid price")); + } + auto type = td_api::make_object(); + + promise_.set_value(make_tl_object( + get_product_info_object(td_, payment_receipt->title_, payment_receipt->description_, photo), + payment_receipt->date_, td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), + td_api::make_object(payment_receipt->invoice_->prices_[0]->amount_, + payment_receipt->transaction_id_))); + break; + } + case telegram_api::payments_paymentReceipt::ID: { + auto payment_receipt = telegram_api::move_object_as(ptr); + + td_->user_manager_->on_get_users(std::move(payment_receipt->users_), "GetPaymentReceiptQuery 2"); - td_->contacts_manager_->on_get_users(std::move(payment_receipt->users_), "GetPaymentReceiptQuery"); + UserId payments_provider_user_id(payment_receipt->provider_id_); + if (!payments_provider_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id; + return on_error(Status::Error(500, "Receive invalid payments provider identifier")); + } + UserId seller_bot_user_id(payment_receipt->bot_id_); + if (!seller_bot_user_id.is_valid()) { + LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; + return on_error(Status::Error(500, "Receive invalid seller identifier")); + } + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_receipt->photo_), dialog_id_); + if (payment_receipt->tip_amount_ < 0 || !check_currency_amount(payment_receipt->tip_amount_)) { + LOG(ERROR) << "Receive invalid tip amount " << payment_receipt->tip_amount_; + payment_receipt->tip_amount_ = 0; + } - UserId payments_provider_user_id(payment_receipt->provider_id_); - if (!payments_provider_user_id.is_valid()) { - LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id; - return on_error(Status::Error(500, "Receive invalid payments provider identifier")); - } - UserId seller_bot_user_id(payment_receipt->bot_id_); - if (!seller_bot_user_id.is_valid()) { - LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id; - return on_error(Status::Error(500, "Receive invalid seller identifier")); - } - auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_receipt->photo_), dialog_id_); - if (payment_receipt->tip_amount_ < 0 || !check_currency_amount(payment_receipt->tip_amount_)) { - LOG(ERROR) << "Receive invalid tip amount " << payment_receipt->tip_amount_; - payment_receipt->tip_amount_ = 0; + promise_.set_value(make_tl_object( + get_product_info_object(td_, payment_receipt->title_, payment_receipt->description_, photo), + payment_receipt->date_, td_->user_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), + td_api::make_object( + td_->user_manager_->get_user_id_object(payments_provider_user_id, "paymentReceipt provider"), + convert_invoice(std::move(payment_receipt->invoice_)), + convert_order_info(std::move(payment_receipt->info_)), + convert_shipping_option(std::move(payment_receipt->shipping_)), + std::move(payment_receipt->credentials_title_), payment_receipt->tip_amount_))); + break; + } + default: + UNREACHABLE(); } - - promise_.set_value(make_tl_object( - payment_receipt->title_, get_product_description_object(payment_receipt->description_), - get_photo_object(td_->file_manager_.get(), photo), payment_receipt->date_, - td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"), - td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentReceipt provider"), - convert_invoice(std::move(payment_receipt->invoice_)), convert_order_info(std::move(payment_receipt->info_)), - convert_shipping_option(std::move(payment_receipt->shipping_)), std::move(payment_receipt->credentials_title_), - payment_receipt->tip_amount_)); } void on_error(Status status) final { @@ -734,6 +868,34 @@ class ExportInvoiceQuery final : public Td::ResultHandler { } }; +class RefundStarsChargeQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit RefundStarsChargeQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(telegram_api::object_ptr &&input_user, const string &telegram_payment_charge_id) { + send_query(G()->net_query_creator().create( + telegram_api::payments_refundStarsCharge(std::move(input_user), telegram_payment_charge_id))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for RefundStarsChargeQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetBankCardInfoQuery final : public Td::ResultHandler { Promise> promise_; @@ -765,6 +927,44 @@ class GetBankCardInfoQuery final : public Td::ResultHandler { } }; +class GetCollectibleInfoQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetCollectibleInfoQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(telegram_api::object_ptr &&input_collectible) { + send_query( + G()->net_query_creator().create(telegram_api::fragment_getCollectibleInfo(std::move(input_collectible)))); + } + + 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(); + if (result->amount_ <= 0 || !check_currency_amount(result->amount_)) { + LOG(ERROR) << "Receive invalid collectible item price " << result->amount_; + result->amount_ = 0; + } + if (result->crypto_currency_.empty() || result->crypto_amount_ <= 0) { + LOG(ERROR) << "Receive invalid collectible item cryptocurrency price " << result->crypto_amount_; + result->crypto_amount_ = 0; + } + promise_.set_value(td_api::make_object(result->purchase_date_, result->currency_, + result->amount_, result->crypto_currency_, + result->crypto_amount_, result->url_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + void answer_shipping_query(Td *td, int64 shipping_query_id, vector> &&shipping_options, const string &error_message, Promise &&promise) { @@ -870,7 +1070,12 @@ void send_payment_form(Td *td, td_api::object_ptr &&input_ TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice))); if (credentials == nullptr) { - return promise.set_error(Status::Error(400, "Input payment credentials must be non-empty")); + if (tip_amount != 0 || !order_info_id.empty() || !shipping_option_id.empty()) { + return promise.set_error(Status::Error(400, "Invalid payment form parameters specified")); + } + td->create_handler(std::move(promise)) + ->send(std::move(input_invoice_info), payment_form_id); + return; } tl_object_ptr input_credentials; @@ -953,9 +1158,44 @@ void export_invoice(Td *td, td_api::object_ptr &&in td->create_handler(std::move(promise))->send(std::move(input_media)); } +void refund_star_payment(Td *td, UserId user_id, const string &telegram_payment_charge_id, Promise &&promise) { + TRY_RESULT_PROMISE(promise, input_user, td->user_manager_->get_input_user(user_id)); + td->create_handler(std::move(promise)) + ->send(std::move(input_user), telegram_payment_charge_id); +} + void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise) { td->create_handler(std::move(promise))->send(bank_card_number); } +void get_collectible_info(Td *td, td_api::object_ptr type, + Promise> &&promise) { + if (type == nullptr) { + return promise.set_error(Status::Error(400, "Item type must be non-empty")); + } + switch (type->get_id()) { + case td_api::collectibleItemTypeUsername::ID: { + auto username = td_api::move_object_as(type); + if (!clean_input_string(username->username_)) { + return promise.set_error(Status::Error(400, "Username must be encoded in UTF-8")); + } + td->create_handler(std::move(promise)) + ->send(telegram_api::make_object(username->username_)); + break; + } + case td_api::collectibleItemTypePhoneNumber::ID: { + auto phone_number = td_api::move_object_as(type); + if (!clean_input_string(phone_number->phone_number_)) { + return promise.set_error(Status::Error(400, "Phone number must be encoded in UTF-8")); + } + td->create_handler(std::move(promise)) + ->send(telegram_api::make_object(phone_number->phone_number_)); + break; + } + default: + UNREACHABLE(); + } +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Payments.h b/lib/tgchat/ext/td/td/telegram/Payments.h index 07fbf006..82ad0bfb 100644 --- a/lib/tgchat/ext/td/td/telegram/Payments.h +++ b/lib/tgchat/ext/td/td/telegram/Payments.h @@ -8,6 +8,7 @@ #include "td/telegram/MessageFullId.h" #include "td/telegram/td_api.h" +#include "td/telegram/UserId.h" #include "td/utils/common.h" #include "td/utils/Promise.h" @@ -48,7 +49,12 @@ void delete_saved_credentials(Td *td, Promise &&promise); void export_invoice(Td *td, td_api::object_ptr &&invoice, Promise &&promise); +void refund_star_payment(Td *td, UserId user_id, const string &telegram_payment_charge_id, Promise &&promise); + void get_bank_card_info(Td *td, const string &bank_card_number, Promise> &&promise); +void get_collectible_info(Td *td, td_api::object_ptr type, + Promise> &&promise); + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.cpp b/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.cpp new file mode 100644 index 00000000..a5bae311 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.cpp @@ -0,0 +1,450 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/PeopleNearbyManager.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Global.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" + +#include +#include + +namespace td { + +class SearchDialogsNearbyQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit SearchDialogsNearbyQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const Location &location, bool from_background, int32 expire_date) { + int32 flags = 0; + if (from_background) { + flags |= telegram_api::contacts_getLocated::BACKGROUND_MASK; + } + if (expire_date != -1) { + flags |= telegram_api::contacts_getLocated::SELF_EXPIRES_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::contacts_getLocated(flags, false /*ignored*/, location.get_input_geo_point(), expire_date))); + } + + 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)); + } +}; + +PeopleNearbyManager::PeopleNearbyManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + if (!td_->auth_manager_->is_bot()) { + location_visibility_expire_date_ = + to_integer(G()->td_db()->get_binlog_pmc()->get("location_visibility_expire_date")); + if (location_visibility_expire_date_ != 0 && location_visibility_expire_date_ <= G()->unix_time()) { + location_visibility_expire_date_ = 0; + G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date"); + } + auto pending_location_visibility_expire_date_string = + G()->td_db()->get_binlog_pmc()->get("pending_location_visibility_expire_date"); + if (!pending_location_visibility_expire_date_string.empty()) { + pending_location_visibility_expire_date_ = to_integer(pending_location_visibility_expire_date_string); + } + update_is_location_visible(); + LOG(INFO) << "Loaded location_visibility_expire_date = " << location_visibility_expire_date_ + << " and pending_location_visibility_expire_date = " << pending_location_visibility_expire_date_; + } + + user_nearby_timeout_.set_callback(on_user_nearby_timeout_callback); + user_nearby_timeout_.set_callback_data(static_cast(this)); +} + +void PeopleNearbyManager::tear_down() { + parent_.reset(); +} + +void PeopleNearbyManager::start_up() { + if (!pending_location_visibility_expire_date_) { + try_send_set_location_visibility_query(); + } +} + +void PeopleNearbyManager::on_user_nearby_timeout_callback(void *people_nearby_manager_ptr, int64 user_id_long) { + if (G()->close_flag()) { + return; + } + + auto people_nearby_manager = static_cast(people_nearby_manager_ptr); + send_closure_later(people_nearby_manager->actor_id(people_nearby_manager), + &PeopleNearbyManager::on_user_nearby_timeout, UserId(user_id_long)); +} + +void PeopleNearbyManager::on_user_nearby_timeout(UserId user_id) { + if (G()->close_flag()) { + return; + } + + LOG(INFO) << "Remove " << user_id << " from nearby list"; + DialogId dialog_id(user_id); + for (size_t i = 0; i < users_nearby_.size(); i++) { + if (users_nearby_[i].dialog_id == dialog_id) { + users_nearby_.erase(users_nearby_.begin() + i); + send_update_users_nearby(); + return; + } + } +} + +bool PeopleNearbyManager::is_user_nearby(UserId user_id) const { + return all_users_nearby_.count(user_id) != 0; +} + +void PeopleNearbyManager::search_dialogs_nearby(const Location &location, + Promise> &&promise) { + if (location.empty()) { + return promise.set_error(Status::Error(400, "Invalid location specified")); + } + last_user_location_ = location; + try_send_set_location_visibility_query(); + + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( + Result> result) mutable { + send_closure(actor_id, &PeopleNearbyManager::on_get_dialogs_nearby, std::move(result), std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(location, false, -1); +} + +vector> PeopleNearbyManager::get_chats_nearby_object( + const vector &dialogs_nearby) const { + return transform(dialogs_nearby, [td = td_](const DialogNearby &dialog_nearby) { + return td_api::make_object( + td->dialog_manager_->get_chat_id_object(dialog_nearby.dialog_id, "chatNearby"), dialog_nearby.distance); + }); +} + +void PeopleNearbyManager::send_update_users_nearby() const { + send_closure(G()->td(), &Td::send_update, + td_api::make_object(get_chats_nearby_object(users_nearby_))); +} + +void PeopleNearbyManager::on_get_dialogs_nearby(Result> result, + Promise> &&promise) { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + + auto updates_ptr = result.move_as_ok(); + if (updates_ptr->get_id() != telegram_api::updates::ID) { + LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates"; + return promise.set_error(Status::Error(500, "Receive unsupported response from the server")); + } + + auto update = telegram_api::move_object_as(updates_ptr); + LOG(INFO) << "Receive chats nearby in " << to_string(update); + + td_->user_manager_->on_get_users(std::move(update->users_), "on_get_dialogs_nearby"); + td_->chat_manager_->on_get_chats(std::move(update->chats_), "on_get_dialogs_nearby"); + + for (auto &dialog_nearby : users_nearby_) { + user_nearby_timeout_.cancel_timeout(dialog_nearby.dialog_id.get_user_id().get()); + } + auto old_users_nearby = std::move(users_nearby_); + users_nearby_.clear(); + channels_nearby_.clear(); + int32 location_visibility_expire_date = 0; + for (auto &update_ptr : update->updates_) { + if (update_ptr->get_id() != telegram_api::updatePeerLocated::ID) { + LOG(ERROR) << "Receive unexpected " << to_string(update); + continue; + } + + auto expire_date = on_update_peer_located( + std::move(static_cast(update_ptr.get())->peers_), false); + if (expire_date != -1) { + location_visibility_expire_date = expire_date; + } + } + if (location_visibility_expire_date != location_visibility_expire_date_) { + set_location_visibility_expire_date(location_visibility_expire_date); + update_is_location_visible(); + } + + std::sort(users_nearby_.begin(), users_nearby_.end()); + if (old_users_nearby != users_nearby_) { + send_update_users_nearby(); // for other clients connected to the same TDLib instance + } + promise.set_value(td_api::make_object(get_chats_nearby_object(users_nearby_), + get_chats_nearby_object(channels_nearby_))); +} + +void PeopleNearbyManager::set_location(const Location &location, Promise &&promise) { + if (location.empty()) { + return promise.set_error(Status::Error(400, "Invalid location specified")); + } + last_user_location_ = location; + try_send_set_location_visibility_query(); + + auto query_promise = PromiseCreator::lambda( + [promise = std::move(promise)](Result> result) mutable { + promise.set_value(Unit()); + }); + td_->create_handler(std::move(query_promise))->send(location, true, -1); +} + +void PeopleNearbyManager::set_location_visibility(Td *td) { + bool is_location_visible = td->option_manager_->get_option_boolean("is_location_visible"); + auto pending_location_visibility_expire_date = is_location_visible ? std::numeric_limits::max() : 0; + if (td->people_nearby_manager_ == nullptr) { + G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date", + to_string(pending_location_visibility_expire_date)); + return; + } + if (td->people_nearby_manager_->pending_location_visibility_expire_date_ == -1 && + pending_location_visibility_expire_date == td->people_nearby_manager_->location_visibility_expire_date_) { + return; + } + if (td->people_nearby_manager_->pending_location_visibility_expire_date_ != pending_location_visibility_expire_date) { + td->people_nearby_manager_->pending_location_visibility_expire_date_ = pending_location_visibility_expire_date; + G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date", + to_string(pending_location_visibility_expire_date)); + } + td->people_nearby_manager_->try_send_set_location_visibility_query(); +} + +void PeopleNearbyManager::try_send_set_location_visibility_query() { + if (G()->close_flag()) { + return; + } + if (pending_location_visibility_expire_date_ == -1) { + return; + } + + LOG(INFO) << "Trying to send set location visibility query"; + if (is_set_location_visibility_request_sent_) { + return; + } + if (pending_location_visibility_expire_date_ != 0 && last_user_location_.empty()) { + return; + } + + is_set_location_visibility_request_sent_ = true; + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), set_expire_date = pending_location_visibility_expire_date_]( + Result> result) { + send_closure(actor_id, &PeopleNearbyManager::on_set_location_visibility_expire_date, set_expire_date, + result.is_ok() ? 0 : result.error().code()); + }); + td_->create_handler(std::move(query_promise)) + ->send(last_user_location_, true, pending_location_visibility_expire_date_); +} + +void PeopleNearbyManager::on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code) { + bool success = error_code == 0; + is_set_location_visibility_request_sent_ = false; + + if (set_expire_date != pending_location_visibility_expire_date_) { + return try_send_set_location_visibility_query(); + } + + if (success) { + set_location_visibility_expire_date(pending_location_visibility_expire_date_); + } else { + if (G()->close_flag()) { + // request will be re-sent after restart + return; + } + if (error_code != 406) { + LOG(ERROR) << "Failed to set location visibility expire date to " << pending_location_visibility_expire_date_; + } + } + G()->td_db()->get_binlog_pmc()->erase("pending_location_visibility_expire_date"); + pending_location_visibility_expire_date_ = -1; + update_is_location_visible(); +} + +void PeopleNearbyManager::get_is_location_visible(Promise &&promise) { + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( + Result> result) mutable { + send_closure(actor_id, &PeopleNearbyManager::on_get_is_location_visible, std::move(result), std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(Location(), true, -1); +} + +void PeopleNearbyManager::on_get_is_location_visible(Result> &&result, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + if (result.is_error()) { + if (result.error().message() == "GEO_POINT_INVALID" && pending_location_visibility_expire_date_ == -1 && + location_visibility_expire_date_ > 0) { + set_location_visibility_expire_date(0); + update_is_location_visible(); + } + return promise.set_value(Unit()); + } + + auto updates_ptr = result.move_as_ok(); + if (updates_ptr->get_id() != telegram_api::updates::ID) { + LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates"; + return promise.set_value(Unit()); + } + + auto updates = std::move(telegram_api::move_object_as(updates_ptr)->updates_); + if (updates.size() != 1 || updates[0]->get_id() != telegram_api::updatePeerLocated::ID) { + LOG(ERROR) << "Receive unexpected " << to_string(updates); + return promise.set_value(Unit()); + } + + auto peers = std::move(static_cast(updates[0].get())->peers_); + if (peers.size() != 1 || peers[0]->get_id() != telegram_api::peerSelfLocated::ID) { + LOG(ERROR) << "Receive unexpected " << to_string(peers); + return promise.set_value(Unit()); + } + + auto location_visibility_expire_date = static_cast(peers[0].get())->expires_; + if (location_visibility_expire_date != location_visibility_expire_date_) { + set_location_visibility_expire_date(location_visibility_expire_date); + update_is_location_visible(); + } + + promise.set_value(Unit()); +} + +int32 PeopleNearbyManager::on_update_peer_located(vector> &&peers, + bool from_update) { + auto now = G()->unix_time(); + bool need_update = false; + int32 location_visibility_expire_date = -1; + for (auto &peer_located_ptr : peers) { + if (peer_located_ptr->get_id() == telegram_api::peerSelfLocated::ID) { + auto peer_self_located = telegram_api::move_object_as(peer_located_ptr); + if (peer_self_located->expires_ == 0 || peer_self_located->expires_ > G()->unix_time()) { + location_visibility_expire_date = peer_self_located->expires_; + } + continue; + } + + CHECK(peer_located_ptr->get_id() == telegram_api::peerLocated::ID); + auto peer_located = telegram_api::move_object_as(peer_located_ptr); + DialogId dialog_id(peer_located->peer_); + int32 expires_at = peer_located->expires_; + int32 distance = peer_located->distance_; + if (distance < 0 || distance > 50000000) { + LOG(ERROR) << "Receive wrong distance to " << to_string(peer_located); + continue; + } + if (expires_at <= now) { + LOG(INFO) << "Skip expired result " << to_string(peer_located); + continue; + } + + auto dialog_type = dialog_id.get_type(); + if (dialog_type == DialogType::User) { + auto user_id = dialog_id.get_user_id(); + if (!td_->user_manager_->have_user(user_id)) { + LOG(ERROR) << "Can't find " << user_id; + continue; + } + if (expires_at < now + 86400) { + user_nearby_timeout_.set_timeout_in(user_id.get(), expires_at - now + 1); + } + } else if (dialog_type == DialogType::Channel) { + auto channel_id = dialog_id.get_channel_id(); + if (!td_->chat_manager_->have_channel(channel_id)) { + LOG(ERROR) << "Can't find " << channel_id; + continue; + } + if (expires_at != std::numeric_limits::max()) { + LOG(ERROR) << "Receive expiring at " << expires_at << " group location in " << to_string(peer_located); + } + if (from_update) { + LOG(ERROR) << "Receive nearby " << channel_id << " from update"; + continue; + } + } else { + LOG(ERROR) << "Receive chat of wrong type in " << to_string(peer_located); + continue; + } + + td_->dialog_manager_->force_create_dialog(dialog_id, "on_update_peer_located"); + + if (from_update) { + CHECK(dialog_type == DialogType::User); + bool is_found = false; + for (auto &dialog_nearby : users_nearby_) { + if (dialog_nearby.dialog_id == dialog_id) { + if (dialog_nearby.distance != distance) { + dialog_nearby.distance = distance; + need_update = true; + } + is_found = true; + break; + } + } + if (!is_found) { + users_nearby_.emplace_back(dialog_id, distance); + all_users_nearby_.insert(dialog_id.get_user_id()); + need_update = true; + } + } else { + if (dialog_type == DialogType::User) { + users_nearby_.emplace_back(dialog_id, distance); + all_users_nearby_.insert(dialog_id.get_user_id()); + } else { + channels_nearby_.emplace_back(dialog_id, distance); + } + } + } + if (need_update) { + std::sort(users_nearby_.begin(), users_nearby_.end()); + send_update_users_nearby(); + } + return location_visibility_expire_date; +} + +void PeopleNearbyManager::set_location_visibility_expire_date(int32 expire_date) { + if (location_visibility_expire_date_ == expire_date) { + return; + } + + LOG(INFO) << "Set set_location_visibility_expire_date to " << expire_date; + location_visibility_expire_date_ = expire_date; + if (expire_date == 0) { + G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date"); + } else { + G()->td_db()->get_binlog_pmc()->set("location_visibility_expire_date", to_string(expire_date)); + } + // the caller must call update_is_location_visible() itself +} + +void PeopleNearbyManager::update_is_location_visible() { + auto expire_date = pending_location_visibility_expire_date_ != -1 ? pending_location_visibility_expire_date_ + : location_visibility_expire_date_; + td_->option_manager_->set_option_boolean("is_location_visible", expire_date != 0); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.h b/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.h new file mode 100644 index 00000000..2b464987 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/PeopleNearbyManager.h @@ -0,0 +1,105 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/DialogId.h" +#include "td/telegram/Location.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" + +#include "td/actor/actor.h" +#include "td/actor/MultiTimeout.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" + +namespace td { + +class Td; + +class PeopleNearbyManager final : public Actor { + public: + PeopleNearbyManager(Td *td, ActorShared<> parent); + + void search_dialogs_nearby(const Location &location, Promise> &&promise); + + void set_location(const Location &location, Promise &&promise); + + static void set_location_visibility(Td *td); + + void get_is_location_visible(Promise &&promise); + + int32 on_update_peer_located(vector> &&peers, bool from_update); + + bool is_user_nearby(UserId user_id) const; + + private: + struct DialogNearby { + DialogId dialog_id; + int32 distance; + + DialogNearby(DialogId dialog_id, int32 distance) : dialog_id(dialog_id), distance(distance) { + } + + bool operator<(const DialogNearby &other) const { + return distance < other.distance || (distance == other.distance && dialog_id.get() < other.dialog_id.get()); + } + + bool operator==(const DialogNearby &other) const { + return distance == other.distance && dialog_id == other.dialog_id; + } + + bool operator!=(const DialogNearby &other) const { + return !(*this == other); + } + }; + + void start_up() final; + + void tear_down() final; + + static void on_user_nearby_timeout_callback(void *people_nearby_manager_ptr, int64 user_id_long); + + void on_user_nearby_timeout(UserId user_id); + + vector> get_chats_nearby_object( + const vector &dialogs_nearby) const; + + void send_update_users_nearby() const; + + void on_get_dialogs_nearby(Result> result, + Promise> &&promise); + + void try_send_set_location_visibility_query(); + + void on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code); + + void set_location_visibility_expire_date(int32 expire_date); + + void on_get_is_location_visible(Result> &&result, + Promise &&promise); + + void update_is_location_visible(); + + Td *td_; + ActorShared<> parent_; + + vector users_nearby_; + vector channels_nearby_; + FlatHashSet all_users_nearby_; + MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"}; + + int32 location_visibility_expire_date_ = 0; + int32 pending_location_visibility_expire_date_ = -1; + bool is_set_location_visibility_request_sent_ = false; + Location last_user_location_; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.cpp b/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.cpp index f8d9ce59..f83c5bea 100644 --- a/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.cpp @@ -7,259 +7,343 @@ #include "td/telegram/PhoneNumberManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" -#include "td/telegram/net/NetQueryDispatcher.h" +#include "td/telegram/misc.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/Td.h" -#include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" +#include "td/utils/buffer.h" #include "td/utils/logging.h" namespace td { -void PhoneNumberManager::get_state(uint64 query_id) { - tl_object_ptr obj; - switch (state_) { - case State::Ok: - obj = make_tl_object(); - break; - case State::WaitCode: - obj = send_code_helper_.get_authentication_code_info_object(); - break; +class SendCodeQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit SendCodeQuery(Promise> &&promise) + : promise_(std::move(promise)) { } - CHECK(obj); - send_closure(G()->td(), &Td::send_result, query_id, std::move(obj)); -} -PhoneNumberManager::PhoneNumberManager(PhoneNumberManager::Type type, ActorShared<> parent) - : type_(type), parent_(std::move(parent)) { -} + void send(const telegram_api::Function &send_code) { + send_query(G()->net_query_creator().create(send_code)); + } -void PhoneNumberManager::send_new_send_code_query(uint64 query_id, const telegram_api::Function &send_code) { - on_new_query(query_id); - start_net_query(NetQueryType::SendCode, G()->net_query_creator().create(send_code)); -} + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } -void PhoneNumberManager::set_phone_number(uint64 query_id, string phone_number, Settings settings) { - if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); + auto ptr = result_ptr.move_as_ok(); + switch (ptr->get_id()) { + case telegram_api::auth_sentCodeSuccess::ID: + return on_error(Status::Error(500, "Receive invalid response")); + case telegram_api::auth_sentCode::ID: + return promise_.set_value(telegram_api::move_object_as(ptr)); + default: + UNREACHABLE(); + } } - switch (type_) { - case Type::ChangePhone: - send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action, - SuggestedAction{SuggestedAction::Type::CheckPhoneNumber}); - return send_new_send_code_query(query_id, send_code_helper_.send_change_phone_code(phone_number, settings)); - case Type::VerifyPhone: - return send_new_send_code_query(query_id, send_code_helper_.send_verify_phone_code(phone_number, settings)); - case Type::ConfirmPhone: - default: - UNREACHABLE(); + void on_error(Status status) final { + promise_.set_error(std::move(status)); } -} +}; -void PhoneNumberManager::set_phone_number_and_hash(uint64 query_id, string hash, string phone_number, - Settings settings) { - if (phone_number.empty()) { - return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty")); +class RequestFirebaseSmsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit RequestFirebaseSmsQuery(Promise &&promise) : promise_(std::move(promise)) { } - if (hash.empty()) { - return on_query_error(query_id, Status::Error(400, "Hash must be non-empty")); + + void send(const telegram_api::auth_requestFirebaseSms &query) { + send_query(G()->net_query_creator().create(query)); } - switch (type_) { - case Type::ConfirmPhone: - return send_new_send_code_query(query_id, - send_code_helper_.send_confirm_phone_code(hash, phone_number, settings)); - case Type::ChangePhone: - case Type::VerifyPhone: - default: - UNREACHABLE(); + 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 PhoneNumberManager::resend_authentication_code(uint64 query_id) { - if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(400, "Can't resend code")); + void on_error(Status status) final { + promise_.set_error(std::move(status)); } +}; - auto r_resend_code = send_code_helper_.resend_code(); - if (r_resend_code.is_error()) { - return on_query_error(query_id, r_resend_code.move_as_error()); +class ReportMissingCodeQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ReportMissingCodeQuery(Promise &&promise) : promise_(std::move(promise)) { } - on_new_query(query_id); + void send(const telegram_api::auth_reportMissingCode &query) { + send_query(G()->net_query_creator().create(query)); + } - start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(r_resend_code.move_as_ok())); -} + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } -void PhoneNumberManager::send_new_check_code_query(const telegram_api::Function &check_code) { - start_net_query(NetQueryType::CheckCode, G()->net_query_creator().create(check_code)); -} + promise_.set_value(Unit()); + } -void PhoneNumberManager::check_code(uint64 query_id, string code) { - if (state_ != State::WaitCode) { - return on_query_error(query_id, Status::Error(400, "Can't check code")); + void on_error(Status status) final { + promise_.set_error(std::move(status)); } +}; - on_new_query(query_id); +class ChangePhoneQuery final : public Td::ResultHandler { + Promise promise_; - switch (type_) { - case Type::ChangePhone: - return send_new_check_code_query(telegram_api::account_changePhone( - send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code)); - case Type::ConfirmPhone: - return send_new_check_code_query( - telegram_api::account_confirmPhone(send_code_helper_.phone_code_hash().str(), code)); - case Type::VerifyPhone: - return send_new_check_code_query(telegram_api::account_verifyPhone( - send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code)); - default: - UNREACHABLE(); + public: + explicit ChangePhoneQuery(Promise &&promise) : promise_(std::move(promise)) { } -} -void PhoneNumberManager::on_new_query(uint64 query_id) { - if (query_id_ != 0) { - on_current_query_error(Status::Error(400, "Another query has started")); + void send(const string &phone_number, const string &phone_code_hash, const string &code) { + send_query(G()->net_query_creator().create(telegram_api::account_changePhone(phone_number, phone_code_hash, code))); } - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - query_id_ = query_id; - // TODO: cancel older net_query -} -void PhoneNumberManager::on_current_query_error(Status status) { - if (query_id_ == 0) { - return; + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_get_user(result_ptr.move_as_ok(), "ChangePhoneQuery"); + promise_.set_value(Unit()); } - auto id = query_id_; - query_id_ = 0; - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - on_query_error(id, std::move(status)); -} -void PhoneNumberManager::on_query_error(uint64 id, Status status) { - send_closure(G()->td(), &Td::send_error, id, std::move(status)); -} + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; -void PhoneNumberManager::on_current_query_ok() { - if (query_id_ == 0) { - return; +class VerifyPhoneQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit VerifyPhoneQuery(Promise &&promise) : promise_(std::move(promise)) { } - auto id = query_id_; - net_query_id_ = 0; - net_query_type_ = NetQueryType::None; - query_id_ = 0; - get_state(id); -} -void PhoneNumberManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_query) { - // TODO: cancel old net_query? - net_query_type_ = net_query_type; - net_query_id_ = net_query->id(); - G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this)); -} + void send(const string &phone_number, const string &phone_code_hash, const string &code) { + send_query(G()->net_query_creator().create(telegram_api::account_verifyPhone(phone_number, phone_code_hash, code))); + } -void PhoneNumberManager::process_check_code_result(Result> &&result) { - if (result.is_error()) { - return on_current_query_error(result.move_as_error()); + 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()); } - send_closure(G()->contacts_manager(), &ContactsManager::on_get_user, result.move_as_ok(), - "process_check_code_result"); - state_ = State::Ok; - on_current_query_ok(); -} -void PhoneNumberManager::process_check_code_result(Result &&result) { - if (result.is_error()) { - return on_current_query_error(result.move_as_error()); + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ConfirmPhoneQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ConfirmPhoneQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &phone_code_hash, const string &code) { + send_query(G()->net_query_creator().create(telegram_api::account_confirmPhone(phone_code_hash, code))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + promise_.set_value(Unit()); } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +PhoneNumberManager::PhoneNumberManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { +} + +void PhoneNumberManager::tear_down() { + parent_.reset(); +} + +void PhoneNumberManager::inc_generation() { + generation_++; state_ = State::Ok; - on_current_query_ok(); + send_code_helper_ = {}; } -void PhoneNumberManager::on_check_code_result(NetQueryPtr &&net_query) { - switch (type_) { - case Type::ChangePhone: - return process_check_code_result(fetch_result(std::move(net_query))); - case Type::VerifyPhone: - return process_check_code_result(fetch_result(std::move(net_query))); - case Type::ConfirmPhone: - return process_check_code_result(fetch_result(std::move(net_query))); - default: - UNREACHABLE(); - } +void PhoneNumberManager::send_new_send_code_query( + const telegram_api::Function &send_code, Promise> &&promise) { + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), generation = generation_, promise = std::move(promise)]( + Result> r_sent_code) mutable { + send_closure(actor_id, &PhoneNumberManager::on_send_code_result, std::move(r_sent_code), generation, + std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(send_code); } -void PhoneNumberManager::on_send_code_result(NetQueryPtr &&net_query) { - auto r_sent_code = [&] { - switch (type_) { - case Type::ChangePhone: - return fetch_result(std::move(net_query)); - case Type::VerifyPhone: - return fetch_result(std::move(net_query)); - case Type::ConfirmPhone: - return fetch_result(std::move(net_query)); - default: - UNREACHABLE(); - return fetch_result(std::move(net_query)); - } - }(); +void PhoneNumberManager::on_send_code_result(Result> r_sent_code, + int64 generation, + Promise> &&promise) { + G()->ignore_result_if_closing(r_sent_code); if (r_sent_code.is_error()) { - return on_current_query_error(r_sent_code.move_as_error()); + return promise.set_error(r_sent_code.move_as_error()); } - auto sent_code_ptr = r_sent_code.move_as_ok(); - auto sent_code_id = sent_code_ptr->get_id(); - if (sent_code_id != telegram_api::auth_sentCode::ID) { - CHECK(sent_code_id == telegram_api::auth_sentCodeSuccess::ID); - return on_current_query_error(Status::Error(500, "Receive invalid response")); + if (generation != generation_) { + return promise.set_error(Status::Error(500, "Request was canceled")); } - auto sent_code = telegram_api::move_object_as(sent_code_ptr); + auto sent_code = r_sent_code.move_as_ok(); LOG(INFO) << "Receive " << to_string(sent_code); switch (sent_code->type_->get_id()) { case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID: case telegram_api::auth_sentCodeTypeEmailCode::ID: - return on_current_query_error(Status::Error(500, "Receive incorrect response")); + return promise.set_error(Status::Error(500, "Receive incorrect response")); default: break; } send_code_helper_.on_sent_code(std::move(sent_code)); - state_ = State::WaitCode; - on_current_query_ok(); + + promise.set_value(send_code_helper_.get_authentication_code_info_object()); } -void PhoneNumberManager::on_result(NetQueryPtr net_query) { - NetQueryType type = NetQueryType::None; - if (net_query->id() == net_query_id_) { - net_query_id_ = 0; - type = net_query_type_; - net_query_type_ = NetQueryType::None; +void PhoneNumberManager::set_phone_number(string phone_number, + td_api::object_ptr settings, + td_api::object_ptr type, + Promise> &&promise) { + inc_generation(); + if (phone_number.empty()) { + return promise.set_error(Status::Error(400, "Phone number must be non-empty")); + } + if (type == nullptr) { + return promise.set_error(Status::Error(400, "Type must be non-empty")); + } + + switch (type->get_id()) { + case td_api::phoneNumberCodeTypeChange::ID: + type_ = Type::ChangePhone; + send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action, + SuggestedAction{SuggestedAction::Type::CheckPhoneNumber}); + return send_new_send_code_query(send_code_helper_.send_change_phone_code(phone_number, settings), + std::move(promise)); + case td_api::phoneNumberCodeTypeVerify::ID: + type_ = Type::VerifyPhone; + return send_new_send_code_query(send_code_helper_.send_verify_phone_code(phone_number, settings), + std::move(promise)); + case td_api::phoneNumberCodeTypeConfirmOwnership::ID: { + auto hash = std::move(static_cast(type.get())->hash_); + if (!clean_input_string(hash)) { + return promise.set_error(Status::Error(400, "Hash must be encoded in UTF-8")); + } + if (hash.empty()) { + return promise.set_error(Status::Error(400, "Hash must be non-empty")); + } + + type_ = Type::ConfirmPhone; + return send_new_send_code_query(send_code_helper_.send_confirm_phone_code(hash, phone_number, settings), + std::move(promise)); + } + default: + UNREACHABLE(); } - switch (type) { - case NetQueryType::None: - net_query->clear(); +} + +void PhoneNumberManager::send_firebase_sms(const string &token, Promise &&promise) { + if (state_ != State::WaitCode) { + return promise.set_error(Status::Error(400, "Can't send Firebase SMS")); + } + + td_->create_handler(std::move(promise))->send(send_code_helper_.request_firebase_sms(token)); +} + +void PhoneNumberManager::report_missing_code(const string &mobile_network_code, Promise &&promise) { + if (state_ != State::WaitCode) { + return promise.set_error(Status::Error(400, "Can't report missing code")); + } + + td_->create_handler(std::move(promise)) + ->send(send_code_helper_.report_missing_code(mobile_network_code)); +} + +void PhoneNumberManager::resend_authentication_code( + td_api::object_ptr &&reason, + Promise> &&promise) { + if (state_ != State::WaitCode) { + return promise.set_error(Status::Error(400, "Can't resend code")); + } + + auto r_resend_code = send_code_helper_.resend_code(std::move(reason)); + if (r_resend_code.is_error()) { + return promise.set_error(r_resend_code.move_as_error()); + } + + send_new_send_code_query(r_resend_code.move_as_ok(), std::move(promise)); +} + +void PhoneNumberManager::check_code(string code, Promise &&promise) { + if (state_ != State::WaitCode) { + return promise.set_error(Status::Error(400, "Can't check code")); + } + + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), generation = generation_, promise = std::move(promise)](Result result) mutable { + send_closure(actor_id, &PhoneNumberManager::on_check_code_result, std::move(result), generation, + std::move(promise)); + }); + switch (type_) { + case Type::ChangePhone: + td_->create_handler(std::move(query_promise)) + ->send(send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code); break; - case NetQueryType::SendCode: - on_send_code_result(std::move(net_query)); + case Type::VerifyPhone: + td_->create_handler(std::move(query_promise)) + ->send(send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code); break; - case NetQueryType::CheckCode: - on_check_code_result(std::move(net_query)); + case Type::ConfirmPhone: + td_->create_handler(std::move(query_promise)) + ->send(send_code_helper_.phone_code_hash().str(), code); break; default: UNREACHABLE(); } } -void PhoneNumberManager::tear_down() { - parent_.reset(); +void PhoneNumberManager::on_check_code_result(Result result, int64 generation, Promise &&promise) { + G()->ignore_result_if_closing(result); + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + if (generation != generation_) { + return promise.set_error(Status::Error(500, "Request was canceled")); + } + + inc_generation(); + + promise.set_value(Unit()); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.h b/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.h index 8ea1ea5f..3c09f0ed 100644 --- a/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.h +++ b/lib/tgchat/ext/td/td/telegram/PhoneNumberManager.h @@ -6,8 +6,6 @@ // #pragma once -#include "td/telegram/net/NetActor.h" -#include "td/telegram/net/NetQuery.h" #include "td/telegram/SendCodeHelper.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" @@ -15,62 +13,52 @@ #include "td/actor/actor.h" #include "td/utils/common.h" +#include "td/utils/Promise.h" #include "td/utils/Status.h" namespace td { -class PhoneNumberManager final : public NetActor { - public: - enum class Type : int32 { ChangePhone, VerifyPhone, ConfirmPhone }; - PhoneNumberManager(Type type, ActorShared<> parent); - void get_state(uint64 query_id); - - using Settings = td_api::object_ptr; - - void set_phone_number(uint64 query_id, string phone_number, Settings settings); - void set_phone_number_and_hash(uint64 query_id, string hash, string phone_number, Settings settings); - - void resend_authentication_code(uint64 query_id); - void check_code(uint64 query_id, string code); - - private: - Type type_; - - enum class State : int32 { Ok, WaitCode } state_ = State::Ok; - enum class NetQueryType : int32 { None, SendCode, CheckCode }; - - ActorShared<> parent_; - uint64 query_id_ = 0; - uint64 net_query_id_ = 0; - NetQueryType net_query_type_ = NetQueryType::None; +class Td; - SendCodeHelper send_code_helper_; +class PhoneNumberManager final : public Actor { + public: + PhoneNumberManager(Td *td, ActorShared<> parent); - void on_new_query(uint64 query_id); + void set_phone_number(string phone_number, td_api::object_ptr settings, + td_api::object_ptr type, + Promise> &&promise); - void on_current_query_ok(); + void send_firebase_sms(const string &token, Promise &&promise); - void on_current_query_error(Status status); + void report_missing_code(const string &mobile_network_code, Promise &&promise); - static void on_query_error(uint64 id, Status status); + void resend_authentication_code(td_api::object_ptr &&reason, + Promise> &&promise); - void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query); + void check_code(string code, Promise &&promise); - void send_new_send_code_query(uint64 query_id, const telegram_api::Function &send_code); + private: + enum class Type : int32 { ChangePhone, VerifyPhone, ConfirmPhone }; + enum class State : int32 { Ok, WaitCode } state_ = State::Ok; - void send_new_check_code_query(const telegram_api::Function &check_code); + void tear_down() final; - void process_check_code_result(Result> &&result); + void inc_generation(); - void process_check_code_result(Result &&result); + void send_new_send_code_query(const telegram_api::Function &send_code, + Promise> &&promise); - void on_result(NetQueryPtr net_query) final; + void on_send_code_result(Result> r_sent_code, int64 generation, + Promise> &&promise); - void on_send_code_result(NetQueryPtr &&net_query); + void on_check_code_result(Result result, int64 generation, Promise &&promise); - void on_check_code_result(NetQueryPtr &&net_query); + Td *td_; + ActorShared<> parent_; - void tear_down() final; + Type type_; + SendCodeHelper send_code_helper_; + int64 generation_ = 0; }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/PhotoSize.cpp b/lib/tgchat/ext/td/td/telegram/PhotoSize.cpp index d66443a9..e9d5531d 100644 --- a/lib/tgchat/ext/td/td/telegram/PhotoSize.cpp +++ b/lib/tgchat/ext/td/td/telegram/PhotoSize.cpp @@ -242,6 +242,16 @@ Variant get_photo_size(FileManager *file_manager, PhotoSizeSo res.type = 0; } } + if (format == PhotoFormat::Tgs) { + if (res.type == 's') { + format = PhotoFormat::Webp; + } else if (res.type == 'v') { + format = PhotoFormat::Webm; + } else if (res.type != 'a') { + LOG(ERROR) << "Receive sticker set thumbnail of type " << res.type; + format = PhotoFormat::Webp; + } + } if (source.get_type("get_photo_size") == PhotoSizeSource::Type::Thumbnail) { source.thumbnail().thumbnail_type = res.type; } diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.cpp b/lib/tgchat/ext/td/td/telegram/PollManager.cpp index 07eb0fad..5af85b5e 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PollManager.cpp @@ -9,7 +9,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ChainId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" @@ -18,7 +18,6 @@ #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" -#include "td/telegram/misc.h" #include "td/telegram/PollId.hpp" #include "td/telegram/PollManager.hpp" #include "td/telegram/StateManager.h" @@ -26,6 +25,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/db/binlog/BinlogEvent.h" #include "td/db/binlog/BinlogHelper.h" @@ -197,14 +197,15 @@ class StopPollQuery final : public Td::ResultHandler { } int32 flags = telegram_api::messages_editMessage::MEDIA_MASK; - auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup); + auto input_reply_markup = get_input_reply_markup(td_->user_manager_.get(), reply_markup); if (input_reply_markup != nullptr) { flags |= telegram_api::messages_editMessage::REPLY_MARKUP_MASK; } auto message_id = message_full_id.get_message_id().get_server_message_id().get(); - auto poll = telegram_api::make_object(); - poll->flags_ |= telegram_api::poll::CLOSED_MASK; + auto poll = telegram_api::make_object( + poll_id.get(), telegram_api::poll::CLOSED_MASK, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, telegram_api::make_object(string(), Auto()), Auto(), 0, 0); auto input_media = telegram_api::make_object(0, std::move(poll), vector(), string(), Auto()); send_query(G()->net_query_creator().create( @@ -339,13 +340,13 @@ void PollManager::notify_on_poll_update(PollId poll_id) { if (server_poll_messages_.count(poll_id) > 0) { server_poll_messages_[poll_id].foreach([&](const MessageFullId &message_full_id) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "notify_on_poll_update 1"); }); } if (other_poll_messages_.count(poll_id) > 0) { other_poll_messages_[poll_id].foreach([&](const MessageFullId &message_full_id) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "notify_on_poll_update 2"); }); } } @@ -383,7 +384,7 @@ void PollManager::on_load_poll_from_database(PollId poll_id, string value) { } for (const auto &recent_voter_min_channel : poll->recent_voter_min_channels_) { LOG(INFO) << "Add min voted " << recent_voter_min_channel.first; - td_->contacts_manager_->add_min_channel(recent_voter_min_channel.first, recent_voter_min_channel.second); + td_->chat_manager_->add_min_channel(recent_voter_min_channel.first, recent_voter_min_channel.second); } Dependencies dependencies; for (auto dialog_id : poll->recent_voter_dialog_ids_) { @@ -428,9 +429,13 @@ PollManager::Poll *PollManager::get_poll_force(PollId poll_id) { return get_poll_editable(poll_id); } +void PollManager::remove_unallowed_entities(FormattedText &text) { + td::remove_if(text.entities, [](MessageEntity &entity) { return entity.type != MessageEntity::Type::CustomEmoji; }); +} + td_api::object_ptr PollManager::get_poll_option_object(const PollOption &poll_option) { - return td_api::make_object(poll_option.text_, poll_option.voter_count_, 0, poll_option.is_chosen_, - false); + return td_api::make_object(get_formatted_text_object(poll_option.text_, true, -1), + poll_option.voter_count_, 0, poll_option.is_chosen_, false); } vector PollManager::get_vote_percentage(const vector &voter_counts, int32 total_voter_count) { @@ -558,8 +563,8 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co voter_count_diff = -1; } poll_options.push_back(td_api::make_object( - poll_option.text_, poll_option.voter_count_ - static_cast(poll_option.is_chosen_), 0, false, - is_being_chosen)); + get_formatted_text_object(poll_option.text_, true, -1), + poll_option.voter_count_ - static_cast(poll_option.is_chosen_), 0, false, is_being_chosen)); } } @@ -632,18 +637,24 @@ td_api::object_ptr PollManager::get_poll_object(PollId poll_id, co recent_voters.push_back(std::move(recent_voter)); } } - return td_api::make_object(poll_id.get(), poll->question_, std::move(poll_options), total_voter_count, - std::move(recent_voters), poll->is_anonymous_, std::move(poll_type), - open_period, close_date, poll->is_closed_); + return td_api::make_object( + poll_id.get(), get_formatted_text_object(poll->question_, true, -1), std::move(poll_options), total_voter_count, + std::move(recent_voters), poll->is_anonymous_, std::move(poll_type), open_period, close_date, poll->is_closed_); } telegram_api::object_ptr PollManager::get_input_poll_option(const PollOption &poll_option) { - return telegram_api::make_object(poll_option.text_, BufferSlice(poll_option.data_)); + return telegram_api::make_object( + get_input_text_with_entities(nullptr, poll_option.text_, "get_input_poll_option"), + BufferSlice(poll_option.data_)); } -PollId PollManager::create_poll(string &&question, vector &&options, bool is_anonymous, +PollId PollManager::create_poll(FormattedText &&question, vector &&options, bool is_anonymous, bool allow_multiple_answers, bool is_quiz, int32 correct_option_id, FormattedText &&explanation, int32 open_period, int32 close_date, bool is_closed) { + remove_unallowed_entities(question); + for (auto &option : options) { + remove_unallowed_entities(option); + } auto poll = make_unique(); poll->question_ = std::move(question); int pos = '0'; @@ -792,10 +803,10 @@ string PollManager::get_poll_search_text(PollId poll_id) const { auto poll = get_poll(poll_id); CHECK(poll != nullptr); - string result = poll->question_; + string result = poll->question_.text; for (auto &option : poll->options_) { result += ' '; - result += option.text_; + result += option.text_.text; } return result; } @@ -903,7 +914,7 @@ void PollManager::do_set_poll_answer(PollId poll_id, MessageFullId message_full_ log_event.message_full_id_ = message_full_id; log_event.options_ = options; auto storer = get_log_event_storer(log_event); - if (pending_answer.generation_ == 0) { + if (pending_answer.generation_ == 0 || pending_answer.is_finished_) { CHECK(pending_answer.log_event_id_ == 0); log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SetPollAnswer, storer); LOG(INFO) << "Add set poll answer log event " << log_event_id; @@ -1012,7 +1023,7 @@ void PollManager::on_set_poll_answer_finished(PollId poll_id, Result &&res if (!G()->close_flag()) { auto poll = get_poll(poll_id); if (poll != nullptr && !poll->was_saved_) { - // no updates was sent during updates processing, so send them + // no updates were sent during updates processing, so send them // poll wasn't changed, so there is no reason to actually save it if (!(poll->is_closed_ && poll->is_updated_after_close_)) { LOG(INFO) << "Schedule updating of " << poll_id << " soon"; @@ -1187,8 +1198,8 @@ void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id, string off } auto vote_list = result.move_as_ok(); - td_->contacts_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters"); - td_->contacts_manager_->on_get_chats(std::move(vote_list->chats_), "on_get_poll_voters"); + td_->user_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters"); + td_->chat_manager_->on_get_chats(std::move(vote_list->chats_), "on_get_poll_voters"); voters.next_offset_ = std::move(vote_list->next_offset_); if (poll->options_[option_id].voter_count_ != vote_list->count_) { @@ -1324,7 +1335,7 @@ void PollManager::on_stop_poll_finished(PollId poll_id, MessageFullId message_fu if (td_->auth_manager_->is_bot()) { if ((server_poll_messages_.count(poll_id) > 0 && server_poll_messages_[poll_id].count(message_full_id) > 0) || (other_poll_messages_.count(poll_id) > 0 && other_poll_messages_[poll_id].count(message_full_id) > 0)) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_stop_poll_finished"); } } @@ -1491,13 +1502,18 @@ void PollManager::on_online() { }); } -PollId PollManager::dup_poll(PollId poll_id) { +PollId PollManager::dup_poll(DialogId dialog_id, PollId poll_id) { auto poll = get_poll(poll_id); CHECK(poll != nullptr); auto question = poll->question_; + ::td::remove_unallowed_entities(td_, question, dialog_id); auto options = transform(poll->options_, [](auto &option) { return option.text_; }); + for (auto &option : options) { + ::td::remove_unallowed_entities(td_, option, dialog_id); + } auto explanation = poll->explanation_; + ::td::remove_unallowed_entities(td_, explanation, dialog_id); return create_poll(std::move(question), std::move(options), poll->is_anonymous_, poll->allow_multiple_answers_, poll->is_quiz_, poll->correct_option_id_, std::move(explanation), poll->open_period_, poll->open_period_ == 0 ? 0 : G()->unix_time(), false); @@ -1548,17 +1564,19 @@ tl_object_ptr PollManager::get_input_media(PollId poll return telegram_api::make_object( flags, telegram_api::make_object( - 0, poll_flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, poll->question_, + 0, poll_flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + get_input_text_with_entities(nullptr, poll->question_, "get_input_media_poll"), transform(poll->options_, get_input_poll_option), poll->open_period_, poll->close_date_), std::move(correct_answers), poll->explanation_.text, - get_input_message_entities(td_->contacts_manager_.get(), poll->explanation_.entities, "get_input_media_poll")); + get_input_message_entities(td_->user_manager_.get(), poll->explanation_.entities, "get_input_media_poll")); } vector PollManager::get_poll_options( - vector> &&poll_options) { - return transform(std::move(poll_options), [](tl_object_ptr &&poll_option) { + vector> &&poll_options) { + return transform(std::move(poll_options), [](telegram_api::object_ptr &&poll_option) { PollOption option; - option.text_ = std::move(poll_option->text_); + option.text_ = get_formatted_text(nullptr, std::move(poll_option->text_), true, true, "get_poll_options"); + remove_unallowed_entities(option.text_); option.data_ = poll_option->option_.as_slice().str(); return option; }); @@ -1635,13 +1653,14 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptroptions_ = get_poll_options(std::move(poll_server->answers_)); are_options_changed = true; } else { - for (size_t i = 0; i < poll->options_.size(); i++) { - if (poll->options_[i].text_ != poll_server->answers_[i]->text_) { - poll->options_[i].text_ = std::move(poll_server->answers_[i]->text_); + auto options = get_poll_options(std::move(poll_server->answers_)); + for (size_t i = 0; i < options.size(); i++) { + if (poll->options_[i].text_ != options[i].text_) { + poll->options_[i].text_ = std::move(options[i].text_); is_changed = true; } - if (poll->options_[i].data_ != poll_server->answers_[i]->option_.as_slice()) { - poll->options_[i].data_ = poll_server->answers_[i]->option_.as_slice().str(); + if (poll->options_[i].data_ != options[i].data_) { + poll->options_[i].data_ = std::move(options[i].data_); poll->options_[i].voter_count_ = 0; poll->options_[i].is_chosen_ = false; are_options_changed = true; @@ -1667,8 +1686,10 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptrquestion_ != poll_server->question_) { - poll->question_ = std::move(poll_server->question_); + auto question = get_formatted_text(nullptr, std::move(poll_server->question_), true, true, "on_get_poll"); + remove_unallowed_entities(question); + if (poll->question_ != question) { + poll->question_ = std::move(question); is_changed = true; } poll_server_is_closed = (poll_server->flags_ & telegram_api::poll::CLOSED_MASK) != 0; @@ -1811,17 +1832,8 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptrcontacts_manager_.get(), std::move(poll_results->solution_entities_), source); - auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, true, false); - if (status.is_error()) { - if (!clean_input_string(poll_results->solution_)) { - poll_results->solution_.clear(); - } - entities = find_entities(poll_results->solution_, true, true); - } - FormattedText explanation{std::move(poll_results->solution_), std::move(entities)}; - + auto explanation = get_formatted_text(td_->user_manager_.get(), std::move(poll_results->solution_), + std::move(poll_results->solution_entities_), true, false, source); if (poll->is_quiz_) { if (poll->correct_option_id_ != correct_option_id) { if (correct_option_id == -1 && poll->correct_option_id_ != -1) { diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.h b/lib/tgchat/ext/td/td/telegram/PollManager.h index d690d9ed..e7a7659b 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.h +++ b/lib/tgchat/ext/td/td/telegram/PollManager.h @@ -49,9 +49,9 @@ class PollManager final : public Actor { static bool is_local_poll_id(PollId poll_id); - PollId create_poll(string &&question, vector &&options, bool is_anonymous, bool allow_multiple_answers, - bool is_quiz, int32 correct_option_id, FormattedText &&explanation, int32 open_period, - int32 close_date, bool is_closed); + PollId create_poll(FormattedText &&question, vector &&options, bool is_anonymous, + bool allow_multiple_answers, bool is_quiz, int32 correct_option_id, FormattedText &&explanation, + int32 open_period, int32 close_date, bool is_closed); void register_poll(PollId poll_id, MessageFullId message_full_id, const char *source); @@ -78,7 +78,7 @@ class PollManager final : public Actor { void stop_local_poll(PollId poll_id); - PollId dup_poll(PollId poll_id); + PollId dup_poll(DialogId dialog_id, PollId poll_id); bool has_input_media(PollId poll_id) const; @@ -103,7 +103,7 @@ class PollManager final : public Actor { private: struct PollOption { - string text_; + FormattedText text_; string data_; int32 voter_count_ = 0; bool is_chosen_ = false; @@ -115,7 +115,7 @@ class PollManager final : public Actor { }; struct Poll { - string question_; + FormattedText question_; vector options_; vector recent_voter_dialog_ids_; vector> recent_voter_min_channels_; @@ -159,6 +159,8 @@ class PollManager final : public Actor { static void on_unload_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int); + static void remove_unallowed_entities(FormattedText &text); + static td_api::object_ptr get_poll_option_object(const PollOption &poll_option); static telegram_api::object_ptr get_input_poll_option(const PollOption &poll_option); diff --git a/lib/tgchat/ext/td/td/telegram/PollManager.hpp b/lib/tgchat/ext/td/td/telegram/PollManager.hpp index 03ed5d64..88ba0354 100644 --- a/lib/tgchat/ext/td/td/telegram/PollManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/PollManager.hpp @@ -20,25 +20,35 @@ namespace td { template void PollManager::PollOption::store(StorerT &storer) const { using ::td::store; + bool has_entities = !text_.entities.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_chosen_); + STORE_FLAG(has_entities); END_STORE_FLAGS(); - store(text_, storer); + store(text_.text, storer); store(data_, storer); store(voter_count_, storer); + if (has_entities) { + store(text_.entities, storer); + } } template void PollManager::PollOption::parse(ParserT &parser) { using ::td::parse; + bool has_entities; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_chosen_); + PARSE_FLAG(has_entities); END_PARSE_FLAGS(); - parse(text_, parser); + parse(text_.text, parser); parse(data_, parser); parse(voter_count_, parser); + if (has_entities) { + parse(text_.entities, parser); + } } template @@ -50,6 +60,7 @@ void PollManager::Poll::store(StorerT &storer) const { bool has_explanation = !explanation_.text.empty(); bool has_recent_voter_dialog_ids = !recent_voter_dialog_ids_.empty(); bool has_recent_voter_min_channels = !recent_voter_min_channels_.empty(); + bool has_question_entities = !question_.entities.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(is_closed_); STORE_FLAG(is_public); @@ -62,9 +73,10 @@ void PollManager::Poll::store(StorerT &storer) const { STORE_FLAG(is_updated_after_close_); STORE_FLAG(has_recent_voter_dialog_ids); STORE_FLAG(has_recent_voter_min_channels); + STORE_FLAG(has_question_entities); END_STORE_FLAGS(); - store(question_, storer); + store(question_.text, storer); store(options_, storer); store(total_voter_count_, storer); if (is_quiz_) { @@ -85,6 +97,9 @@ void PollManager::Poll::store(StorerT &storer) const { if (has_recent_voter_min_channels) { store(recent_voter_min_channels_, storer); } + if (has_question_entities) { + store(question_.entities, storer); + } } template @@ -97,6 +112,7 @@ void PollManager::Poll::parse(ParserT &parser) { bool has_explanation; bool has_recent_voter_dialog_ids; bool has_recent_voter_min_channels; + bool has_question_entities; BEGIN_PARSE_FLAGS(); PARSE_FLAG(is_closed_); PARSE_FLAG(is_public); @@ -109,16 +125,17 @@ void PollManager::Poll::parse(ParserT &parser) { PARSE_FLAG(is_updated_after_close_); PARSE_FLAG(has_recent_voter_dialog_ids); PARSE_FLAG(has_recent_voter_min_channels); + PARSE_FLAG(has_question_entities); END_PARSE_FLAGS(); is_anonymous_ = !is_public; - parse(question_, parser); + parse(question_.text, parser); parse(options_, parser); parse(total_voter_count_, parser); if (is_quiz_) { parse(correct_option_id_, parser); if (correct_option_id_ < -1 || correct_option_id_ >= static_cast(options_.size())) { - parser.set_error("Wrong correct_option_id"); + parser.set_error("Wrong quiz correct_option_id"); } } if (has_recent_voter_user_ids) { @@ -141,6 +158,9 @@ void PollManager::Poll::parse(ParserT &parser) { if (has_recent_voter_min_channels) { parse(recent_voter_min_channels_, parser); } + if (has_question_entities) { + parse(question_.entities, parser); + } } template @@ -152,6 +172,9 @@ void PollManager::store_poll(PollId poll_id, StorerT &storer) const { bool has_open_period = poll->open_period_ != 0; bool has_close_date = poll->close_date_ != 0; bool has_explanation = !poll->explanation_.text.empty(); + bool has_question_entities = !poll->question_.entities.empty(); + bool has_option_entities = + any_of(poll->options_, [](const auto &option) { return !option.text_.entities.empty(); }); BEGIN_STORE_FLAGS(); STORE_FLAG(poll->is_closed_); STORE_FLAG(poll->is_anonymous_); @@ -160,9 +183,11 @@ void PollManager::store_poll(PollId poll_id, StorerT &storer) const { STORE_FLAG(has_open_period); STORE_FLAG(has_close_date); STORE_FLAG(has_explanation); + STORE_FLAG(has_question_entities); + STORE_FLAG(has_option_entities); END_STORE_FLAGS(); - store(poll->question_, storer); - vector options = transform(poll->options_, [](const PollOption &option) { return option.text_; }); + store(poll->question_.text, storer); + vector options = transform(poll->options_, [](const PollOption &option) { return option.text_.text; }); store(options, storer); if (poll->is_quiz_) { store(poll->correct_option_id_, storer); @@ -176,6 +201,13 @@ void PollManager::store_poll(PollId poll_id, StorerT &storer) const { if (has_explanation) { store(poll->explanation_, storer); } + if (has_question_entities) { + store(poll->question_.entities, storer); + } + if (has_option_entities) { + auto option_entities = transform(poll->options_, [](const PollOption &option) { return option.text_.entities; }); + store(option_entities, storer); + } } } @@ -185,8 +217,7 @@ PollId PollManager::parse_poll(ParserT &parser) { td::parse(poll_id_int, parser); PollId poll_id(poll_id_int); if (is_local_poll_id(poll_id)) { - string question; - vector options; + FormattedText question; FormattedText explanation; int32 open_period = 0; int32 close_date = 0; @@ -197,6 +228,8 @@ PollId PollManager::parse_poll(ParserT &parser) { bool has_open_period = false; bool has_close_date = false; bool has_explanation = false; + bool has_question_entities = false; + bool has_option_entities = false; int32 correct_option_id = -1; if (parser.version() >= static_cast(Version::SupportPolls2_0)) { @@ -208,14 +241,17 @@ PollId PollManager::parse_poll(ParserT &parser) { PARSE_FLAG(has_open_period); PARSE_FLAG(has_close_date); PARSE_FLAG(has_explanation); + PARSE_FLAG(has_question_entities); + PARSE_FLAG(has_option_entities); END_PARSE_FLAGS(); } - parse(question, parser); - parse(options, parser); + parse(question.text, parser); + vector option_texts; + parse(option_texts, parser); if (is_quiz) { parse(correct_option_id, parser); - if (correct_option_id < -1 || correct_option_id >= static_cast(options.size())) { - parser.set_error("Wrong correct_option_id"); + if (correct_option_id < -1 || correct_option_id >= static_cast(option_texts.size())) { + parser.set_error("Wrong local quiz correct_option_id"); } } if (has_open_period) { @@ -227,6 +263,21 @@ PollId PollManager::parse_poll(ParserT &parser) { if (has_explanation) { parse(explanation, parser); } + if (has_question_entities) { + parse(question.entities, parser); + } + vector> option_entities; + if (has_option_entities) { + parse(option_entities, parser); + CHECK(option_entities.size() == option_texts.size()); + } else { + option_entities.resize(option_texts.size()); + } + vector options; + for (size_t i = 0; i < option_texts.size(); i++) { + options.push_back({std::move(option_texts[i]), std::move(option_entities[i])}); + } + if (parser.get_error() != nullptr) { return PollId(); } diff --git a/lib/tgchat/ext/td/td/telegram/Premium.cpp b/lib/tgchat/ext/td/td/telegram/Premium.cpp index e9f7cdc2..d4d3ade3 100644 --- a/lib/tgchat/ext/td/td/telegram/Premium.cpp +++ b/lib/tgchat/ext/td/td/telegram/Premium.cpp @@ -10,18 +10,20 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/Application.h" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" #include "td/telegram/DocumentsManager.h" #include "td/telegram/GiveawayParameters.h" #include "td/telegram/Global.h" +#include "td/telegram/InputInvoice.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageSender.h" #include "td/telegram/MessagesManager.h" #include "td/telegram/misc.h" +#include "td/telegram/Photo.h" #include "td/telegram/PremiumGiftOption.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/SuggestedAction.h" @@ -29,6 +31,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -108,6 +111,52 @@ static td_api::object_ptr get_premium_feature_object(Sli if (premium_feature == "last_seen") { return td_api::make_object(); } + if (premium_feature == "business") { + return td_api::make_object(); + } + if (G()->is_test_dc()) { + LOG(ERROR) << "Receive unsupported premium feature " << premium_feature; + } + return nullptr; +} + +static td_api::object_ptr get_business_feature_object(Slice business_feature) { + if (business_feature == "business_location") { + return td_api::make_object(); + } + if (business_feature == "business_hours") { + return td_api::make_object(); + } + if (business_feature == "quick_replies") { + return td_api::make_object(); + } + if (business_feature == "greeting_message") { + return td_api::make_object(); + } + if (business_feature == "away_message") { + return td_api::make_object(); + } + if (business_feature == "business_links") { + return td_api::make_object(); + } + if (business_feature == "business_intro") { + return td_api::make_object(); + } + if (business_feature == "business_bots") { + return td_api::make_object(); + } + if (business_feature == "emoji_status") { + return td_api::make_object(); + } + if (business_feature == "folder_tags") { + return td_api::make_object(); + } + if (business_feature == "stories") { + return td_api::make_object(); + } + if (G()->is_test_dc()) { + LOG(ERROR) << "Receive unsupported business feature " << business_feature; + } return nullptr; } @@ -122,7 +171,7 @@ Result> get_boost_input_peer(T if (dialog_id.get_type() != DialogType::Channel) { return Status::Error(400, "Can't boost the chat"); } - if (!td->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_administrator()) { + if (!td->chat_manager_->get_channel_status(dialog_id.get_channel_id()).is_administrator()) { return Status::Error(400, "Not enough rights in the chat"); } auto boost_input_peer = td->dialog_manager_->get_input_peer(dialog_id, AccessRights::Write); @@ -131,14 +180,14 @@ Result> get_boost_input_peer(T } static Result> get_input_store_payment_purpose( - Td *td, const td_api::object_ptr &purpose) { + Td *td, td_api::object_ptr &purpose) { if (purpose == nullptr) { return Status::Error(400, "Purchase purpose must be non-empty"); } switch (purpose->get_id()) { case td_api::storePaymentPurposePremiumSubscription::ID: { - auto p = static_cast(purpose.get()); + auto p = static_cast(purpose.get()); int32 flags = 0; if (p->is_restore_) { flags |= telegram_api::inputStorePaymentPremiumSubscription::RESTORE_MASK; @@ -150,25 +199,31 @@ static Result> get_input_s false /*ignored*/); } case td_api::storePaymentPurposeGiftedPremium::ID: { - auto p = static_cast(purpose.get()); + auto p = static_cast(purpose.get()); UserId user_id(p->user_id_); - TRY_RESULT(input_user, td->contacts_manager_->get_input_user(user_id)); + TRY_RESULT(input_user, td->user_manager_->get_input_user(user_id)); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } return make_tl_object(std::move(input_user), p->currency_, p->amount_); } case td_api::storePaymentPurposePremiumGiftCodes::ID: { - auto p = static_cast(purpose.get()); + auto p = static_cast(purpose.get()); vector> input_users; for (auto user_id : p->user_ids_) { - TRY_RESULT(input_user, td->contacts_manager_->get_input_user(UserId(user_id))); + TRY_RESULT(input_user, td->user_manager_->get_input_user(UserId(user_id))); input_users.push_back(std::move(input_user)); } if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } DialogId boosted_dialog_id(p->boosted_chat_id_); TRY_RESULT(boost_input_peer, get_boost_input_peer(td, boosted_dialog_id)); int32 flags = 0; @@ -179,13 +234,27 @@ static Result> get_input_s flags, std::move(input_users), std::move(boost_input_peer), p->currency_, p->amount_); } case td_api::storePaymentPurposePremiumGiveaway::ID: { - auto p = static_cast(purpose.get()); + auto p = static_cast(purpose.get()); if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { return Status::Error(400, "Invalid amount of the currency specified"); } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } TRY_RESULT(parameters, GiveawayParameters::get_giveaway_parameters(td, p->parameters_.get())); return parameters.get_input_store_payment_premium_giveaway(td, p->currency_, p->amount_); } + case td_api::storePaymentPurposeStars::ID: { + auto p = static_cast(purpose.get()); + if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) { + return Status::Error(400, "Invalid amount of the currency specified"); + } + if (!clean_input_string(p->currency_)) { + return Status::Error(400, "Strings must be encoded in UTF-8"); + } + return telegram_api::make_object(0, p->star_count_, p->currency_, + p->amount_); + } default: UNREACHABLE(); return nullptr; @@ -213,9 +282,9 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { auto promo = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetPremiumPromoQuery: " << to_string(promo); - td_->contacts_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery"); + td_->user_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery"); - auto state = get_message_text(td_->contacts_manager_.get(), std::move(promo->status_text_), + auto state = get_message_text(td_->user_manager_.get(), std::move(promo->status_text_), std::move(promo->status_entities_), true, true, 0, false, "GetPremiumPromoQuery"); if (promo->video_sections_.size() != promo->videos_.size()) { @@ -223,14 +292,10 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { } vector> animations; + vector> business_animations; FlatHashSet video_sections; for (size_t i = 0; i < promo->video_sections_.size(); i++) { - auto feature = get_premium_feature_object(promo->video_sections_[i]); - if (feature == nullptr) { - LOG(INFO) << "Receive unknown Premium feature animation " << promo->video_sections_[i]; - continue; - } - if (!video_sections.insert(promo->video_sections_[i]).second) { + if (promo->video_sections_[i].empty() || !video_sections.insert(promo->video_sections_[i]).second) { LOG(ERROR) << "Receive duplicate Premium feature animation " << promo->video_sections_[i]; continue; } @@ -249,15 +314,27 @@ class GetPremiumPromoQuery final : public Td::ResultHandler { continue; } - auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); - animations.push_back(td_api::make_object(std::move(feature), - std::move(animation_object))); + auto feature = get_premium_feature_object(promo->video_sections_[i]); + if (feature != nullptr) { + auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); + animations.push_back(td_api::make_object( + std::move(feature), std::move(animation_object))); + } else { + auto business_feature = get_business_feature_object(promo->video_sections_[i]); + if (business_feature != nullptr) { + auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id); + business_animations.push_back(td_api::make_object( + std::move(business_feature), std::move(animation_object))); + } else if (G()->is_test_dc()) { + LOG(ERROR) << "Receive unsupported feature " << promo->video_sections_[i]; + } + } } auto period_options = get_premium_gift_options(std::move(promo->period_options_)); promise_.set_value(td_api::make_object( get_formatted_text_object(state, true, 0), get_premium_state_payment_options_object(period_options), - std::move(animations))); + std::move(animations), std::move(business_animations))); } void on_error(Status status) final { @@ -337,8 +414,8 @@ class CheckGiftCodeQuery final : public Td::ResultHandler { auto result = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for CheckGiftCodeQuery: " << to_string(result); - td_->contacts_manager_->on_get_users(std::move(result->users_), "CheckGiftCodeQuery"); - td_->contacts_manager_->on_get_chats(std::move(result->chats_), "CheckGiftCodeQuery"); + td_->user_manager_->on_get_users(std::move(result->users_), "CheckGiftCodeQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "CheckGiftCodeQuery"); if (result->date_ <= 0 || result->months_ <= 0 || result->used_date_ < 0) { LOG(ERROR) << "Receive " << to_string(result); @@ -375,7 +452,7 @@ class CheckGiftCodeQuery final : public Td::ResultHandler { creator_dialog_id == DialogId() ? nullptr : get_message_sender_object(td_, creator_dialog_id, "premiumGiftCodeInfo"), result->date_, result->via_giveaway_, message_id.get(), result->months_, - td_->contacts_manager_->get_user_id_object(user_id, "premiumGiftCodeInfo"), result->used_date_)); + td_->user_manager_->get_user_id_object(user_id, "premiumGiftCodeInfo"), result->used_date_)); } void on_error(Status status) final { @@ -478,8 +555,7 @@ class GetGiveawayInfoQuery final : public Td::ResultHandler { } if (info->admin_disallowed_chat_id_ > 0) { ChannelId channel_id(info->admin_disallowed_chat_id_); - if (!channel_id.is_valid() || - !td_->contacts_manager_->have_channel_force(channel_id, "GetGiveawayInfoQuery")) { + if (!channel_id.is_valid() || !td_->chat_manager_->have_channel_force(channel_id, "GetGiveawayInfoQuery")) { LOG(ERROR) << "Receive " << to_string(info); } else { DialogId dialog_id(channel_id); @@ -533,6 +609,122 @@ class GetGiveawayInfoQuery final : public Td::ResultHandler { } }; +class GetStarsTopupOptionsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStarsTopupOptionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::payments_getStarsTopupOptions())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto results = result_ptr.move_as_ok(); + vector> options; + for (auto &result : results) { + options.push_back(td_api::make_object( + result->currency_, result->amount_, result->stars_, result->store_product_, result->extended_)); + } + + promise_.set_value(td_api::make_object(std::move(options))); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetStarsTransactionsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetStarsTransactionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(const string &offset, td_api::object_ptr &&direction) { + int32 flags = 0; + if (direction != nullptr) { + switch (direction->get_id()) { + case td_api::starTransactionDirectionIncoming::ID: + flags |= telegram_api::payments_getStarsTransactions::INBOUND_MASK; + break; + case td_api::starTransactionDirectionOutgoing::ID: + flags |= telegram_api::payments_getStarsTransactions::OUTBOUND_MASK; + break; + default: + UNREACHABLE(); + } + } + send_query(G()->net_query_creator().create( + telegram_api::payments_getStarsTransactions(flags, false /*ignored*/, 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(); + td_->user_manager_->on_get_users(std::move(result->users_), "GetStarsTransactionsQuery"); + td_->chat_manager_->on_get_chats(std::move(result->chats_), "GetStarsTransactionsQuery"); + + vector> transactions; + for (auto &transaction : result->history_) { + td_api::object_ptr product_info; + if (!transaction->title_.empty() || !transaction->description_.empty() || transaction->photo_ != nullptr) { + auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(transaction->photo_), DialogId()); + product_info = get_product_info_object(td_, transaction->title_, transaction->description_, photo); + } + auto source = [&]() -> td_api::object_ptr { + switch (transaction->peer_->get_id()) { + case telegram_api::starsTransactionPeerUnsupported::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerPremiumBot::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerAppStore::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerPlayMarket::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeerFragment::ID: + return td_api::make_object(); + case telegram_api::starsTransactionPeer::ID: { + DialogId dialog_id( + static_cast(transaction->peer_.get())->peer_); + if (dialog_id.get_type() == DialogType::User) { + return td_api::make_object( + td_->user_manager_->get_user_id_object(dialog_id.get_user_id(), "starTransactionSourceUser"), + std::move(product_info)); + } + return td_api::make_object(); + } + default: + UNREACHABLE(); + } + }(); + transactions.push_back(td_api::make_object( + transaction->id_, transaction->stars_, transaction->refund_, transaction->date_, std::move(source))); + } + + promise_.set_value( + td_api::make_object(result->balance_, std::move(transactions), result->next_offset_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class CanPurchasePremiumQuery final : public Td::ResultHandler { Promise promise_; @@ -768,6 +960,42 @@ static string get_premium_source(const td_api::PremiumFeature *feature) { return "message_privacy"; case td_api::premiumFeatureLastSeenTimes::ID: return "last_seen"; + case td_api::premiumFeatureBusiness::ID: + return "business"; + default: + UNREACHABLE(); + } + return string(); +} + +static string get_premium_source(const td_api::BusinessFeature *feature) { + if (feature == nullptr) { + return "business"; + } + + switch (feature->get_id()) { + case td_api::businessFeatureLocation::ID: + return "business_location"; + case td_api::businessFeatureOpeningHours::ID: + return "business_hours"; + case td_api::businessFeatureQuickReplies::ID: + return "quick_replies"; + case td_api::businessFeatureGreetingMessage::ID: + return "greeting_message"; + case td_api::businessFeatureAwayMessage::ID: + return "away_message"; + case td_api::businessFeatureAccountLinks::ID: + return "business_links"; + case td_api::businessFeatureStartPage::ID: + return "business_intro"; + case td_api::businessFeatureBots::ID: + return "business_bots"; + case td_api::businessFeatureEmojiStatus::ID: + return "emoji_status"; + case td_api::businessFeatureChatFolderTags::ID: + return "folder_tags"; + case td_api::businessFeatureUpgradedStories::ID: + return "stories"; default: UNREACHABLE(); } @@ -813,6 +1041,10 @@ static string get_premium_source(const td_api::object_ptr auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); } + case td_api::premiumSourceBusinessFeature::ID: { + auto *feature = static_cast(source.get())->feature_.get(); + return get_premium_source(feature); + } case td_api::premiumSourceStoryFeature::ID: { auto *feature = static_cast(source.get())->feature_.get(); return get_premium_source(feature); @@ -961,6 +1193,39 @@ void get_premium_features(Td *td, const td_api::object_ptr(std::move(features), std::move(limits), std::move(payment_link))); } +void get_business_features(Td *td, const td_api::object_ptr &source, + Promise> &&promise) { + auto business_features = + full_split(G()->get_option_string("business_features", + "business_location,business_hours,quick_replies,greeting_message,away_message," + "business_links,business_intro,business_bots,emoji_status,folder_tags,stories"), + ','); + vector> features; + for (const auto &business_feature : business_features) { + auto feature = get_business_feature_object(business_feature); + if (feature != nullptr) { + features.push_back(std::move(feature)); + } + } + + auto source_str = get_premium_source(source.get()); + if (!source_str.empty()) { + vector> data; + vector> promo_order; + for (const auto &business_feature : business_features) { + promo_order.push_back(telegram_api::make_object(business_feature)); + } + data.push_back(telegram_api::make_object( + "business_promo_order", telegram_api::make_object(std::move(promo_order)))); + data.push_back(telegram_api::make_object( + "source", telegram_api::make_object(source_str))); + save_app_log(td, "business.promo_screen_show", DialogId(), + telegram_api::make_object(std::move(data)), Promise()); + } + + promise.set_value(td_api::make_object(std::move(features))); +} + void view_premium_feature(Td *td, const td_api::object_ptr &feature, Promise &&promise) { auto source = get_premium_source(feature.get()); if (source.empty()) { @@ -1012,6 +1277,16 @@ void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, ->send(message_full_id.get_dialog_id(), server_message_id); } +void get_star_payment_options(Td *td, Promise> &&promise) { + td->create_handler(std::move(promise))->send(); +} + +void get_star_transactions(Td *td, const string &offset, + td_api::object_ptr &&direction, + Promise> &&promise) { + td->create_handler(std::move(promise))->send(offset, std::move(direction)); +} + void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise) { td->create_handler(std::move(promise))->send(std::move(purpose)); } diff --git a/lib/tgchat/ext/td/td/telegram/Premium.h b/lib/tgchat/ext/td/td/telegram/Premium.h index 1f5ba36a..a4bb53d8 100644 --- a/lib/tgchat/ext/td/td/telegram/Premium.h +++ b/lib/tgchat/ext/td/td/telegram/Premium.h @@ -30,6 +30,9 @@ void get_premium_limit(const td_api::object_ptr &limit void get_premium_features(Td *td, const td_api::object_ptr &source, Promise> &&promise); +void get_business_features(Td *td, const td_api::object_ptr &source, + Promise> &&promise); + void view_premium_feature(Td *td, const td_api::object_ptr &feature, Promise &&promise); void click_premium_subscription_button(Td *td, Promise &&promise); @@ -51,6 +54,12 @@ void launch_prepaid_premium_giveaway(Td *td, int64 giveaway_id, void get_premium_giveaway_info(Td *td, MessageFullId message_full_id, Promise> &&promise); +void get_star_payment_options(Td *td, Promise> &&promise); + +void get_star_transactions(Td *td, const string &offset, + td_api::object_ptr &&direction, + Promise> &&promise); + void can_purchase_premium(Td *td, td_api::object_ptr &&purpose, Promise &&promise); void assign_app_store_transaction(Td *td, const string &receipt, diff --git a/lib/tgchat/ext/td/td/telegram/PrivacyManager.cpp b/lib/tgchat/ext/td/td/telegram/PrivacyManager.cpp index 0da2084e..2466f7a4 100644 --- a/lib/tgchat/ext/td/td/telegram/PrivacyManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/PrivacyManager.cpp @@ -6,13 +6,13 @@ // #include "td/telegram/PrivacyManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Global.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -211,7 +211,7 @@ void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting, if (!G()->close_flag() && (from_update || was_synchronized)) { switch (user_privacy_setting.type()) { case UserPrivacySetting::Type::UserStatus: { - send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_online_status_privacy); + send_closure_later(G()->user_manager(), &UserManager::on_update_online_status_privacy); auto old_restricted = info.rules_.get_restricted_user_ids(); auto new_restricted = privacy_rules.get_restricted_user_ids(); @@ -223,14 +223,14 @@ void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting, new_restricted.end(), std::back_inserter(unrestricted), [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); }); for (auto &user_id : unrestricted) { - send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user, user_id, Promise(), + send_closure_later(G()->user_manager(), &UserManager::reload_user, user_id, Promise(), "do_update_privacy"); } } break; } case UserPrivacySetting::Type::UserPhoneNumber: - send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_phone_number_privacy); + send_closure_later(G()->user_manager(), &UserManager::on_update_phone_number_privacy); break; default: break; diff --git a/lib/tgchat/ext/td/td/telegram/PublicDialogType.h b/lib/tgchat/ext/td/td/telegram/PublicDialogType.h index ebc9ff37..a6bbd884 100644 --- a/lib/tgchat/ext/td/td/telegram/PublicDialogType.h +++ b/lib/tgchat/ext/td/td/telegram/PublicDialogType.h @@ -10,7 +10,7 @@ namespace td { -enum class PublicDialogType : int32 { HasUsername, IsLocationBased }; +enum class PublicDialogType : int32 { HasUsername, IsLocationBased, ForPersonalDialog }; inline PublicDialogType get_public_dialog_type(const td_api::object_ptr &type) { if (type == nullptr || type->get_id() == td_api::publicChatTypeHasUsername::ID) { diff --git a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp index 4d16e4b5..b139bfc1 100644 --- a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.cpp @@ -8,20 +8,25 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FileReferenceManager.h" #include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" #include "td/telegram/Global.h" +#include "td/telegram/InlineQueriesManager.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/logevent/LogEventHelper.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageContentType.h" #include "td/telegram/MessageCopyOptions.h" +#include "td/telegram/MessageInputReplyTo.h" +#include "td/telegram/MessageQuote.h" #include "td/telegram/MessageReplyHeader.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/misc.h" +#include "td/telegram/OptionManager.h" #include "td/telegram/ReplyMarkup.h" #include "td/telegram/ReplyMarkup.hpp" #include "td/telegram/ServerMessageId.h" @@ -29,19 +34,24 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/format.h" +#include "td/utils/HashTableUtils.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/Random.h" #include "td/utils/Time.h" #include "td/utils/tl_helpers.h" #include "td/utils/unicode.h" #include "td/utils/utf8.h" #include +#include +#include namespace td { @@ -168,6 +178,7 @@ class GetQuickReplyMessagesQuery final : public Td::ResultHandler { if (!message_ids.empty()) { flags |= telegram_api::messages_getQuickReplyMessages::ID_MASK; } + CHECK(shortcut_id.is_server()); send_query(G()->net_query_creator().create( telegram_api::messages_getQuickReplyMessages(flags, shortcut_id.get(), MessageId::get_server_message_ids(message_ids), hash), @@ -200,6 +211,7 @@ class DeleteQuickReplyMessagesQuery final : public Td::ResultHandler { void send(QuickReplyShortcutId shortcut_id, const vector &message_ids) { shortcut_id_ = shortcut_id; + CHECK(shortcut_id.is_server()); send_query(G()->net_query_creator().create(telegram_api::messages_deleteQuickReplyMessages( shortcut_id.get(), MessageId::get_server_message_ids(message_ids)), {{"quick_reply"}})); @@ -220,6 +232,492 @@ class DeleteQuickReplyMessagesQuery final : public Td::ResultHandler { } }; +class QuickReplyManager::SendQuickReplyMessageQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + + public: + void send(const QuickReplyMessage *m) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + + int32 flags = telegram_api::messages_sendMessage::QUICK_REPLY_SHORTCUT_MASK; + if (m->disable_web_page_preview) { + flags |= telegram_api::messages_sendMessage::NO_WEBPAGE_MASK; + } + if (m->invert_media) { + flags |= telegram_api::messages_sendMessage::INVERT_MEDIA_MASK; + } + auto reply_to = + MessageInputReplyTo(m->reply_to_message_id, DialogId(), MessageQuote()).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMessage::REPLY_TO_MASK; + } + CHECK(m->edited_content == nullptr); + const FormattedText *message_text = get_message_content_text(m->content.get()); + CHECK(message_text != nullptr); + auto entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendQuickReplyMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMessage::ENTITIES_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendMessage( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, telegram_api::make_object(), + std::move(reply_to), message_text->text, m->random_id, nullptr, std::move(entities), 0, nullptr, + td_->quick_reply_manager_->get_input_quick_reply_shortcut(m->shortcut_id), 0), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendQuickReplyMessageQuery for " << random_id_ << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), {random_id_}); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + LOG(INFO) << "Receive error for SendQuickReplyMessageQuery: " << status; + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, {random_id_}, std::move(status)); + } +}; + +class QuickReplyManager::SendQuickReplyInlineMessageQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + + public: + void send(const QuickReplyMessage *m) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + + int32 flags = telegram_api::messages_sendInlineBotResult::QUICK_REPLY_SHORTCUT_MASK; + if (m->hide_via_bot) { + flags |= telegram_api::messages_sendInlineBotResult::HIDE_VIA_MASK; + } + auto reply_to = + MessageInputReplyTo(m->reply_to_message_id, DialogId(), MessageQuote()).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendInlineBotResult::REPLY_TO_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendInlineBotResult( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + telegram_api::make_object(), std::move(reply_to), m->random_id, + m->inline_query_id, m->inline_result_id, 0, nullptr, + td_->quick_reply_manager_->get_input_quick_reply_shortcut(m->shortcut_id)), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendQuickReplyInlineMessageQuery for " << random_id_ << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), {random_id_}); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + LOG(INFO) << "Receive error for SendQuickReplyInlineMessageQuery: " << status; + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, {random_id_}, std::move(status)); + } +}; + +class QuickReplyManager::SendQuickReplyMediaQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + FileId file_id_; + FileId thumbnail_file_id_; + string file_reference_; + bool was_uploaded_ = false; + bool was_thumbnail_uploaded_ = false; + + public: + void send(FileId file_id, FileId thumbnail_file_id, const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + file_id_ = file_id; + thumbnail_file_id_ = thumbnail_file_id; + file_reference_ = FileManager::extract_file_reference(input_media); + was_uploaded_ = FileManager::extract_was_uploaded(input_media); + was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); + + int32 flags = telegram_api::messages_sendMedia::QUICK_REPLY_SHORTCUT_MASK; + if (m->invert_media) { + flags |= telegram_api::messages_sendMedia::INVERT_MEDIA_MASK; + } + auto reply_to = + MessageInputReplyTo(m->reply_to_message_id, DialogId(), MessageQuote()).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMedia::REPLY_TO_MASK; + } + CHECK(m->edited_content == nullptr); + vector> entities; + const FormattedText *message_text = get_message_content_text(m->content.get()); + if (message_text != nullptr) { + entities = get_input_message_entities(td_->user_manager_.get(), message_text, "SendQuickReplyMediaQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_sendMedia::ENTITIES_MASK; + } + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendMedia( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, telegram_api::make_object(), std::move(reply_to), + std::move(input_media), message_text == nullptr ? string() : message_text->text, m->random_id, nullptr, + std::move(entities), 0, nullptr, td_->quick_reply_manager_->get_input_quick_reply_shortcut(m->shortcut_id), + 0), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendQuickReplyMediaQuery for " << random_id_ << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), {random_id_}); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + LOG(INFO) << "Receive error for SendQuickReplyMediaQuery: " << status; + if (was_uploaded_) { + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + CHECK(file_id_.is_valid()); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->quick_reply_manager_->on_send_message_file_parts_missing(shortcut_id_, random_id_, std::move(bad_parts)); + return; + } else { + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); + } + } else if (FileReferenceManager::is_file_reference_error(status)) { + if (file_id_.is_valid() && !was_uploaded_) { + VLOG(file_references) << "Receive " << status << " for " << file_id_; + td_->file_manager_->delete_file_reference(file_id_, file_reference_); + td_->quick_reply_manager_->on_send_message_file_reference_error(shortcut_id_, random_id_); + return; + } else { + LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_ + << ", was_uploaded = " << was_uploaded_; + } + } + + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, {random_id_}, std::move(status)); + } +}; + +class QuickReplyManager::UploadQuickReplyMediaQuery final : public Td::ResultHandler { + int64 random_id_; + QuickReplyShortcutId shortcut_id_; + MessageId message_id_; + FileId file_id_; + FileId thumbnail_file_id_; + string file_reference_; + bool was_uploaded_ = false; + bool was_thumbnail_uploaded_ = false; + + public: + void send(FileId file_id, FileId thumbnail_file_id, const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media) { + random_id_ = m->random_id; + shortcut_id_ = m->shortcut_id; + message_id_ = m->message_id; + file_id_ = file_id; + thumbnail_file_id_ = thumbnail_file_id; + file_reference_ = FileManager::extract_file_reference(input_media); + was_uploaded_ = FileManager::extract_was_uploaded(input_media); + was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); + + int32 flags = 0; + send_query(G()->net_query_creator().create(telegram_api::messages_uploadMedia( + flags, string(), telegram_api::make_object(), std::move(input_media)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for UploadQuickReplyMediaQuery: " << to_string(ptr); + td_->quick_reply_manager_->on_upload_message_media_success(shortcut_id_, message_id_, file_id_, std::move(ptr)); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + LOG(INFO) << "Receive error for UploadQuickReplyMediaQuery: " << status; + if (was_uploaded_) { + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + CHECK(file_id_.is_valid()); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + td_->quick_reply_manager_->on_send_message_file_parts_missing(shortcut_id_, random_id_, std::move(bad_parts)); + return; + } else { + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); + } + } else if (FileReferenceManager::is_file_reference_error(status)) { + LOG(ERROR) << "Receive file reference error for UploadMediaQuery"; + } + + td_->quick_reply_manager_->on_upload_message_media_fail(shortcut_id_, message_id_, std::move(status)); + } +}; + +class QuickReplyManager::SendQuickReplyMultiMediaQuery final : public Td::ResultHandler { + vector file_ids_; + vector file_references_; + vector random_ids_; + QuickReplyShortcutId shortcut_id_; + + public: + void send(QuickReplyShortcutId shortcut_id, MessageId reply_to_message_id, bool invert_media, + vector &&random_ids, vector &&file_ids, + vector> &&input_single_media) { + for (auto &single_media : input_single_media) { + CHECK(FileManager::extract_was_uploaded(single_media->media_) == false); + file_references_.push_back(FileManager::extract_file_reference(single_media->media_)); + } + shortcut_id_ = shortcut_id; + file_ids_ = std::move(file_ids); + random_ids_ = std::move(random_ids); + CHECK(file_ids_.size() == random_ids_.size()); + + int32 flags = telegram_api::messages_sendMultiMedia::QUICK_REPLY_SHORTCUT_MASK; + auto reply_to = + MessageInputReplyTo(reply_to_message_id, DialogId(), MessageQuote()).get_input_reply_to(td_, MessageId()); + if (reply_to != nullptr) { + flags |= telegram_api::messages_sendMultiMedia::REPLY_TO_MASK; + } + if (invert_media) { + flags |= telegram_api::messages_sendMultiMedia::INVERT_MEDIA_MASK; + } + + send_query(G()->net_query_creator().create( + telegram_api::messages_sendMultiMedia( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, telegram_api::make_object(), std::move(reply_to), + std::move(input_single_media), 0, nullptr, + td_->quick_reply_manager_->get_input_quick_reply_shortcut(shortcut_id_), 0), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for SendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr); + td_->quick_reply_manager_->process_send_quick_reply_updates(shortcut_id_, std::move(ptr), std::move(random_ids_)); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-sent after restart + return; + } + LOG(INFO) << "Receive error for SendQuickReplyMultiMediaQuery: " << status; + if (FileReferenceManager::is_file_reference_error(status)) { + auto pos = FileReferenceManager::get_file_reference_error_pos(status); + if (1 <= pos && pos <= file_ids_.size() && file_ids_[pos - 1].is_valid()) { + VLOG(file_references) << "Receive " << status << " for " << file_ids_[pos - 1]; + td_->file_manager_->delete_file_reference(file_ids_[pos - 1], file_references_[pos - 1]); + td_->quick_reply_manager_->on_send_media_group_file_reference_error(shortcut_id_, std::move(random_ids_)); + return; + } else { + LOG(ERROR) << "Receive file reference error " << status << ", but file_ids = " << file_ids_ + << ", message_count = " << file_ids_.size(); + } + } + td_->quick_reply_manager_->on_failed_send_quick_reply_messages(shortcut_id_, std::move(random_ids_), + std::move(status)); + } +}; + +class QuickReplyManager::EditQuickReplyMessageQuery final : public Td::ResultHandler { + QuickReplyShortcutId shortcut_id_; + MessageId message_id_; + int64 edit_generation_ = 0; + FileId file_id_; + FileId thumbnail_file_id_; + string file_reference_; + bool was_uploaded_ = false; + bool was_thumbnail_uploaded_ = false; + + public: + void send(FileId file_id, FileId thumbnail_file_id, const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media) { + CHECK(m != nullptr); + CHECK(m->edited_content != nullptr); + CHECK(m->edit_generation > 0); + shortcut_id_ = m->shortcut_id; + message_id_ = m->message_id; + edit_generation_ = m->edit_generation; + file_id_ = file_id; + thumbnail_file_id_ = thumbnail_file_id; + file_reference_ = FileManager::extract_file_reference(input_media); + was_uploaded_ = FileManager::extract_was_uploaded(input_media); + was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media); + + auto *content = m->edited_content.get(); + const auto *text = get_message_content_text(content); + vector> entities; + int32 flags = telegram_api::messages_editMessage::QUICK_REPLY_SHORTCUT_ID_MASK; + if (text != nullptr) { + entities = get_input_message_entities(td_->user_manager_.get(), text, "EditQuickReplyMessageQuery"); + if (!entities.empty()) { + flags |= telegram_api::messages_editMessage::ENTITIES_MASK; + } + flags |= telegram_api::messages_editMessage::MESSAGE_MASK; + } + if (m->edited_invert_media) { + flags |= telegram_api::messages_editMessage::INVERT_MEDIA_MASK; + } + if (m->edited_disable_web_page_preview) { + flags |= telegram_api::messages_editMessage::NO_WEBPAGE_MASK; + } + if (input_media != nullptr) { + flags |= telegram_api::messages_editMessage::MEDIA_MASK; + } + + CHECK(m->shortcut_id.is_server()); + send_query(G()->net_query_creator().create( + telegram_api::messages_editMessage( + flags, false /*ignored*/, false /*ignored*/, telegram_api::make_object(), + m->message_id.get_server_message_id().get(), text != nullptr ? text->text : string(), + std::move(input_media), nullptr, std::move(entities), 0, m->shortcut_id.get()), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for EditQuickReplyMessageQuery: " << to_string(ptr); + td_->quick_reply_manager_->on_edit_quick_reply_message(shortcut_id_, message_id_, edit_generation_, file_id_, + was_uploaded_, std::move(ptr)); + } + + void on_error(Status status) final { + if (G()->close_flag()) { + // do not send error, message will be re-edited after restart + return; + } + if (status.message() == "MESSAGE_NOT_MODIFIED") { + if (was_thumbnail_uploaded_) { + CHECK(thumbnail_file_id_.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_); + } + + return td_->quick_reply_manager_->on_edit_quick_reply_message(shortcut_id_, message_id_, edit_generation_, + file_id_, was_uploaded_, nullptr); + } + td_->quick_reply_manager_->fail_edit_quick_reply_message(shortcut_id_, message_id_, edit_generation_, file_id_, + thumbnail_file_id_, file_reference_, was_uploaded_, + was_thumbnail_uploaded_, std::move(status)); + } +}; + +class QuickReplyManager::UploadMediaCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::on_upload_media, file_id, std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::on_upload_media_error, file_id, + std::move(error)); + } +}; + +class QuickReplyManager::UploadThumbnailCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::on_upload_thumbnail, file_id, + std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->quick_reply_manager(), &QuickReplyManager::on_upload_thumbnail, file_id, nullptr); + } +}; + QuickReplyManager::QuickReplyMessage::~QuickReplyMessage() = default; template @@ -236,6 +734,9 @@ void QuickReplyManager::QuickReplyMessage::store(StorerT &storer) const { bool has_try_resend_at = !is_server && try_resend_at != 0; bool has_media_album_id = media_album_id != 0; bool has_reply_markup = reply_markup != nullptr; + bool has_inline_query_id = inline_query_id != 0; + bool has_inline_result_id = !inline_result_id.empty(); + bool has_edited_content = edited_content != nullptr; BEGIN_STORE_FLAGS(); STORE_FLAG(has_edit_date); STORE_FLAG(has_random_id); @@ -254,6 +755,11 @@ void QuickReplyManager::QuickReplyMessage::store(StorerT &storer) const { STORE_FLAG(has_try_resend_at); STORE_FLAG(has_media_album_id); STORE_FLAG(has_reply_markup); + STORE_FLAG(has_inline_query_id); + STORE_FLAG(has_inline_result_id); + STORE_FLAG(has_edited_content); + STORE_FLAG(edited_invert_media); + STORE_FLAG(edited_disable_web_page_preview); END_STORE_FLAGS(); td::store(message_id, storer); td::store(shortcut_id, storer); @@ -291,6 +797,15 @@ void QuickReplyManager::QuickReplyMessage::store(StorerT &storer) const { if (has_reply_markup) { td::store(reply_markup, storer); } + if (has_inline_query_id) { + td::store(inline_query_id, storer); + } + if (has_inline_result_id) { + td::store(inline_result_id, storer); + } + if (has_edited_content) { + store_message_content(edited_content.get(), storer); + } } template @@ -306,6 +821,9 @@ void QuickReplyManager::QuickReplyMessage::parse(ParserT &parser) { bool has_try_resend_at; bool has_media_album_id; bool has_reply_markup; + bool has_inline_query_id; + bool has_inline_result_id; + bool has_edited_content; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_edit_date); PARSE_FLAG(has_random_id); @@ -324,6 +842,11 @@ void QuickReplyManager::QuickReplyMessage::parse(ParserT &parser) { PARSE_FLAG(has_try_resend_at); PARSE_FLAG(has_media_album_id); PARSE_FLAG(has_reply_markup); + PARSE_FLAG(has_inline_query_id); + PARSE_FLAG(has_inline_result_id); + PARSE_FLAG(has_edited_content); + PARSE_FLAG(edited_invert_media); + PARSE_FLAG(edited_disable_web_page_preview); END_PARSE_FLAGS(); td::parse(message_id, parser); td::parse(shortcut_id, parser); @@ -361,6 +884,15 @@ void QuickReplyManager::QuickReplyMessage::parse(ParserT &parser) { if (has_reply_markup) { td::parse(reply_markup, parser); } + if (has_inline_query_id) { + td::parse(inline_query_id, parser); + } + if (has_inline_result_id) { + td::parse(inline_result_id, parser); + } + if (has_edited_content) { + parse_message_content(edited_content, parser); + } } QuickReplyManager::Shortcut::~Shortcut() = default; @@ -372,7 +904,7 @@ void QuickReplyManager::Shortcut::store(StorerT &storer) const { for (const auto &message : messages_) { if (message->message_id.is_server()) { server_total_count++; - } else if (message->message_id.is_local()) { + } else { local_total_count++; } } @@ -394,9 +926,7 @@ void QuickReplyManager::Shortcut::store(StorerT &storer) const { td::store(local_total_count, storer); } for (const auto &message : messages_) { - if (message->message_id.is_server() || message->message_id.is_local()) { - td::store(message, storer); - } + td::store(message, storer); } } @@ -444,6 +974,8 @@ void QuickReplyManager::Shortcuts::parse(ParserT &parser) { } QuickReplyManager::QuickReplyManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + upload_media_callback_ = std::make_shared(); + upload_thumbnail_callback_ = std::make_shared(); } void QuickReplyManager::tear_down() { @@ -513,8 +1045,8 @@ unique_ptr QuickReplyManager::create_messa message->fwd_from_ != nullptr || message->views_ != 0 || message->forwards_ != 0 || message->replies_ != nullptr || message->reactions_ != nullptr || message->ttl_period_ != 0 || !message->out_ || message->post_ || message->from_scheduled_ || message->pinned_ || message->noforwards_ || - message->mentioned_ || message->media_unread_ || !message->restriction_reason_.empty() || - !message->post_author_.empty() || message->from_boosts_applied_ != 0) { + message->mentioned_ || !message->restriction_reason_.empty() || !message->post_author_.empty() || + message->from_boosts_applied_ != 0 || message->effect_ != 0) { LOG(ERROR) << "Receive an invalid quick reply from " << source << ": " << to_string(message); } if (message->saved_peer_id_ != nullptr) { @@ -535,8 +1067,8 @@ unique_ptr QuickReplyManager::create_messa bool disable_web_page_preview = false; auto content = get_message_content( td_, - get_message_text(td_->contacts_manager_.get(), std::move(message->message_), std::move(message->entities_), - true, td_->auth_manager_->is_bot(), 0, media_album_id != 0, source), + get_message_text(td_->user_manager_.get(), std::move(message->message_), std::move(message->entities_), true, + td_->auth_manager_->is_bot(), 0, media_album_id != 0, source), std::move(message->media_), my_dialog_id, message->date_, true, via_bot_user_id, &ttl, &disable_web_page_preview, source); @@ -560,7 +1092,7 @@ unique_ptr QuickReplyManager::create_messa auto content_type = content->get_type(); if (is_service_message_content(content_type) || content_type == MessageContentType::LiveLocation || - is_expired_message_content(content_type)) { + is_expired_message_content(content_type) || content_type == MessageContentType::Poll) { LOG(ERROR) << "Receive " << content_type << " from " << source; break; } @@ -614,21 +1146,21 @@ void QuickReplyManager::add_quick_reply_message_dependencies(Dependencies &depen auto is_bot = td_->auth_manager_->is_bot(); dependencies.add(m->via_bot_user_id); add_message_content_dependencies(dependencies, m->content.get(), is_bot); + if (m->edited_content != nullptr) { + add_message_content_dependencies(dependencies, m->edited_content.get(), is_bot); + } add_reply_markup_dependencies(dependencies, m->reply_markup.get()); } bool QuickReplyManager::can_edit_quick_reply_message(const QuickReplyMessage *m) const { return m->message_id.is_server() && !m->via_bot_user_id.is_valid() && - is_editable_message_content(m->content->get_type()); + is_editable_message_content(m->content->get_type()) && m->content->get_type() != MessageContentType::Game; } bool QuickReplyManager::can_resend_quick_reply_message(const QuickReplyMessage *m) const { if (m->send_error_code != 429) { return false; } - if (m->via_bot_user_id.is_valid() || m->hide_via_bot) { - return false; - } return true; } @@ -636,7 +1168,7 @@ td_api::object_ptr QuickReplyManager::get_message_s const QuickReplyMessage *m) const { CHECK(m != nullptr); if (m->message_id.is_yet_unsent()) { - return td_api::make_object(m->sending_id); + return td_api::make_object(0); } if (m->is_failed_to_send) { auto can_retry = can_resend_quick_reply_message(m); @@ -652,6 +1184,10 @@ td_api::object_ptr QuickReplyManager::get_message_s td_api::object_ptr QuickReplyManager::get_quick_reply_message_message_content_object( const QuickReplyMessage *m) const { + if (m->edited_content != nullptr) { + return get_message_content_object(m->edited_content.get(), td_, DialogId(), 0, false, true, -1, + m->edited_invert_media, m->edited_disable_web_page_preview); + } return get_message_content_object(m->content.get(), td_, DialogId(), 0, false, true, -1, m->invert_media, m->disable_web_page_preview); } @@ -662,9 +1198,9 @@ td_api::object_ptr QuickReplyManager::get_quick_reply auto can_be_edited = can_edit_quick_reply_message(m); return td_api::make_object( m->message_id.get(), get_message_sending_state_object(m), can_be_edited, m->reply_to_message_id.get(), - td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"), m->media_album_id, + td_->user_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id"), m->media_album_id, get_quick_reply_message_message_content_object(m), - get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup)); + get_reply_markup_object(td_->user_manager_.get(), m->reply_markup)); } int32 QuickReplyManager::get_shortcut_message_count(const Shortcut *s) { @@ -727,8 +1263,8 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( break; case telegram_api::messages_quickReplies::ID: { auto shortcuts = telegram_api::move_object_as(shortcuts_ptr); - td_->contacts_manager_->on_get_users(std::move(shortcuts->users_), "messages.quickReplies"); - td_->contacts_manager_->on_get_chats(std::move(shortcuts->chats_), "messages.quickReplies"); + td_->user_manager_->on_get_users(std::move(shortcuts->users_), "messages.quickReplies"); + td_->chat_manager_->on_get_chats(std::move(shortcuts->chats_), "messages.quickReplies"); FlatHashMap, MessageIdHash> message_id_to_message; for (auto &message : shortcuts->messages_) { @@ -741,6 +1277,7 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( FlatHashSet old_shortcut_ids; for (auto &shortcut : shortcuts_.shortcuts_) { + CHECK(shortcut->shortcut_id_.is_valid()); old_shortcut_ids.insert(shortcut->shortcut_id_); } FlatHashSet added_shortcut_ids; @@ -822,7 +1359,7 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( CHECK(is_changed); send_update_quick_reply_shortcut_deleted(old_shortcut); } else { - // some local messages has left + // some local messages left if (added_shortcut_names.count(old_shortcut->name_)) { LOG(INFO) << "Local shortcut " << old_shortcut->name_ << " has been created server-side"; for (auto &shortcut : new_shortcuts) { @@ -831,11 +1368,16 @@ void QuickReplyManager::on_reload_quick_reply_shortcuts( << shortcut->shortcut_id_; CHECK(shortcut->local_total_count_ == 0); shortcut->local_total_count_ = static_cast(old_shortcut->messages_.size()); + for (auto &message : old_shortcut->messages_) { + CHECK(message->shortcut_id == shortcut_id); + message->shortcut_id = shortcut->shortcut_id_; + } append(shortcut->messages_, std::move(old_shortcut->messages_)); sort_quick_reply_messages(shortcut->messages_); send_update_quick_reply_shortcut_deleted(old_shortcut); changed_shortcut_ids.push_back(shortcut->shortcut_id_); changed_message_shortcut_ids.push_back(shortcut->shortcut_id_); + persistent_shortcut_ids_[shortcut_id] = shortcut->shortcut_id_; break; } } @@ -907,6 +1449,7 @@ int64 QuickReplyManager::get_shortcuts_hash() const { for (auto &shortcut : shortcuts_.shortcuts_) { for (auto &message : shortcut->messages_) { if (message->message_id.is_server()) { + CHECK(shortcut->shortcut_id_.is_server()); numbers.push_back(shortcut->shortcut_id_.get()); numbers.push_back(get_md5_string_hash(shortcut->name_)); numbers.push_back(message->message_id.get_server_message_id().get()); @@ -995,6 +1538,7 @@ void QuickReplyManager::reorder_quick_reply_shortcuts(const vectoredited_content = std::move(old_message->edited_content); + new_message->edited_invert_media = old_message->edited_invert_media; + new_message->edited_disable_web_page_preview = old_message->edited_disable_web_page_preview; + new_message->edit_generation = old_message->edit_generation; old_message = std::move(new_message); change_message_files({shortcut_id, old_message->message_id}, old_message.get(), old_file_ids); } @@ -1099,7 +1647,7 @@ void QuickReplyManager::delete_quick_reply_messages_from_updates(QuickReplyShort } load_quick_reply_shortcuts(); - auto s = get_shortcut(shortcut_id); + auto *s = get_shortcut(shortcut_id); if (s == nullptr) { return; } @@ -1119,10 +1667,15 @@ void QuickReplyManager::delete_quick_reply_messages(Shortcut *s, const vectormessages_.end()) { - delete_message_files(s->shortcut_id_, it->get()); + const auto *m = it->get(); + delete_message_files(s->shortcut_id_, m); if (message_id.is_server()) { s->server_total_count_--; } else { + if (m->media_album_id != 0 && m->message_id.is_yet_unsent()) { + send_closure_later(actor_id(this), &QuickReplyManager::on_upload_message_media_finished, m->media_album_id, + m->shortcut_id, m->message_id, Status::OK()); + } s->local_total_count_--; } is_changed = true; @@ -1149,7 +1702,7 @@ void QuickReplyManager::delete_quick_reply_shortcut_messages(QuickReplyShortcutI const vector &message_ids, Promise &&promise) { load_quick_reply_shortcuts(); - auto s = get_shortcut(shortcut_id); + auto *s = get_shortcut(shortcut_id); if (s == nullptr) { return promise.set_error(Status::Error(400, "Shortcut not found")); } @@ -1177,59 +1730,1201 @@ void QuickReplyManager::delete_quick_reply_shortcut_messages(QuickReplyShortcutI void QuickReplyManager::delete_quick_reply_messages_on_server(QuickReplyShortcutId shortcut_id, const vector &message_ids, Promise &&promise) { + if (message_ids.empty()) { + return promise.set_value(Unit()); + } td_->create_handler(std::move(promise))->send(shortcut_id, message_ids); } -void QuickReplyManager::get_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, Promise &&promise) { - load_quick_reply_shortcuts(); - auto s = get_shortcut(shortcut_id); - if (s == nullptr) { - return promise.set_error(Status::Error(400, "Shortcut not found")); +telegram_api::object_ptr QuickReplyManager::get_input_quick_reply_shortcut( + QuickReplyShortcutId shortcut_id) const { + if (shortcut_id.is_server()) { + return telegram_api::make_object(shortcut_id.get()); } - if (have_all_shortcut_messages(s)) { - return promise.set_value(Unit()); + const auto *s = get_shortcut(shortcut_id); + CHECK(s != nullptr); + return telegram_api::make_object(s->name_); +} + +Status QuickReplyManager::check_send_quick_reply_messages_response( + QuickReplyShortcutId shortcut_id, const telegram_api::object_ptr &updates_ptr, + const vector &random_ids) { + if (updates_ptr->get_id() != telegram_api::updates::ID) { + return Status::Error("Receive unexpected updates class"); + } + const auto &updates = static_cast(updates_ptr.get())->updates_; + FlatHashSet sent_random_ids; + for (auto &update : updates) { + if (update->get_id() == telegram_api::updateMessageID::ID) { + auto update_message_id = static_cast(update.get()); + int64 random_id = update_message_id->random_id_; + if (random_id == 0) { + return Status::Error("Receive zero random_id"); + } + if (!sent_random_ids.insert(random_id).second) { + return Status::Error("Receive duplicate random_id"); + } + } } - - CHECK(shortcut_id.is_server()); - reload_quick_reply_messages(shortcut_id, std::move(promise)); -} - -void QuickReplyManager::reload_quick_reply_messages(QuickReplyShortcutId shortcut_id, Promise &&promise) { - if (td_->auth_manager_->is_bot()) { - return promise.set_error(Status::Error(400, "Not supported by bots")); + if (sent_random_ids.size() != random_ids.size()) { + return Status::Error("Receive duplicate random_id"); } - - load_quick_reply_shortcuts(); - CHECK(shortcut_id.is_valid()); - if (!shortcut_id.is_server()) { - return promise.set_value(Unit()); + for (auto random_id : random_ids) { + if (sent_random_ids.count(random_id) != 1) { + return Status::Error("Don't receive expected random_id"); + } } - auto &queries = get_shortcut_messages_queries_[shortcut_id]; - queries.push_back(std::move(promise)); - if (queries.size() != 1) { - return; + int32 new_shortcut_count = 0; + for (auto &update : updates) { + if (update->get_id() == telegram_api::updateNewQuickReply::ID) { + if (!QuickReplyShortcutId( + static_cast(update.get())->quick_reply_->shortcut_id_) + .is_server()) { + return Status::Error("Receive unexpected new shortcut"); + } + new_shortcut_count++; + } } - auto query_promise = - PromiseCreator::lambda([actor_id = actor_id(this), shortcut_id]( - Result> r_messages) { - send_closure(actor_id, &QuickReplyManager::on_reload_quick_reply_messages, shortcut_id, std::move(r_messages)); - }); - td_->create_handler(std::move(query_promise)) - ->send(shortcut_id, vector(), get_quick_reply_messages_hash(get_shortcut(shortcut_id))); + if (new_shortcut_count >= 2 || (new_shortcut_count >= 1 && shortcut_id.is_server())) { + return Status::Error("Receive unexpected number of new shortcuts"); + } + return Status::OK(); } -void QuickReplyManager::on_reload_quick_reply_messages( - QuickReplyShortcutId shortcut_id, Result> r_messages) { - G()->ignore_result_if_closing(r_messages); - auto queries_it = get_shortcut_messages_queries_.find(shortcut_id); - CHECK(queries_it != get_shortcut_messages_queries_.end()); - CHECK(!queries_it->second.empty()); - auto promises = std::move(queries_it->second); - get_shortcut_messages_queries_.erase(queries_it); - if (r_messages.is_error()) { - return fail_promises(promises, r_messages.move_as_error()); +void QuickReplyManager::process_send_quick_reply_updates(QuickReplyShortcutId shortcut_id, + telegram_api::object_ptr updates_ptr, + vector random_ids) { + auto check_status = check_send_quick_reply_messages_response(shortcut_id, updates_ptr, random_ids); + if (check_status.is_error()) { + LOG(ERROR) << check_status << " for sending messages " << random_ids << ": " << to_string(updates_ptr); + on_failed_send_quick_reply_messages(shortcut_id, std::move(random_ids), + Status::Error(500, "Receive wrong response")); + return; } - auto messages_ptr = r_messages.move_as_ok(); + + auto updates = telegram_api::move_object_as(updates_ptr); + td_->user_manager_->on_get_users(std::move(updates->users_), "process_send_quick_reply_updates"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "process_send_quick_reply_updates"); + + bool is_shortcut_new = false; + if (shortcut_id.is_local()) { + QuickReplyShortcutId new_shortcut_id; + for (auto &update : updates->updates_) { + if (update->get_id() == telegram_api::updateNewQuickReply::ID) { + new_shortcut_id = QuickReplyShortcutId( + static_cast(update.get())->quick_reply_->shortcut_id_); + update = nullptr; + } + } + if (!new_shortcut_id.is_server()) { + for (const auto &update : updates->updates_) { + if (update != nullptr && update->get_id() == telegram_api::updateQuickReplyMessage::ID) { + auto &message = static_cast(update.get())->message_; + if (message->get_id() == telegram_api::message::ID) { + new_shortcut_id = QuickReplyShortcutId( + static_cast(message.get())->quick_reply_shortcut_id_); + break; + } + } + } + } + if (!new_shortcut_id.is_server()) { + LOG(ERROR) << "Failed to find new shortcut identifier for " << shortcut_id; + reload_quick_reply_shortcuts(); + on_failed_send_quick_reply_messages(shortcut_id, std::move(random_ids), + Status::Error(500, "Receive wrong response")); + return; + } + auto it = get_shortcut_it(shortcut_id); + if (it != shortcuts_.shortcuts_.end() && (*it)->shortcut_id_ == shortcut_id) { + send_update_quick_reply_shortcut_deleted(it->get()); + + for (auto &message : (*it)->messages_) { + CHECK(message->shortcut_id == shortcut_id); + message->shortcut_id = new_shortcut_id; + } + + auto *s = get_shortcut(new_shortcut_id); + if (s == nullptr) { + (*it)->shortcut_id_ = new_shortcut_id; + is_shortcut_new = true; + } else { + if ((*it)->last_assigned_message_id_ > s->last_assigned_message_id_) { + s->last_assigned_message_id_ = (*it)->last_assigned_message_id_; + } + for (auto &message : (*it)->messages_) { + CHECK(!message->message_id.is_server()); + s->messages_.push_back(std::move(message)); + s->local_total_count_++; + } + shortcuts_.shortcuts_.erase(it); + } + persistent_shortcut_ids_[shortcut_id] = new_shortcut_id; + } else if (get_shortcut(new_shortcut_id) == nullptr) { + return; + } + shortcut_id = new_shortcut_id; + } + auto *s = get_shortcut(shortcut_id); + CHECK(s != nullptr); + + for (auto &random_id : random_ids) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + if ((*it)->random_id == random_id && (*it)->message_id.is_yet_unsent()) { + MessageId new_message_id; + for (auto &update : updates->updates_) { + if (update != nullptr && update->get_id() == telegram_api::updateMessageID::ID && + static_cast(update.get())->random_id_ == random_id) { + new_message_id = + MessageId(ServerMessageId(static_cast(update.get())->id_)); + update = nullptr; + } + } + if (new_message_id.is_valid()) { + for (auto &update : updates->updates_) { + if (update != nullptr && update->get_id() == telegram_api::updateQuickReplyMessage::ID && + MessageId::get_message_id( + static_cast(update.get())->message_, false) == + new_message_id) { + auto message = create_message( + std::move(static_cast(update.get())->message_), + "process_send_quick_reply_updates"); + if (message != nullptr && message->shortcut_id == shortcut_id) { + update_message_content(it->get(), message.get(), false); + auto old_message_it = get_message_it(s, message->message_id); + if (old_message_it == s->messages_.end()) { + change_message_files({shortcut_id, message->message_id}, message.get(), {}); + *it = std::move(message); + s->server_total_count_++; + } else { + // the message has already been added + update_quick_reply_message(shortcut_id, *old_message_it, std::move(message)); + s->messages_.erase(it); + } + s->local_total_count_--; + } + update = nullptr; + break; + } + } + } + + break; + } + } + } + + sort_quick_reply_messages(s->messages_); + send_update_quick_reply_shortcut(s, "process_send_quick_reply_updates"); + send_update_quick_reply_shortcut_messages(s, "process_send_quick_reply_updates"); + if (is_shortcut_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); +} + +void QuickReplyManager::update_message_content(const QuickReplyMessage *old_message, QuickReplyMessage *new_message, + bool is_edit) { + CHECK(is_edit ? old_message->message_id.is_server() : old_message->message_id.is_yet_unsent()); + CHECK(new_message->edited_content == nullptr); + update_message_content(is_edit ? old_message->edited_content : old_message->content, new_message->content, + is_edit || new_message->edit_date == 0); +} + +void QuickReplyManager::update_message_content(const unique_ptr &old_content, + unique_ptr &new_content, bool need_merge_files) { + MessageContentType old_content_type = old_content->get_type(); + MessageContentType new_content_type = new_content->get_type(); + + auto old_file_id = get_message_content_any_file_id(old_content.get()); + need_merge_files = need_merge_files && old_file_id.is_valid(); + if (old_content_type != new_content_type) { + if (need_merge_files) { + td_->file_manager_->try_merge_documents(old_file_id, get_message_content_any_file_id(new_content.get())); + } + } else { + bool is_content_changed = false; + bool need_update = false; + merge_message_contents(td_, old_content.get(), new_content.get(), true, DialogId(), need_merge_files, + is_content_changed, need_update); + } + if (old_file_id.is_valid()) { + // the file is likely to be already merged with a server file, but if not we need to + // cancel file upload of the main file to allow next upload with the same file to succeed + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, old_file_id); + + update_message_content_file_id_remote(new_content.get(), old_file_id); + } +} + +void QuickReplyManager::on_failed_send_quick_reply_messages(QuickReplyShortcutId shortcut_id, vector random_ids, + Status error) { + auto *s = get_shortcut(shortcut_id); + if (s == nullptr) { + // the shortcut was deleted + return; + } + + for (auto &random_id : random_ids) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + if ((*it)->random_id == random_id && (*it)->message_id.is_yet_unsent()) { + auto old_message_id = (*it)->message_id; + auto new_message_id = old_message_id.get_next_message_id(MessageType::Local); + if (get_message_it(s, new_message_id) != s->messages_.end() || + deleted_message_full_ids_.count({shortcut_id, new_message_id})) { + new_message_id = get_next_local_message_id(s); + } else if (new_message_id > s->last_assigned_message_id_) { + s->last_assigned_message_id_ = new_message_id; + } + CHECK(new_message_id.is_valid()); + (*it)->message_id = new_message_id; + (*it)->is_failed_to_send = true; + (*it)->send_error_code = error.code(); + (*it)->send_error_message = error.message().str(); + (*it)->try_resend_at = 0.0; + auto retry_after = Global::get_retry_after((*it)->send_error_code, (*it)->send_error_message); + if (retry_after > 0) { + (*it)->try_resend_at = Time::now() + retry_after; + } + CHECK((*it)->edited_content == nullptr); + update_failed_to_send_message_content(td_, (*it)->content); + + break; + } + } + } + + sort_quick_reply_messages(s->messages_); + send_update_quick_reply_shortcut(s, "on_failed_send_quick_reply_messages"); + send_update_quick_reply_shortcut_messages(s, "on_failed_send_quick_reply_messages"); + save_quick_reply_shortcuts(); +} + +Result> QuickReplyManager::send_message( + const string &shortcut_name, MessageId reply_to_message_id, + td_api::object_ptr &&input_message_content) { + TRY_RESULT(message_content, process_input_message_content(std::move(input_message_content))); + TRY_RESULT(s, create_new_local_shortcut(shortcut_name, 1)); + bool is_new = s->messages_.empty(); + reply_to_message_id = get_input_reply_to_message_id(s, reply_to_message_id); + + auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), message_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + auto *m = add_local_message(s, reply_to_message_id, std::move(content), message_content.invert_media, + message_content.via_bot_user_id, false, message_content.disable_web_page_preview, + std::move(message_content.emoji)); + + send_update_quick_reply_shortcut(s, "send_message"); + send_update_quick_reply_shortcut_messages(s, "send_message"); + if (is_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); + + do_send_message(m); + + return get_quick_reply_message_object(m, "send_message"); +} + +Result> QuickReplyManager::send_inline_query_result_message( + const string &shortcut_name, MessageId reply_to_message_id, int64 query_id, const string &result_id, + bool hide_via_bot) { + const InlineMessageContent *message_content = + td_->inline_queries_manager_->get_inline_message_content(query_id, result_id); + if (message_content == nullptr || query_id == 0) { + return Status::Error(400, "Inline query result not found"); + } + TRY_RESULT(s, create_new_local_shortcut(shortcut_name, 1)); + bool is_new = s->messages_.empty(); + reply_to_message_id = get_input_reply_to_message_id(s, reply_to_message_id); + + UserId via_bot_user_id; + if (!hide_via_bot) { + via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id); + } + auto content = + dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), message_content->message_content.get(), + MessageContentDupType::SendViaBot, MessageCopyOptions()); + auto *m = add_local_message(s, reply_to_message_id, std::move(content), message_content->invert_media, + via_bot_user_id, hide_via_bot, message_content->disable_web_page_preview, string()); + m->reply_markup = dup_reply_markup(message_content->message_reply_markup); + m->inline_query_id = query_id; + m->inline_result_id = result_id; + + send_update_quick_reply_shortcut(s, "send_inline_query_result_message"); + send_update_quick_reply_shortcut_messages(s, "send_inline_query_result_message"); + if (is_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); + + do_send_message(m); + + return get_quick_reply_message_object(m, "send_inline_query_result_message"); +} + +Result> QuickReplyManager::send_message_group( + const string &shortcut_name, MessageId reply_to_message_id, + vector> &&input_message_contents) { + vector message_contents; + for (auto &input_message_content : input_message_contents) { + TRY_RESULT(message_content, process_input_message_content(std::move(input_message_content))); + message_contents.push_back(std::move(message_content)); + } + TRY_STATUS(check_message_group_message_contents(message_contents)); + + TRY_RESULT(s, create_new_local_shortcut(shortcut_name, static_cast(message_contents.size()))); + bool is_new = s->messages_.empty(); + reply_to_message_id = get_input_reply_to_message_id(s, reply_to_message_id); + + int64 media_album_id = 0; + if (message_contents.size() > 1) { + media_album_id = generate_new_media_album_id(); + } + + vector> messages; + for (auto &message_content : message_contents) { + auto content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), message_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + auto *m = add_local_message(s, reply_to_message_id, std::move(content), message_content.invert_media, + message_content.via_bot_user_id, false, message_content.disable_web_page_preview, + std::move(message_content.emoji)); + m->media_album_id = media_album_id; + + do_send_message(m); + + messages.push_back(get_quick_reply_message_object(m, "send_message_group")); + } + + send_update_quick_reply_shortcut(s, "send_message_group"); + send_update_quick_reply_shortcut_messages(s, "send_message_group"); + if (is_new) { + send_update_quick_reply_shortcuts(); + } + save_quick_reply_shortcuts(); + + return td_api::make_object(std::move(messages)); +} + +void QuickReplyManager::do_send_message(const QuickReplyMessage *m, vector bad_parts) { + CHECK(m != nullptr); + bool is_edit = m->message_id.is_server(); + auto message_full_id = QuickReplyMessageFullId(m->shortcut_id, m->message_id); + LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << message_full_id; + + if (m->media_album_id != 0 && bad_parts.empty() && !is_edit) { + auto &request = pending_message_group_sends_[m->media_album_id]; + request.message_ids.push_back(m->message_id); + request.is_finished.push_back(false); + request.results.push_back(Status::OK()); + } + + auto content = is_edit ? m->edited_content.get() : m->content.get(); + CHECK(content != nullptr); + auto content_type = content->get_type(); + if (content_type == MessageContentType::Unsupported) { + if (is_edit) { + return fail_edit_quick_reply_message(m->shortcut_id, m->message_id, m->edit_generation, FileId(), FileId(), + string(), false, false, Status::Error(400, "Failed to upload file")); + } + return on_failed_send_quick_reply_messages(m->shortcut_id, {m->random_id}, + Status::Error(400, "Failed to upload file")); + } + + if (!is_edit && m->inline_query_id != 0) { + td_->create_handler()->send(m); + return; + } + + if (content_type == MessageContentType::Text) { + if (is_edit) { + td_->create_handler()->send(FileId(), FileId(), m, nullptr); + return; + } + auto input_media = get_message_content_input_media_web_page(td_, content); + if (input_media == nullptr) { + td_->create_handler()->send(m); + } else { + td_->create_handler()->send(FileId(), FileId(), m, std::move(input_media)); + } + return; + } + + FileId file_id = get_message_content_any_file_id(content); // any_file_id, because it could be a photo sent by ID + FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content, td_); + LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id; + auto input_media = get_input_media(content, td_, {}, m->send_emoji, false); + if (input_media == nullptr) { + if (content_type == MessageContentType::Game || content_type == MessageContentType::Story) { + return; + } + CHECK(file_id.is_valid()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (get_main_file_type(file_view.get_type()) == FileType::Photo) { + thumbnail_file_id = FileId(); + } + + LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts; + CHECK(file_id.is_valid()); + + bool is_inserted = + being_uploaded_files_.emplace(file_id, std::make_tuple(message_full_id, thumbnail_file_id, m->edit_generation)) + .second; + CHECK(is_inserted); + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get()); + } else { + on_message_media_uploaded(m, std::move(input_media), file_id, thumbnail_file_id); + } +} + +void QuickReplyManager::on_send_message_file_parts_missing(QuickReplyShortcutId shortcut_id, int64 random_id, + vector &&bad_parts) { + auto *s = get_shortcut(shortcut_id); + if (s != nullptr) { + for (auto &message : s->messages_) { + if (message->random_id == random_id && message->message_id.is_yet_unsent()) { + do_send_message(message.get(), std::move(bad_parts)); + } + } + } +} + +void QuickReplyManager::on_send_message_file_reference_error(QuickReplyShortcutId shortcut_id, int64 random_id) { + auto *s = get_shortcut(shortcut_id); + if (s != nullptr) { + for (auto &message : s->messages_) { + if (message->random_id == random_id && message->message_id.is_yet_unsent()) { + do_send_message(message.get(), {-1}); + } + } + } +} + +void QuickReplyManager::on_upload_media(FileId file_id, telegram_api::object_ptr input_file) { + LOG(INFO) << "File " << file_id << " has been uploaded"; + + auto it = being_uploaded_files_.find(file_id); + CHECK(it != being_uploaded_files_.end()); + + auto message_full_id = std::get<0>(it->second); + auto thumbnail_file_id = std::get<1>(it->second); + auto edit_generation = std::get<2>(it->second); + + being_uploaded_files_.erase(it); + + auto *m = get_message(message_full_id); + if (m == nullptr || (m->message_id.is_server() && m->edit_generation != edit_generation)) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + return; + } + + if (input_file && thumbnail_file_id.is_valid()) { + // TODO: download thumbnail if needed (like in secret chats) + LOG(INFO) << "Ask to upload thumbnail " << thumbnail_file_id; + bool is_inserted = being_uploaded_thumbnails_ + .emplace(thumbnail_file_id, UploadedThumbnailInfo{message_full_id, file_id, + std::move(input_file), edit_generation}) + .second; + CHECK(is_inserted); + td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 32, m->message_id.get()); + } else { + do_send_media(m, file_id, thumbnail_file_id, std::move(input_file), nullptr); + } +} + +void QuickReplyManager::do_send_media(QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, + telegram_api::object_ptr input_file, + telegram_api::object_ptr input_thumbnail) { + CHECK(m != nullptr); + + bool have_input_file = input_file != nullptr; + bool have_input_thumbnail = input_thumbnail != nullptr; + LOG(INFO) << "Do send media file " << file_id << " with thumbnail " << thumbnail_file_id + << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail; + + auto content = m->message_id.is_server() ? m->edited_content.get() : m->content.get(); + CHECK(content != nullptr); + auto input_media = get_input_media(content, td_, std::move(input_file), std::move(input_thumbnail), file_id, + thumbnail_file_id, {}, m->send_emoji, true); + CHECK(input_media != nullptr); + + on_message_media_uploaded(m, std::move(input_media), file_id, thumbnail_file_id); +} + +void QuickReplyManager::on_upload_media_error(FileId file_id, Status status) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(WARNING) << "File " << file_id << " has upload error " << status; + CHECK(status.is_error()); + + auto it = being_uploaded_files_.find(file_id); + CHECK(it != being_uploaded_files_.end()); + + auto message_full_id = std::get<0>(it->second); + + being_uploaded_files_.erase(it); + + auto *m = get_message(message_full_id); + if (m == nullptr) { + return; + } + + on_failed_send_quick_reply_messages(message_full_id.get_quick_reply_shortcut_id(), {m->random_id}, std::move(status)); +} + +void QuickReplyManager::on_upload_thumbnail(FileId thumbnail_file_id, + telegram_api::object_ptr thumbnail_input_file) { + if (G()->close_flag()) { + // do not fail upload if closing + return; + } + + LOG(INFO) << "Thumbnail " << thumbnail_file_id << " has been uploaded as " << to_string(thumbnail_input_file); + + auto it = being_uploaded_thumbnails_.find(thumbnail_file_id); + CHECK(it != being_uploaded_thumbnails_.end()); + + auto message_full_id = it->second.quick_reply_message_full_id; + auto file_id = it->second.file_id; + auto input_file = std::move(it->second.input_file); + auto edit_generation = it->second.edit_generation; + + being_uploaded_thumbnails_.erase(it); + + auto *m = get_message(message_full_id); + if (m == nullptr || (m->message_id.is_server() && m->edit_generation != edit_generation)) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, thumbnail_file_id); + return; + } + + if (thumbnail_input_file == nullptr) { + auto content = m->message_id.is_server() ? m->edited_content.get() : m->content.get(); + delete_message_content_thumbnail(content, td_); + } + + do_send_media(m, file_id, thumbnail_file_id, std::move(input_file), std::move(thumbnail_input_file)); +} + +void QuickReplyManager::on_message_media_uploaded(const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media, + FileId file_id, FileId thumbnail_file_id) { + if (G()->close_flag()) { + return; + } + + CHECK(m != nullptr); + CHECK(input_media != nullptr); + auto message_id = m->message_id; + if (message_id.is_any_server()) { + CHECK(m->edited_content != nullptr); + CHECK(m->edited_content->get_type() != MessageContentType::Text); + td_->create_handler()->send(file_id, thumbnail_file_id, m, std::move(input_media)); + return; + } + + if (m->media_album_id != 0) { + // must use UploadMedia and wait for other messages + switch (input_media->get_id()) { + case telegram_api::inputMediaUploadedDocument::ID: + static_cast(input_media.get())->flags_ |= + telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK; + // fallthrough + case telegram_api::inputMediaUploadedPhoto::ID: + case telegram_api::inputMediaDocumentExternal::ID: + case telegram_api::inputMediaPhotoExternal::ID: + td_->create_handler()->send(file_id, thumbnail_file_id, m, std::move(input_media)); + break; + case telegram_api::inputMediaDocument::ID: + case telegram_api::inputMediaPhoto::ID: + send_closure_later(actor_id(this), &QuickReplyManager::on_upload_message_media_finished, m->media_album_id, + m->shortcut_id, m->message_id, Status::OK()); + break; + default: + LOG(ERROR) << "Have wrong input media " << to_string(input_media); + send_closure_later(actor_id(this), &QuickReplyManager::on_upload_message_media_finished, m->media_album_id, + m->shortcut_id, m->message_id, Status::Error(400, "Invalid input media")); + } + return; + } + + td_->create_handler()->send(file_id, thumbnail_file_id, m, std::move(input_media)); +} + +void QuickReplyManager::on_upload_message_media_success(QuickReplyShortcutId shortcut_id, MessageId message_id, + FileId file_id, + telegram_api::object_ptr &&media) { + auto *s = get_shortcut(shortcut_id); + auto *m = get_message(s, message_id); + if (m == nullptr) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + return; + } + + CHECK(message_id.is_yet_unsent()); + + auto caption = get_message_content_caption(m->content.get()); + auto has_spoiler = get_message_content_has_spoiler(m->content.get()); + auto content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), + td_->dialog_manager_->get_my_dialog_id(), 0, false, UserId(), nullptr, nullptr, + "on_upload_message_media_success"); + set_message_content_has_spoiler(content.get(), has_spoiler); + + update_message_content(m->content, content, true); + + if (s->messages_[0]->message_id == message_id) { + // send_update_quick_reply_shortcut(s, "on_upload_message_media_success"); + } + // send_update_quick_reply_shortcut_messages(s, "on_upload_message_media_success"); + save_quick_reply_shortcuts(); + + auto input_media = get_input_media(m->content.get(), td_, {}, m->send_emoji, true); + Status result; + if (input_media == nullptr) { + result = Status::Error(400, "Failed to upload file"); + } + + send_closure_later(actor_id(this), &QuickReplyManager::on_upload_message_media_finished, m->media_album_id, + shortcut_id, message_id, std::move(result)); +} + +void QuickReplyManager::on_upload_message_media_fail(QuickReplyShortcutId shortcut_id, MessageId message_id, + Status error) { + auto *m = get_message({shortcut_id, message_id}); + if (m == nullptr) { + return; + } + + send_closure_later(actor_id(this), &QuickReplyManager::on_upload_message_media_finished, m->media_album_id, + shortcut_id, m->message_id, std::move(error)); +} + +void QuickReplyManager::on_upload_message_media_finished(int64 media_album_id, QuickReplyShortcutId shortcut_id, + MessageId message_id, Status result) { + CHECK(media_album_id < 0); + auto it = pending_message_group_sends_.find(media_album_id); + if (it == pending_message_group_sends_.end()) { + CHECK(result.is_ok()); + // the message is being sent but was deleted + return; + } + + auto &request = it->second; + auto message_it = std::find(request.message_ids.begin(), request.message_ids.end(), message_id); + CHECK(message_it != request.message_ids.end()); + auto pos = static_cast(message_it - request.message_ids.begin()); + + if (request.is_finished[pos]) { + LOG(INFO) << "Upload media of " << message_id << " in " << shortcut_id << " from group " << media_album_id + << " at pos " << pos << " has already been finished"; + return; + } + LOG(INFO) << "Finish to upload media of " << message_id << " in " << shortcut_id << " from group " << media_album_id + << " at pos " << pos << " with result " << result + << " and previous finished_count = " << request.finished_count; + + request.results[pos] = std::move(result); + request.is_finished[pos] = true; + request.finished_count++; + + if (request.finished_count == request.message_ids.size()) { + do_send_message_group(shortcut_id, media_album_id); + } +} + +void QuickReplyManager::do_send_message_group(QuickReplyShortcutId shortcut_id, int64 media_album_id) { + if (G()->close_flag()) { + return; + } + + CHECK(media_album_id < 0); + auto it = pending_message_group_sends_.find(media_album_id); + CHECK(it != pending_message_group_sends_.end()); + + auto &request = it->second; + auto *s = get_shortcut(shortcut_id); + if (s == nullptr) { + return; + } + + vector file_ids; + vector random_ids; + MessageId reply_to_message_id; + bool invert_media = false; + vector> input_single_media; + Status error = Status::OK(); + for (size_t i = 0; i < request.message_ids.size(); i++) { + CHECK(request.is_finished[i]); + auto *m = get_message(s, request.message_ids[i]); + if (m == nullptr) { + // skip deleted messages + continue; + } + if (request.results[i].is_error()) { + if (error.is_ok()) { + error = std::move(request.results[i]); + } + continue; + } + + reply_to_message_id = m->reply_to_message_id; + invert_media = m->invert_media; + file_ids.push_back(get_message_content_any_file_id(m->content.get())); + random_ids.push_back(m->random_id); + + LOG(INFO) << "Have file " << file_ids.back() << " in " << m->message_id << " with result " << request.results[i] + << " and is_finished = " << static_cast(request.is_finished[i]); + + const FormattedText *caption = get_message_content_caption(m->content.get()); + auto input_media = get_input_media(m->content.get(), td_, {}, m->send_emoji, true); + CHECK(input_media != nullptr); + auto entities = get_input_message_entities(td_->user_manager_.get(), caption, "do_send_message_group"); + int32 input_single_media_flags = 0; + if (!entities.empty()) { + input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK; + } + input_single_media.push_back(telegram_api::make_object( + input_single_media_flags, std::move(input_media), random_ids.back(), caption == nullptr ? "" : caption->text, + std::move(entities))); + } + pending_message_group_sends_.erase(it); + if (error.is_error()) { + on_failed_send_quick_reply_messages(shortcut_id, std::move(random_ids), std::move(error)); + return; + } + + LOG(INFO) << "Begin to send media group " << media_album_id << " to " << shortcut_id; + + if (input_single_media.empty()) { + LOG(INFO) << "Media group " << media_album_id << " from " << shortcut_id << " is empty"; + } + td_->create_handler()->send(shortcut_id, reply_to_message_id, invert_media, + std::move(random_ids), std::move(file_ids), + std::move(input_single_media)); +} + +void QuickReplyManager::on_send_media_group_file_reference_error(QuickReplyShortcutId shortcut_id, + vector random_ids) { + auto *s = get_shortcut(shortcut_id); + if (s == nullptr) { + return; + } + + int64 media_album_id = 0; + vector message_ids; + for (auto &random_id : random_ids) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + const auto *m = it->get(); + if (m->random_id == random_id && m->message_id.is_yet_unsent()) { + CHECK(m->media_album_id != 0); + CHECK(media_album_id == 0 || media_album_id == m->media_album_id); + media_album_id = m->media_album_id; + + message_ids.push_back(m->message_id); + } + } + } + if (message_ids.empty()) { + // all messages were deleted, nothing to do + return; + } + + auto &request = pending_message_group_sends_[media_album_id]; + CHECK(request.finished_count == 0); + CHECK(request.is_finished.empty()); + CHECK(request.results.empty()); + request.message_ids = std::move(message_ids); + request.is_finished.resize(request.message_ids.size()); + for (size_t i = 0; i < request.message_ids.size(); i++) { + request.results.push_back(Status::OK()); + } + + for (auto message_id : request.message_ids) { + do_send_message(get_message(s, message_id), {-1}); + } +} + +int64 QuickReplyManager::generate_new_media_album_id() const { + int64 media_album_id = 0; + do { + media_album_id = Random::secure_int64(); + } while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) > 0); + return media_album_id; +} + +Result> QuickReplyManager::resend_messages( + const string &shortcut_name, vector message_ids) { + if (message_ids.empty()) { + return Status::Error(400, "There are no messages to resend"); + } + + load_quick_reply_shortcuts(); + + auto *s = get_shortcut(shortcut_name); + if (s == nullptr) { + return Status::Error(400, "Quick reply shortcut not found"); + } + + MessageId last_message_id; + for (auto &message_id : message_ids) { + const auto *m = get_message(s, message_id); + if (m == nullptr) { + return Status::Error(400, "Message not found"); + } + if (!m->is_failed_to_send) { + return Status::Error(400, "Message is not failed to send"); + } + if (!can_resend_quick_reply_message(m)) { + return Status::Error(400, "Message can't be re-sent"); + } + if (m->try_resend_at > Time::now()) { + return Status::Error(400, "Message can't be re-sent yet"); + } + if (last_message_id != MessageId() && m->message_id <= last_message_id) { + return Status::Error(400, "Message identifiers must be in a strictly increasing order"); + } + last_message_id = m->message_id; + } + + vector> new_contents(message_ids.size()); + std::unordered_map, Hash> new_media_album_ids; + for (size_t i = 0; i < message_ids.size(); i++) { + MessageId message_id = message_ids[i]; + const auto *m = get_message(s, message_id); + CHECK(m != nullptr); + CHECK(m->edited_content == nullptr); + + unique_ptr content = + dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), m->content.get(), + m->inline_query_id != 0 ? MessageContentDupType::SendViaBot : MessageContentDupType::Send, + MessageCopyOptions()); + if (content == nullptr) { + LOG(INFO) << "Can't resend " << m->message_id; + continue; + } + + new_contents[i] = std::move(content); + + if (m->media_album_id != 0) { + auto &new_media_album_id = new_media_album_ids[m->media_album_id]; + new_media_album_id.second++; + if (new_media_album_id.second == 2) { // have at least 2 messages in the new album + CHECK(new_media_album_id.first == 0); + new_media_album_id.first = generate_new_media_album_id(); + } + if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) { + CHECK(new_media_album_id.first != 0); + new_media_album_id.first = 0; // just in case + } + } + } + + bool is_changed = false; + auto result = td_api::make_object(); + for (size_t i = 0; i < message_ids.size(); i++) { + if (new_contents[i] == nullptr) { + result->messages_.push_back(nullptr); + continue; + } + + auto *m = get_message(s, message_ids[i]); + CHECK(m != nullptr); + m->message_id = get_next_yet_unsent_message_id(s); + m->media_album_id = new_media_album_ids[m->media_album_id].first; + m->is_failed_to_send = false; + m->send_error_code = 0; + m->send_error_message = string(); + m->try_resend_at = 0.0; + + do_send_message(m); + + result->messages_.push_back(get_quick_reply_message_object(m, "resend_message")); + is_changed = true; + } + + if (is_changed) { + sort_quick_reply_messages(s->messages_); + send_update_quick_reply_shortcut(s, "resend_message"); + send_update_quick_reply_shortcut_messages(s, "resend_message"); + save_quick_reply_shortcuts(); + } + + return std::move(result); +} + +void QuickReplyManager::edit_quick_reply_message( + QuickReplyShortcutId shortcut_id, MessageId message_id, + td_api::object_ptr &&input_message_content, Promise &&promise) { + load_quick_reply_shortcuts(); + auto *s = get_shortcut(shortcut_id); + if (s == nullptr) { + return promise.set_error(Status::Error(400, "Shortcut not found")); + } + auto *m = get_message(s, message_id); + if (m == nullptr) { + return promise.set_error(Status::Error(400, "Message not found")); + } + if (!can_edit_quick_reply_message(m)) { + return promise.set_error(Status::Error(400, "Message can't be edited")); + } + + TRY_RESULT_PROMISE(promise, message_content, process_input_message_content(std::move(input_message_content))); + auto new_message_content_type = message_content.content->get_type(); + auto old_message_content_type = m->content->get_type(); + switch (old_message_content_type) { + case MessageContentType::Text: + if (new_message_content_type != MessageContentType::Text) { + return promise.set_error(Status::Error(400, "Text messages can be edited only to text messages")); + } + break; + case MessageContentType::Animation: + case MessageContentType::Audio: + case MessageContentType::Document: + case MessageContentType::Photo: + case MessageContentType::Video: + if (new_message_content_type != MessageContentType::Animation && + new_message_content_type != MessageContentType::Audio && + new_message_content_type != MessageContentType::Document && + new_message_content_type != MessageContentType::Photo && + new_message_content_type != MessageContentType::Video) { + return promise.set_error(Status::Error(400, "Media messages can be edited only to media messages")); + } + if (m->media_album_id != 0) { + if (old_message_content_type != new_message_content_type) { + if (!is_allowed_media_group_content(new_message_content_type)) { + return promise.set_error(Status::Error(400, "Message content type can't be used in an album")); + } + if (is_homogenous_media_group_content(old_message_content_type) || + is_homogenous_media_group_content(new_message_content_type)) { + return promise.set_error(Status::Error(400, "Can't change media type in the album")); + } + } + } + break; + case MessageContentType::VoiceNote: + if (new_message_content_type != MessageContentType::VoiceNote || + get_message_content_any_file_id(m->content.get()) != + get_message_content_any_file_id(message_content.content.get())) { + return promise.set_error(Status::Error(400, "Only caption can be edited in voice note messages")); + } + break; + default: + UNREACHABLE(); + } + + auto old_file_ids = get_message_file_ids(m); + m->edited_content = dup_message_content(td_, td_->dialog_manager_->get_my_dialog_id(), message_content.content.get(), + MessageContentDupType::Send, MessageCopyOptions()); + CHECK(m->edited_content != nullptr); + m->edited_invert_media = message_content.invert_media; + m->edited_disable_web_page_preview = message_content.disable_web_page_preview; + m->edit_generation = ++current_message_edit_generation_; + + change_message_files({shortcut_id, message_id}, m, old_file_ids); + + if (s->messages_[0]->message_id == message_id) { + send_update_quick_reply_shortcut(s, "edit_quick_reply_message 1"); + } + send_update_quick_reply_shortcut_messages(s, "edit_quick_reply_message 2"); + save_quick_reply_shortcuts(); + + do_send_message(m); + + promise.set_value(Unit()); +} + +void QuickReplyManager::on_edit_quick_reply_message(QuickReplyShortcutId shortcut_id, MessageId message_id, + int64 edit_generation, FileId file_id, bool was_uploaded, + telegram_api::object_ptr updates_ptr) { + auto *s = get_shortcut(shortcut_id); + auto *m = get_message(s, message_id); + if (m == nullptr) { + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + return; + } + if (m->edit_generation != edit_generation) { + LOG(INFO) << "Ignore successful edit of " << QuickReplyMessageFullId(m->shortcut_id, m->message_id) + << " with generation " << edit_generation << " instead of " << m->edit_generation; + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + return; + } + LOG(INFO) << "Receive result for editing of " << QuickReplyMessageFullId(m->shortcut_id, m->message_id) << ": " + << to_string(updates_ptr); + + bool was_updated = updates_ptr == nullptr; + if (updates_ptr == nullptr || updates_ptr->get_id() != telegram_api::updates::ID) { + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + reload_quick_reply_message(shortcut_id, message_id, Promise()); + } else { + auto updates = telegram_api::move_object_as(updates_ptr); + td_->user_manager_->on_get_users(std::move(updates->users_), "on_edit_quick_reply_message"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "on_edit_quick_reply_message"); + + if (updates->updates_.size() != 1 || updates->updates_[0]->get_id() != telegram_api::updateQuickReplyMessage::ID) { + LOG(ERROR) << "Receive " << to_string(updates); + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + } else { + auto update_message = telegram_api::move_object_as(updates->updates_[0]); + auto message = create_message(std::move(update_message->message_), "on_edit_quick_reply_message"); + if (message == nullptr || message->shortcut_id != shortcut_id || message->message_id != message_id) { + LOG(ERROR) << "Receive unexpected message"; + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + } else { + update_message_content(m, message.get(), true); + auto old_message_it = get_message_it(s, message_id); + update_quick_reply_message(shortcut_id, *old_message_it, std::move(message)); + m = old_message_it->get(); + was_updated = true; + } + } + } + + auto old_file_ids = get_message_file_ids(m); + CHECK(m->edited_content != nullptr); + if (!was_updated) { + m->content = std::move(m->edited_content); + m->invert_media = m->edited_invert_media; + m->disable_web_page_preview = m->edited_disable_web_page_preview; + } + + m->edit_generation = 0; + m->edited_content = nullptr; + m->edited_invert_media = false; + m->edited_disable_web_page_preview = false; + change_message_files({shortcut_id, message_id}, m, old_file_ids); + + if (s->messages_[0]->message_id == m->message_id) { + send_update_quick_reply_shortcut(s, "on_edit_quick_reply_message 1"); + } + send_update_quick_reply_shortcut_messages(s, "on_edit_quick_reply_message 2"); + save_quick_reply_shortcuts(); +} + +void QuickReplyManager::fail_edit_quick_reply_message(QuickReplyShortcutId shortcut_id, MessageId message_id, + int64 edit_generation, FileId file_id, FileId thumbnail_file_id, + string file_reference, bool was_uploaded, + bool was_thumbnail_uploaded, Status status) { + auto *m = get_message({shortcut_id, message_id}); + if (m == nullptr) { + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + return; + } + if (m->edit_generation != edit_generation) { + LOG(INFO) << "Ignore failed edit of " << QuickReplyMessageFullId(m->shortcut_id, m->message_id) + << " with generation " << edit_generation << " instead of " << m->edit_generation; + if (was_uploaded) { + send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id); + } + return; + } + if (was_uploaded) { + if (was_thumbnail_uploaded) { + CHECK(thumbnail_file_id.is_valid()); + // always delete partial remote location for the thumbnail, because it can't be reused anyway + td_->file_manager_->delete_partial_remote_location(thumbnail_file_id); + } + + CHECK(file_id.is_valid()); + auto bad_parts = FileManager::get_missing_file_parts(status); + if (!bad_parts.empty()) { + do_send_message(m, std::move(bad_parts)); + return; + } else { + td_->file_manager_->delete_partial_remote_location_if_needed(file_id, status); + } + } else if (FileReferenceManager::is_file_reference_error(status)) { + if (file_id.is_valid() && !was_uploaded) { + VLOG(file_references) << "Receive " << status << " for " << file_id; + td_->file_manager_->delete_file_reference(file_id, file_reference); + do_send_message(m, {-1}); + return; + } else { + LOG(ERROR) << "Receive file reference error, but file_id = " << file_id << ", was_uploaded = " << was_uploaded; + } + } + + auto old_file_ids = get_message_file_ids(m); + m->edit_generation = 0; + m->edited_content = nullptr; + m->edited_invert_media = false; + m->edited_disable_web_page_preview = false; + change_message_files({m->shortcut_id, m->message_id}, m, old_file_ids); + + auto *s = get_shortcut(m->shortcut_id); + CHECK(s != nullptr); + if (s->messages_[0]->message_id == m->message_id) { + send_update_quick_reply_shortcut(s, "fail_edit_quick_reply_message 1"); + } + send_update_quick_reply_shortcut_messages(s, "fail_edit_quick_reply_message 2"); + save_quick_reply_shortcuts(); + reload_quick_reply_message(shortcut_id, message_id, Promise()); +} + +void QuickReplyManager::get_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, Promise &&promise) { + load_quick_reply_shortcuts(); + auto *s = get_shortcut(shortcut_id); + if (s == nullptr) { + return promise.set_error(Status::Error(400, "Shortcut not found")); + } + if (have_all_shortcut_messages(s)) { + return promise.set_value(Unit()); + } + + CHECK(shortcut_id.is_server()); + reload_quick_reply_messages(shortcut_id, std::move(promise)); +} + +void QuickReplyManager::reload_quick_reply_messages(QuickReplyShortcutId shortcut_id, Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + return promise.set_error(Status::Error(400, "Not supported by bots")); + } + + load_quick_reply_shortcuts(); + CHECK(shortcut_id.is_valid()); + if (!shortcut_id.is_server()) { + return promise.set_value(Unit()); + } + auto &queries = get_shortcut_messages_queries_[shortcut_id]; + queries.push_back(std::move(promise)); + if (queries.size() != 1) { + return; + } + auto query_promise = + PromiseCreator::lambda([actor_id = actor_id(this), shortcut_id]( + Result> r_messages) { + send_closure(actor_id, &QuickReplyManager::on_reload_quick_reply_messages, shortcut_id, std::move(r_messages)); + }); + td_->create_handler(std::move(query_promise)) + ->send(shortcut_id, vector(), get_quick_reply_messages_hash(get_shortcut(shortcut_id))); +} + +void QuickReplyManager::on_reload_quick_reply_messages( + QuickReplyShortcutId shortcut_id, Result> r_messages) { + G()->ignore_result_if_closing(r_messages); + auto queries_it = get_shortcut_messages_queries_.find(shortcut_id); + CHECK(queries_it != get_shortcut_messages_queries_.end()); + CHECK(!queries_it->second.empty()); + auto promises = std::move(queries_it->second); + get_shortcut_messages_queries_.erase(queries_it); + if (r_messages.is_error()) { + return fail_promises(promises, r_messages.move_as_error()); + } + auto messages_ptr = r_messages.move_as_ok(); switch (messages_ptr->get_id()) { case telegram_api::messages_messagesSlice::ID: case telegram_api::messages_channelMessages::ID: @@ -1239,8 +2934,8 @@ void QuickReplyManager::on_reload_quick_reply_messages( break; case telegram_api::messages_messages::ID: { auto messages = telegram_api::move_object_as(messages_ptr); - td_->contacts_manager_->on_get_users(std::move(messages->users_), "on_reload_quick_reply_messages"); - td_->contacts_manager_->on_get_chats(std::move(messages->chats_), "on_reload_quick_reply_messages"); + td_->user_manager_->on_get_users(std::move(messages->users_), "on_reload_quick_reply_messages"); + td_->chat_manager_->on_get_chats(std::move(messages->chats_), "on_reload_quick_reply_messages"); vector> quick_reply_messages; for (auto &server_message : messages->messages_) { @@ -1301,7 +2996,7 @@ void QuickReplyManager::on_reload_quick_reply_messages( default: UNREACHABLE(); } - auto s = get_shortcut(shortcut_id); + auto *s = get_shortcut(shortcut_id); if (s == nullptr) { return fail_promises(promises, Status::Error(400, "Shortcut not found")); } @@ -1329,7 +3024,7 @@ void QuickReplyManager::reload_quick_reply_message(QuickReplyShortcutId shortcut } load_quick_reply_shortcuts(); - auto s = get_shortcut(shortcut_id); + auto *s = get_shortcut(shortcut_id); if (s == nullptr) { return promise.set_error(Status::Error(400, "Shortcut not found")); } @@ -1353,7 +3048,7 @@ void QuickReplyManager::on_reload_quick_reply_message( if (r_messages.is_error()) { return promise.set_error(r_messages.move_as_error()); } - auto s = get_shortcut(shortcut_id); + auto *s = get_shortcut(shortcut_id); if (s == nullptr) { return promise.set_error(Status::Error(400, "Shortcut not found")); } @@ -1366,8 +3061,8 @@ void QuickReplyManager::on_reload_quick_reply_message( return promise.set_error(Status::Error(400, "Receive wrong response")); case telegram_api::messages_messages::ID: { auto messages = telegram_api::move_object_as(messages_ptr); - td_->contacts_manager_->on_get_users(std::move(messages->users_), "on_reload_quick_reply_message"); - td_->contacts_manager_->on_get_chats(std::move(messages->chats_), "on_reload_quick_reply_message"); + td_->user_manager_->on_get_users(std::move(messages->users_), "on_reload_quick_reply_message"); + td_->chat_manager_->on_get_chats(std::move(messages->chats_), "on_reload_quick_reply_message"); if (messages->messages_.size() > 1u) { LOG(ERROR) << "Receive " << to_string(messages_ptr); @@ -1407,13 +3102,9 @@ Result> QuickReplyManager::g return Status::Error(400, "Shortcut messages aren't loaded yet"); } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "get_quick_reply_message_contents")) { - return Status::Error(400, "Chat not found"); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { - return Status::Error(400, "Have no write access to the chat"); - } - if (dialog_id.get_type() != DialogType::User || td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) { + TRY_STATUS(td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "get_quick_reply_message_contents")); + if (dialog_id.get_type() != DialogType::User || td_->user_manager_->is_user_bot(dialog_id.get_user_id())) { return Status::Error(400, "Can't use quick replies in the chat"); } @@ -1428,15 +3119,17 @@ Result> QuickReplyManager::g auto can_send_status = can_send_message_content(dialog_id, content.get(), false, true, td_); if (can_send_status.is_error()) { LOG(INFO) << "Can't send " << message->message_id << ": " << can_send_status.message(); - continue; + // if we skip the message, the sending will fail anyway with MESSAGE_IDS_MISMATCH + // continue; } auto disable_web_page_preview = message->disable_web_page_preview && content->get_type() == MessageContentType::Text && !has_message_content_web_page(content.get()); result.push_back({std::move(content), message->message_id, message->reply_to_message_id, - dup_reply_markup(message->reply_markup), message->media_album_id, message->invert_media, - disable_web_page_preview}); + dup_reply_markup(message->reply_markup), + message->hide_via_bot ? UserId() : message->via_bot_user_id, message->media_album_id, + message->invert_media, disable_web_page_preview}); } return std::move(result); @@ -1451,6 +3144,12 @@ QuickReplyManager::Shortcut *QuickReplyManager::get_shortcut(QuickReplyShortcutI return shortcut.get(); } } + if (shortcut_id.is_local()) { + auto it = persistent_shortcut_ids_.find(shortcut_id); + if (it != persistent_shortcut_ids_.end()) { + return get_shortcut(it->second); + } + } return nullptr; } @@ -1463,6 +3162,12 @@ const QuickReplyManager::Shortcut *QuickReplyManager::get_shortcut(QuickReplySho return shortcut.get(); } } + if (shortcut_id.is_local()) { + auto it = persistent_shortcut_ids_.find(shortcut_id); + if (it != persistent_shortcut_ids_.end()) { + return get_shortcut(it->second); + } + } return nullptr; } @@ -1485,6 +3190,12 @@ vector>::iterator QuickReplyManager::get return it; } } + if (shortcut_id.is_local()) { + auto it = persistent_shortcut_ids_.find(shortcut_id); + if (it != persistent_shortcut_ids_.end()) { + return get_shortcut_it(it->second); + } + } return shortcuts_.shortcuts_.end(); } @@ -1499,6 +3210,139 @@ vector>::iterator QuickReplyMan return s->messages_.end(); } +QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message(QuickReplyMessageFullId message_full_id) { + auto *s = get_shortcut(message_full_id.get_quick_reply_shortcut_id()); + return get_message(s, message_full_id.get_message_id()); +} + +QuickReplyManager::QuickReplyMessage *QuickReplyManager::get_message(Shortcut *s, MessageId message_id) { + if (s != nullptr) { + for (auto it = s->messages_.begin(); it != s->messages_.end(); ++it) { + if ((*it)->message_id == message_id) { + return it->get(); + } + } + } + return nullptr; +} + +Result QuickReplyManager::create_new_local_shortcut(const string &name, + int32 new_message_count) { + TRY_STATUS(check_shortcut_name(name)); + + load_quick_reply_shortcuts(); + if (!shortcuts_.are_inited_) { + return Status::Error(400, "Quick reply shortcuts must be loaded first"); + } + + auto *s = get_shortcut(name); + auto max_message_count = td_->option_manager_->get_option_integer("quick_reply_shortcut_message_count_max"); + if (s != nullptr) { + if (!have_all_shortcut_messages(s)) { + return Status::Error(400, "The quick reply shortcut must be loaded first"); + } + max_message_count -= s->server_total_count_ + s->local_total_count_; + } else { + auto max_shortcut_count = td_->option_manager_->get_option_integer("quick_reply_shortcut_count_max"); + if (static_cast(shortcuts_.shortcuts_.size()) >= max_shortcut_count) { + return Status::Error(400, "Quick reply shortcut count exceeded"); + } + } + if (new_message_count > max_message_count) { + return Status::Error(400, "Quick reply message count exceeded"); + } + if (s != nullptr) { + return s; + } + if (next_local_shortcut_id_ >= std::numeric_limits::max() - 10) { + return Status::Error(400, "Too many local shortcuts created"); + } + + auto shortcut = td::make_unique(); + shortcut->name_ = name; + shortcut->shortcut_id_ = QuickReplyShortcutId(next_local_shortcut_id_++); + s = shortcut.get(); + + shortcuts_.shortcuts_.push_back(std::move(shortcut)); + + return s; +} + +MessageId QuickReplyManager::get_input_reply_to_message_id(const Shortcut *s, MessageId reply_to_message_id) { + if (s == nullptr || !reply_to_message_id.is_valid() || !reply_to_message_id.is_server()) { + return MessageId(); + } + for (auto &message : s->messages_) { + CHECK(message != nullptr); + if (message->message_id == reply_to_message_id) { + return reply_to_message_id; + } + } + return MessageId(); +} + +Result QuickReplyManager::process_input_message_content( + td_api::object_ptr &&input_message_content) { + if (input_message_content == nullptr) { + return Status::Error(400, "Can't add quick reply without content"); + } + if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) { + return Status::Error(400, "Can't forward messages to quick replies"); + } + if (input_message_content->get_id() == td_api::inputMessagePoll::ID) { + return Status::Error(400, "Can't add poll as a quick reply"); + } + if (input_message_content->get_id() == td_api::inputMessageLocation::ID && + static_cast(input_message_content.get())->live_period_ != 0) { + return Status::Error(400, "Can't add live location as a quick reply"); + } + return get_input_message_content(DialogId(), std::move(input_message_content), td_, true); +} + +MessageId QuickReplyManager::get_next_message_id(Shortcut *s, MessageType type) const { + CHECK(s != nullptr); + MessageId last_message_id = s->last_assigned_message_id_; + if (!s->messages_.empty() && s->messages_.back() != nullptr && s->messages_.back()->message_id > last_message_id) { + last_message_id = s->messages_.back()->message_id; + } + s->last_assigned_message_id_ = last_message_id.get_next_message_id(type); + CHECK(s->last_assigned_message_id_.is_valid()); + return s->last_assigned_message_id_; +} + +MessageId QuickReplyManager::get_next_yet_unsent_message_id(Shortcut *s) const { + return get_next_message_id(s, MessageType::YetUnsent); +} + +MessageId QuickReplyManager::get_next_local_message_id(Shortcut *s) const { + return get_next_message_id(s, MessageType::Local); +} + +QuickReplyManager::QuickReplyMessage *QuickReplyManager::add_local_message( + Shortcut *s, MessageId reply_to_message_id, unique_ptr &&content, bool invert_media, + UserId via_bot_user_id, bool hide_via_bot, bool disable_web_page_preview, string &&send_emoji) { + CHECK(s != nullptr); + auto message = make_unique(); + auto *m = message.get(); + m->shortcut_id = s->shortcut_id_; + m->message_id = get_next_yet_unsent_message_id(s); + m->reply_to_message_id = reply_to_message_id; + m->send_emoji = std::move(send_emoji); + m->via_bot_user_id = via_bot_user_id; + m->hide_via_bot = hide_via_bot; + m->invert_media = invert_media; + m->disable_web_page_preview = disable_web_page_preview; + m->content = std::move(content); + do { + m->random_id = Random::secure_int64(); + } while (m->random_id == 0); + + s->messages_.push_back(std::move(message)); + s->local_total_count_++; + + return m; +} + vector QuickReplyManager::get_shortcut_ids() const { return transform(shortcuts_.shortcuts_, [](const unique_ptr &shortcut) { return shortcut->shortcut_id_; }); } @@ -1541,6 +3385,7 @@ void QuickReplyManager::update_shortcut_from(Shortcut *new_shortcut, Shortcut *o bool *is_shortcut_changed, bool *are_messages_changed) { CHECK(old_shortcut != nullptr); CHECK(new_shortcut != nullptr); + CHECK(old_shortcut->shortcut_id_.is_server()); CHECK(old_shortcut->shortcut_id_ == new_shortcut->shortcut_id_); CHECK(!old_shortcut->messages_.empty()); CHECK(!new_shortcut->messages_.empty()); @@ -1621,6 +3466,7 @@ string QuickReplyManager::get_quick_reply_shortcuts_database_key() { void QuickReplyManager::save_quick_reply_shortcuts() { CHECK(shortcuts_.are_inited_); + LOG(INFO) << "Save quick reply shortcuts"; G()->td_db()->get_binlog_pmc()->set(get_quick_reply_shortcuts_database_key(), log_event_store(shortcuts_).as_slice().str()); } @@ -1634,12 +3480,15 @@ void QuickReplyManager::load_quick_reply_shortcuts() { CHECK(shortcuts_.load_queries_.empty()); auto shortcuts_str = G()->td_db()->get_binlog_pmc()->get(get_quick_reply_shortcuts_database_key()); + if (shortcuts_str.empty()) { + return reload_quick_reply_shortcuts(); + } auto status = log_event_parse(shortcuts_, shortcuts_str); if (status.is_error()) { LOG(ERROR) << "Can't load quick replies: " << status; G()->td_db()->get_binlog_pmc()->erase(get_quick_reply_shortcuts_database_key()); shortcuts_.shortcuts_.clear(); - return; + return reload_quick_reply_shortcuts(); } Dependencies dependencies; @@ -1650,12 +3499,20 @@ void QuickReplyManager::load_quick_reply_shortcuts() { } if (!dependencies.resolve_force(td_, "load_quick_reply_shortcuts")) { shortcuts_.shortcuts_.clear(); - return; + return reload_quick_reply_shortcuts(); } shortcuts_.are_inited_ = true; - for (const auto &shortcut : shortcuts_.shortcuts_) { - for (const auto &message : shortcut->messages_) { + for (auto &shortcut : shortcuts_.shortcuts_) { + if (shortcut->shortcut_id_.get() >= next_local_shortcut_id_) { + next_local_shortcut_id_ = shortcut->shortcut_id_.get() + 1; + } + for (auto &message : shortcut->messages_) { + if (message->shortcut_id != shortcut->shortcut_id_) { + LOG(ERROR) << "Receive quick reply " << message->message_id << " in " << message->shortcut_id << " instead of " + << shortcut->shortcut_id_; + message->shortcut_id = shortcut->shortcut_id_; + } change_message_files({shortcut->shortcut_id_, message->message_id}, message.get(), {}); if (message->message_id.is_server()) { @@ -1663,6 +3520,12 @@ void QuickReplyManager::load_quick_reply_shortcuts() { (message->legacy_layer != 0 && message->legacy_layer < MTPROTO_LAYER)) { reload_quick_reply_message(shortcut->shortcut_id_, message->message_id, Promise()); } + if (message->edited_content != nullptr) { + message->edit_generation = ++current_message_edit_generation_; + do_send_message(message.get()); + } + } else if (message->message_id.is_yet_unsent()) { + do_send_message(message.get()); } } send_update_quick_reply_shortcut(shortcut.get(), "load_quick_reply_shortcuts"); @@ -1723,6 +3586,16 @@ vector QuickReplyManager::get_message_file_ids(const QuickReplyMessage * if (m == nullptr) { return {}; } + if (m->edited_content != nullptr) { + auto file_ids = get_message_content_file_ids(m->edited_content.get(), td_); + if (!file_ids.empty()) { + for (auto file_id : get_message_content_file_ids(m->content.get(), td_)) { + if (!td::contains(file_ids, file_id)) { + file_ids.push_back(file_id); + } + } + } + } return get_message_content_file_ids(m->content.get(), td_); } @@ -1749,6 +3622,7 @@ void QuickReplyManager::change_message_files(QuickReplyMessageFullId message_ful return; } + LOG(INFO) << "Change files of " << message_full_id << " from " << old_file_ids << " to " << new_file_ids; for (auto file_id : old_file_ids) { if (!td::contains(new_file_ids, file_id)) { send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise(), "change_message_files"); diff --git a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.h b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.h index 2590ab54..9ba9bb07 100644 --- a/lib/tgchat/ext/td/td/telegram/QuickReplyManager.h +++ b/lib/tgchat/ext/td/td/telegram/QuickReplyManager.h @@ -25,11 +25,14 @@ #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include +#include #include namespace td { class Dependencies; +struct InputMessageContent; class MessageContent; struct ReplyMarkup; class Td; @@ -57,6 +60,27 @@ class QuickReplyManager final : public Actor { void delete_quick_reply_shortcut_messages(QuickReplyShortcutId shortcut_id, const vector &message_ids, Promise &&promise); + Result> send_message( + const string &shortcut_name, MessageId reply_to_message_id, + td_api::object_ptr &&input_message_content); + + Result> send_inline_query_result_message(const string &shortcut_name, + MessageId reply_to_message_id, + int64 query_id, + const string &result_id, + bool hide_via_bot); + + Result> send_message_group( + const string &shortcut_name, MessageId reply_to_message_id, + vector> &&input_message_contents); + + Result> resend_messages(const string &shortcut_name, + vector message_ids); + + void edit_quick_reply_message(QuickReplyShortcutId shortcut_id, MessageId message_id, + td_api::object_ptr &&input_message_content, + Promise &&promise); + void reload_quick_reply_shortcuts(); void reload_quick_reply_messages(QuickReplyShortcutId shortcut_id, Promise &&promise); @@ -68,6 +92,7 @@ class QuickReplyManager final : public Actor { MessageId original_message_id_; MessageId original_reply_to_message_id_; unique_ptr reply_markup_; + UserId via_bot_user_id_; int64 media_album_id_; bool invert_media_; bool disable_web_page_preview_; @@ -80,6 +105,8 @@ class QuickReplyManager final : public Actor { void get_current_state(vector> &updates) const; private: + static constexpr size_t MAX_GROUPED_MESSAGES = 10; // server side limit + struct QuickReplyMessage { QuickReplyMessage() = default; QuickReplyMessage(const QuickReplyMessage &) = delete; @@ -90,7 +117,6 @@ class QuickReplyManager final : public Actor { MessageId message_id; QuickReplyShortcutId shortcut_id; - int32 sending_id = 0; // for yet unsent messages int32 edit_date = 0; int64 random_id = 0; // for send_message @@ -99,6 +125,9 @@ class QuickReplyManager final : public Actor { string send_emoji; // for send_message + int64 inline_query_id = 0; // for send_message + string inline_result_id; // for send_message + UserId via_bot_user_id; bool is_failed_to_send = false; @@ -109,6 +138,9 @@ class QuickReplyManager final : public Actor { bool from_background = false; // for send_message bool hide_via_bot = false; // for resend_message + bool edited_invert_media = false; + bool edited_disable_web_page_preview = false; + int32 legacy_layer = 0; int32 send_error_code = 0; @@ -120,7 +152,8 @@ class QuickReplyManager final : public Actor { unique_ptr content; unique_ptr reply_markup; - mutable uint64 send_message_log_event_id = 0; + unique_ptr edited_content; + int64 edit_generation = 0; template void store(StorerT &storer) const; @@ -142,6 +175,7 @@ class QuickReplyManager final : public Actor { int32 server_total_count_ = 0; int32 local_total_count_ = 0; vector> messages_; + MessageId last_assigned_message_id_; template void store(StorerT &storer) const; @@ -164,6 +198,19 @@ class QuickReplyManager final : public Actor { void parse(ParserT &parser); }; + class EditQuickReplyMessageQuery; + class SendQuickReplyInlineMessageQuery; + class SendQuickReplyMediaQuery; + class SendQuickReplyMessageQuery; + class SendQuickReplyMultiMediaQuery; + class UploadQuickReplyMediaQuery; + + class UploadMediaCallback; + class UploadThumbnailCallback; + + std::shared_ptr upload_media_callback_; + std::shared_ptr upload_thumbnail_callback_; + void tear_down() final; static bool is_shortcut_name_letter(uint32 code); @@ -229,6 +276,27 @@ class QuickReplyManager final : public Actor { vector>::iterator get_message_it(Shortcut *s, MessageId message_id); + QuickReplyMessage *get_message(QuickReplyMessageFullId message_full_id); + + QuickReplyMessage *get_message(Shortcut *s, MessageId message_id); + + Result create_new_local_shortcut(const string &name, int32 new_message_count); + + static MessageId get_input_reply_to_message_id(const Shortcut *s, MessageId reply_to_message_id); + + Result process_input_message_content( + td_api::object_ptr &&input_message_content); + + MessageId get_next_message_id(Shortcut *s, MessageType type) const; + + MessageId get_next_yet_unsent_message_id(Shortcut *s) const; + + MessageId get_next_local_message_id(Shortcut *s) const; + + QuickReplyMessage *add_local_message(Shortcut *s, MessageId reply_to_message_id, unique_ptr &&content, + bool invert_media, UserId via_bot_user_id, bool hide_via_bot, + bool disable_web_page_preview, string &&send_emoji); + bool is_shortcut_list_changed(const vector> &new_shortcuts) const; vector get_shortcut_ids() const; @@ -279,6 +347,67 @@ class QuickReplyManager final : public Actor { void delete_quick_reply_messages_on_server(QuickReplyShortcutId shortcut_id, const vector &message_ids, Promise &&promise); + telegram_api::object_ptr get_input_quick_reply_shortcut( + QuickReplyShortcutId shortcut_id) const; + + Status check_send_quick_reply_messages_response(QuickReplyShortcutId shortcut_id, + const telegram_api::object_ptr &updates_ptr, + const vector &random_ids); + + void process_send_quick_reply_updates(QuickReplyShortcutId shortcut_id, + telegram_api::object_ptr updates_ptr, + vector random_ids); + + void on_failed_send_quick_reply_messages(QuickReplyShortcutId shortcut_id, vector random_ids, Status error); + + void update_message_content(const QuickReplyMessage *old_message, QuickReplyMessage *new_message, bool is_edit); + + void update_message_content(const unique_ptr &old_content, unique_ptr &new_content, + bool need_merge_files); + + void do_send_message(const QuickReplyMessage *m, vector bad_parts = {}); + + void on_send_message_file_parts_missing(QuickReplyShortcutId shortcut_id, int64 random_id, vector &&bad_parts); + + void on_send_message_file_reference_error(QuickReplyShortcutId shortcut_id, int64 random_id); + + void on_upload_media(FileId file_id, telegram_api::object_ptr input_file); + + void do_send_media(QuickReplyMessage *m, FileId file_id, FileId thumbnail_file_id, + telegram_api::object_ptr input_file, + telegram_api::object_ptr input_thumbnail); + + void on_upload_media_error(FileId file_id, Status status); + + void on_upload_thumbnail(FileId thumbnail_file_id, + telegram_api::object_ptr thumbnail_input_file); + + void on_upload_message_media_success(QuickReplyShortcutId shortcut_id, MessageId message_id, FileId file_id, + telegram_api::object_ptr &&media); + + void on_upload_message_media_fail(QuickReplyShortcutId shortcut_id, MessageId message_id, Status error); + + void on_upload_message_media_finished(int64 media_album_id, QuickReplyShortcutId shortcut_id, MessageId message_id, + Status result); + + void do_send_message_group(QuickReplyShortcutId shortcut_id, int64 media_album_id); + + void on_message_media_uploaded(const QuickReplyMessage *m, + telegram_api::object_ptr &&input_media, FileId file_id, + FileId thumbnail_file_id); + + void on_send_media_group_file_reference_error(QuickReplyShortcutId shortcut_id, vector random_ids); + + int64 generate_new_media_album_id() const; + + void on_edit_quick_reply_message(QuickReplyShortcutId shortcut_id, MessageId message_id, int64 edit_generation, + FileId file_id, bool was_uploaded, + telegram_api::object_ptr updates_ptr); + + void fail_edit_quick_reply_message(QuickReplyShortcutId shortcut_id, MessageId message_id, int64 edit_generation, + FileId file_id, FileId thumbnail_file_id, string file_reference, bool was_uploaded, + bool was_thumbnail_uploaded, Status status); + string get_quick_reply_shortcuts_database_key(); void save_quick_reply_shortcuts(); @@ -294,14 +423,39 @@ class QuickReplyManager final : public Actor { Shortcuts shortcuts_; + int32 next_local_shortcut_id_ = QuickReplyShortcutId::MAX_SERVER_SHORTCUT_ID + 1; + FlatHashSet deleted_shortcut_ids_; + FlatHashMap persistent_shortcut_ids_; + FlatHashMap>, QuickReplyShortcutIdHash> get_shortcut_messages_queries_; FlatHashSet deleted_message_full_ids_; FlatHashMap message_full_id_to_file_source_id_; + FlatHashMap, FileIdHash> + being_uploaded_files_; // file_id -> message, thumbnail_file_id + + struct UploadedThumbnailInfo { + QuickReplyMessageFullId quick_reply_message_full_id; + FileId file_id; // original file file_id + telegram_api::object_ptr input_file; // original file InputFile + int64 edit_generation; + }; + FlatHashMap being_uploaded_thumbnails_; // thumbnail_file_id -> ... + + struct PendingMessageGroupSend { + size_t finished_count = 0; + vector message_ids; + vector is_finished; + vector results; + }; + FlatHashMap pending_message_group_sends_; // media_album_id -> ... + + int64 current_message_edit_generation_ = 0; + Td *td_; ActorShared<> parent_; }; diff --git a/lib/tgchat/ext/td/td/telegram/QuickReplyShortcutId.h b/lib/tgchat/ext/td/td/telegram/QuickReplyShortcutId.h index 7347672a..b213ccd3 100644 --- a/lib/tgchat/ext/td/td/telegram/QuickReplyShortcutId.h +++ b/lib/tgchat/ext/td/td/telegram/QuickReplyShortcutId.h @@ -66,6 +66,10 @@ class QuickReplyShortcutId { return id > 0 && id <= MAX_SERVER_SHORTCUT_ID; } + bool is_local() const { + return id > MAX_SERVER_SHORTCUT_ID; + } + template void store(StorerT &storer) const { storer.store_int(id); diff --git a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp index 93e78e90..6dec8dcf 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ReactionManager.cpp @@ -126,7 +126,7 @@ class ClearRecentReactionsQuery final : public Td::ResultHandler { return on_error(result_ptr.move_as_error()); } - td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent); + td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent, "ClearRecentReactionsQuery"); promise_.set_value(Unit()); } @@ -134,7 +134,7 @@ class ClearRecentReactionsQuery final : public Td::ResultHandler { if (!G()->is_expected_error(status)) { LOG(ERROR) << "Receive error for clear recent reactions: " << status; } - td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent); + td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent, "ClearRecentReactionsQuery"); promise_.set_error(std::move(status)); } }; @@ -246,6 +246,35 @@ class UpdateSavedReactionTagQuery final : public Td::ResultHandler { } }; +class GetMessageAvailableEffectsQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetMessageAvailableEffectsQuery( + Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(int32 hash) { + send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableEffects(hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetMessageAvailableEffectsQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + ReactionManager::SavedReactionTag::SavedReactionTag(telegram_api::object_ptr &&tag) : reaction_type_(tag->reaction_) , hash_(reaction_type_.get_hash()) @@ -446,6 +475,7 @@ void ReactionManager::init() { td_->stickers_manager_->init(); load_active_reactions(); + load_active_message_effects(); if (td_->option_manager_->get_option_boolean("default_reaction_needs_sync")) { send_set_default_reaction_query(); @@ -670,10 +700,11 @@ void ReactionManager::reload_reactions() { td_->create_handler()->send(reactions_.hash_); } -void ReactionManager::reload_reaction_list(ReactionListType reaction_list_type) { +void ReactionManager::reload_reaction_list(ReactionListType reaction_list_type, const char *source) { if (G()->close_flag()) { return; } + LOG(INFO) << "Reload " << reaction_list_type << " from " << source; auto &reaction_list = get_reaction_list(reaction_list_type); if (reaction_list.is_being_reloaded_) { return; @@ -740,13 +771,14 @@ void ReactionManager::load_reactions() { } are_reactions_loaded_from_database_ = true; - LOG(INFO) << "Loading available reactions"; string reactions = G()->td_db()->get_binlog_pmc()->get("reactions"); if (reactions.empty()) { return reload_reactions(); } - auto new_reactions = reactions_; + LOG(INFO) << "Loaded available reactions of size " << reactions.size(); + Reactions new_reactions; + new_reactions.are_being_reloaded_ = reactions_.are_being_reloaded_; auto status = log_event_parse(new_reactions, reactions); if (status.is_error()) { LOG(ERROR) << "Can't load available reactions: " << status; @@ -775,14 +807,14 @@ void ReactionManager::load_reaction_list(ReactionListType reaction_list_type) { LOG(INFO) << "Loading " << reaction_list_type; string reactions_str = G()->td_db()->get_binlog_pmc()->get(get_reaction_list_type_database_key(reaction_list_type)); if (reactions_str.empty()) { - return reload_reaction_list(reaction_list_type); + return reload_reaction_list(reaction_list_type, "load_reaction_list 1"); } auto status = log_event_parse(reaction_list, reactions_str); if (status.is_error()) { LOG(ERROR) << "Can't load " << reaction_list_type << ": " << status; reaction_list = {}; - return reload_reaction_list(reaction_list_type); + return reload_reaction_list(reaction_list_type, "load_reaction_list 2"); } LOG(INFO) << "Successfully loaded " << reaction_list.reaction_types_.size() << ' ' << reaction_list_type; @@ -1168,6 +1200,255 @@ void ReactionManager::set_saved_messages_tag_title(ReactionType reaction_type, s td_->create_handler(std::move(query_promise))->send(reaction_type, title); } +td_api::object_ptr ReactionManager::get_message_effect_object(const Effect &effect) const { + auto type = [&]() -> td_api::object_ptr { + if (effect.is_sticker()) { + return td_api::make_object( + td_->stickers_manager_->get_sticker_object(effect.effect_sticker_id_)); + } + return td_api::make_object( + td_->stickers_manager_->get_sticker_object(effect.effect_sticker_id_), + td_->stickers_manager_->get_sticker_object(effect.effect_animation_id_)); + }(); + return td_api::make_object(effect.id_, + td_->stickers_manager_->get_sticker_object(effect.static_icon_id_), + effect.emoji_, effect.is_premium_, std::move(type)); +} + +td_api::object_ptr ReactionManager::get_message_effect_object(int64 effect_id) const { + for (auto &effect : message_effects_.effects_) { + if (effect.id_ == effect_id) { + return get_message_effect_object(effect); + } + } + return nullptr; +} + +td_api::object_ptr ReactionManager::get_update_available_message_effects_object() + const { + return td_api::make_object( + vector(active_message_effects_.reaction_effects_), + vector(active_message_effects_.sticker_effects_)); +} + +void ReactionManager::reload_message_effects() { + if (G()->close_flag() || message_effects_.are_being_reloaded_) { + return; + } + CHECK(!td_->auth_manager_->is_bot()); + message_effects_.are_being_reloaded_ = true; + load_message_effects(); // must be after are_being_reloaded_ is set to true to avoid recursion + auto promise = PromiseCreator::lambda( + [actor_id = actor_id(this)]( + Result> r_effects) mutable { + send_closure(actor_id, &ReactionManager::on_get_message_effects, std::move(r_effects)); + }); + td_->create_handler(std::move(promise))->send(message_effects_.hash_); +} + +void ReactionManager::load_message_effects() { + if (are_message_effects_loaded_from_database_) { + return; + } + are_message_effects_loaded_from_database_ = true; + + string message_effects = G()->td_db()->get_binlog_pmc()->get("message_effects"); + if (message_effects.empty()) { + return reload_message_effects(); + } + LOG(INFO) << "Loaded message effects of size " << message_effects.size(); + + Effects new_message_effects; + new_message_effects.are_being_reloaded_ = message_effects_.are_being_reloaded_; + auto status = log_event_parse(new_message_effects, message_effects); + if (status.is_error()) { + LOG(ERROR) << "Can't load message effects: " << status; + return reload_message_effects(); + } + for (auto &effect : new_message_effects.effects_) { + if (!effect.is_valid()) { + LOG(ERROR) << "Loaded invalid message effect"; + return reload_message_effects(); + } + } + message_effects_ = std::move(new_message_effects); + + LOG(INFO) << "Successfully loaded " << message_effects_.effects_.size() << " message effects"; + + update_active_message_effects(); +} + +void ReactionManager::save_message_effects() { + LOG(INFO) << "Save " << message_effects_.effects_.size() << " message effects"; + are_message_effects_loaded_from_database_ = true; + G()->td_db()->get_binlog_pmc()->set("message_effects", log_event_store(message_effects_).as_slice().str()); +} + +void ReactionManager::on_get_message_effects( + Result> r_effects) { + G()->ignore_result_if_closing(r_effects); + CHECK(message_effects_.are_being_reloaded_); + message_effects_.are_being_reloaded_ = false; + + auto get_message_effect_queries = std::move(pending_get_message_effect_queries_); + pending_get_message_effect_queries_.clear(); + SCOPE_EXIT { + for (auto &query : get_message_effect_queries) { + query.second.set_value(get_message_effect_object(query.first)); + } + }; + + if (r_effects.is_error()) { + return; + } + auto message_effects = r_effects.move_as_ok(); + + switch (message_effects->get_id()) { + case telegram_api::messages_availableEffectsNotModified::ID: + break; + case telegram_api::messages_availableEffects::ID: { + auto effects = telegram_api::move_object_as(message_effects); + FlatHashMap stickers; + for (auto &document : effects->documents_) { + auto sticker = td_->stickers_manager_->on_get_sticker_document(std::move(document), StickerFormat::Unknown); + if (sticker.first != 0 && sticker.second.is_valid()) { + stickers.emplace(sticker.first, sticker.second); + } else { + LOG(ERROR) << "Receive " << sticker.first << ' ' << sticker.second; + } + } + vector new_effects; + bool was_sticker = false; + bool have_invalid_order = false; + for (const auto &available_effect : effects->effects_) { + Effect effect; + effect.id_ = available_effect->id_; + effect.emoji_ = std::move(available_effect->emoticon_); + effect.is_premium_ = available_effect->premium_required_; + if (available_effect->static_icon_id_ != 0) { + auto it = stickers.find(available_effect->static_icon_id_); + if (it == stickers.end()) { + LOG(ERROR) << "Can't find " << available_effect->static_icon_id_; + } else { + auto sticker_id = it->second; + if (td_->stickers_manager_->get_sticker_format(sticker_id) != StickerFormat::Webp) { + LOG(ERROR) << "Receive static sticker in wrong format"; + } else { + effect.static_icon_id_ = sticker_id; + } + } + } + if (available_effect->effect_sticker_id_ != 0) { + auto it = stickers.find(available_effect->effect_sticker_id_); + if (it == stickers.end()) { + LOG(ERROR) << "Can't find " << available_effect->effect_sticker_id_; + } else { + auto sticker_id = it->second; + if (td_->stickers_manager_->get_sticker_format(sticker_id) != StickerFormat::Tgs) { + LOG(ERROR) << "Receive effect sticker in wrong format"; + } else { + effect.effect_sticker_id_ = sticker_id; + } + } + } + if (available_effect->effect_animation_id_ != 0) { + auto it = stickers.find(available_effect->effect_animation_id_); + if (it == stickers.end()) { + LOG(ERROR) << "Can't find " << available_effect->effect_animation_id_; + } else { + auto sticker_id = it->second; + if (td_->stickers_manager_->get_sticker_format(sticker_id) != StickerFormat::Tgs) { + LOG(ERROR) << "Receive effect animation in wrong format"; + } else { + effect.effect_animation_id_ = sticker_id; + } + } + } + if (effect.is_valid()) { + if (was_sticker && !effect.is_sticker()) { + have_invalid_order = true; + } + new_effects.push_back(std::move(effect)); + } else { + LOG(ERROR) << "Receive " << to_string(available_effect); + } + } + if (have_invalid_order) { + LOG(ERROR) << "Have invalid effect order"; + std::stable_sort(new_effects.begin(), new_effects.end(), + [](const Effect &lhs, const Effect &rhs) { return !lhs.is_sticker() && rhs.is_sticker(); }); + } + + message_effects_.effects_ = std::move(new_effects); + message_effects_.hash_ = effects->hash_; + + save_message_effects(); + + update_active_message_effects(); + break; + } + default: + UNREACHABLE(); + } +} + +void ReactionManager::save_active_message_effects() { + LOG(INFO) << "Save " << active_message_effects_.reaction_effects_.size() << " + " + << active_message_effects_.sticker_effects_.size() << " available message effects"; + G()->td_db()->get_binlog_pmc()->set("active_message_effects", + log_event_store(active_message_effects_).as_slice().str()); +} + +void ReactionManager::load_active_message_effects() { + LOG(INFO) << "Loading active message effects"; + string active_message_effects = G()->td_db()->get_binlog_pmc()->get("active_message_effects"); + if (active_message_effects.empty()) { + return reload_message_effects(); + } + + auto status = log_event_parse(active_message_effects_, active_message_effects); + if (status.is_error()) { + LOG(ERROR) << "Can't load active message effects: " << status; + active_message_effects_ = {}; + return reload_message_effects(); + } + + LOG(INFO) << "Successfully loaded " << active_message_effects_.reaction_effects_.size() << " + " + << active_message_effects_.sticker_effects_.size() << " active message effects"; + + send_closure(G()->td(), &Td::send_update, get_update_available_message_effects_object()); +} + +void ReactionManager::update_active_message_effects() { + ActiveEffects active_message_effects; + for (auto &effect : message_effects_.effects_) { + if (effect.is_sticker()) { + active_message_effects.sticker_effects_.push_back(effect.id_); + } else { + active_message_effects.reaction_effects_.push_back(effect.id_); + } + } + if (active_message_effects.reaction_effects_ == active_message_effects_.reaction_effects_ && + active_message_effects.sticker_effects_ == active_message_effects_.sticker_effects_) { + return; + } + active_message_effects_ = std::move(active_message_effects); + + save_active_message_effects(); + + send_closure(G()->td(), &Td::send_update, get_update_available_message_effects_object()); +} + +void ReactionManager::get_message_effect(int64 effect_id, + Promise> &&promise) { + load_message_effects(); + if (message_effects_.effects_.empty() && message_effects_.are_being_reloaded_) { + pending_get_message_effect_queries_.emplace_back(effect_id, std::move(promise)); + return; + } + promise.set_value(get_message_effect_object(effect_id)); +} + void ReactionManager::get_current_state(vector> &updates) const { if (td_->auth_manager_->is_bot()) { return; @@ -1182,6 +1463,9 @@ void ReactionManager::get_current_state(vector &&reactions_ptr); @@ -81,6 +81,10 @@ class ReactionManager final : public Actor { void set_saved_messages_tag_title(ReactionType reaction_type, string title, Promise &&promise); + void reload_message_effects(); + + void get_message_effect(int64 effect_id, Promise> &&promise); + void get_current_state(vector> &updates) const; private: @@ -189,6 +193,56 @@ class ReactionManager final : public Actor { void parse(ParserT &parser); }; + struct Effect { + int64 id_ = 0; + string emoji_; + FileId static_icon_id_; + FileId effect_sticker_id_; + FileId effect_animation_id_; + bool is_premium_ = false; + + bool is_valid() const { + return id_ != 0 && effect_sticker_id_.is_valid(); + } + + bool is_sticker() const { + return effect_animation_id_ == FileId(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct Effects { + int32 hash_ = 0; + bool are_being_reloaded_ = false; + vector effects_; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct ActiveEffects { + vector reaction_effects_; + vector sticker_effects_; + + bool is_empty() const { + return reaction_effects_.empty() && sticker_effects_.empty(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + td_api::object_ptr get_emoji_reaction_object(const string &emoji) const; ReactionList &get_reaction_list(ReactionListType reaction_list_type); @@ -233,12 +287,31 @@ class ReactionManager final : public Actor { void send_update_saved_messages_tags(SavedMessagesTopicId saved_messages_topic_id, const SavedReactionTags *tags, bool from_database = false); + td_api::object_ptr get_message_effect_object(const Effect &effect) const; + + td_api::object_ptr get_message_effect_object(int64 effect_id) const; + + td_api::object_ptr get_update_available_message_effects_object() const; + + void load_message_effects(); + + void save_message_effects(); + + void on_get_message_effects(Result> r_effects); + + void save_active_message_effects(); + + void load_active_message_effects(); + + void update_active_message_effects(); + Td *td_; ActorShared<> parent_; bool is_inited_ = false; bool are_reactions_loaded_from_database_ = false; bool are_all_tags_loaded_from_database_ = false; + bool are_message_effects_loaded_from_database_ = false; vector>>> pending_get_emoji_reaction_queries_; @@ -254,6 +327,11 @@ class ReactionManager final : public Actor { FlatHashMap>>, SavedMessagesTopicIdHash> pending_get_topic_saved_reaction_tags_queries_; + + Effects message_effects_; + ActiveEffects active_message_effects_; + + vector>>> pending_get_message_effect_queries_; }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionManager.hpp b/lib/tgchat/ext/td/td/telegram/ReactionManager.hpp index ffe49982..dc5af622 100644 --- a/lib/tgchat/ext/td/td/telegram/ReactionManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/ReactionManager.hpp @@ -119,4 +119,102 @@ void ReactionManager::ReactionList::parse(ParserT &parser) { } } +template +void ReactionManager::Effect::store(StorerT &storer) const { + StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get(); + bool has_static_icon = static_icon_id_.is_valid(); + bool has_effect_animation = effect_animation_id_.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_premium_); + STORE_FLAG(has_static_icon); + STORE_FLAG(has_effect_animation); + END_STORE_FLAGS(); + td::store(id_, storer); + td::store(emoji_, storer); + if (has_static_icon) { + stickers_manager->store_sticker(static_icon_id_, false, storer, "Effect"); + } + stickers_manager->store_sticker(effect_sticker_id_, false, storer, "Effect"); + if (has_effect_animation) { + stickers_manager->store_sticker(effect_animation_id_, false, storer, "Effect"); + } +} + +template +void ReactionManager::Effect::parse(ParserT &parser) { + StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get(); + bool has_static_icon; + bool has_effect_animation; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_premium_); + PARSE_FLAG(has_static_icon); + PARSE_FLAG(has_effect_animation); + END_PARSE_FLAGS(); + td::parse(id_, parser); + td::parse(emoji_, parser); + if (has_static_icon) { + static_icon_id_ = stickers_manager->parse_sticker(false, parser); + } + effect_sticker_id_ = stickers_manager->parse_sticker(false, parser); + if (has_effect_animation) { + effect_animation_id_ = stickers_manager->parse_sticker(false, parser); + } +} + +template +void ReactionManager::Effects::store(StorerT &storer) const { + bool has_effects = !effects_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_effects); + END_STORE_FLAGS(); + if (has_effects) { + td::store(effects_, storer); + td::store(hash_, storer); + } +} + +template +void ReactionManager::Effects::parse(ParserT &parser) { + bool has_effects; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_effects); + END_PARSE_FLAGS(); + if (has_effects) { + td::parse(effects_, parser); + td::parse(hash_, parser); + } +} + +template +void ReactionManager::ActiveEffects::store(StorerT &storer) const { + bool has_reaction_effects = !reaction_effects_.empty(); + bool has_sticker_effects = !sticker_effects_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_reaction_effects); + STORE_FLAG(has_sticker_effects); + END_STORE_FLAGS(); + if (has_reaction_effects) { + td::store(reaction_effects_, storer); + } + if (has_sticker_effects) { + td::store(sticker_effects_, storer); + } +} + +template +void ReactionManager::ActiveEffects::parse(ParserT &parser) { + bool has_reaction_effects; + bool has_sticker_effects; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_reaction_effects); + PARSE_FLAG(has_sticker_effects); + END_PARSE_FLAGS(); + if (has_reaction_effects) { + td::parse(reaction_effects_, parser); + } + if (has_sticker_effects) { + td::parse(sticker_effects_, parser); + } +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.cpp b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.cpp new file mode 100644 index 00000000..ef34e1b6 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.cpp @@ -0,0 +1,75 @@ +// +// 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/ReactionNotificationSettings.h" + +namespace td { + +ReactionNotificationSettings::ReactionNotificationSettings( + td_api::object_ptr &¬ification_settings) { + if (notification_settings == nullptr) { + return; + } + message_reactions_ = ReactionNotificationsFrom(std::move(notification_settings->message_reaction_source_)); + story_reactions_ = ReactionNotificationsFrom(std::move(notification_settings->story_reaction_source_)); + sound_ = get_notification_sound(false, notification_settings->sound_id_); + show_preview_ = notification_settings->show_preview_; +} + +ReactionNotificationSettings::ReactionNotificationSettings( + telegram_api::object_ptr &¬ify_settings) { + if (notify_settings == nullptr) { + return; + } + message_reactions_ = ReactionNotificationsFrom(std::move(notify_settings->messages_notify_from_)); + story_reactions_ = ReactionNotificationsFrom(std::move(notify_settings->stories_notify_from_)); + sound_ = get_notification_sound(notify_settings->sound_.get()); + show_preview_ = notify_settings->show_previews_; +} + +td_api::object_ptr +ReactionNotificationSettings::get_reaction_notification_settings_object() const { + return td_api::make_object( + message_reactions_.get_reaction_notification_source_object(), + story_reactions_.get_reaction_notification_source_object(), get_notification_sound_ringtone_id(sound_), + show_preview_); +} + +telegram_api::object_ptr +ReactionNotificationSettings::get_input_reactions_notify_settings() const { + int32 flags = 0; + auto messages_notify_from = message_reactions_.get_input_reaction_notifications_from(); + if (messages_notify_from != nullptr) { + flags |= telegram_api::reactionsNotifySettings::MESSAGES_NOTIFY_FROM_MASK; + } + auto stories_notify_from = story_reactions_.get_input_reaction_notifications_from(); + if (stories_notify_from != nullptr) { + flags |= telegram_api::reactionsNotifySettings::STORIES_NOTIFY_FROM_MASK; + } + return telegram_api::make_object( + flags, std::move(messages_notify_from), std::move(stories_notify_from), + get_input_notification_sound(sound_, true), show_preview_); +} + +void ReactionNotificationSettings::update_default_notification_sound(const ReactionNotificationSettings &other) { + if (is_notification_sound_default(sound_) && is_notification_sound_default(other.sound_)) { + sound_ = dup_notification_sound(other.sound_); + } +} + +bool operator==(const ReactionNotificationSettings &lhs, const ReactionNotificationSettings &rhs) { + return lhs.message_reactions_ == rhs.message_reactions_ && lhs.story_reactions_ == rhs.story_reactions_ && + are_equivalent_notification_sounds(lhs.sound_, rhs.sound_) && lhs.show_preview_ == rhs.show_preview_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionNotificationSettings ¬ification_settings) { + return string_builder << "ReactionNotificationSettings[messages: " << notification_settings.message_reactions_ + << ", stories: " << notification_settings.story_reactions_ + << ", sound: " << notification_settings.sound_ + << ", show_preview: " << notification_settings.show_preview_ << ']'; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.h b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.h new file mode 100644 index 00000000..6200e973 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.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/NotificationSound.h" +#include "td/telegram/ReactionNotificationsFrom.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 ReactionNotificationSettings { + ReactionNotificationsFrom message_reactions_; + ReactionNotificationsFrom story_reactions_; + unique_ptr sound_; + bool show_preview_ = true; + + friend bool operator==(const ReactionNotificationSettings &lhs, const ReactionNotificationSettings &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, + const ReactionNotificationSettings ¬ification_settings); + + public: + ReactionNotificationSettings() = default; + + explicit ReactionNotificationSettings( + td_api::object_ptr &¬ification_settings); + + explicit ReactionNotificationSettings( + telegram_api::object_ptr &¬ify_settings); + + td_api::object_ptr get_reaction_notification_settings_object() const; + + telegram_api::object_ptr get_input_reactions_notify_settings() const; + + void update_default_notification_sound(const ReactionNotificationSettings &other); + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const ReactionNotificationSettings &lhs, const ReactionNotificationSettings &rhs); + +inline bool operator!=(const ReactionNotificationSettings &lhs, const ReactionNotificationSettings &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionNotificationSettings ¬ification_settings); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.hpp b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.hpp new file mode 100644 index 00000000..90c09d8e --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationSettings.hpp @@ -0,0 +1,45 @@ +// +// 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/NotificationSound.h" +#include "td/telegram/ReactionNotificationSettings.h" +#include "td/telegram/ReactionNotificationsFrom.hpp" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void ReactionNotificationSettings::store(StorerT &storer) const { + bool has_sound = sound_ != nullptr; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_sound); + STORE_FLAG(show_preview_); + END_STORE_FLAGS(); + td::store(message_reactions_, storer); + td::store(story_reactions_, storer); + if (has_sound) { + td::store(sound_, storer); + } +} + +template +void ReactionNotificationSettings::parse(ParserT &parser) { + bool has_sound; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_sound); + PARSE_FLAG(show_preview_); + END_PARSE_FLAGS(); + td::parse(message_reactions_, parser); + td::parse(story_reactions_, parser); + if (has_sound) { + parse_notification_sound(sound_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.cpp b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.cpp new file mode 100644 index 00000000..18bc2f00 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.cpp @@ -0,0 +1,98 @@ +// +// 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/ReactionNotificationsFrom.h" + +namespace td { + +ReactionNotificationsFrom::ReactionNotificationsFrom( + td_api::object_ptr &¬ification_source) { + if (notification_source == nullptr) { + type_ = Type::None; + return; + } + switch (notification_source->get_id()) { + case td_api::reactionNotificationSourceNone::ID: + type_ = Type::None; + break; + case td_api::reactionNotificationSourceContacts::ID: + type_ = Type::Contacts; + break; + case td_api::reactionNotificationSourceAll::ID: + type_ = Type::All; + break; + default: + UNREACHABLE(); + } +} + +ReactionNotificationsFrom::ReactionNotificationsFrom( + telegram_api::object_ptr &¬ifications_from) { + if (notifications_from == nullptr) { + type_ = Type::None; + return; + } + switch (notifications_from->get_id()) { + case telegram_api::reactionNotificationsFromContacts::ID: + type_ = Type::Contacts; + break; + case telegram_api::reactionNotificationsFromAll::ID: + type_ = Type::All; + break; + default: + UNREACHABLE(); + } +} + +td_api::object_ptr +ReactionNotificationsFrom::get_reaction_notification_source_object() const { + switch (type_) { + case Type::None: + return td_api::make_object(); + case Type::Contacts: + return td_api::make_object(); + case Type::All: + return td_api::make_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +telegram_api::object_ptr +ReactionNotificationsFrom::get_input_reaction_notifications_from() const { + switch (type_) { + case Type::None: + return nullptr; + case Type::Contacts: + return telegram_api::make_object(); + case Type::All: + return telegram_api::make_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +bool operator==(const ReactionNotificationsFrom &lhs, const ReactionNotificationsFrom &rhs) { + return lhs.type_ == rhs.type_; +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionNotificationsFrom ¬ifications_from) { + switch (notifications_from.type_) { + case ReactionNotificationsFrom::Type::None: + return string_builder << "disabled"; + case ReactionNotificationsFrom::Type::Contacts: + return string_builder << "contacts"; + case ReactionNotificationsFrom::Type::All: + return string_builder << "all"; + default: + UNREACHABLE(); + return string_builder; + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.h b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.h new file mode 100644 index 00000000..67e8de65 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.h @@ -0,0 +1,52 @@ +// +// 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 ReactionNotificationsFrom { + enum class Type : int32 { None, Contacts, All }; + Type type_ = Type::Contacts; + + friend bool operator==(const ReactionNotificationsFrom &lhs, const ReactionNotificationsFrom &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const ReactionNotificationsFrom ¬ifications_from); + + public: + ReactionNotificationsFrom() = default; + + explicit ReactionNotificationsFrom(td_api::object_ptr &¬ification_source); + + explicit ReactionNotificationsFrom( + telegram_api::object_ptr &¬ifications_from); + + td_api::object_ptr get_reaction_notification_source_object() const; + + telegram_api::object_ptr get_input_reaction_notifications_from() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); +}; + +bool operator==(const ReactionNotificationsFrom &lhs, const ReactionNotificationsFrom &rhs); + +inline bool operator!=(const ReactionNotificationsFrom &lhs, const ReactionNotificationsFrom &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const ReactionNotificationsFrom ¬ifications_from); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.hpp b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.hpp new file mode 100644 index 00000000..2c1673d2 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/ReactionNotificationsFrom.hpp @@ -0,0 +1,29 @@ +// +// 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/ReactionNotificationsFrom.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void ReactionNotificationsFrom::store(StorerT &storer) const { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + td::store(type_, storer); +} + +template +void ReactionNotificationsFrom::parse(ParserT &parser) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + td::parse(type_, parser); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp b/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp index f37a7679..b3361457 100644 --- a/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp +++ b/lib/tgchat/ext/td/td/telegram/RecentDialogList.cpp @@ -7,7 +7,7 @@ #include "td/telegram/RecentDialogList.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogListId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FolderId.h" @@ -16,6 +16,7 @@ #include "td/telegram/Td.h" #include "td/telegram/td_api.h" #include "td/telegram/TdDb.h" +#include "td/telegram/UserManager.h" #include "td/actor/MultiPromise.h" @@ -49,14 +50,14 @@ void RecentDialogList::save_dialogs() const { string username; switch (dialog_id.get_type()) { case DialogType::User: - if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id())) { - username = td_->contacts_manager_->get_user_first_username(dialog_id.get_user_id()); + if (!td_->user_manager_->is_user_contact(dialog_id.get_user_id())) { + username = td_->user_manager_->get_user_first_username(dialog_id.get_user_id()); } break; case DialogType::Chat: break; case DialogType::Channel: - username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()); + username = td_->chat_manager_->get_channel_first_username(dialog_id.get_channel_id()); break; case DialogType::SecretChat: break; @@ -116,7 +117,7 @@ void RecentDialogList::load_dialogs(Promise &&promise) { PromiseCreator::lambda([promise = mpas.get_promise()](td_api::object_ptr &&chats) mutable { promise.set_value(Unit()); })); - td_->contacts_manager_->search_contacts("", 1, mpas.get_promise()); + td_->user_manager_->search_contacts("", 1, mpas.get_promise()); } } @@ -143,7 +144,7 @@ void RecentDialogList::on_load_dialogs(vector &&found_dialogs) { } if (dialog_id.is_valid() && td::contains(removed_dialog_ids_, dialog_id) == 0 && td_->dialog_manager_->have_dialog_info(dialog_id) && - td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + td_->dialog_manager_->have_input_peer(dialog_id, true, AccessRights::Read)) { td_->dialog_manager_->force_create_dialog(dialog_id, "recent dialog"); do_add_dialog(dialog_id); } @@ -206,7 +207,7 @@ void RecentDialogList::update_dialogs() { // always keep break; case DialogType::Chat: { - auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); + auto channel_id = td_->chat_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id()); if (channel_id.is_valid() && td_->messages_manager_->have_dialog(DialogId(channel_id))) { dialog_id = DialogId(channel_id); } diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp index 1ab21e00..5c8bb981 100644 --- a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.cpp @@ -13,7 +13,6 @@ #include "td/telegram/MessageCopyOptions.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessagesManager.h" -#include "td/telegram/misc.h" #include "td/telegram/OptionManager.h" #include "td/telegram/ScheduledServerMessageId.h" #include "td/telegram/ServerMessageId.h" @@ -115,20 +114,8 @@ RepliedMessageInfo::RepliedMessageInfo(Td *td, tl_object_ptrquote_text_.empty()) { - is_quote_manual_ = reply_header->quote_; - auto entities = get_message_entities(td->contacts_manager_.get(), std::move(reply_header->quote_entities_), - "RepliedMessageInfo"); - auto status = fix_formatted_text(reply_header->quote_text_, entities, true, true, true, true, false); - if (status.is_error()) { - if (!clean_input_string(reply_header->quote_text_)) { - reply_header->quote_text_.clear(); - } - entities.clear(); - } - quote_ = FormattedText{std::move(reply_header->quote_text_), std::move(entities)}; - quote_position_ = max(0, reply_header->quote_offset_); - remove_unallowed_quote_entities(quote_); + if (!origin_.is_empty() || message_id_ != MessageId()) { + quote_ = MessageQuote(td, reply_header); } } @@ -137,11 +124,7 @@ RepliedMessageInfo::RepliedMessageInfo(Td *td, const MessageInputReplyTo &input_ return; } message_id_ = input_reply_to.message_id_; - if (!input_reply_to.quote_.text.empty()) { - quote_ = input_reply_to.quote_; - quote_position_ = input_reply_to.quote_position_; - is_quote_manual_ = true; - } + quote_ = input_reply_to.quote_.clone(); if (input_reply_to.dialog_id_ != DialogId()) { auto info = td->messages_manager_->get_forwarded_message_info({input_reply_to.dialog_id_, input_reply_to.message_id_}); @@ -154,11 +137,8 @@ RepliedMessageInfo::RepliedMessageInfo(Td *td, const MessageInputReplyTo &input_ content_ = std::move(info.content_); auto content_text = get_message_content_text_mutable(content_.get()); if (content_text != nullptr) { - if (!is_quote_manual_) { - quote_ = std::move(*content_text); - remove_unallowed_quote_entities(quote_); - truncate_formatted_text( - quote_, static_cast(td->option_manager_->get_option_integer("message_reply_quote_length_max"))); + if (quote_.is_empty()) { + quote_ = MessageQuote::create_automatic_quote(td, std::move(*content_text)); } *content_text = FormattedText(); } @@ -184,9 +164,7 @@ RepliedMessageInfo RepliedMessageInfo::clone(Td *td) const { result.content_ = dup_message_content(td, td->dialog_manager_->get_my_dialog_id(), content_.get(), MessageContentDupType::Forward, MessageCopyOptions()); } - result.quote_ = quote_; - result.quote_position_ = quote_position_; - result.is_quote_manual_ = is_quote_manual_; + result.quote_ = quote_.clone(); return result; } @@ -207,22 +185,9 @@ bool RepliedMessageInfo::need_reply_changed_warning( // only signature can change in the message origin return true; } - if (old_info.quote_position_ != new_info.quote_position_ && - max(old_info.quote_position_, new_info.quote_position_) < - static_cast(min(old_info.quote_.text.size(), new_info.quote_.text.size()))) { - // quote position can't change - return true; - } - if (old_info.is_quote_manual_ != new_info.is_quote_manual_) { - // quote manual property can't change - return true; - } - if (old_info.quote_ != new_info.quote_) { - if (old_info.is_quote_manual_) { - return true; - } - // automatic quote can change if the original message was edited - return false; + auto need_quote_warning = MessageQuote::need_quote_changed_warning(old_info.quote_, new_info.quote_); + if (need_quote_warning != 0) { + return need_quote_warning > 0; } if (old_info.dialog_id_ != new_info.dialog_id_ && old_info.dialog_id_ != DialogId() && new_info.dialog_id_ != DialogId()) { @@ -278,7 +243,7 @@ vector RepliedMessageInfo::get_min_user_ids(Td *td) const { user_ids.push_back(dialog_id_.get_user_id()); } origin_.add_user_ids(user_ids); - // not supported server-side: add_formatted_text_user_ids(user_ids, "e_); + // not supported server-side: quote_.add_user_ids(user_ids); if (content_ != nullptr) { append(user_ids, get_message_content_min_user_ids(td, content_.get())); } @@ -300,7 +265,7 @@ vector RepliedMessageInfo::get_min_channel_ids(Td *td) const { void RepliedMessageInfo::add_dependencies(Dependencies &dependencies, bool is_bot) const { dependencies.add_dialog_and_dependencies(dialog_id_); origin_.add_dependencies(dependencies); - add_formatted_text_dependencies(dependencies, "e_); + quote_.add_dependencies(dependencies); if (content_ != nullptr) { add_message_content_dependencies(dependencies, content_.get(), is_bot); } @@ -318,12 +283,6 @@ td_api::object_ptr RepliedMessageInfo::get_messag chat_id = 0; } - td_api::object_ptr quote; - if (!quote_.text.empty()) { - quote = td_api::make_object(get_formatted_text_object(quote_, true, -1), quote_position_, - is_quote_manual_); - } - td_api::object_ptr origin; if (!origin_.is_empty()) { origin = origin_.get_message_origin_object(td); @@ -350,15 +309,14 @@ td_api::object_ptr RepliedMessageInfo::get_messag } } - return td_api::make_object(chat_id, message_id_.get(), std::move(quote), + return td_api::make_object(chat_id, message_id_.get(), quote_.get_text_quote_object(), std::move(origin), origin_date_, std::move(content)); } MessageInputReplyTo RepliedMessageInfo::get_input_reply_to() const { CHECK(!is_external()); if (message_id_.is_valid()) { - FormattedText quote = quote_; - return MessageInputReplyTo(message_id_, dialog_id_, std::move(quote), quote_position_); + return MessageInputReplyTo(message_id_, dialog_id_, quote_.clone(true)); } return {}; } @@ -397,8 +355,7 @@ void RepliedMessageInfo::unregister_content(Td *td) const { bool operator==(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs) { if (!(lhs.message_id_ == rhs.message_id_ && lhs.dialog_id_ == rhs.dialog_id_ && - lhs.origin_date_ == rhs.origin_date_ && lhs.origin_ == rhs.origin_ && lhs.quote_ == rhs.quote_ && - lhs.quote_position_ == rhs.quote_position_ && lhs.is_quote_manual_ == rhs.is_quote_manual_)) { + lhs.origin_date_ == rhs.origin_date_ && lhs.origin_ == rhs.origin_ && lhs.quote_ == rhs.quote_)) { return false; } bool need_update = false; @@ -422,13 +379,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const RepliedMessageInf if (info.origin_date_ != 0) { string_builder << " sent at " << info.origin_date_ << " by " << info.origin_; } - if (!info.quote_.text.empty()) { - string_builder << " with " << info.quote_.text.size() << (info.is_quote_manual_ ? " manually" : "") - << " quoted bytes"; - if (info.quote_position_ != 0) { - string_builder << " at position " << info.quote_position_; - } - } + string_builder << info.quote_; if (info.content_ != nullptr) { string_builder << " and content of the type " << info.content_->get_type(); } diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h index be44cba4..6bbf117e 100644 --- a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.h @@ -10,11 +10,11 @@ #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" #include "td/telegram/MessageContent.h" -#include "td/telegram/MessageEntity.h" #include "td/telegram/MessageFullId.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessageInputReplyTo.h" #include "td/telegram/MessageOrigin.h" +#include "td/telegram/MessageQuote.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" @@ -36,9 +36,7 @@ class RepliedMessageInfo { int32 origin_date_ = 0; // for replies in other chats MessageOrigin origin_; // for replies in other chats unique_ptr content_; // for replies in other chats - FormattedText quote_; - int32 quote_position_ = 0; - bool is_quote_manual_ = false; + MessageQuote quote_; friend bool operator==(const RepliedMessageInfo &lhs, const RepliedMessageInfo &rhs); diff --git a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp index 9d91356e..f28dadb1 100644 --- a/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp +++ b/lib/tgchat/ext/td/td/telegram/RepliedMessageInfo.hpp @@ -22,18 +22,18 @@ void RepliedMessageInfo::store(StorerT &storer) const { bool has_dialog_id = dialog_id_.is_valid(); bool has_origin_date = origin_date_ != 0; bool has_origin = !origin_.is_empty(); - bool has_quote = !quote_.text.empty(); bool has_content = content_ != nullptr; - bool has_quote_position = quote_position_ != 0; + bool has_quote = !quote_.is_empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_message_id); STORE_FLAG(has_dialog_id); STORE_FLAG(has_origin_date); STORE_FLAG(has_origin); - STORE_FLAG(has_quote); - STORE_FLAG(is_quote_manual_); + STORE_FLAG(false); + STORE_FLAG(false); STORE_FLAG(has_content); - STORE_FLAG(has_quote_position); + STORE_FLAG(false); + STORE_FLAG(has_quote); END_STORE_FLAGS(); if (has_message_id) { td::store(message_id_, storer); @@ -47,14 +47,11 @@ void RepliedMessageInfo::store(StorerT &storer) const { if (has_origin) { td::store(origin_, storer); } - if (has_quote) { - td::store(quote_, storer); - } if (has_content) { store_message_content(content_.get(), storer); } - if (has_quote_position) { - td::store(quote_position_, storer); + if (has_quote) { + td::store(quote_, storer); } } @@ -64,18 +61,21 @@ void RepliedMessageInfo::parse(ParserT &parser) { bool has_dialog_id; bool has_origin_date; bool has_origin; - bool has_quote; + bool has_quote_legacy; + bool is_quote_manual_legacy; bool has_content; - bool has_quote_position; + bool has_quote_position_legacy; + bool has_quote; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_message_id); PARSE_FLAG(has_dialog_id); PARSE_FLAG(has_origin_date); PARSE_FLAG(has_origin); - PARSE_FLAG(has_quote); - PARSE_FLAG(is_quote_manual_); + PARSE_FLAG(has_quote_legacy); + PARSE_FLAG(is_quote_manual_legacy); PARSE_FLAG(has_content); - PARSE_FLAG(has_quote_position); + PARSE_FLAG(has_quote_position_legacy); + PARSE_FLAG(has_quote); END_PARSE_FLAGS(); if (has_message_id) { td::parse(message_id_, parser); @@ -89,15 +89,21 @@ void RepliedMessageInfo::parse(ParserT &parser) { if (has_origin) { td::parse(origin_, parser); } - if (has_quote) { - td::parse(quote_, parser); - remove_unallowed_quote_entities(quote_); + FormattedText quote_legacy; + if (has_quote_legacy) { + td::parse(quote_legacy, parser); } if (has_content) { parse_message_content(content_, parser); } - if (has_quote_position) { - td::parse(quote_position_, parser); + int32 quote_position_legacy = 0; + if (has_quote_position_legacy) { + td::parse(quote_position_legacy, parser); + } + if (has_quote) { + td::parse(quote_, parser); + } else if (has_quote_legacy) { + quote_ = MessageQuote(std::move(quote_legacy), quote_position_legacy, is_quote_manual_legacy); } } diff --git a/lib/tgchat/ext/td/td/telegram/ReplyMarkup.cpp b/lib/tgchat/ext/td/td/telegram/ReplyMarkup.cpp index 23e08a10..d56c1885 100644 --- a/lib/tgchat/ext/td/td/telegram/ReplyMarkup.cpp +++ b/lib/tgchat/ext/td/td/telegram/ReplyMarkup.cpp @@ -6,13 +6,13 @@ // #include "td/telegram/ReplyMarkup.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Global.h" #include "td/telegram/LinkManager.h" #include "td/telegram/misc.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -825,8 +825,7 @@ Result> get_reply_markup(td_api::object_ptr> get_reply_markup(td_api::object_ptr &&reply_markup_ptr, - DialogId dialog_id, bool is_bot, bool is_anonymous) { - auto dialog_type = dialog_id.get_type(); + DialogType dialog_type, bool is_bot, bool is_anonymous) { bool only_inline_keyboard = is_anonymous; bool request_buttons_allowed = dialog_type == DialogType::User; bool switch_inline_buttons_allowed = !is_anonymous; @@ -896,10 +895,7 @@ static tl_object_ptr get_input_keyboard_button(con return make_tl_object(keyboard_button.text, keyboard_button.url); case KeyboardButton::Type::RequestDialog: CHECK(keyboard_button.requested_dialog_type != nullptr); - return make_tl_object( - keyboard_button.text, keyboard_button.requested_dialog_type->get_button_id(), - keyboard_button.requested_dialog_type->get_input_request_peer_type_object(), - keyboard_button.requested_dialog_type->get_max_quantity()); + return keyboard_button.requested_dialog_type->get_input_keyboard_button_request_peer(keyboard_button.text); default: UNREACHABLE(); return nullptr; @@ -907,7 +903,7 @@ static tl_object_ptr get_input_keyboard_button(con } static tl_object_ptr get_input_keyboard_button( - ContactsManager *contacts_manager, const InlineKeyboardButton &keyboard_button) { + UserManager *user_manager, const InlineKeyboardButton &keyboard_button) { switch (keyboard_button.type) { case InlineKeyboardButton::Type::Url: return make_tl_object(keyboard_button.text, keyboard_button.data); @@ -956,7 +952,7 @@ static tl_object_ptr get_input_keyboard_button( if (!keyboard_button.forward_text.empty()) { flags |= telegram_api::inputKeyboardButtonUrlAuth::FWD_TEXT_MASK; } - auto r_input_user = contacts_manager->get_input_user(UserId(bot_user_id)); + auto r_input_user = user_manager->get_input_user(UserId(bot_user_id)); if (r_input_user.is_error()) { LOG(ERROR) << "Failed to get InputUser for " << bot_user_id << ": " << r_input_user.error(); return make_tl_object(keyboard_button.text, keyboard_button.data); @@ -969,7 +965,7 @@ static tl_object_ptr get_input_keyboard_button( UNREACHABLE(); break; case InlineKeyboardButton::Type::User: { - auto r_input_user = contacts_manager->get_input_user(keyboard_button.user_id); + auto r_input_user = user_manager->get_input_user(keyboard_button.user_id); if (r_input_user.is_error()) { LOG(ERROR) << "Failed to get InputUser for " << keyboard_button.user_id << ": " << r_input_user.error(); r_input_user = make_tl_object(); @@ -985,7 +981,7 @@ static tl_object_ptr get_input_keyboard_button( } } -tl_object_ptr ReplyMarkup::get_input_reply_markup(ContactsManager *contacts_manager) const { +tl_object_ptr ReplyMarkup::get_input_reply_markup(UserManager *user_manager) const { LOG(DEBUG) << "Send " << *this; switch (type) { case ReplyMarkup::Type::InlineKeyboard: { @@ -995,7 +991,7 @@ tl_object_ptr ReplyMarkup::get_input_reply_markup(Con vector> buttons; buttons.reserve(row.size()); for (auto &button : row) { - buttons.push_back(get_input_keyboard_button(contacts_manager, button)); + buttons.push_back(get_input_keyboard_button(user_manager, button)); } rows.push_back(make_tl_object(std::move(buttons))); } @@ -1071,7 +1067,7 @@ static tl_object_ptr get_keyboard_button_object(const Ke } static tl_object_ptr get_inline_keyboard_button_object( - ContactsManager *contacts_manager, const InlineKeyboardButton &keyboard_button) { + UserManager *user_manager, const InlineKeyboardButton &keyboard_button) { tl_object_ptr type; switch (keyboard_button.type) { case InlineKeyboardButton::Type::Url: @@ -1110,9 +1106,9 @@ static tl_object_ptr get_inline_keyboard_button_ob type = make_tl_object(keyboard_button.data); break; case InlineKeyboardButton::Type::User: { - bool need_user = contacts_manager != nullptr && !contacts_manager->is_user_bot(contacts_manager->get_my_id()); + bool need_user = user_manager != nullptr && !user_manager->is_user_bot(user_manager->get_my_id()); auto user_id = - need_user ? contacts_manager->get_user_id_object(keyboard_button.user_id, "get_inline_keyboard_button_object") + need_user ? user_manager->get_user_id_object(keyboard_button.user_id, "get_inline_keyboard_button_object") : keyboard_button.user_id.get(); type = make_tl_object(user_id); break; @@ -1127,7 +1123,7 @@ static tl_object_ptr get_inline_keyboard_button_ob return make_tl_object(keyboard_button.text, std::move(type)); } -tl_object_ptr ReplyMarkup::get_reply_markup_object(ContactsManager *contacts_manager) const { +tl_object_ptr ReplyMarkup::get_reply_markup_object(UserManager *user_manager) const { switch (type) { case ReplyMarkup::Type::InlineKeyboard: { vector>> rows; @@ -1136,7 +1132,7 @@ tl_object_ptr ReplyMarkup::get_reply_markup_object(Contacts vector> buttons; buttons.reserve(row.size()); for (auto &button : row) { - buttons.push_back(get_inline_keyboard_button_object(contacts_manager, button)); + buttons.push_back(get_inline_keyboard_button_object(user_manager, button)); } rows.push_back(std::move(buttons)); } @@ -1190,22 +1186,22 @@ Status ReplyMarkup::check_shared_dialog_count(int32 button_id, size_t count) con return Status::Error(400, "Button not found"); } -tl_object_ptr get_input_reply_markup(ContactsManager *contacts_manager, +tl_object_ptr get_input_reply_markup(UserManager *user_manager, const unique_ptr &reply_markup) { if (reply_markup == nullptr) { return nullptr; } - return reply_markup->get_input_reply_markup(contacts_manager); + return reply_markup->get_input_reply_markup(user_manager); } -tl_object_ptr get_reply_markup_object(ContactsManager *contacts_manager, +tl_object_ptr get_reply_markup_object(UserManager *user_manager, const unique_ptr &reply_markup) { if (reply_markup == nullptr) { return nullptr; } - return reply_markup->get_reply_markup_object(contacts_manager); + return reply_markup->get_reply_markup_object(user_manager); } void add_reply_markup_dependencies(Dependencies &dependencies, const ReplyMarkup *reply_markup) { diff --git a/lib/tgchat/ext/td/td/telegram/ReplyMarkup.h b/lib/tgchat/ext/td/td/telegram/ReplyMarkup.h index e1b5dd3d..5f4ad3d7 100644 --- a/lib/tgchat/ext/td/td/telegram/ReplyMarkup.h +++ b/lib/tgchat/ext/td/td/telegram/ReplyMarkup.h @@ -18,8 +18,8 @@ namespace td { -class ContactsManager; class Dependencies; +class UserManager; struct KeyboardButton { // append only @@ -86,9 +86,9 @@ struct ReplyMarkup { StringBuilder &print(StringBuilder &string_builder) const; - tl_object_ptr get_input_reply_markup(ContactsManager *contacts_manager) const; + tl_object_ptr get_input_reply_markup(UserManager *user_manager) const; - tl_object_ptr get_reply_markup_object(ContactsManager *contacts_manager) const; + tl_object_ptr get_reply_markup_object(UserManager *user_manager) const; Status check_shared_dialog(Td *td, int32 button_id, DialogId dialog_id) const; @@ -108,14 +108,14 @@ Result> get_reply_markup(td_api::object_ptr> get_reply_markup(td_api::object_ptr &&reply_markup_ptr, - DialogId dialog_id, bool is_bot, bool is_anonymous); + DialogType dialog_type, bool is_bot, bool is_anonymous); unique_ptr dup_reply_markup(const unique_ptr &reply_markup); -tl_object_ptr get_input_reply_markup(ContactsManager *contacts_manager, +tl_object_ptr get_input_reply_markup(UserManager *user_manager, const unique_ptr &reply_markup); -tl_object_ptr get_reply_markup_object(ContactsManager *contacts_manager, +tl_object_ptr get_reply_markup_object(UserManager *user_manager, const unique_ptr &reply_markup); void add_reply_markup_dependencies(Dependencies &dependencies, const ReplyMarkup *reply_markup); diff --git a/lib/tgchat/ext/td/td/telegram/RequestedDialogType.cpp b/lib/tgchat/ext/td/td/telegram/RequestedDialogType.cpp index 63c98c27..2afedb1c 100644 --- a/lib/tgchat/ext/td/td/telegram/RequestedDialogType.cpp +++ b/lib/tgchat/ext/td/td/telegram/RequestedDialogType.cpp @@ -7,8 +7,9 @@ #include "td/telegram/RequestedDialogType.h" #include "td/telegram/ChannelType.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" namespace td { @@ -21,6 +22,9 @@ RequestedDialogType::RequestedDialogType(td_api::object_ptruser_is_bot_; restrict_is_premium_ = request_users->restrict_user_is_premium_; is_premium_ = request_users->user_is_premium_; + request_name_ = request_users->request_name_; + request_username_ = request_users->request_username_; + request_photo_ = request_users->request_photo_; } RequestedDialogType::RequestedDialogType(td_api::object_ptr &&request_dialog) { @@ -38,6 +42,9 @@ RequestedDialogType::RequestedDialogType(td_api::object_ptrchat_is_channel_ ? ChannelType::Broadcast : ChannelType::Megagroup; user_administrator_rights_ = AdministratorRights(request_dialog->user_administrator_rights_, channel_type); bot_administrator_rights_ = AdministratorRights(request_dialog->bot_administrator_rights_, channel_type); + request_name_ = request_dialog->request_title_; + request_username_ = request_dialog->request_username_; + request_photo_ = request_dialog->request_photo_; } RequestedDialogType::RequestedDialogType(telegram_api::object_ptr &&peer_type, @@ -90,7 +97,8 @@ RequestedDialogType::RequestedDialogType(telegram_api::object_ptr RequestedDialogType::get_keyboard_button_type_object() const { if (type_ == Type::User) { return td_api::make_object( - button_id_, restrict_is_bot_, is_bot_, restrict_is_premium_, is_premium_, max_quantity_); + button_id_, restrict_is_bot_, is_bot_, restrict_is_premium_, is_premium_, max_quantity_, request_name_, + request_username_, request_photo_); } else { auto user_administrator_rights = restrict_user_administrator_rights_ ? user_administrator_rights_.get_chat_administrator_rights_object() @@ -99,7 +107,8 @@ td_api::object_ptr RequestedDialogType::get_keyboard restrict_bot_administrator_rights_ ? bot_administrator_rights_.get_chat_administrator_rights_object() : nullptr; return td_api::make_object( button_id_, type_ == Type::Channel, restrict_is_forum_, is_forum_, restrict_has_username_, has_username_, - is_created_, std::move(user_administrator_rights), std::move(bot_administrator_rights), bot_is_participant_); + is_created_, std::move(user_administrator_rights), std::move(bot_administrator_rights), bot_is_participant_, + request_name_, request_username_, request_photo_); } } @@ -171,12 +180,25 @@ telegram_api::object_ptr RequestedDialogType::get } } -int32 RequestedDialogType::get_button_id() const { - return button_id_; +telegram_api::object_ptr +RequestedDialogType::get_input_keyboard_button_request_peer(const string &text) const { + int32 flags = 0; + if (request_name_) { + flags |= telegram_api::inputKeyboardButtonRequestPeer::NAME_REQUESTED_MASK; + } + if (request_username_) { + flags |= telegram_api::inputKeyboardButtonRequestPeer::USERNAME_REQUESTED_MASK; + } + if (request_photo_) { + flags |= telegram_api::inputKeyboardButtonRequestPeer::PHOTO_REQUESTED_MASK; + } + return telegram_api::make_object( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, text, button_id_, + get_input_request_peer_type_object(), max_quantity_); } -int32 RequestedDialogType::get_max_quantity() const { - return max_quantity_; +int32 RequestedDialogType::get_button_id() const { + return button_id_; } Status RequestedDialogType::check_shared_dialog(Td *td, DialogId dialog_id) const { @@ -186,10 +208,10 @@ Status RequestedDialogType::check_shared_dialog(Td *td, DialogId dialog_id) cons return Status::Error(400, "Wrong chat type"); } auto user_id = dialog_id.get_user_id(); - if (restrict_is_bot_ && td->contacts_manager_->is_user_bot(user_id) != is_bot_) { + if (restrict_is_bot_ && td->user_manager_->is_user_bot(user_id) != is_bot_) { return Status::Error(400, "Wrong is_bot value"); } - if (restrict_is_premium_ && td->contacts_manager_->is_user_premium(user_id) != is_premium_) { + if (restrict_is_premium_ && td->user_manager_->is_user_premium(user_id) != is_premium_) { return Status::Error(400, "Wrong is_premium value"); } break; @@ -205,10 +227,10 @@ Status RequestedDialogType::check_shared_dialog(Td *td, DialogId dialog_id) cons return Status::Error(400, "Wrong has_username value"); } auto chat_id = dialog_id.get_chat_id(); - if (!td->contacts_manager_->get_chat_is_active(chat_id)) { + if (!td->chat_manager_->get_chat_is_active(chat_id)) { return Status::Error(400, "Chat is deactivated"); } - auto status = td->contacts_manager_->get_chat_status(chat_id); + auto status = td->chat_manager_->get_chat_status(chat_id); if (is_created_ && !status.is_creator()) { return Status::Error(400, "The chat must be created by the current user"); } @@ -231,23 +253,23 @@ Status RequestedDialogType::check_shared_dialog(Td *td, DialogId dialog_id) cons } case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - bool is_broadcast = td->contacts_manager_->is_broadcast_channel(channel_id); + bool is_broadcast = td->chat_manager_->is_broadcast_channel(channel_id); if (type_ != (is_broadcast ? Type::Channel : Type::Group)) { return Status::Error(400, "Wrong chat type"); } - if (!is_broadcast && restrict_is_forum_ && td->contacts_manager_->is_forum_channel(channel_id) != is_forum_) { + if (!is_broadcast && restrict_is_forum_ && td->chat_manager_->is_forum_channel(channel_id) != is_forum_) { return Status::Error(400, "Wrong is_forum value"); } if (restrict_has_username_ && - td->contacts_manager_->get_channel_first_username(channel_id).empty() == has_username_) { + td->chat_manager_->get_channel_first_username(channel_id).empty() == has_username_) { return Status::Error(400, "Wrong has_username value"); } - auto status = td->contacts_manager_->get_channel_status(channel_id); + auto status = td->chat_manager_->get_channel_status(channel_id); if (is_created_ && !status.is_creator()) { return Status::Error(400, "The chat must be created by the current user"); } - bool can_invite_bot = status.can_invite_users() && - (status.is_administrator() || !td->contacts_manager_->is_channel_public(channel_id)); + bool can_invite_bot = + status.can_invite_users() && (status.is_administrator() || !td->chat_manager_->is_channel_public(channel_id)); if (!is_broadcast && bot_is_participant_) { // can't synchronously check that the bot is already participant if (!can_invite_bot) { diff --git a/lib/tgchat/ext/td/td/telegram/RequestedDialogType.h b/lib/tgchat/ext/td/td/telegram/RequestedDialogType.h index f55d3f4f..aba0e3b7 100644 --- a/lib/tgchat/ext/td/td/telegram/RequestedDialogType.h +++ b/lib/tgchat/ext/td/td/telegram/RequestedDialogType.h @@ -28,6 +28,10 @@ class RequestedDialogType { bool restrict_is_premium_ = false; // User only bool is_premium_ = false; // User only + bool request_name_ = false; + bool request_username_ = false; + bool request_photo_ = false; + bool restrict_is_forum_ = false; // Group only bool is_forum_ = false; // Group only bool bot_is_participant_ = false; // Group only @@ -39,6 +43,8 @@ class RequestedDialogType { AdministratorRights user_administrator_rights_; // Group and Channel only AdministratorRights bot_administrator_rights_; // Group and Channel only + telegram_api::object_ptr get_input_request_peer_type_object() const; + public: RequestedDialogType() = default; @@ -51,12 +57,11 @@ class RequestedDialogType { td_api::object_ptr get_keyboard_button_type_object() const; - telegram_api::object_ptr get_input_request_peer_type_object() const; + telegram_api::object_ptr get_input_keyboard_button_request_peer( + const string &text) const; int32 get_button_id() const; - int32 get_max_quantity() const; - Status check_shared_dialog(Td *td, DialogId dialog_id) const; Status check_shared_dialog_count(size_t count) const; diff --git a/lib/tgchat/ext/td/td/telegram/SavedMessagesManager.cpp b/lib/tgchat/ext/td/td/telegram/SavedMessagesManager.cpp index eba0299d..4859b908 100644 --- a/lib/tgchat/ext/td/td/telegram/SavedMessagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SavedMessagesManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AffectedHistory.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -18,6 +18,7 @@ #include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -584,8 +585,8 @@ void SavedMessagesManager::on_get_saved_messages_topics( default: UNREACHABLE(); } - td_->contacts_manager_->on_get_users(std::move(users), "on_get_saved_messages_topics"); - td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_saved_messages_topics"); + td_->user_manager_->on_get_users(std::move(users), "on_get_saved_messages_topics"); + td_->chat_manager_->on_get_chats(std::move(chats), "on_get_saved_messages_topics"); FlatHashMap, MessageIdHash> message_id_to_message; for (auto &message : messages) { diff --git a/lib/tgchat/ext/td/td/telegram/SavedMessagesTopicId.cpp b/lib/tgchat/ext/td/td/telegram/SavedMessagesTopicId.cpp index da0bb796..61df0fa8 100644 --- a/lib/tgchat/ext/td/td/telegram/SavedMessagesTopicId.cpp +++ b/lib/tgchat/ext/td/td/telegram/SavedMessagesTopicId.cpp @@ -67,7 +67,7 @@ bool SavedMessagesTopicId::have_input_peer(Td *td) const { !td->dialog_manager_->have_dialog_info_force(dialog_id_, "SavedMessagesTopicId::have_input_peer")) { return false; } - return td->dialog_manager_->have_input_peer(dialog_id_, AccessRights::Know); + return td->dialog_manager_->have_input_peer(dialog_id_, false, AccessRights::Know); } Status SavedMessagesTopicId::is_valid_status(Td *td) const { @@ -75,7 +75,7 @@ Status SavedMessagesTopicId::is_valid_status(Td *td) const { return Status::Error(400, "Invalid Saved Messages topic specified"); } if (!have_input_peer(td)) { - return Status::Error(400, "Invalid Saved Messages topic specified"); + return Status::Error(400, "Unknown Saved Messages topic specified"); } return Status::OK(); } @@ -86,7 +86,7 @@ Status SavedMessagesTopicId::is_valid_in(Td *td, DialogId dialog_id) const { return Status::Error(400, "Can't use Saved Messages topic in the chat"); } if (!have_input_peer(td)) { - return Status::Error(400, "Invalid Saved Messages topic specified"); + return Status::Error(400, "Unknown Saved Messages topic specified"); } } return Status::OK(); diff --git a/lib/tgchat/ext/td/td/telegram/SecretChatActor.cpp b/lib/tgchat/ext/td/td/telegram/SecretChatActor.cpp index 421dc9f7..2acd2dc5 100644 --- a/lib/tgchat/ext/td/td/telegram/SecretChatActor.cpp +++ b/lib/tgchat/ext/td/td/telegram/SecretChatActor.cpp @@ -67,7 +67,7 @@ SecretChatActor::SecretChatActor(int32 id, unique_ptr context, bool can template NetQueryPtr SecretChatActor::create_net_query(QueryType type, const T &function) { return context_->net_query_creator().create(UniqueId::next(UniqueId::Type::Default, static_cast(type)), - function, {}, DcId::main(), NetQuery::Type::Common, + nullptr, function, {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); } diff --git a/lib/tgchat/ext/td/td/telegram/SecretChatActor.h b/lib/tgchat/ext/td/td/telegram/SecretChatActor.h index 30d86ae8..0bfb6705 100644 --- a/lib/tgchat/ext/td/td/telegram/SecretChatActor.h +++ b/lib/tgchat/ext/td/td/telegram/SecretChatActor.h @@ -71,7 +71,7 @@ class SecretChatActor final : public NetQueryCallback { virtual bool close_flag() = 0; - // We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager. + // We don't want to expose the whole NetQueryDispatcher, MessagesManager and UserManager. // So it is more clear which parts of MessagesManager are really used. And it is much easier to create tests. virtual void send_net_query(NetQueryPtr query, ActorShared callback, bool ordered) = 0; diff --git a/lib/tgchat/ext/td/td/telegram/SecretChatsManager.cpp b/lib/tgchat/ext/td/td/telegram/SecretChatsManager.cpp index a7d92180..966ce39b 100644 --- a/lib/tgchat/ext/td/td/telegram/SecretChatsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SecretChatsManager.cpp @@ -6,7 +6,6 @@ // #include "td/telegram/SecretChatsManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DhCache.h" #include "td/telegram/EncryptedFile.h" #include "td/telegram/FolderId.h" @@ -20,6 +19,7 @@ #include "td/telegram/StateManager.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/mtproto/DhCallback.h" @@ -168,7 +168,7 @@ void SecretChatsManager::on_update_chat(tl_object_ptrchat_->get_id() == telegram_api::encryptedChatRequested::ID) { -#if TD_ANDROID || TD_DARWIN_IOS || TD_DARWIN_WATCH_OS || TD_TIZEN +#if TD_ANDROID || TD_DARWIN_IOS pending_update.offline_process_time_ = Timestamp::in(1.0); #else pending_update.online_process_time_ = Timestamp::in(2.0); @@ -366,8 +366,8 @@ unique_ptr SecretChatsManager::make_secret_chat_contex void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer, FolderId initial_folder_id) final { - send_closure(G()->contacts_manager(), &ContactsManager::on_update_secret_chat, secret_chat_id_, access_hash, - user_id, state, is_outbound, ttl, date, key_hash, layer, initial_folder_id); + send_closure(G()->user_manager(), &UserManager::on_update_secret_chat, secret_chat_id_, access_hash, user_id, + state, is_outbound, ttl, date, key_hash, layer, initial_folder_id); } void on_inbound_message(UserId user_id, MessageId message_id, int32 date, unique_ptr file, diff --git a/lib/tgchat/ext/td/td/telegram/SecureManager.cpp b/lib/tgchat/ext/td/td/telegram/SecureManager.cpp index 36c0b345..b1b0c768 100644 --- a/lib/tgchat/ext/td/td/telegram/SecureManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SecureManager.cpp @@ -6,7 +6,6 @@ // #include "td/telegram/SecureManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/files/FileId.h" #include "td/telegram/files/FileManager.h" @@ -17,6 +16,7 @@ #include "td/telegram/PasswordManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -987,8 +987,8 @@ void SecureManager::on_get_passport_authorization_form( auto authorization_form = r_authorization_form.move_as_ok(); LOG(INFO) << "Receive " << to_string(authorization_form); - G()->td().get_actor_unsafe()->contacts_manager_->on_get_users(std::move(authorization_form->users_), - "on_get_passport_authorization_form"); + G()->td().get_actor_unsafe()->user_manager_->on_get_users(std::move(authorization_form->users_), + "on_get_passport_authorization_form"); vector> required_types; std::map all_types; diff --git a/lib/tgchat/ext/td/td/telegram/SendCodeHelper.cpp b/lib/tgchat/ext/td/td/telegram/SendCodeHelper.cpp index 4c1df046..8a284f4f 100644 --- a/lib/tgchat/ext/td/td/telegram/SendCodeHelper.cpp +++ b/lib/tgchat/ext/td/td/telegram/SendCodeHelper.cpp @@ -6,9 +6,13 @@ // #include "td/telegram/SendCodeHelper.h" +#include "td/telegram/misc.h" + #include "td/utils/base64.h" #include "td/utils/buffer.h" +#include "td/utils/logging.h" #include "td/utils/Time.h" +#include "td/utils/utf8.h" namespace td { @@ -19,7 +23,8 @@ void SendCodeHelper::on_sent_code(telegram_api::object_ptrtimeout_; if (next_code_info_.type == AuthenticationCodeInfo::Type::None && - (sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseAndroid || + (sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseAndroidSafetyNet || + sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseAndroidPlayIntegrity || sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseIos)) { next_code_info_ = {AuthenticationCodeInfo::Type::Sms, sent_code_info_.length, string()}; } @@ -40,11 +45,20 @@ td_api::object_ptr SendCodeHelper::get_authentic max(static_cast(next_code_timestamp_ - Time::now() + 1 - 1e-9), 0)); } -Result SendCodeHelper::resend_code() const { +Result SendCodeHelper::resend_code( + td_api::object_ptr &&reason) const { if (next_code_info_.type == AuthenticationCodeInfo::Type::None) { return Status::Error(400, "Authentication code can't be resend"); } - return telegram_api::auth_resendCode(phone_number_, phone_code_hash_); + int32 flags = 0; + string reason_str; + if (reason->get_id() == td_api::resendCodeReasonVerificationFailed::ID) { + reason_str = std::move(static_cast(reason.get())->error_message_); + } + if (!reason_str.empty() && clean_input_string(reason_str)) { + flags |= telegram_api::auth_resendCode::REASON_MASK; + } + return telegram_api::auth_resendCode(flags, phone_number_, phone_code_hash_, reason_str); } telegram_api::object_ptr SendCodeHelper::get_input_code_settings(const Settings &settings) { @@ -62,6 +76,9 @@ telegram_api::object_ptr SendCodeHelper::get_input_c if (settings->is_current_phone_number_) { flags |= telegram_api::codeSettings::CURRENT_NUMBER_MASK; } + if (settings->has_unknown_phone_number_) { + flags |= telegram_api::codeSettings::UNKNOWN_NUMBER_MASK; + } if (settings->allow_sms_retriever_api_) { flags |= telegram_api::codeSettings::ALLOW_APP_HASH_MASK; } @@ -89,9 +106,9 @@ telegram_api::object_ptr SendCodeHelper::get_input_c flags |= telegram_api::codeSettings::LOGOUT_TOKENS_MASK; } } - return telegram_api::make_object(flags, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, false /*ignored*/, - std::move(logout_tokens), device_token, is_app_sandbox); + return telegram_api::make_object( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, std::move(logout_tokens), device_token, is_app_sandbox); } telegram_api::auth_sendCode SendCodeHelper::send_code(string phone_number, const Settings &settings, int32 api_id, @@ -102,17 +119,29 @@ telegram_api::auth_sendCode SendCodeHelper::send_code(string phone_number, const telegram_api::auth_requestFirebaseSms SendCodeHelper::request_firebase_sms(const string &token) { string safety_net_token; + string play_integrity_token; string ios_push_secret; int32 flags = 0; #if TD_ANDROID - flags |= telegram_api::auth_requestFirebaseSms::SAFETY_NET_TOKEN_MASK; - safety_net_token = token; + if (sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseAndroidSafetyNet) { + flags |= telegram_api::auth_requestFirebaseSms::SAFETY_NET_TOKEN_MASK; + safety_net_token = token; + } else if (sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseAndroidPlayIntegrity) { + flags |= telegram_api::auth_requestFirebaseSms::PLAY_INTEGRITY_TOKEN_MASK; + play_integrity_token = token; + } #elif TD_DARWIN - flags |= telegram_api::auth_requestFirebaseSms::IOS_PUSH_SECRET_MASK; - ios_push_secret = token; + if (sent_code_info_.type == AuthenticationCodeInfo::Type::FirebaseIos) { + flags |= telegram_api::auth_requestFirebaseSms::IOS_PUSH_SECRET_MASK; + ios_push_secret = token; + } #endif return telegram_api::auth_requestFirebaseSms(flags, phone_number_, phone_code_hash_, safety_net_token, - ios_push_secret); + play_integrity_token, ios_push_secret); +} + +telegram_api::auth_reportMissingCode SendCodeHelper::report_missing_code(const string &mobile_network_code) { + return telegram_api::auth_reportMissingCode(phone_number_, phone_code_hash_, mobile_network_code); } telegram_api::account_sendVerifyEmailCode SendCodeHelper::send_verify_email_code(const string &email_address) { @@ -193,16 +222,35 @@ SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_sent_authentication_c } case telegram_api::auth_sentCodeTypeFirebaseSms::ID: { auto code_type = move_tl_object_as(sent_code_type_ptr); +#if TD_ANDROID if ((code_type->flags_ & telegram_api::auth_sentCodeTypeFirebaseSms::NONCE_MASK) != 0) { - return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FirebaseAndroid, code_type->length_, + return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FirebaseAndroidSafetyNet, code_type->length_, code_type->nonce_.as_slice().str()}; } + if ((code_type->flags_ & telegram_api::auth_sentCodeTypeFirebaseSms::PLAY_INTEGRITY_NONCE_MASK) != 0) { + return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FirebaseAndroidPlayIntegrity, code_type->length_, + code_type->play_integrity_nonce_.as_slice().str()}; + } +#elif TD_DARWIN if ((code_type->flags_ & telegram_api::auth_sentCodeTypeFirebaseSms::RECEIPT_MASK) != 0) { return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FirebaseIos, code_type->length_, std::move(code_type->receipt_), code_type->push_timeout_}; } +#endif return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Sms, code_type->length_, ""}; } + case telegram_api::auth_sentCodeTypeSmsWord::ID: { + auto code_type = move_tl_object_as(sent_code_type_ptr); + if (utf8_length(code_type->beginning_) > 1u) { + LOG(ERROR) << "Receive \"" << code_type->beginning_ << "\" as word first letter"; + code_type->beginning_ = string(); + } + return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::SmsWord, 0, code_type->beginning_}; + } + case telegram_api::auth_sentCodeTypeSmsPhrase::ID: { + auto code_type = move_tl_object_as(sent_code_type_ptr); + return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::SmsPhrase, 0, code_type->beginning_}; + } case telegram_api::auth_sentCodeTypeEmailCode::ID: case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID: default: @@ -230,12 +278,19 @@ td_api::object_ptr SendCodeHelper::get_authentic case AuthenticationCodeInfo::Type::Fragment: return td_api::make_object(authentication_code_info.pattern, authentication_code_info.length); - case AuthenticationCodeInfo::Type::FirebaseAndroid: - return td_api::make_object(authentication_code_info.pattern, + case AuthenticationCodeInfo::Type::FirebaseAndroidSafetyNet: + return td_api::make_object(false, authentication_code_info.pattern, + authentication_code_info.length); + case AuthenticationCodeInfo::Type::FirebaseAndroidPlayIntegrity: + return td_api::make_object(true, authentication_code_info.pattern, authentication_code_info.length); case AuthenticationCodeInfo::Type::FirebaseIos: return td_api::make_object( authentication_code_info.pattern, authentication_code_info.push_timeout, authentication_code_info.length); + case AuthenticationCodeInfo::Type::SmsWord: + return td_api::make_object(authentication_code_info.pattern); + case AuthenticationCodeInfo::Type::SmsPhrase: + return td_api::make_object(authentication_code_info.pattern); default: UNREACHABLE(); return nullptr; diff --git a/lib/tgchat/ext/td/td/telegram/SendCodeHelper.h b/lib/tgchat/ext/td/td/telegram/SendCodeHelper.h index 194263be..67d57e4e 100644 --- a/lib/tgchat/ext/td/td/telegram/SendCodeHelper.h +++ b/lib/tgchat/ext/td/td/telegram/SendCodeHelper.h @@ -25,7 +25,7 @@ class SendCodeHelper { td_api::object_ptr get_authentication_code_info_object() const; - Result resend_code() const; + Result resend_code(td_api::object_ptr &&reason) const; using Settings = td_api::object_ptr; @@ -34,6 +34,8 @@ class SendCodeHelper { telegram_api::auth_requestFirebaseSms request_firebase_sms(const string &token); + telegram_api::auth_reportMissingCode report_missing_code(const string &mobile_network_code); + telegram_api::account_sendVerifyEmailCode send_verify_email_code(const string &email_address); telegram_api::account_sendChangePhoneCode send_change_phone_code(Slice phone_number, const Settings &settings); @@ -59,7 +61,20 @@ class SendCodeHelper { private: struct AuthenticationCodeInfo { - enum class Type : int32 { None, Message, Sms, Call, FlashCall, MissedCall, Fragment, FirebaseAndroid, FirebaseIos }; + enum class Type : int32 { + None, + Message, + Sms, + Call, + FlashCall, + MissedCall, + Fragment, + FirebaseAndroidSafetyNet, + FirebaseIos, + SmsWord, + SmsPhrase, + FirebaseAndroidPlayIntegrity + }; Type type = Type::None; int32 length = 0; int32 push_timeout = 0; diff --git a/lib/tgchat/ext/td/td/telegram/SharedDialog.cpp b/lib/tgchat/ext/td/td/telegram/SharedDialog.cpp new file mode 100644 index 00000000..edcfb44f --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SharedDialog.cpp @@ -0,0 +1,83 @@ +// +// 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/SharedDialog.h" + +#include "td/telegram/AuthManager.h" +#include "td/telegram/ChannelId.h" +#include "td/telegram/ChatId.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/Td.h" +#include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" + +namespace td { + +SharedDialog::SharedDialog(Td *td, telegram_api::object_ptr &&requested_peer_ptr) { + CHECK(requested_peer_ptr != nullptr); + switch (requested_peer_ptr->get_id()) { + case telegram_api::requestedPeerUser::ID: { + auto requested_peer = telegram_api::move_object_as(requested_peer_ptr); + dialog_id_ = DialogId(UserId(requested_peer->user_id_)); + first_name_ = std::move(requested_peer->first_name_); + last_name_ = std::move(requested_peer->last_name_); + username_ = std::move(requested_peer->username_); + photo_ = get_photo(td, std::move(requested_peer->photo_), dialog_id_); + break; + } + case telegram_api::requestedPeerChat::ID: { + auto requested_peer = telegram_api::move_object_as(requested_peer_ptr); + dialog_id_ = DialogId(ChatId(requested_peer->chat_id_)); + first_name_ = std::move(requested_peer->title_); + photo_ = get_photo(td, std::move(requested_peer->photo_), dialog_id_); + break; + } + case telegram_api::requestedPeerChannel::ID: { + auto requested_peer = telegram_api::move_object_as(requested_peer_ptr); + dialog_id_ = DialogId(ChannelId(requested_peer->channel_id_)); + first_name_ = std::move(requested_peer->title_); + username_ = std::move(requested_peer->username_); + photo_ = get_photo(td, std::move(requested_peer->photo_), dialog_id_); + break; + } + default: + UNREACHABLE(); + } +} + +td_api::object_ptr SharedDialog::get_shared_user_object(Td *td) const { + CHECK(is_user()); + auto user_id = td->auth_manager_->is_bot() + ? dialog_id_.get_user_id().get() + : td->user_manager_->get_user_id_object(dialog_id_.get_user_id(), "sharedUser"); + return td_api::make_object(user_id, first_name_, last_name_, username_, + get_photo_object(td->file_manager_.get(), photo_)); +} + +td_api::object_ptr SharedDialog::get_shared_chat_object(Td *td) const { + CHECK(is_dialog()); + auto chat_id = td->auth_manager_->is_bot() ? dialog_id_.get() + : td->dialog_manager_->get_chat_id_object(dialog_id_, "sharedChat"); + return td_api::make_object(chat_id, first_name_, username_, + get_photo_object(td->file_manager_.get(), photo_)); +} + +bool operator==(const SharedDialog &lhs, const SharedDialog &rhs) { + return lhs.dialog_id_ == rhs.dialog_id_ && lhs.first_name_ == rhs.first_name_ && lhs.last_name_ == rhs.last_name_ && + lhs.username_ == rhs.username_ && lhs.photo_ == rhs.photo_; +} + +bool operator!=(const SharedDialog &lhs, const SharedDialog &rhs) { + return !(lhs == rhs); +} + +StringBuilder &operator<<(StringBuilder &string_builder, const SharedDialog &shared_dialog) { + return string_builder << "shared " << shared_dialog.dialog_id_ << '(' << shared_dialog.first_name_ << ' ' + << shared_dialog.last_name_ << ' ' << shared_dialog.username_ << ' ' << shared_dialog.photo_ + << ')'; +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SharedDialog.h b/lib/tgchat/ext/td/td/telegram/SharedDialog.h new file mode 100644 index 00000000..3258cbaf --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SharedDialog.h @@ -0,0 +1,71 @@ +// +// 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/Photo.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 SharedDialog { + public: + SharedDialog() = default; + + explicit SharedDialog(DialogId dialog_id) : dialog_id_(dialog_id) { + } + + SharedDialog(Td *td, telegram_api::object_ptr &&requested_peer_ptr); + + bool is_valid() const { + return dialog_id_.is_valid(); + } + + bool is_user() const { + return dialog_id_.get_type() == DialogType::User; + } + + bool is_dialog() const { + auto dialog_type = dialog_id_.get_type(); + return dialog_type == DialogType::Chat || dialog_type == DialogType::Channel; + } + + td_api::object_ptr get_shared_user_object(Td *td) const; + + td_api::object_ptr get_shared_chat_object(Td *td) const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + + private: + friend bool operator==(const SharedDialog &lhs, const SharedDialog &rhs); + friend bool operator!=(const SharedDialog &lhs, const SharedDialog &rhs); + + friend StringBuilder &operator<<(StringBuilder &string_builder, const SharedDialog &shared_dialog); + + DialogId dialog_id_; + string first_name_; + string last_name_; + string username_; + Photo photo_; +}; + +bool operator==(const SharedDialog &lhs, const SharedDialog &rhs); +bool operator!=(const SharedDialog &lhs, const SharedDialog &rhs); + +StringBuilder &operator<<(StringBuilder &string_builder, const SharedDialog &shared_dialog); + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SharedDialog.hpp b/lib/tgchat/ext/td/td/telegram/SharedDialog.hpp new file mode 100644 index 00000000..ae050612 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SharedDialog.hpp @@ -0,0 +1,70 @@ +// +// 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/Photo.hpp" +#include "td/telegram/SharedDialog.h" + +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void SharedDialog::store(StorerT &storer) const { + bool has_first_name = !first_name_.empty(); + bool has_last_name = !last_name_.empty(); + bool has_username = !username_.empty(); + bool has_photo = !photo_.is_empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_first_name); + STORE_FLAG(has_last_name); + STORE_FLAG(has_username); + STORE_FLAG(has_photo); + END_STORE_FLAGS(); + td::store(dialog_id_, storer); + if (has_first_name) { + td::store(first_name_, storer); + } + if (has_last_name) { + td::store(last_name_, storer); + } + if (has_username) { + td::store(username_, storer); + } + if (has_photo) { + td::store(photo_, storer); + } +} + +template +void SharedDialog::parse(ParserT &parser) { + bool has_first_name; + bool has_last_name; + bool has_username; + bool has_photo; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_first_name); + PARSE_FLAG(has_last_name); + PARSE_FLAG(has_username); + PARSE_FLAG(has_photo); + END_PARSE_FLAGS(); + td::parse(dialog_id_, parser); + if (has_first_name) { + td::parse(first_name_, parser); + } + if (has_last_name) { + td::parse(last_name_, parser); + } + if (has_username) { + td::parse(username_, parser); + } + if (has_photo) { + td::parse(photo_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp index 94859b86..65d7bf99 100644 --- a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.cpp @@ -6,28 +6,27 @@ // #include "td/telegram/SponsoredMessageManager.h" +#include "td/telegram/AccentColorId.h" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" -#include "td/telegram/DialogInviteLinkManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" -#include "td/telegram/LinkManager.h" #include "td/telegram/MessageContent.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/MessageSelfDestructType.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/OptionManager.h" +#include "td/telegram/PeerColor.h" #include "td/telegram/Photo.h" -#include "td/telegram/ServerMessageId.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" #include "td/telegram/UserId.h" -#include "td/telegram/WebApp.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" -#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" namespace td { @@ -44,7 +43,7 @@ class GetSponsoredMessagesQuery final : public Td::ResultHandler { void send(ChannelId channel_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Chat info not found")); } @@ -63,7 +62,7 @@ class GetSponsoredMessagesQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetSponsoredMessagesQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetSponsoredMessagesQuery"); promise_.set_error(std::move(status)); } }; @@ -74,7 +73,7 @@ class ViewSponsoredMessageQuery final : public Td::ResultHandler { public: void send(ChannelId channel_id, const string &message_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return; } @@ -90,7 +89,7 @@ class ViewSponsoredMessageQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ViewSponsoredMessageQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ViewSponsoredMessageQuery"); } }; @@ -104,7 +103,7 @@ class ClickSponsoredMessageQuery final : public Td::ResultHandler { void send(ChannelId channel_id, const string &message_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_value(Unit()); } @@ -121,7 +120,70 @@ class ClickSponsoredMessageQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ClickSponsoredMessageQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ClickSponsoredMessageQuery"); + promise_.set_error(std::move(status)); + } +}; + +class ReportSponsoredMessageQuery final : public Td::ResultHandler { + Promise> promise_; + ChannelId channel_id_; + + public: + explicit ReportSponsoredMessageQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, const string &message_id, const string &option_id) { + channel_id_ = channel_id; + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return promise_.set_value(td_api::make_object()); + } + send_query(G()->net_query_creator().create(telegram_api::channels_reportSponsoredMessage( + std::move(input_channel), BufferSlice(message_id), BufferSlice(option_id)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for ReportSponsoredMessageQuery: " << to_string(ptr); + switch (ptr->get_id()) { + case telegram_api::channels_sponsoredMessageReportResultReported::ID: + return promise_.set_value(td_api::make_object()); + case telegram_api::channels_sponsoredMessageReportResultAdsHidden::ID: + return promise_.set_value(td_api::make_object()); + case telegram_api::channels_sponsoredMessageReportResultChooseOption::ID: { + auto options = + telegram_api::move_object_as(ptr); + if (options->options_.empty()) { + return promise_.set_value(td_api::make_object()); + } + vector> report_options; + for (auto &option : options->options_) { + report_options.push_back(td_api::make_object( + option->option_.as_slice().str(), option->text_)); + } + return promise_.set_value(td_api::make_object( + options->title_, std::move(report_options))); + } + default: + UNREACHABLE(); + } + } + + void on_error(Status status) final { + if (status.message() == "AD_EXPIRED") { + return promise_.set_value(td_api::make_object()); + } + if (status.message() == "PREMIUM_ACCOUNT_REQUIRED") { + return promise_.set_value(td_api::make_object()); + } + td_->chat_manager_->on_get_channel_error(channel_id_, status, "ReportSponsoredMessageQuery"); promise_.set_error(std::move(status)); } }; @@ -129,39 +191,30 @@ class ClickSponsoredMessageQuery final : public Td::ResultHandler { struct SponsoredMessageManager::SponsoredMessage { int64 local_id = 0; bool is_recommended = false; - bool show_dialog_photo = false; - DialogId sponsor_dialog_id; - ServerMessageId server_message_id; - string start_param; - string invite_hash; - WebApp web_app; + bool can_be_reported = false; unique_ptr content; + string url; + Photo photo; + string title; string button_text; + PeerColor peer_color; string sponsor_info; string additional_info; - string site_url; - string site_name; - DialogPhoto site_photo; - - SponsoredMessage(int64 local_id, bool is_recommended, bool show_dialog_photo, DialogId sponsor_dialog_id, - ServerMessageId server_message_id, string start_param, string invite_hash, WebApp web_app, - unique_ptr content, string button_text, string sponsor_info, string additional_info, - string site_url, string site_name, DialogPhoto site_photo) + + SponsoredMessage(int64 local_id, bool is_recommended, bool can_be_reported, unique_ptr content, + string url, Photo photo, string title, string button_text, PeerColor peer_color, string sponsor_info, + string additional_info) : local_id(local_id) , is_recommended(is_recommended) - , show_dialog_photo(show_dialog_photo) - , sponsor_dialog_id(sponsor_dialog_id) - , server_message_id(server_message_id) - , start_param(std::move(start_param)) - , invite_hash(std::move(invite_hash)) - , web_app(std::move(web_app)) + , can_be_reported(can_be_reported) , content(std::move(content)) + , url(std::move(url)) + , photo(std::move(photo)) + , title(std::move(title)) , button_text(std::move(button_text)) + , peer_color(std::move(peer_color)) , sponsor_info(std::move(sponsor_info)) - , additional_info(std::move(additional_info)) - , site_url(std::move(site_url)) - , site_name(std::move(site_name)) - , site_photo(std::move(site_photo)) { + , additional_info(std::move(additional_info)) { } }; @@ -214,86 +267,9 @@ void SponsoredMessageManager::delete_cached_sponsored_messages(DialogId dialog_i td_api::object_ptr SponsoredMessageManager::get_message_sponsor_object( const SponsoredMessage &sponsored_message) const { - td_api::object_ptr type; - td_api::object_ptr photo; - switch (sponsored_message.sponsor_dialog_id.get_type()) { - case DialogType::User: { - auto user_id = sponsored_message.sponsor_dialog_id.get_user_id(); - if (!td_->contacts_manager_->is_user_bot(user_id)) { - LOG(ERROR) << "Sponsor " << user_id << " is not a bot"; - return nullptr; - } - auto bot_username = td_->contacts_manager_->get_user_first_username(user_id); - if (bot_username.empty()) { - LOG(ERROR) << "Sponsor " << user_id << " has no username"; - return nullptr; - } - if (!sponsored_message.web_app.is_empty()) { - type = sponsored_message.web_app.get_message_sponsor_type_web_app(bot_username, sponsored_message.start_param); - } else { - type = td_api::make_object( - td_->contacts_manager_->get_user_id_object(user_id, "messageSponsorTypeBot"), - td_api::make_object(bot_username, sponsored_message.start_param, false)); - } - if (sponsored_message.show_dialog_photo) { - photo = get_chat_photo_info_object(td_->file_manager_.get(), - td_->contacts_manager_->get_user_dialog_photo(user_id)); - } - break; - } - case DialogType::Channel: { - auto channel_id = sponsored_message.sponsor_dialog_id.get_channel_id(); - if (!td_->contacts_manager_->is_broadcast_channel(channel_id)) { - LOG(ERROR) << "Sponsor " << channel_id << " is not a channel"; - return nullptr; - } - td_api::object_ptr link; - if (sponsored_message.server_message_id.is_valid()) { - link = td_api::make_object( - PSTRING() << LinkManager::get_t_me_url() << "c/" << channel_id.get() << '/' - << sponsored_message.server_message_id.get()); - } - type = td_api::make_object( - td_->dialog_manager_->get_chat_id_object(sponsored_message.sponsor_dialog_id, "sponsoredMessage"), - std::move(link)); - if (sponsored_message.show_dialog_photo) { - photo = get_chat_photo_info_object(td_->file_manager_.get(), - td_->contacts_manager_->get_channel_dialog_photo(channel_id)); - } - break; - } - case DialogType::None: { - if (sponsored_message.invite_hash.empty()) { - if (sponsored_message.site_url.empty()) { - return nullptr; - } - type = td_api::make_object(sponsored_message.site_url, - sponsored_message.site_name); - if (sponsored_message.show_dialog_photo) { - photo = get_chat_photo_info_object(td_->file_manager_.get(), &sponsored_message.site_photo); - } - break; - } - auto invite_link = LinkManager::get_dialog_invite_link(sponsored_message.invite_hash, false); - auto chat_invite_link_info = td_->dialog_invite_link_manager_->get_chat_invite_link_info_object(invite_link); - if (chat_invite_link_info == nullptr) { - LOG(ERROR) << "Failed to get invite link info for " << invite_link; - return nullptr; - } - if (chat_invite_link_info->type_->get_id() != td_api::inviteLinkChatTypeChannel::ID) { - LOG(ERROR) << "Receive sponsor chat of a wrong type " << to_string(chat_invite_link_info->type_); - return nullptr; - } - type = td_api::make_object(chat_invite_link_info->title_, invite_link); - if (sponsored_message.show_dialog_photo) { - photo = std::move(chat_invite_link_info->photo_); - } - break; - } - default: - break; - } - return td_api::make_object(std::move(type), std::move(photo), sponsored_message.sponsor_info); + return td_api::make_object( + sponsored_message.url, get_photo_object(td_->file_manager_.get(), sponsored_message.photo), + sponsored_message.sponsor_info); } td_api::object_ptr SponsoredMessageManager::get_sponsored_message_object( @@ -303,9 +279,11 @@ td_api::object_ptr SponsoredMessageManager::get_sponso return nullptr; } return td_api::make_object( - sponsored_message.local_id, sponsored_message.is_recommended, + sponsored_message.local_id, sponsored_message.is_recommended, sponsored_message.can_be_reported, get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1, false, false), - std::move(sponsor), sponsored_message.button_text, sponsored_message.additional_info); + 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); } td_api::object_ptr SponsoredMessageManager::get_sponsored_messages_object( @@ -376,63 +354,17 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( auto sponsored_messages = telegram_api::move_object_as(sponsored_messages_ptr); - td_->contacts_manager_->on_get_users(std::move(sponsored_messages->users_), "on_get_dialog_sponsored_messages"); - td_->contacts_manager_->on_get_chats(std::move(sponsored_messages->chats_), "on_get_dialog_sponsored_messages"); + td_->user_manager_->on_get_users(std::move(sponsored_messages->users_), "on_get_dialog_sponsored_messages"); + td_->chat_manager_->on_get_chats(std::move(sponsored_messages->chats_), "on_get_dialog_sponsored_messages"); for (auto &sponsored_message : sponsored_messages->messages_) { - DialogId sponsor_dialog_id; - ServerMessageId server_message_id; - string invite_hash; - string site_url; - string site_name; - DialogPhoto site_photo; - if (sponsored_message->from_id_ != nullptr) { - sponsor_dialog_id = DialogId(sponsored_message->from_id_); - if (!sponsor_dialog_id.is_valid() || - !td_->dialog_manager_->have_dialog_info_force(sponsor_dialog_id, "on_get_dialog_sponsored_messages")) { - LOG(ERROR) << "Receive unknown sponsor " << sponsor_dialog_id; - continue; - } - server_message_id = ServerMessageId(sponsored_message->channel_post_); - if (!server_message_id.is_valid() && server_message_id != ServerMessageId()) { - LOG(ERROR) << "Receive invalid channel post in " << to_string(sponsored_message); - server_message_id = ServerMessageId(); - } - td_->dialog_manager_->force_create_dialog(sponsor_dialog_id, "on_get_dialog_sponsored_messages"); - } else if (sponsored_message->chat_invite_ != nullptr && !sponsored_message->chat_invite_hash_.empty()) { - auto invite_link = LinkManager::get_dialog_invite_link(sponsored_message->chat_invite_hash_, false); - if (invite_link.empty()) { - LOG(ERROR) << "Receive invalid invite link hash in " << to_string(sponsored_message); - continue; - } - auto chat_invite = to_string(sponsored_message->chat_invite_); - td_->dialog_invite_link_manager_->on_get_dialog_invite_link_info( - invite_link, std::move(sponsored_message->chat_invite_), Promise()); - auto chat_invite_link_info = td_->dialog_invite_link_manager_->get_chat_invite_link_info_object(invite_link); - if (chat_invite_link_info == nullptr) { - LOG(ERROR) << "Failed to get invite link info from " << chat_invite << " for " - << to_string(sponsored_message); - continue; - } - invite_hash = std::move(sponsored_message->chat_invite_hash_); - } else if (sponsored_message->webpage_ != nullptr && !sponsored_message->webpage_->url_.empty()) { - site_url = std::move(sponsored_message->webpage_->url_); - site_name = std::move(sponsored_message->webpage_->site_name_); - if (sponsored_message->webpage_->photo_ != nullptr) { - auto photo = get_photo(td_, std::move(sponsored_message->webpage_->photo_), DialogId()); - site_photo = as_fake_dialog_photo(photo, DialogId(), false); - } - } else { - LOG(ERROR) << "Receive " << to_string(sponsored_message); - continue; - } - - auto message_text = get_message_text(td_->contacts_manager_.get(), std::move(sponsored_message->message_), + Photo photo = get_photo(td_, std::move(sponsored_message->photo_), DialogId()); + auto message_text = get_message_text(td_->user_manager_.get(), std::move(sponsored_message->message_), 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, sponsor_dialog_id, G()->unix_time(), - true, UserId(), &ttl, nullptr, "on_get_dialog_sponsored_messages"); + auto content = get_message_content(td_, std::move(message_text), nullptr, 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; @@ -451,16 +383,11 @@ void SponsoredMessageManager::on_get_dialog_sponsored_messages( message_info.random_id_ = sponsored_message->random_id_.as_slice().str(); auto is_inserted = messages->message_infos.emplace(local_id, std::move(message_info)).second; CHECK(is_inserted); - WebApp web_app; - if (sponsored_message->app_ != nullptr && sponsored_message->app_->get_id() == telegram_api::botApp::ID) { - web_app = WebApp(td_, telegram_api::move_object_as(sponsored_message->app_), dialog_id); - } messages->messages.emplace_back( - local_id, sponsored_message->recommended_, sponsored_message->show_peer_photo_, sponsor_dialog_id, - server_message_id, std::move(sponsored_message->start_param_), std::move(invite_hash), std::move(web_app), - std::move(content), std::move(sponsored_message->button_text_), std::move(sponsored_message->sponsor_info_), - std::move(sponsored_message->additional_info_), std::move(site_url), std::move(site_name), - std::move(site_photo)); + local_id, sponsored_message->recommended_, sponsored_message->can_report_, std::move(content), + std::move(sponsored_message->url_), std::move(photo), std::move(sponsored_message->title_), + std::move(sponsored_message->button_text_), PeerColor(sponsored_message->color_), + std::move(sponsored_message->sponsor_info_), std::move(sponsored_message->additional_info_)); } messages->messages_between = sponsored_messages->posts_between_; break; @@ -511,4 +438,23 @@ void SponsoredMessageManager::click_sponsored_message(DialogId dialog_id, Messag ->send(dialog_id.get_channel_id(), random_id_it->second.random_id_); } +void SponsoredMessageManager::report_sponsored_message( + DialogId dialog_id, MessageId sponsored_message_id, const string &option_id, + Promise> &&promise) { + if (!dialog_id.is_valid() || !sponsored_message_id.is_valid_sponsored()) { + return promise.set_error(Status::Error(400, "Invalid message specified")); + } + auto it = dialog_sponsored_messages_.find(dialog_id); + if (it == dialog_sponsored_messages_.end()) { + return promise.set_value(td_api::make_object()); + } + auto random_id_it = it->second->message_infos.find(sponsored_message_id.get()); + if (random_id_it == it->second->message_infos.end()) { + return promise.set_value(td_api::make_object()); + } + + td_->create_handler(std::move(promise)) + ->send(dialog_id.get_channel_id(), random_id_it->second.random_id_, option_id); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.h b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.h index 41dce961..b28157d2 100644 --- a/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.h +++ b/lib/tgchat/ext/td/td/telegram/SponsoredMessageManager.h @@ -39,6 +39,9 @@ class SponsoredMessageManager final : public Actor { void click_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id, Promise &&promise); + void report_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id, const string &option_id, + Promise> &&promise); + private: struct SponsoredMessage; struct SponsoredMessageInfo; diff --git a/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp b/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp index 046f1d84..ec433e14 100644 --- a/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StatisticsManager.cpp @@ -7,17 +7,19 @@ #include "td/telegram/StatisticsManager.h" #include "td/telegram/AccessRights.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessageId.h" #include "td/telegram/MessagesManager.h" +#include "td/telegram/PasswordManager.h" #include "td/telegram/ServerMessageId.h" #include "td/telegram/StoryId.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" @@ -84,7 +86,7 @@ static td_api::object_ptr convert_megagroup_st Td *td, telegram_api::object_ptr obj) { CHECK(obj != nullptr); - td->contacts_manager_->on_get_users(std::move(obj->users_), "convert_megagroup_stats"); + td->user_manager_->on_get_users(std::move(obj->users_), "convert_megagroup_stats"); // just in case td::remove_if(obj->top_posters_, [](auto &obj) { @@ -99,19 +101,19 @@ static td_api::object_ptr convert_megagroup_st auto top_senders = transform( std::move(obj->top_posters_), [td](telegram_api::object_ptr &&top_poster) { return td_api::make_object( - td->contacts_manager_->get_user_id_object(UserId(top_poster->user_id_), "get_top_senders"), + td->user_manager_->get_user_id_object(UserId(top_poster->user_id_), "get_top_senders"), top_poster->messages_, top_poster->avg_chars_); }); auto top_administrators = transform( std::move(obj->top_admins_), [td](telegram_api::object_ptr &&top_admin) { return td_api::make_object( - td->contacts_manager_->get_user_id_object(UserId(top_admin->user_id_), "get_top_administrators"), + td->user_manager_->get_user_id_object(UserId(top_admin->user_id_), "get_top_administrators"), top_admin->deleted_, top_admin->kicked_, top_admin->banned_); }); auto top_inviters = transform( std::move(obj->top_inviters_), [td](telegram_api::object_ptr &&top_inviter) { return td_api::make_object( - td->contacts_manager_->get_user_id_object(UserId(top_inviter->user_id_), "get_top_inviters"), + td->user_manager_->get_user_id_object(UserId(top_inviter->user_id_), "get_top_inviters"), top_inviter->invitations_); }); @@ -184,7 +186,7 @@ class GetMegagroupStatsQuery final : public Td::ResultHandler { void send(ChannelId channel_id, bool is_dark, DcId dc_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; @@ -205,7 +207,7 @@ class GetMegagroupStatsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetMegagroupStatsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetMegagroupStatsQuery"); promise_.set_error(std::move(status)); } }; @@ -222,7 +224,7 @@ class GetBroadcastStatsQuery final : public Td::ResultHandler { void send(ChannelId channel_id, bool is_dark, DcId dc_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); CHECK(input_channel != nullptr); int32 flags = 0; @@ -259,7 +261,195 @@ class GetBroadcastStatsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastStatsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastStatsQuery"); + promise_.set_error(std::move(status)); + } +}; + +static int64 get_amount(int64 amount, bool allow_negative = false) { + if (!allow_negative && amount < 0) { + LOG(ERROR) << "Receive currency amount = " << amount; + return 0; + } + return amount; +} + +static td_api::object_ptr convert_broadcast_revenue_balances( + telegram_api::object_ptr obj) { + CHECK(obj != nullptr); + return td_api::make_object( + "TON", get_amount(obj->overall_revenue_), get_amount(obj->current_balance_), get_amount(obj->available_balance_)); +} + +static td_api::object_ptr convert_broadcast_revenue_stats( + telegram_api::object_ptr obj) { + CHECK(obj != nullptr); + return td_api::make_object( + convert_stats_graph(std::move(obj->top_hours_graph_)), convert_stats_graph(std::move(obj->revenue_graph_)), + convert_broadcast_revenue_balances(std::move(obj->balances_)), + obj->usd_rate_ > 0 ? clamp(obj->usd_rate_ * 1e-7, 1e-18, 1e18) : 1.0); +} + +class GetBroadcastRevenueStatsQuery final : public Td::ResultHandler { + Promise> promise_; + ChannelId channel_id_; + + public: + explicit GetBroadcastRevenueStatsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, bool is_dark) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return on_error(Status::Error(500, "Chat info not found")); + } + + int32 flags = 0; + if (is_dark) { + flags |= telegram_api::stats_getBroadcastRevenueStats::DARK_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::stats_getBroadcastRevenueStats(flags, false /*ignored*/, std::move(input_channel)))); + } + + 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(convert_broadcast_revenue_stats(result_ptr.move_as_ok())); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastStatsQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetBroadcastRevenueWithdrawalUrlQuery final : public Td::ResultHandler { + Promise promise_; + ChannelId channel_id_; + + public: + explicit GetBroadcastRevenueWithdrawalUrlQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, telegram_api::object_ptr input_check_password) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return on_error(Status::Error(500, "Chat info not found")); + } + + send_query(G()->net_query_creator().create(telegram_api::stats_getBroadcastRevenueWithdrawalUrl( + std::move(input_channel), std::move(input_check_password)))); + } + + 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(std::move(result_ptr.ok_ref()->url_)); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastRevenueWithdrawalUrlQuery"); + promise_.set_error(std::move(status)); + } +}; + +class GetBroadcastRevenueTransactionsQuery final : public Td::ResultHandler { + Promise> promise_; + ChannelId channel_id_; + + public: + explicit GetBroadcastRevenueTransactionsQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id, int32 offset, int32 limit) { + channel_id_ = channel_id; + + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); + if (input_channel == nullptr) { + return on_error(Status::Error(500, "Chat info not found")); + } + + send_query(G()->net_query_creator().create( + telegram_api::stats_getBroadcastRevenueTransactions(std::move(input_channel), offset, limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetBroadcastRevenueTransactionsQuery: " << to_string(ptr); + auto total_count = ptr->count_; + if (total_count < static_cast(ptr->transactions_.size())) { + LOG(ERROR) << "Receive total_count = " << total_count << " and " << ptr->transactions_.size() << " transactions"; + total_count = static_cast(ptr->transactions_.size()); + } + vector> transactions; + for (auto &transaction_ptr : ptr->transactions_) { + int64 amount = 0; + auto type = [&]() -> td_api::object_ptr { + switch (transaction_ptr->get_id()) { + case telegram_api::broadcastRevenueTransactionProceeds::ID: { + auto transaction = + telegram_api::move_object_as(transaction_ptr); + amount = get_amount(transaction->amount_); + return td_api::make_object(transaction->from_date_, + transaction->to_date_); + } + case telegram_api::broadcastRevenueTransactionWithdrawal::ID: { + auto transaction = + telegram_api::move_object_as(transaction_ptr); + amount = get_amount(transaction->amount_, true); + auto state = [&]() -> td_api::object_ptr { + if (transaction->transaction_date_ > 0) { + return td_api::make_object(transaction->transaction_date_, + transaction->transaction_url_); + } + if (transaction->pending_) { + return td_api::make_object(); + } + if (!transaction->failed_) { + LOG(ERROR) << "Transaction has unknown state"; + } + return td_api::make_object(); + }(); + return td_api::make_object( + transaction->date_, transaction->provider_, std::move(state)); + } + case telegram_api::broadcastRevenueTransactionRefund::ID: { + auto transaction = + telegram_api::move_object_as(transaction_ptr); + amount = get_amount(transaction->amount_); + return td_api::make_object(transaction->date_, + transaction->provider_); + } + default: + UNREACHABLE(); + return nullptr; + } + }(); + transactions.push_back(td_api::make_object("TON", amount, std::move(type))); + } + promise_.set_value(td_api::make_object(total_count, std::move(transactions))); + } + + void on_error(Status status) final { + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastRevenueTransactionsQuery"); promise_.set_error(std::move(status)); } }; @@ -283,7 +473,7 @@ class GetMessageStatsQuery final : public Td::ResultHandler { void send(ChannelId channel_id, MessageId message_id, bool is_dark, DcId dc_id) { channel_id_ = channel_id; - auto input_channel = td_->contacts_manager_->get_input_channel(channel_id); + auto input_channel = td_->chat_manager_->get_input_channel(channel_id); if (input_channel == nullptr) { return promise_.set_error(Status::Error(400, "Supergroup not found")); } @@ -308,7 +498,7 @@ class GetMessageStatsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetMessageStatsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetMessageStatsQuery"); promise_.set_error(std::move(status)); } }; @@ -354,7 +544,7 @@ class GetStoryStatsQuery final : public Td::ResultHandler { } void on_error(Status status) final { - td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetStoryStatsQuery"); + td_->chat_manager_->on_get_channel_error(channel_id_, status, "GetStoryStatsQuery"); promise_.set_error(std::move(status)); } }; @@ -402,7 +592,7 @@ class GetMessagePublicForwardsQuery final : public Td::ResultHandler { void send(DcId dc_id, MessageFullId message_full_id, const string &offset, int32 limit) { dialog_id_ = message_full_id.get_dialog_id(); - auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()); + auto input_channel = td_->chat_manager_->get_input_channel(dialog_id_.get_channel_id()); CHECK(input_channel != nullptr); send_query(G()->net_query_creator().create( @@ -483,20 +673,93 @@ void StatisticsManager::get_channel_statistics(DialogId dialog_id, bool is_dark, send_closure(actor_id, &StatisticsManager::send_get_channel_stats_query, r_dc_id.move_as_ok(), dialog_id.get_channel_id(), is_dark, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(dialog_id, true, std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(dialog_id, true, std::move(dc_id_promise)); } void StatisticsManager::send_get_channel_stats_query(DcId dc_id, ChannelId channel_id, bool is_dark, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - if (td_->contacts_manager_->is_megagroup_channel(channel_id)) { + if (td_->chat_manager_->is_megagroup_channel(channel_id)) { td_->create_handler(std::move(promise))->send(channel_id, is_dark, dc_id); } else { td_->create_handler(std::move(promise))->send(channel_id, is_dark, dc_id); } } +void StatisticsManager::get_channel_revenue_statistics( + DialogId dialog_id, bool is_dark, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "get_channel_revenue_statistics")); + if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + td_->create_handler(std::move(promise))->send(dialog_id.get_channel_id(), is_dark); +} + +void StatisticsManager::on_update_dialog_revenue_transactions( + DialogId dialog_id, telegram_api::object_ptr balances) { + if (!dialog_id.is_valid()) { + LOG(ERROR) << "Receive updateBroadcastRevenueTransactions in invalid " << dialog_id; + return; + } + if (!td_->messages_manager_->have_dialog(dialog_id)) { + LOG(INFO) << "Ignore unneeded updateBroadcastRevenueTransactions in " << dialog_id; + return; + } + send_closure(G()->td(), &Td::send_update, + td_api::make_object( + td_->dialog_manager_->get_chat_id_object(dialog_id, "updateChatRevenueAmount"), + convert_broadcast_revenue_balances(std::move(balances)))); +} + +void StatisticsManager::get_channel_revenue_withdrawal_url(DialogId dialog_id, const string &password, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "get_channel_revenue_withdrawal_url")); + if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + auto channel_id = dialog_id.get_channel_id(); + if (!td_->chat_manager_->get_channel_permissions(channel_id).is_creator()) { + return promise.set_error(Status::Error(400, "Not enough rights to withdraw revenue")); + } + if (password.empty()) { + return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID")); + } + send_closure( + td_->password_manager_, &PasswordManager::get_input_check_password_srp, password, + PromiseCreator::lambda([actor_id = actor_id(this), channel_id, promise = std::move(promise)]( + Result> result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &StatisticsManager::send_get_channel_revenue_withdrawal_url_query, channel_id, + result.move_as_ok(), std::move(promise)); + })); +} + +void StatisticsManager::send_get_channel_revenue_withdrawal_url_query( + ChannelId channel_id, telegram_api::object_ptr input_check_password, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + td_->create_handler(std::move(promise)) + ->send(channel_id, std::move(input_check_password)); +} + +void StatisticsManager::get_channel_revenue_transactions( + DialogId dialog_id, int32 offset, int32 limit, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Write, + "get_channel_revenue_transactions")); + if (!td_->dialog_manager_->is_broadcast_channel(dialog_id)) { + return promise.set_error(Status::Error(400, "Chat is not a channel")); + } + td_->create_handler(std::move(promise)) + ->send(dialog_id.get_channel_id(), offset, limit); +} + void StatisticsManager::get_channel_message_statistics( MessageFullId message_full_id, bool is_dark, Promise> &&promise) { auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), message_full_id, is_dark, @@ -507,8 +770,7 @@ void StatisticsManager::get_channel_message_statistics( send_closure(actor_id, &StatisticsManager::send_get_channel_message_stats_query, r_dc_id.move_as_ok(), message_full_id, is_dark, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, - std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, std::move(dc_id_promise)); } void StatisticsManager::send_get_channel_message_stats_query( @@ -538,7 +800,7 @@ void StatisticsManager::get_channel_story_statistics(StoryFullId story_full_id, send_closure(actor_id, &StatisticsManager::send_get_channel_story_stats_query, r_dc_id.move_as_ok(), story_full_id, is_dark, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(story_full_id.get_dialog_id(), false, std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(story_full_id.get_dialog_id(), false, std::move(dc_id_promise)); } void StatisticsManager::send_get_channel_story_stats_query( @@ -568,7 +830,7 @@ void StatisticsManager::load_statistics_graph(DialogId dialog_id, string token, send_closure(actor_id, &StatisticsManager::send_load_async_graph_query, r_dc_id.move_as_ok(), std::move(token), x, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(dialog_id, false, std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(dialog_id, false, std::move(dc_id_promise)); } void StatisticsManager::send_load_async_graph_query(DcId dc_id, string token, int64 x, @@ -592,8 +854,7 @@ void StatisticsManager::get_message_public_forwards(MessageFullId message_full_i send_closure(actor_id, &StatisticsManager::send_get_message_public_forwards_query, r_dc_id.move_as_ok(), message_full_id, std::move(offset), limit, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, - std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(message_full_id.get_dialog_id(), false, std::move(dc_id_promise)); } void StatisticsManager::send_get_message_public_forwards_query( @@ -636,7 +897,7 @@ void StatisticsManager::get_story_public_forwards(StoryFullId story_full_id, str send_closure(actor_id, &StatisticsManager::send_get_story_public_forwards_query, r_dc_id.move_as_ok(), story_full_id, std::move(offset), limit, std::move(promise)); }); - td_->contacts_manager_->get_channel_statistics_dc_id(dialog_id, false, std::move(dc_id_promise)); + td_->chat_manager_->get_channel_statistics_dc_id(dialog_id, false, std::move(dc_id_promise)); } void StatisticsManager::send_get_story_public_forwards_query( @@ -713,8 +974,8 @@ void StatisticsManager::on_get_public_forwards( void StatisticsManager::get_channel_differences_if_needed( telegram_api::object_ptr &&public_forwards, Promise> promise, const char *source) { - td_->contacts_manager_->on_get_users(std::move(public_forwards->users_), "stats_publicForwards"); - td_->contacts_manager_->on_get_chats(std::move(public_forwards->chats_), "stats_publicForwards"); + td_->user_manager_->on_get_users(std::move(public_forwards->users_), "stats_publicForwards"); + td_->chat_manager_->on_get_chats(std::move(public_forwards->chats_), "stats_publicForwards"); vector *> messages; for (const auto &forward : public_forwards->forwards_) { diff --git a/lib/tgchat/ext/td/td/telegram/StatisticsManager.h b/lib/tgchat/ext/td/td/telegram/StatisticsManager.h index bc87973c..5f2bc24a 100644 --- a/lib/tgchat/ext/td/td/telegram/StatisticsManager.h +++ b/lib/tgchat/ext/td/td/telegram/StatisticsManager.h @@ -30,6 +30,17 @@ class StatisticsManager final : public Actor { void get_channel_statistics(DialogId dialog_id, bool is_dark, Promise> &&promise); + void get_channel_revenue_statistics(DialogId dialog_id, bool is_dark, + Promise> &&promise); + + void get_channel_revenue_withdrawal_url(DialogId dialog_id, const string &password, Promise &&promise); + + void get_channel_revenue_transactions(DialogId dialog_id, int32 offset, int32 limit, + Promise> &&promise); + + void on_update_dialog_revenue_transactions(DialogId dialog_id, + telegram_api::object_ptr balances); + void get_channel_message_statistics(MessageFullId message_full_id, bool is_dark, Promise> &&promise); @@ -73,6 +84,10 @@ class StatisticsManager final : public Actor { void send_get_story_public_forwards_query(DcId dc_id, StoryFullId story_full_id, string offset, int32 limit, Promise> &&promise); + void send_get_channel_revenue_withdrawal_url_query( + ChannelId channel_id, telegram_api::object_ptr input_check_password, + Promise &&promise); + Td *td_; ActorShared<> parent_; }; diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.cpp b/lib/tgchat/ext/td/td/telegram/StickersManager.cpp index 49f68a42..096bef0d 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.cpp @@ -9,7 +9,6 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Document.h" @@ -38,6 +37,7 @@ #include "td/telegram/td_api.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/db/SqliteKeyValue.h" @@ -961,7 +961,7 @@ class UploadStickerFileQuery final : public Td::ResultHandler { is_url_ = is_url; was_uploaded_ = FileManager::extract_was_uploaded(input_media); send_query(G()->net_query_creator().create( - telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))); + telegram_api::messages_uploadMedia(0, string(), std::move(input_peer), std::move(input_media)))); } void on_result(BufferSlice packet) final { @@ -981,9 +981,7 @@ class UploadStickerFileQuery final : public Td::ResultHandler { // TODO td_->stickers_manager_->on_upload_sticker_file_parts_missing(file_id_, std::move(bad_parts)); // return; } else { - if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) { - td_->file_manager_->delete_partial_remote_location(file_id_); - } + td_->file_manager_->delete_partial_remote_location_if_needed(file_id_, status); } } else if (FileReferenceManager::is_file_reference_error(status)) { LOG(ERROR) << "Receive file reference error for UploadStickerFileQuery"; @@ -1055,7 +1053,7 @@ class CreateNewStickerSetQuery final : public Td::ResultHandler { } void send(tl_object_ptr &&input_user, const string &title, const string &short_name, - StickerType sticker_type, bool has_text_color, StickerFormat sticker_format, + StickerType sticker_type, bool has_text_color, vector> &&input_stickers, const string &software) { CHECK(input_user != nullptr); @@ -1069,20 +1067,14 @@ class CreateNewStickerSetQuery final : public Td::ResultHandler { if (has_text_color) { flags |= telegram_api::stickers_createStickerSet::TEXT_COLOR_MASK; } - if (sticker_format == StickerFormat::Tgs) { - flags |= telegram_api::stickers_createStickerSet::ANIMATED_MASK; - } - if (sticker_format == StickerFormat::Webm) { - flags |= telegram_api::stickers_createStickerSet::VIDEOS_MASK; - } if (!software.empty()) { flags |= telegram_api::stickers_createStickerSet::SOFTWARE_MASK; } send_query(G()->net_query_creator().create( telegram_api::stickers_createStickerSet(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, - false /*ignored*/, false /*ignored*/, std::move(input_user), title, - short_name, nullptr, std::move(input_stickers), software), + std::move(input_user), title, short_name, nullptr, + std::move(input_stickers), software), {{short_name}})); } @@ -1112,14 +1104,23 @@ class AddStickerToSetQuery final : public Td::ResultHandler { explicit AddStickerToSetQuery(Promise &&promise) : promise_(std::move(promise)) { } - void send(const string &short_name, tl_object_ptr &&input_sticker) { - send_query(G()->net_query_creator().create( - telegram_api::stickers_addStickerToSet(make_tl_object(short_name), - std::move(input_sticker)), - {{short_name}})); + void send(const string &short_name, telegram_api::object_ptr &&input_sticker, + telegram_api::object_ptr &&input_document) { + if (input_document != nullptr) { + send_query(G()->net_query_creator().create( + telegram_api::stickers_replaceSticker(std::move(input_document), std::move(input_sticker)), {{short_name}})); + } else { + send_query(G()->net_query_creator().create( + telegram_api::stickers_addStickerToSet(make_tl_object(short_name), + std::move(input_sticker)), + {{short_name}})); + } } void on_result(BufferSlice packet) final { + static_assert(std::is_same::value, + ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); @@ -1385,6 +1386,35 @@ class ChangeStickerQuery final : public Td::ResultHandler { } }; +class GetMyStickersQuery final : public Td::ResultHandler { + Promise> promise_; + + public: + explicit GetMyStickersQuery(Promise> &&promise) + : promise_(std::move(promise)) { + } + + void send(StickerSetId offset_sticker_set_id, int32 limit) { + send_query( + G()->net_query_creator().create(telegram_api::messages_getMyStickers(offset_sticker_set_id.get(), limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetMyStickersQuery: " << to_string(ptr); + promise_.set_value(std::move(ptr)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + class GetCustomEmojiDocumentsQuery final : public Td::ResultHandler { Promise>> promise_; @@ -1433,6 +1463,9 @@ class GetEmojiGroupsQuery final : public Td::ResultHandler { case EmojiGroupType::ProfilePhoto: send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiProfilePhotoGroups(hash))); break; + case EmojiGroupType::RegularStickers: + send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiStickerGroups(hash))); + break; default: UNREACHABLE(); } @@ -1445,6 +1478,9 @@ class GetEmojiGroupsQuery final : public Td::ResultHandler { static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, + ""); auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); @@ -1977,7 +2013,7 @@ void StickersManager::on_load_special_sticker_set(const SpecialStickerSetType &t it->second.foreach([&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); CHECK(!message_full_ids.empty()); for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_load_special_sticker_set"); } } @@ -1991,6 +2027,12 @@ StickerType StickersManager::get_sticker_type(FileId file_id) const { return sticker->type_; } +StickerFormat StickersManager::get_sticker_format(FileId file_id) const { + const auto *sticker = get_sticker(file_id); + CHECK(sticker != nullptr); + return sticker->format_; +} + bool StickersManager::is_premium_custom_emoji(CustomEmojiId custom_emoji_id, bool default_result) const { auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id); if (!sticker_id.is_valid()) { @@ -2446,24 +2488,33 @@ int32 StickersManager::get_dice_success_animation_frame_number(const string &emo return result.first == value ? result.second : std::numeric_limits::max(); } -PhotoFormat StickersManager::get_sticker_set_thumbnail_format(StickerFormat sticker_format) { - switch (sticker_format) { - case StickerFormat::Unknown: - return PhotoFormat::Webp; - case StickerFormat::Webp: - return PhotoFormat::Webp; - case StickerFormat::Tgs: - return PhotoFormat::Tgs; - case StickerFormat::Webm: - return PhotoFormat::Webm; - default: - UNREACHABLE(); - return PhotoFormat::Webp; +PhotoFormat StickersManager::get_sticker_set_thumbnail_format(const StickerSet *sticker_set) const { + if (sticker_set->thumbnail_document_id_ != 0 && sticker_set->sticker_type_ == StickerType::CustomEmoji) { + for (auto sticker_id : sticker_set->sticker_ids_) { + auto file_view = td_->file_manager_->get_file_view(sticker_id); + if (file_view.has_remote_location() && !file_view.remote_location().is_web() && + file_view.remote_location().get_id() == sticker_set->thumbnail_document_id_) { + const Sticker *s = get_sticker(sticker_id); + CHECK(s != nullptr); + return get_sticker_format_photo_format(s->format_); + } + } + } + auto type = sticker_set->thumbnail_.type; + if (type == 's') { + return PhotoFormat::Webp; } + if (type == 'v') { + return PhotoFormat::Webm; + } + if (type == 'a') { + return PhotoFormat::Tgs; + } + return PhotoFormat::Tgs; } -double StickersManager::get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set) { - if (sticker_set->sticker_format_ == StickerFormat::Tgs) { +double StickersManager::get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set) const { + if (get_sticker_set_thumbnail_format(sticker_set) == PhotoFormat::Tgs) { return 100.0 / 512.0; } return 1.0; @@ -2478,7 +2529,7 @@ td_api::object_ptr StickersManager::get_sticker_set_thumbnail if (file_view.has_remote_location() && !file_view.remote_location().is_web() && file_view.remote_location().get_id() == sticker_set->thumbnail_document_id_) { const Sticker *s = get_sticker(sticker_id); - auto thumbnail_format = get_sticker_set_thumbnail_format(s->format_); + auto thumbnail_format = get_sticker_format_photo_format(s->format_); PhotoSize thumbnail; thumbnail.type = 't'; thumbnail.size = static_cast(file_view.size()); @@ -2488,7 +2539,7 @@ td_api::object_ptr StickersManager::get_sticker_set_thumbnail } } } - auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format_); + auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set); return get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail_, thumbnail_format); } @@ -2515,10 +2566,9 @@ tl_object_ptr StickersManager::get_sticker_set_object(Sticke get_sticker_set_thumbnail_object(sticker_set), get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -2, get_sticker_set_minithumbnail_zoom(sticker_set)), - sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, - get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), - sticker_set->has_text_color_, sticker_set->channel_emoji_status_, sticker_set->is_viewed_, std::move(stickers), - std::move(emojis)); + sticker_set->is_created_, sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, + sticker_set->is_official_, get_sticker_type_object(sticker_set->sticker_type_), sticker_set->has_text_color_, + sticker_set->channel_emoji_status_, sticker_set->is_viewed_, std::move(stickers), std::move(emojis)); } tl_object_ptr StickersManager::get_sticker_sets_object(int32 total_count, @@ -2592,9 +2642,9 @@ tl_object_ptr StickersManager::get_sticker_set_info_obje get_sticker_set_thumbnail_object(sticker_set), get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -3, get_sticker_set_minithumbnail_zoom(sticker_set)), - sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_, - get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_), - sticker_set->has_text_color_, sticker_set->channel_emoji_status_, sticker_set->is_viewed_, + sticker_set->is_created_, sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, + sticker_set->is_official_, get_sticker_type_object(sticker_set->sticker_type_), sticker_set->has_text_color_, + sticker_set->channel_emoji_status_, sticker_set->is_viewed_, sticker_set->was_loaded_ ? actual_count : max(actual_count, sticker_set->sticker_count_), std::move(stickers)); } @@ -3597,10 +3647,9 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrflags_ & telegram_api::stickerSet::INSTALLED_DATE_MASK) != 0; bool is_archived = set->archived_; bool is_official = set->official_; + bool is_created = set->creator_; bool has_text_color = set->emojis_ && set->text_color_; bool channel_emoji_status = set->emojis_ && set->channel_emoji_status_; - StickerFormat sticker_format = - set->videos_ ? StickerFormat::Webm : (set->animated_ ? StickerFormat::Tgs : StickerFormat::Webp); StickerType sticker_type = set->emojis_ ? StickerType::CustomEmoji : (set->masks_ ? StickerType::Mask : StickerType::Regular); @@ -3610,8 +3659,7 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrfile_manager_.get(), PhotoSizeSource::sticker_set_thumbnail(set_id.get(), s->access_hash_, set->thumb_version_), 0, 0, - "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumbnail_ptr), - get_sticker_set_thumbnail_format(sticker_format)); + "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumbnail_ptr), PhotoFormat::Tgs); if (photo_size.get_offset() == 0) { if (!thumbnail.file_id.is_valid()) { thumbnail = std::move(photo_size.get<0>()); @@ -3635,10 +3683,10 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrsticker_count_ = set->count_; s->hash_ = set->hash_; s->is_official_ = is_official; - s->sticker_format_ = sticker_format; s->sticker_type_ = sticker_type; s->has_text_color_ = has_text_color; s->channel_emoji_status_ = channel_emoji_status; + s->is_created_ = is_created; s->is_changed_ = true; } else { CHECK(s->id_ == set_id); @@ -3709,6 +3757,10 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptris_official_ = is_official; s->is_changed_ = true; } + if (s->is_created_ != is_created) { + s->is_created_ = is_created; + s->is_changed_ = true; + } if (s->has_text_color_ != has_text_color) { LOG(INFO) << "Needs repainting flag of " << set_id << " changed to " << has_text_color; s->has_text_color_ = has_text_color; @@ -3719,12 +3771,6 @@ StickerSetId StickersManager::on_get_sticker_set(tl_object_ptrchannel_emoji_status_ = channel_emoji_status; s->is_changed_ = true; } - if (s->sticker_format_ != sticker_format) { - LOG(ERROR) << "Format of stickers in " << set_id << '/' << s->short_name_ << " has changed from " - << s->sticker_format_ << " to " << sticker_format << " from " << source; - s->sticker_format_ = sticker_format; - s->is_changed_ = true; - } LOG_IF(ERROR, s->sticker_type_ != sticker_type) << "Type of " << set_id << '/' << s->short_name_ << " has changed from " << s->sticker_type_ << " to " << sticker_type << " from " << source; @@ -3762,7 +3808,7 @@ StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptrsticker_ids_; - auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), sticker_set->sticker_format_).second; + auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), StickerFormat::Unknown).second; if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) { sticker_ids.push_back(sticker_id); sticker_set->is_changed_ = true; @@ -3786,7 +3832,7 @@ StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptrsticker_ids_; for (auto &cover : multicovered_set->covers_) { - auto sticker_id = on_get_sticker_document(std::move(cover), sticker_set->sticker_format_).second; + auto sticker_id = on_get_sticker_document(std::move(cover), StickerFormat::Unknown).second; if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) { sticker_ids.push_back(sticker_id); sticker_set->is_changed_ = true; @@ -3861,6 +3907,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s s->are_keywords_loaded_ = true; s->is_sticker_has_text_color_loaded_ = true; s->is_sticker_channel_emoji_status_loaded_ = true; + s->is_created_loaded_ = true; FlatHashMap document_id_to_sticker_id; @@ -3868,7 +3915,7 @@ StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_s s->premium_sticker_positions_.clear(); bool is_bot = td_->auth_manager_->is_bot(); for (auto &document_ptr : set->documents_) { - auto sticker_id = on_get_sticker_document(std::move(document_ptr), s->sticker_format_); + auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown); if (!sticker_id.second.is_valid() || sticker_id.first == 0) { continue; } @@ -4133,7 +4180,7 @@ const std::map> &StickersManager::get_sticker_set_keyword } void StickersManager::find_sticker_set_stickers(const StickerSet *sticker_set, const vector &emojis, - const string &query, vector &result) { + const string &query, vector> &result) const { CHECK(sticker_set != nullptr); FlatHashSet found_sticker_ids; for (auto &emoji : emojis) { @@ -4152,8 +4199,9 @@ void StickersManager::find_sticker_set_stickers(const StickerSet *sticker_set, c if (!found_sticker_ids.empty()) { for (auto sticker_id : sticker_set->sticker_ids_) { if (found_sticker_ids.count(sticker_id) != 0) { + const Sticker *s = get_sticker(sticker_id); LOG(INFO) << "Add " << sticker_id << " sticker from " << sticker_set->id_; - result.push_back(sticker_id); + result.emplace_back(is_sticker_format_animated(s->format_), sticker_id); } } } @@ -4369,12 +4417,12 @@ vector StickersManager::get_stickers(StickerType sticker_type, string qu if (sticker_type == StickerType::CustomEmoji) { switch (dialog_id.get_type()) { case DialogType::User: - if (dialog_id.get_user_id() == td_->contacts_manager_->get_my_id()) { + if (dialog_id.get_user_id() == td_->user_manager_->get_my_id()) { allow_premium = true; } break; case DialogType::SecretChat: - if (td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()) < + if (td_->user_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()) < static_cast(SecretChatLayer::SpoilerAndCustomEmojiEntities)) { promise.set_value(Unit()); return {}; @@ -4414,18 +4462,20 @@ vector StickersManager::get_stickers(StickerType sticker_type, string qu examined_sticker_sets.push_back(sticker_set); } } - std::stable_sort( - examined_sticker_sets.begin(), examined_sticker_sets.end(), [](const StickerSet *lhs, const StickerSet *rhs) { - if (lhs->is_installed_ != rhs->is_installed_) { - return lhs->is_installed_; - } - if (lhs->is_archived_ != rhs->is_archived_) { - return lhs->is_archived_; - } - return is_sticker_format_animated(lhs->sticker_format_) && !is_sticker_format_animated(rhs->sticker_format_); - }); + vector> partial_results[2][2]; for (auto sticker_set : examined_sticker_sets) { - find_sticker_set_stickers(sticker_set, emojis, prepared_query, result); + find_sticker_set_stickers(sticker_set, emojis, prepared_query, + partial_results[sticker_set->is_installed_][sticker_set->is_archived_]); + } + for (int is_installed = 1; is_installed >= 0; is_installed--) { + for (int is_archived = 1; is_archived >= 0; is_archived--) { + auto &partial_result = partial_results[is_installed][is_archived]; + std::stable_sort(partial_result.begin(), partial_result.end(), + [](const auto &lhs, const auto &rhs) { return lhs.first && !rhs.first; }); + for (auto &is_animated_sticker_id_pair : partial_result) { + result.push_back(is_animated_sticker_id_pair.second); + } + } } vector sorted; @@ -5410,9 +5460,9 @@ void StickersManager::on_load_sticker_set_from_database(StickerSetId sticker_set << format::as_hex_dump<4>(Slice(value)); } } - if (!sticker_set->is_sticker_channel_emoji_status_loaded_ || !sticker_set->is_sticker_has_text_color_loaded_ || - !sticker_set->are_keywords_loaded_ || !sticker_set->is_thumbnail_reloaded_ || - !sticker_set->are_legacy_sticker_thumbnails_reloaded_) { + if (!sticker_set->is_created_loaded_ || !sticker_set->is_sticker_channel_emoji_status_loaded_ || + !sticker_set->is_sticker_has_text_color_loaded_ || !sticker_set->are_keywords_loaded_ || + !sticker_set->is_thumbnail_reloaded_ || !sticker_set->are_legacy_sticker_thumbnails_reloaded_) { do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto(), "on_load_sticker_set_from_database 2"); } @@ -5707,7 +5757,7 @@ void StickersManager::try_update_animated_emoji_messages() { } } for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "try_update_animated_emoji_messages"); } } @@ -5725,7 +5775,7 @@ void StickersManager::try_update_custom_emoji_messages(CustomEmojiId custom_emoj [&](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); } for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "try_update_custom_emoji_messages"); } } @@ -5742,7 +5792,7 @@ void StickersManager::try_update_premium_gift_messages() { } } for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "try_update_premium_gift_messages"); } } @@ -6884,7 +6934,7 @@ void StickersManager::schedule_update_animated_emoji_clicked(const StickerSet *s return; } auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Write)) { return; } @@ -6933,7 +6983,7 @@ void StickersManager::send_update_animated_emoji_clicked(MessageFullId message_f return; } auto dialog_id = message_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Write)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Write)) { return; } @@ -7722,7 +7772,6 @@ void StickersManager::move_sticker_set_to_top_by_custom_emoji_ids(const vector> StickersManager::prepare_input_sticker(td_api::inputSticker *sticker, - StickerFormat sticker_format, StickerType sticker_type) { if (sticker == nullptr) { return Status::Error(400, "Input sticker must be non-empty"); @@ -7743,7 +7792,7 @@ Result> StickersManager::prepare_input_sticker(td } } - return prepare_input_file(sticker->sticker_, sticker_format, sticker_type, false); + return prepare_input_file(sticker->sticker_, ::td::get_sticker_format(sticker->format_), sticker_type, false); } Result> StickersManager::prepare_input_file( @@ -7808,10 +7857,10 @@ FileId StickersManager::upload_sticker_file(UserId user_id, StickerFormat sticke Promise &&promise) { bool is_bot = td_->auth_manager_->is_bot(); if (!is_bot) { - user_id = td_->contacts_manager_->get_my_id(); + user_id = td_->user_manager_->get_my_id(); } - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { promise.set_error(r_input_user.move_as_error()); return FileId(); @@ -7908,18 +7957,17 @@ td_api::object_ptr StickersManager::get_check } } -void StickersManager::create_new_sticker_set(UserId user_id, string title, string short_name, - StickerFormat sticker_format, StickerType sticker_type, +void StickersManager::create_new_sticker_set(UserId user_id, string title, string short_name, StickerType sticker_type, bool has_text_color, vector> &&stickers, string software, Promise> &&promise) { bool is_bot = td_->auth_manager_->is_bot(); if (!is_bot) { - user_id = td_->contacts_manager_->get_my_id(); + user_id = td_->user_manager_->get_my_id(); } - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH); if (title.empty()) { @@ -7927,7 +7975,7 @@ void StickersManager::create_new_sticker_set(UserId user_id, string title, strin } short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH); - if (short_name.empty()) { + if (short_name.empty() && is_bot) { return promise.set_error(Status::Error(400, "Sticker set name must be non-empty")); } @@ -7944,7 +7992,7 @@ void StickersManager::create_new_sticker_set(UserId user_id, string title, strin vector local_file_ids; vector url_file_ids; for (auto &sticker : stickers) { - auto r_file_id = prepare_input_sticker(sticker.get(), sticker_format, sticker_type); + auto r_file_id = prepare_input_sticker(sticker.get(), sticker_type); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } @@ -7964,7 +8012,6 @@ void StickersManager::create_new_sticker_set(UserId user_id, string title, strin pending_new_sticker_set->user_id_ = user_id; pending_new_sticker_set->title_ = std::move(title); pending_new_sticker_set->short_name_ = short_name; - pending_new_sticker_set->sticker_format_ = sticker_format; pending_new_sticker_set->sticker_type_ = sticker_type; pending_new_sticker_set->has_text_color_ = has_text_color; pending_new_sticker_set->file_ids_ = std::move(file_ids); @@ -8154,9 +8201,8 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result res CHECK(pending_new_sticker_set->upload_files_multipromise_.promise_count() == 0); auto &promise = pending_new_sticker_set->promise_; - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(pending_new_sticker_set->user_id_)); + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(pending_new_sticker_set->user_id_)); - StickerFormat sticker_format = pending_new_sticker_set->sticker_format_; StickerType sticker_type = pending_new_sticker_set->sticker_type_; auto sticker_count = pending_new_sticker_set->stickers_.size(); @@ -8169,13 +8215,31 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result res td_->create_handler(std::move(promise)) ->send(std::move(input_user), pending_new_sticker_set->title_, pending_new_sticker_set->short_name_, sticker_type, - pending_new_sticker_set->has_text_color_, sticker_format, std::move(input_stickers), - pending_new_sticker_set->software_); + pending_new_sticker_set->has_text_color_, std::move(input_stickers), pending_new_sticker_set->software_); +} + +StickerFormat StickersManager::guess_sticker_set_format(const StickerSet *sticker_set) const { + auto format = StickerFormat::Unknown; + for (auto sticker_id : sticker_set->sticker_ids_) { + const auto *s = get_sticker(sticker_id); + if (format == StickerFormat::Unknown) { + format = s->format_; + } else if (format != s->format_) { + return StickerFormat::Unknown; + } + } + return format; } void StickersManager::add_sticker_to_set(UserId user_id, string short_name, - td_api::object_ptr &&sticker, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + td_api::object_ptr &&sticker, + td_api::object_ptr &&old_sticker, Promise &&promise) { + bool is_bot = td_->auth_manager_->is_bot(); + if (!is_bot) { + user_id = td_->user_manager_->get_my_id(); + } + + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { @@ -8184,25 +8248,27 @@ void StickersManager::add_sticker_to_set(UserId user_id, string short_name, const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set != nullptr && sticker_set->was_loaded_) { - return do_add_sticker_to_set(user_id, short_name, std::move(sticker), std::move(promise)); + return do_add_sticker_to_set(user_id, short_name, std::move(sticker), std::move(old_sticker), std::move(promise)); } - do_reload_sticker_set( - StickerSetId(), make_tl_object(short_name), 0, - PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, sticker = std::move(sticker), - promise = std::move(promise)](Result result) mutable { - if (result.is_error()) { - promise.set_error(result.move_as_error()); - } else { - send_closure(actor_id, &StickersManager::do_add_sticker_to_set, user_id, std::move(short_name), - std::move(sticker), std::move(promise)); - } - }), - "add_sticker_to_set"); + do_reload_sticker_set(StickerSetId(), make_tl_object(short_name), 0, + PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, + sticker = std::move(sticker), old_sticker = std::move(old_sticker), + promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &StickersManager::do_add_sticker_to_set, user_id, + std::move(short_name), std::move(sticker), std::move(old_sticker), + std::move(promise)); + } + }), + "add_sticker_to_set"); } void StickersManager::do_add_sticker_to_set(UserId user_id, string short_name, td_api::object_ptr &&sticker, + td_api::object_ptr &&old_sticker, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); @@ -8210,8 +8276,22 @@ void StickersManager::do_add_sticker_to_set(UserId user_id, string short_name, if (sticker_set == nullptr || !sticker_set->was_loaded_) { return promise.set_error(Status::Error(400, "Sticker set not found")); } + telegram_api::object_ptr input_document; + if (old_sticker != nullptr) { + TRY_RESULT_PROMISE(promise, sticker_input_document, get_sticker_input_document(old_sticker)); + if (sticker_input_document.sticker_set_short_name_ != short_name) { + return promise.set_error(Status::Error(400, "The old sticker isn't from the set")); + } + input_document = std::move(sticker_input_document.input_document_); + } - auto r_file_id = prepare_input_sticker(sticker.get(), sticker_set->sticker_format_, sticker_set->sticker_type_); + if (sticker != nullptr && sticker->format_ == nullptr) { + auto format = guess_sticker_set_format(sticker_set); + if (format != StickerFormat::Unknown) { + sticker->format_ = get_sticker_format_object(format); + } + } + auto r_file_id = prepare_input_sticker(sticker.get(), sticker_set->sticker_type_); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } @@ -8223,6 +8303,7 @@ void StickersManager::do_add_sticker_to_set(UserId user_id, string short_name, pending_add_sticker_to_set->short_name_ = short_name; pending_add_sticker_to_set->file_id_ = file_id; pending_add_sticker_to_set->sticker_ = std::move(sticker); + pending_add_sticker_to_set->input_document_ = std::move(input_document); pending_add_sticker_to_set->promise_ = std::move(promise); int64 random_id; @@ -8262,12 +8343,19 @@ void StickersManager::on_added_sticker_uploaded(int64 random_id, Result re td_->create_handler(std::move(pending_add_sticker_to_set->promise_)) ->send(pending_add_sticker_to_set->short_name_, - get_input_sticker(pending_add_sticker_to_set->sticker_.get(), pending_add_sticker_to_set->file_id_)); + get_input_sticker(pending_add_sticker_to_set->sticker_.get(), pending_add_sticker_to_set->file_id_), + std::move(pending_add_sticker_to_set->input_document_)); } void StickersManager::set_sticker_set_thumbnail(UserId user_id, string short_name, - tl_object_ptr &&thumbnail, Promise &&promise) { - TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id)); + td_api::object_ptr &&thumbnail, StickerFormat format, + Promise &&promise) { + bool is_bot = td_->auth_manager_->is_bot(); + if (!is_bot) { + user_id = td_->user_manager_->get_my_id(); + } + + TRY_RESULT_PROMISE(promise, input_user, td_->user_manager_->get_input_user(user_id)); short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH)); if (short_name.empty()) { @@ -8276,26 +8364,26 @@ void StickersManager::set_sticker_set_thumbnail(UserId user_id, string short_nam const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); if (sticker_set != nullptr && sticker_set->was_loaded_) { - return do_set_sticker_set_thumbnail(user_id, short_name, std::move(thumbnail), std::move(promise)); + return do_set_sticker_set_thumbnail(user_id, short_name, std::move(thumbnail), format, std::move(promise)); } do_reload_sticker_set( StickerSetId(), make_tl_object(short_name), 0, - PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, thumbnail = std::move(thumbnail), + PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, thumbnail = std::move(thumbnail), format, promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { send_closure(actor_id, &StickersManager::do_set_sticker_set_thumbnail, user_id, std::move(short_name), - std::move(thumbnail), std::move(promise)); + std::move(thumbnail), format, std::move(promise)); } }), "set_sticker_set_thumbnail"); } void StickersManager::do_set_sticker_set_thumbnail(UserId user_id, string short_name, - tl_object_ptr &&thumbnail, - Promise &&promise) { + td_api::object_ptr &&thumbnail, + StickerFormat format, Promise &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name)); @@ -8306,8 +8394,11 @@ void StickersManager::do_set_sticker_set_thumbnail(UserId user_id, string short_ return promise.set_error( Status::Error(400, "The method can't be used to set thumbnail of custom emoji sticker sets")); } + if (format == StickerFormat::Unknown) { + format = guess_sticker_set_format(sticker_set); + } - auto r_file_id = prepare_input_file(thumbnail, sticker_set->sticker_format_, sticker_set->sticker_type_, true); + auto r_file_id = prepare_input_file(thumbnail, format, sticker_set->sticker_type_, true); if (r_file_id.is_error()) { return promise.set_error(r_file_id.move_as_error()); } @@ -8511,6 +8602,52 @@ void StickersManager::set_sticker_mask_position(const td_api::object_ptr> &&promise) { + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)]( + Result> r_my_stickers) mutable { + send_closure(actor_id, &StickersManager::on_get_created_sticker_sets, std::move(r_my_stickers), + std::move(promise)); + }); + td_->create_handler(std::move(query_promise))->send(offset_sticker_set_id, limit); +} + +void StickersManager::on_get_created_sticker_sets( + Result> r_my_stickers, + Promise> &&promise) { + G()->ignore_result_if_closing(r_my_stickers); + if (r_my_stickers.is_error()) { + return promise.set_error(r_my_stickers.move_as_error()); + } + auto my_stickers = r_my_stickers.move_as_ok(); + auto total_count = my_stickers->count_; + vector sticker_set_ids; + for (auto &sticker_set_covered : my_stickers->sets_) { + auto sticker_set_id = + on_get_sticker_set_covered(std::move(sticker_set_covered), false, "on_get_created_sticker_sets"); + if (sticker_set_id.is_valid()) { + auto sticker_set = get_sticker_set(sticker_set_id); + CHECK(sticker_set != nullptr); + update_sticker_set(sticker_set, "on_get_created_sticker_sets"); + + if (!td::contains(sticker_set_ids, sticker_set_id) && sticker_set->is_created_) { + sticker_set_ids.push_back(sticker_set_id); + } + } + } + if (static_cast(sticker_set_ids.size()) > total_count) { + LOG(ERROR) << "Expected total of " << total_count << " owned sticker sets, but " << sticker_set_ids.size() + << " received"; + total_count = static_cast(sticker_set_ids.size()); + } + send_update_installed_sticker_sets(); + promise.set_value(get_sticker_sets_object(total_count, sticker_set_ids, 1)); +} + vector StickersManager::get_attached_sticker_file_ids(const vector &int_file_ids) { vector result; @@ -8919,7 +9056,8 @@ void StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_i if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } - if (!sticker->set_id_.is_valid() && sticker->format_ != StickerFormat::Webp) { + if (!sticker->set_id_.is_valid() && + (!add_on_server || (sticker->format_ != StickerFormat::Webp && sticker->format_ != StickerFormat::Webm))) { return promise.set_error(Status::Error(400, "The sticker must be from a sticker set")); } if (sticker->type_ == StickerType::CustomEmoji) { @@ -9300,7 +9438,8 @@ void StickersManager::add_favorite_sticker_impl(FileId sticker_id, bool add_on_s if (sticker == nullptr) { return promise.set_error(Status::Error(400, "Sticker not found")); } - if (!sticker->set_id_.is_valid() && sticker->format_ != StickerFormat::Webp) { + if (!sticker->set_id_.is_valid() && + (!add_on_server || (sticker->format_ != StickerFormat::Webp && sticker->format_ != StickerFormat::Webm))) { return promise.set_error(Status::Error(400, "The sticker must be from a sticker set")); } if (sticker->type_ == StickerType::CustomEmoji) { diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.h b/lib/tgchat/ext/td/td/telegram/StickersManager.h index ebd2c5bf..37bdb80b 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.h +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.h @@ -71,6 +71,8 @@ class StickersManager final : public Actor { StickerType get_sticker_type(FileId file_id) const; + StickerFormat get_sticker_format(FileId file_id) const; + bool is_premium_custom_emoji(CustomEmojiId custom_emoji_id, bool default_result) const; bool have_sticker(StickerSetId sticker_set_id, int64 sticker_id); @@ -289,16 +291,15 @@ class StickersManager final : public Actor { static td_api::object_ptr get_check_sticker_set_name_result_object( CheckStickerSetNameResult result); - void create_new_sticker_set(UserId user_id, string title, string short_name, StickerFormat sticker_format, - StickerType sticker_type, bool has_text_color, - vector> &&stickers, string software, - Promise> &&promise); + void create_new_sticker_set(UserId user_id, string title, string short_name, StickerType sticker_type, + bool has_text_color, vector> &&stickers, + string software, Promise> &&promise); void add_sticker_to_set(UserId user_id, string short_name, td_api::object_ptr &&sticker, - Promise &&promise); + td_api::object_ptr &&old_sticker, Promise &&promise); - void set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr &&thumbnail, - Promise &&promise); + void set_sticker_set_thumbnail(UserId user_id, string short_name, td_api::object_ptr &&thumbnail, + StickerFormat format, Promise &&promise); void set_custom_emoji_sticker_set_thumbnail(string short_name, CustomEmojiId custom_emoji_id, Promise &&promise); @@ -321,6 +322,9 @@ class StickersManager final : public Actor { void set_sticker_mask_position(const td_api::object_ptr &sticker, td_api::object_ptr &&mask_position, Promise &&promise); + void get_created_sticker_sets(StickerSetId offset_sticker_set_id, int32 limit, + Promise> &&promise); + vector get_recent_stickers(bool is_attached, Promise &&promise); void on_get_recent_stickers(bool is_repair, bool is_attached, @@ -477,12 +481,12 @@ class StickersManager final : public Actor { bool are_keywords_loaded_ = false; // stored in telegram_api::messages_stickerSet bool is_sticker_has_text_color_loaded_ = false; bool is_sticker_channel_emoji_status_loaded_ = false; + bool is_created_loaded_ = false; StickerSetId id_; int64 access_hash_ = 0; string title_; string short_name_; - StickerFormat sticker_format_ = StickerFormat::Unknown; StickerType sticker_type_ = StickerType::Regular; int32 sticker_count_ = 0; int32 hash_ = 0; @@ -499,6 +503,7 @@ class StickersManager final : public Actor { mutable std::map> keyword_stickers_map_; // keyword -> stickers FlatHashMap, FileIdHash> sticker_keywords_map_; // sticker -> keywords + bool is_created_ = false; bool is_installed_ = false; bool is_archived_ = false; bool is_official_ = false; @@ -521,7 +526,6 @@ class StickersManager final : public Actor { string title_; string short_name_; StickerType sticker_type_ = StickerType::Regular; - StickerFormat sticker_format_ = StickerFormat::Unknown; bool has_text_color_ = false; vector file_ids_; vector> stickers_; @@ -532,7 +536,8 @@ class StickersManager final : public Actor { struct PendingAddStickerToSet { string short_name_; FileId file_id_; - tl_object_ptr sticker_; + td_api::object_ptr sticker_; + telegram_api::object_ptr input_document_; Promise promise_; }; @@ -591,7 +596,9 @@ class StickersManager final : public Actor { StickerSetId sticker_set_id, int64 document_id, double zoom); - static double get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set); + PhotoFormat get_sticker_set_thumbnail_format(const StickerSet *sticker_set) const; + + double get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set) const; td_api::object_ptr get_sticker_set_thumbnail_object(const StickerSet *sticker_set) const; @@ -651,8 +658,6 @@ class StickersManager final : public Actor { StickerSet *add_sticker_set(StickerSetId sticker_set_id, int64 access_hash); - static PhotoFormat get_sticker_set_thumbnail_format(StickerFormat sticker_format); - static tl_object_ptr get_input_sticker_set(const StickerSet *set); StickerSetId on_get_input_sticker_set(FileId sticker_file_id, tl_object_ptr &&set_ptr, @@ -797,8 +802,7 @@ class StickersManager final : public Actor { StickerFormat sticker_format, StickerType sticker_type, bool for_thumbnail); - Result> prepare_input_sticker(td_api::inputSticker *sticker, - StickerFormat sticker_format, StickerType sticker_type); + Result> prepare_input_sticker(td_api::inputSticker *sticker, StickerType sticker_type); tl_object_ptr get_input_sticker(const td_api::inputSticker *sticker, FileId file_id) const; @@ -816,12 +820,15 @@ class StickersManager final : public Actor { void on_added_sticker_uploaded(int64 random_id, Result result); + StickerFormat guess_sticker_set_format(const StickerSet *sticker_set) const; + void do_add_sticker_to_set(UserId user_id, string short_name, td_api::object_ptr &&sticker, - Promise &&promise); + td_api::object_ptr &&old_sticker, Promise &&promise); void on_sticker_set_thumbnail_uploaded(int64 random_id, Result result); - void do_set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr &&thumbnail, + void do_set_sticker_set_thumbnail(UserId user_id, string short_name, + td_api::object_ptr &&thumbnail, StickerFormat format, Promise &&promise); void do_set_custom_emoji_sticker_set_thumbnail(string short_name, CustomEmojiId custom_emoji_id, @@ -833,6 +840,9 @@ class StickersManager final : public Actor { }; Result get_sticker_input_document(const tl_object_ptr &sticker) const; + void on_get_created_sticker_sets(Result> r_my_stickers, + Promise> &&promise); + bool update_sticker_set_cache(const StickerSet *sticker_set, Promise &promise); const StickerSet *get_premium_gift_sticker_set(); @@ -913,8 +923,8 @@ class StickersManager final : public Actor { static const std::map> &get_sticker_set_keywords(const StickerSet *sticker_set); - static void find_sticker_set_stickers(const StickerSet *sticker_set, const vector &emojis, - const string &query, vector &result); + void find_sticker_set_stickers(const StickerSet *sticker_set, const vector &emojis, const string &query, + vector> &result) const; bool can_find_sticker_by_query(FileId sticker_id, const vector &emojis, const string &query) const; diff --git a/lib/tgchat/ext/td/td/telegram/StickersManager.hpp b/lib/tgchat/ext/td/td/telegram/StickersManager.hpp index d4f54b77..368fb1ec 100644 --- a/lib/tgchat/ext/td/td/telegram/StickersManager.hpp +++ b/lib/tgchat/ext/td/td/telegram/StickersManager.hpp @@ -176,34 +176,36 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with bool has_expires_at = !sticker_set->is_installed_ && sticker_set->expires_at_ != 0; bool has_thumbnail = sticker_set->thumbnail_.file_id.is_valid(); bool has_minithumbnail = !sticker_set->minithumbnail_.empty(); - bool is_tgs = sticker_set->sticker_format_ == StickerFormat::Tgs; - bool is_webm = sticker_set->sticker_format_ == StickerFormat::Webm; bool is_masks = sticker_set->sticker_type_ == StickerType::Mask; bool is_emojis = sticker_set->sticker_type_ == StickerType::CustomEmoji; bool has_thumbnail_document_id = sticker_set->thumbnail_document_id_ != 0; + bool is_mixed_format = true; BEGIN_STORE_FLAGS(); STORE_FLAG(sticker_set->is_inited_); STORE_FLAG(was_loaded); STORE_FLAG(is_loaded); STORE_FLAG(sticker_set->is_installed_); STORE_FLAG(sticker_set->is_archived_); - STORE_FLAG(sticker_set->is_official_); + STORE_FLAG(sticker_set->is_official_); // 5 STORE_FLAG(is_masks); STORE_FLAG(sticker_set->is_viewed_); STORE_FLAG(has_expires_at); STORE_FLAG(has_thumbnail); - STORE_FLAG(sticker_set->is_thumbnail_reloaded_); - STORE_FLAG(is_tgs); + STORE_FLAG(sticker_set->is_thumbnail_reloaded_); // 10 + STORE_FLAG(false); STORE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_); STORE_FLAG(has_minithumbnail); - STORE_FLAG(is_webm); - STORE_FLAG(is_emojis); + STORE_FLAG(false); + STORE_FLAG(is_emojis); // 15 STORE_FLAG(has_thumbnail_document_id); STORE_FLAG(sticker_set->are_keywords_loaded_); STORE_FLAG(sticker_set->is_sticker_has_text_color_loaded_); STORE_FLAG(sticker_set->has_text_color_); - STORE_FLAG(sticker_set->is_sticker_channel_emoji_status_loaded_); + STORE_FLAG(sticker_set->is_sticker_channel_emoji_status_loaded_); // 20 STORE_FLAG(sticker_set->channel_emoji_status_); + STORE_FLAG(is_mixed_format); + STORE_FLAG(sticker_set->is_created_); + STORE_FLAG(sticker_set->is_created_loaded_); END_STORE_FLAGS(); store(sticker_set->id_.get(), storer); store(sticker_set->access_hash_, storer); @@ -262,13 +264,16 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser bool is_masks; bool has_expires_at; bool has_thumbnail; - bool is_tgs; + bool legacy_is_tgs; bool has_minithumbnail; - bool is_webm; + bool legacy_is_webm; bool is_emojis; bool has_thumbnail_document_id; bool has_text_color; bool channel_emoji_status; + bool is_mixed_format; + bool is_created; + bool is_created_loaded; BEGIN_PARSE_FLAGS(); PARSE_FLAG(sticker_set->is_inited_); PARSE_FLAG(sticker_set->was_loaded_); @@ -281,10 +286,10 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser PARSE_FLAG(has_expires_at); PARSE_FLAG(has_thumbnail); PARSE_FLAG(sticker_set->is_thumbnail_reloaded_); - PARSE_FLAG(is_tgs); + PARSE_FLAG(legacy_is_tgs); PARSE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_); PARSE_FLAG(has_minithumbnail); - PARSE_FLAG(is_webm); + PARSE_FLAG(legacy_is_webm); PARSE_FLAG(is_emojis); PARSE_FLAG(has_thumbnail_document_id); PARSE_FLAG(sticker_set->are_keywords_loaded_); @@ -292,6 +297,9 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser PARSE_FLAG(has_text_color); PARSE_FLAG(sticker_set->is_sticker_channel_emoji_status_loaded_); PARSE_FLAG(channel_emoji_status); + PARSE_FLAG(is_mixed_format); + PARSE_FLAG(is_created); + PARSE_FLAG(is_created_loaded); END_PARSE_FLAGS(); int64 sticker_set_id; int64 access_hash; @@ -302,14 +310,6 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser } (void)access_hash; // unused, because only known sticker sets with access hash can be loaded from database - StickerFormat sticker_format = StickerFormat::Unknown; - if (is_webm) { - sticker_format = StickerFormat::Webm; - } else if (is_tgs) { - sticker_format = StickerFormat::Tgs; - } else { - sticker_format = StickerFormat::Webp; - } auto sticker_type = ::td::get_sticker_type(is_masks, is_emojis); if (!is_emojis) { sticker_set->is_sticker_has_text_color_loaded_ = true; @@ -341,7 +341,15 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser if (has_thumbnail_document_id) { parse(thumbnail_document_id, parser); } - + if (!is_mixed_format && thumbnail.file_id.is_valid()) { + if (legacy_is_webm) { + thumbnail.type = 'v'; + } else if (legacy_is_tgs) { + thumbnail.type = 'a'; + } else { + thumbnail.type = 's'; + } + } if (!was_inited) { sticker_set->title_ = std::move(title); sticker_set->short_name_ = std::move(short_name); @@ -353,9 +361,10 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser sticker_set->expires_at_ = expires_at; sticker_set->is_official_ = is_official; sticker_set->sticker_type_ = sticker_type; - sticker_set->sticker_format_ = sticker_format; sticker_set->has_text_color_ = has_text_color; sticker_set->channel_emoji_status_ = channel_emoji_status; + sticker_set->is_created_ = is_created; + sticker_set->is_created_loaded_ = is_created_loaded; auto cleaned_username = clean_username(sticker_set->short_name_); if (!cleaned_username.empty()) { @@ -366,7 +375,8 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser if (sticker_set->title_ != title || sticker_set->minithumbnail_ != minithumbnail || sticker_set->thumbnail_ != thumbnail || sticker_set->thumbnail_document_id_ != thumbnail_document_id || sticker_set->is_official_ != is_official || sticker_set->has_text_color_ != has_text_color || - sticker_set->channel_emoji_status_ != channel_emoji_status) { + sticker_set->channel_emoji_status_ != channel_emoji_status || sticker_set->is_created_ != is_created || + sticker_set->is_created_loaded_ != is_created_loaded) { sticker_set->is_changed_ = true; } if (sticker_set->short_name_ != short_name) { @@ -378,10 +388,6 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser sticker_set->is_loaded_ = false; sticker_set->is_changed_ = true; } - if (sticker_set->sticker_format_ != sticker_format) { - LOG(ERROR) << "Sticker format of " << sticker_set->id_ << " has changed from \"" << sticker_format << "\" to \"" - << sticker_set->sticker_format_ << "\""; - } if (sticker_set->sticker_type_ != sticker_type) { LOG(ERROR) << "Type of " << sticker_set->id_ << " has changed from \"" << sticker_type << "\" to \"" << sticker_set->sticker_type_ << "\""; diff --git a/lib/tgchat/ext/td/td/telegram/StoryId.h b/lib/tgchat/ext/td/td/telegram/StoryId.h index 1bcccb62..66db9338 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryId.h +++ b/lib/tgchat/ext/td/td/telegram/StoryId.h @@ -37,6 +37,15 @@ class StoryId { return input_story_ids; } + static vector get_story_ids(const vector &input_story_ids) { + vector story_ids; + story_ids.reserve(input_story_ids.size()); + for (auto &input_story_id : input_story_ids) { + story_ids.emplace_back(input_story_id); + } + return story_ids; + } + int32 get() const { return id; } diff --git a/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp b/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp index 06ccb75c..ecc8c35d 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryInteractionInfo.cpp @@ -6,10 +6,10 @@ // #include "td/telegram/StoryInteractionInfo.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/FlatHashSet.h" @@ -25,7 +25,7 @@ StoryInteractionInfo::StoryInteractionInfo(Td *td, telegram_api::object_ptrrecent_viewers_) { UserId user_id(viewer_id); - if (user_id.is_valid() && td->contacts_manager_->have_min_user(user_id)) { + if (user_id.is_valid() && td->user_manager_->have_min_user(user_id)) { if (recent_viewer_user_ids_.size() == MAX_RECENT_VIEWERS) { LOG(ERROR) << "Receive too many recent story viewers: " << story_views->recent_viewers_; break; @@ -135,7 +135,7 @@ td_api::object_ptr StoryInteractionInfo::get_story } return td_api::make_object( view_count_, forward_count_, reaction_count_, - td->contacts_manager_->get_user_ids_object(recent_viewer_user_ids_, "get_story_interaction_info_object")); + td->user_manager_->get_user_ids_object(recent_viewer_user_ids_, "get_story_interaction_info_object")); } bool operator==(const StoryInteractionInfo &lhs, const StoryInteractionInfo &rhs) { diff --git a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp index 3c6b32c3..ea5fe256 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/StoryManager.cpp @@ -8,8 +8,8 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/FileReferenceManager.h" @@ -38,6 +38,7 @@ #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/telegram/UpdatesManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/WebPagesManager.h" #include "td/db/binlog/BinlogEvent.h" @@ -150,9 +151,7 @@ class ToggleStoriesHiddenQuery final : public Td::ResultHandler { dialog_id_ = dialog_id; are_hidden_ = are_hidden; 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")); - } + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::stories_togglePeerStoriesHidden(std::move(input_peer), are_hidden), {{dialog_id_}})); } @@ -704,6 +703,39 @@ class DeleteStoriesQuery final : public Td::ResultHandler { } }; +class TogglePinnedStoriesToTopQuery final : public Td::ResultHandler { + Promise promise_; + DialogId dialog_id_; + + public: + explicit TogglePinnedStoriesToTopQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(DialogId dialog_id, vector story_ids) { + dialog_id_ = dialog_id; + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id_, AccessRights::Write); + CHECK(input_peer != nullptr); + send_query(G()->net_query_creator().create( + telegram_api::stories_togglePinnedToTop(std::move(input_peer), StoryId::get_input_story_ids(story_ids)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for TogglePinnedStoriesToTopQuery: " << ptr; + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + td_->dialog_manager_->on_get_dialog_error(dialog_id_, status, "GetStoriesViewsQuery"); + promise_.set_error(std::move(status)); + } +}; + class GetStoriesViewsQuery final : public Td::ResultHandler { vector story_ids_; DialogId dialog_id_; @@ -941,7 +973,7 @@ class StoryManager::SendStoryQuery final : public Td::ResultHandler { } const FormattedText &caption = story->caption_; - auto entities = get_input_message_entities(td_->contacts_manager_.get(), &caption, "SendStoryQuery"); + auto entities = get_input_message_entities(td_->user_manager_.get(), &caption, "SendStoryQuery"); if (!td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { entities.clear(); } @@ -1057,7 +1089,7 @@ class StoryManager::EditStoryQuery final : public Td::ResultHandler { flags |= telegram_api::stories_editStory::CAPTION_MASK; if (td_->option_manager_->get_option_boolean("can_use_text_entities_in_story_caption")) { flags |= telegram_api::stories_editStory::ENTITIES_MASK; - entities = get_input_message_entities(td_->contacts_manager_.get(), &edited_story->caption_, "EditStoryQuery"); + entities = get_input_message_entities(td_->user_manager_.get(), &edited_story->caption_, "EditStoryQuery"); } } @@ -1644,7 +1676,7 @@ bool StoryManager::can_post_stories(DialogId owner_dialog_id) const { case DialogType::User: return is_my_story(owner_dialog_id); case DialogType::Channel: - return td_->contacts_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_post_stories(); + return td_->chat_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_post_stories(); case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: @@ -1658,7 +1690,7 @@ bool StoryManager::can_edit_stories(DialogId owner_dialog_id) const { case DialogType::User: return is_my_story(owner_dialog_id); case DialogType::Channel: - return td_->contacts_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_edit_stories(); + return td_->chat_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_edit_stories(); case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: @@ -1672,7 +1704,7 @@ bool StoryManager::can_delete_stories(DialogId owner_dialog_id) const { case DialogType::User: return is_my_story(owner_dialog_id); case DialogType::Channel: - return td_->contacts_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_delete_stories(); + return td_->chat_manager_->get_channel_status(owner_dialog_id.get_channel_id()).can_delete_stories(); case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: @@ -1843,7 +1875,9 @@ bool StoryManager::can_get_story_statistics(StoryFullId story_full_id, const Sto if (story == nullptr || !story_full_id.get_story_id().is_server()) { return false; } - return td_->contacts_manager_->can_get_channel_story_statistics(story_full_id.get_dialog_id()); + auto dialog_id = story_full_id.get_dialog_id(); + return dialog_id.get_type() == DialogType::Channel && + td_->chat_manager_->can_get_channel_story_statistics(dialog_id.get_channel_id()); } const StoryManager::ActiveStories *StoryManager::get_active_stories(DialogId owner_dialog_id) const { @@ -2098,8 +2132,8 @@ void StoryManager::on_load_active_stories_from_server( } case telegram_api::stories_allStories::ID: { auto stories = telegram_api::move_object_as(all_stories); - td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_load_active_stories_from_server"); - td_->contacts_manager_->on_get_chats(std::move(stories->chats_), "on_load_active_stories_from_server"); + td_->user_manager_->on_get_users(std::move(stories->users_), "on_load_active_stories_from_server"); + td_->chat_manager_->on_get_chats(std::move(stories->chats_), "on_load_active_stories_from_server"); if (stories->state_.empty()) { LOG(ERROR) << "Receive empty state in " << to_string(stories); } else { @@ -2141,7 +2175,7 @@ void StoryManager::on_load_active_stories_from_server( } else { LOG(ERROR) << "Receive " << story_date << " after " << max_story_date << " for " << (is_next ? "next" : "first") << " request with state \"" << old_state << "\" in " - << story_list_id << " of " << td_->contacts_manager_->get_my_id(); + << story_list_id << " of " << td_->user_manager_->get_my_id(); } owner_dialog_ids.push_back(owner_dialog_id); } @@ -2298,12 +2332,8 @@ void StoryManager::on_synchronized_archive_all_stories(bool set_archive_all_stor void StoryManager::toggle_dialog_stories_hidden(DialogId dialog_id, StoryListId story_list_id, Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "toggle_dialog_stories_hidden")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, + "toggle_dialog_stories_hidden")); if (story_list_id == get_dialog_story_list_id(dialog_id)) { return promise.set_value(Unit()); } @@ -2320,13 +2350,8 @@ void StoryManager::get_dialog_pinned_stories(DialogId owner_dialog_id, StoryId f if (limit <= 0) { return promise.set_error(Status::Error(400, "Parameter limit must be positive")); } - - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "get_dialog_pinned_stories")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, + "get_dialog_pinned_stories")); if (from_story_id != StoryId() && !from_story_id.is_server()) { return promise.set_error(Status::Error(400, "Invalid value of parameter from_story_id specified")); @@ -2348,11 +2373,13 @@ void StoryManager::on_get_dialog_pinned_stories(DialogId owner_dialog_id, telegram_api::object_ptr &&stories, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); + auto pinned_story_ids = StoryId::get_story_ids(stories->pinned_to_top_); auto result = on_get_stories(owner_dialog_id, {}, std::move(stories)); on_update_dialog_has_pinned_stories(owner_dialog_id, result.first > 0); - promise.set_value(get_stories_object(result.first, transform(result.second, [owner_dialog_id](StoryId story_id) { - return StoryFullId(owner_dialog_id, story_id); - }))); + promise.set_value(get_stories_object( + result.first, + transform(result.second, [owner_dialog_id](StoryId story_id) { return StoryFullId(owner_dialog_id, story_id); }), + pinned_story_ids)); } void StoryManager::get_story_archive(DialogId owner_dialog_id, StoryId from_story_id, int32 limit, @@ -2386,21 +2413,19 @@ void StoryManager::on_get_story_archive(DialogId owner_dialog_id, telegram_api::object_ptr &&stories, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); + LOG_IF(ERROR, !stories->pinned_to_top_.empty()) << "Receive pinned stories in archive"; auto result = on_get_stories(owner_dialog_id, {}, std::move(stories)); - promise.set_value(get_stories_object(result.first, transform(result.second, [owner_dialog_id](StoryId story_id) { - return StoryFullId(owner_dialog_id, story_id); - }))); + promise.set_value(get_stories_object( + result.first, + transform(result.second, [owner_dialog_id](StoryId story_id) { return StoryFullId(owner_dialog_id, story_id); }), + {})); } void StoryManager::get_dialog_expiring_stories(DialogId owner_dialog_id, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "get_dialog_expiring_stories")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, + "get_dialog_expiring_stories")); LOG(INFO) << "Get active stories in " << owner_dialog_id; auto active_stories = get_active_stories_force(owner_dialog_id, "get_dialog_expiring_stories"); @@ -2428,7 +2453,7 @@ void StoryManager::get_dialog_expiring_stories(DialogId owner_dialog_id, } void StoryManager::reload_dialog_expiring_stories(DialogId dialog_id) { - if (!td_->dialog_manager_->have_input_peer(dialog_id, AccessRights::Read)) { + if (!td_->dialog_manager_->have_input_peer(dialog_id, false, AccessRights::Read)) { return; } td_->dialog_manager_->force_create_dialog(dialog_id, "reload_dialog_expiring_stories"); @@ -2499,8 +2524,8 @@ void StoryManager::on_get_dialog_expiring_stories(DialogId owner_dialog_id, telegram_api::object_ptr &&stories, Promise> &&promise) { TRY_STATUS_PROMISE(promise, G()->close_status()); - td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_get_dialog_expiring_stories"); - td_->contacts_manager_->on_get_chats(std::move(stories->chats_), "on_get_dialog_expiring_stories"); + td_->user_manager_->on_get_users(std::move(stories->users_), "on_get_dialog_expiring_stories"); + td_->chat_manager_->on_get_chats(std::move(stories->chats_), "on_get_dialog_expiring_stories"); owner_dialog_id = on_get_dialog_stories(owner_dialog_id, std::move(stories->stories_), Promise()); if (promise) { CHECK(owner_dialog_id.is_valid()); @@ -2514,13 +2539,31 @@ void StoryManager::on_get_dialog_expiring_stories(DialogId owner_dialog_id, } } -void StoryManager::open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "open_story")) { - return promise.set_error(Status::Error(400, "Story sender not found")); +void StoryManager::set_pinned_stories(DialogId owner_dialog_id, vector story_ids, Promise &&promise) { + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Write, + "set_pinned_stories")); + if (!can_edit_stories(owner_dialog_id)) { + return promise.set_error(Status::Error(400, "Can't change pinned stories in the chat")); } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); + for (const auto &story_id : story_ids) { + StoryFullId story_full_id{owner_dialog_id, story_id}; + const Story *story = get_story(story_full_id); + if (story == nullptr) { + return promise.set_error(Status::Error(400, "Story not found")); + } + if (!story->is_pinned_) { + return promise.set_error(Status::Error(400, "The story must be posted to the chat page first")); + } + if (!story_id.is_server()) { + return promise.set_error(Status::Error(400, "Story must be sent first")); + } } + td_->create_handler(std::move(promise))->send(owner_dialog_id, std::move(story_ids)); +} + +void StoryManager::open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise) { + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, "open_story")); if (!story_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid story identifier specified")); } @@ -2578,12 +2621,8 @@ void StoryManager::open_story(DialogId owner_dialog_id, StoryId story_id, Promis } void StoryManager::close_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise) { - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "close_story")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, "close_story")); if (!story_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid story identifier specified")); } @@ -2630,7 +2669,7 @@ void StoryManager::view_story_message(StoryFullId story_full_id) { } void StoryManager::on_story_replied(StoryFullId story_full_id, UserId replier_user_id) { - if (!replier_user_id.is_valid() || replier_user_id == td_->contacts_manager_->get_my_id() || + if (!replier_user_id.is_valid() || replier_user_id == td_->user_manager_->get_my_id() || !story_full_id.get_story_id().is_server()) { return; } @@ -2690,12 +2729,8 @@ void StoryManager::on_story_chosen_reaction_changed(StoryFullId story_full_id, S void StoryManager::set_story_reaction(StoryFullId story_full_id, ReactionType reaction_type, bool add_to_recent, Promise &&promise) { auto owner_dialog_id = story_full_id.get_dialog_id(); - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "set_story_reaction")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE(promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, + "set_story_reaction")); if (!story_full_id.get_story_id().is_valid()) { return promise.set_error(Status::Error(400, "Invalid story identifier specified")); } @@ -2891,8 +2926,8 @@ bool StoryManager::has_unexpired_viewers(StoryFullId story_full_id, const Story void StoryManager::get_channel_differences_if_needed( telegram_api::object_ptr &&story_views, Promise> promise) { - td_->contacts_manager_->on_get_users(std::move(story_views->users_), "stories_storyViewsList"); - td_->contacts_manager_->on_get_chats(std::move(story_views->chats_), "stories_storyViewsList"); + td_->user_manager_->on_get_users(std::move(story_views->users_), "stories_storyViewsList"); + td_->chat_manager_->on_get_chats(std::move(story_views->chats_), "stories_storyViewsList"); vector *> messages; for (const auto &view : story_views->views_) { @@ -2998,8 +3033,8 @@ void StoryManager::on_get_story_interactions( void StoryManager::get_channel_differences_if_needed( telegram_api::object_ptr &&story_reactions, Promise> promise) { - td_->contacts_manager_->on_get_users(std::move(story_reactions->users_), "stories_storyReactionsList"); - td_->contacts_manager_->on_get_chats(std::move(story_reactions->chats_), "stories_storyReactionsList"); + td_->user_manager_->on_get_users(std::move(story_reactions->users_), "stories_storyReactionsList"); + td_->chat_manager_->on_get_chats(std::move(story_reactions->chats_), "stories_storyReactionsList"); vector *> messages; for (const auto &reaction : story_reactions->reactions_) { @@ -3266,13 +3301,15 @@ td_api::object_ptr StoryManager::get_story_object(StoryFullId sto } td_api::object_ptr StoryManager::get_stories_object(int32 total_count, - const vector &story_full_ids) const { + const vector &story_full_ids, + const vector &pinned_story_ids) const { if (total_count == -1) { total_count = static_cast(story_full_ids.size()); } - return td_api::make_object(total_count, transform(story_full_ids, [this](StoryFullId story_full_id) { - return get_story_object(story_full_id); - })); + return td_api::make_object( + total_count, + transform(story_full_ids, [this](StoryFullId story_full_id) { return get_story_object(story_full_id); }), + StoryId::get_input_story_ids(pinned_story_ids)); } td_api::object_ptr StoryManager::get_chat_active_stories_object( @@ -3406,7 +3443,7 @@ StoryId StoryManager::on_get_new_story(DialogId owner_dialog_id, bool is_bot = td_->auth_manager_->is_bot(); auto caption = - get_message_text(td_->contacts_manager_.get(), std::move(story_item->caption_), std::move(story_item->entities_), + get_message_text(td_->user_manager_.get(), std::move(story_item->caption_), std::move(story_item->entities_), true, is_bot, story_item->date_, false, "on_get_new_story"); auto content = get_story_content(td_, std::move(story_item->media_), owner_dialog_id); if (content == nullptr) { @@ -3772,7 +3809,8 @@ void StoryManager::on_story_changed(StoryFullId story_full_id, const Story *stor [&message_full_ids](const MessageFullId &message_full_id) { message_full_ids.push_back(message_full_id); }); CHECK(!message_full_ids.empty()); for (const auto &message_full_id : message_full_ids) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + send_closure_later(G()->messages_manager(), &MessagesManager::on_external_update_message_content, + message_full_id, "on_story_changed"); } } } @@ -3793,8 +3831,8 @@ void StoryManager::unregister_story_global_id(const Story *story) { std::pair> StoryManager::on_get_stories( DialogId owner_dialog_id, vector &&expected_story_ids, telegram_api::object_ptr &&stories) { - td_->contacts_manager_->on_get_users(std::move(stories->users_), "on_get_stories"); - td_->contacts_manager_->on_get_chats(std::move(stories->chats_), "on_get_stories"); + td_->user_manager_->on_get_users(std::move(stories->users_), "on_get_stories"); + td_->chat_manager_->on_get_chats(std::move(stories->chats_), "on_get_stories"); vector story_ids; for (auto &story : stories->stories_) { @@ -3901,12 +3939,12 @@ void StoryManager::on_update_dialog_max_story_ids(DialogId owner_dialog_id, Stor switch (owner_dialog_id.get_type()) { case DialogType::User: // use send_closure_later because story order can be updated from update_user - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::on_update_user_story_ids, + send_closure_later(td_->user_manager_actor_, &UserManager::on_update_user_story_ids, owner_dialog_id.get_user_id(), max_story_id, max_read_story_id); break; case DialogType::Channel: // use send_closure_later because story order can be updated from update_channel - send_closure_later(td_->contacts_manager_actor_, &ContactsManager::on_update_channel_story_ids, + send_closure_later(td_->chat_manager_actor_, &ChatManager::on_update_channel_story_ids, owner_dialog_id.get_channel_id(), max_story_id, max_read_story_id); break; case DialogType::Chat: @@ -3920,10 +3958,10 @@ void StoryManager::on_update_dialog_max_story_ids(DialogId owner_dialog_id, Stor void StoryManager::on_update_dialog_max_read_story_id(DialogId owner_dialog_id, StoryId max_read_story_id) { switch (owner_dialog_id.get_type()) { case DialogType::User: - td_->contacts_manager_->on_update_user_max_read_story_id(owner_dialog_id.get_user_id(), max_read_story_id); + td_->user_manager_->on_update_user_max_read_story_id(owner_dialog_id.get_user_id(), max_read_story_id); break; case DialogType::Channel: - td_->contacts_manager_->on_update_channel_max_read_story_id(owner_dialog_id.get_channel_id(), max_read_story_id); + td_->chat_manager_->on_update_channel_max_read_story_id(owner_dialog_id.get_channel_id(), max_read_story_id); break; case DialogType::Chat: case DialogType::SecretChat: @@ -3936,11 +3974,10 @@ void StoryManager::on_update_dialog_max_read_story_id(DialogId owner_dialog_id, void StoryManager::on_update_dialog_has_pinned_stories(DialogId owner_dialog_id, bool has_pinned_stories) { switch (owner_dialog_id.get_type()) { case DialogType::User: - td_->contacts_manager_->on_update_user_has_pinned_stories(owner_dialog_id.get_user_id(), has_pinned_stories); + td_->user_manager_->on_update_user_has_pinned_stories(owner_dialog_id.get_user_id(), has_pinned_stories); break; case DialogType::Channel: - td_->contacts_manager_->on_update_channel_has_pinned_stories(owner_dialog_id.get_channel_id(), - has_pinned_stories); + td_->chat_manager_->on_update_channel_has_pinned_stories(owner_dialog_id.get_channel_id(), has_pinned_stories); break; case DialogType::Chat: case DialogType::SecretChat: @@ -3953,10 +3990,10 @@ void StoryManager::on_update_dialog_has_pinned_stories(DialogId owner_dialog_id, void StoryManager::on_update_dialog_stories_hidden(DialogId owner_dialog_id, bool stories_hidden) { switch (owner_dialog_id.get_type()) { case DialogType::User: - td_->contacts_manager_->on_update_user_stories_hidden(owner_dialog_id.get_user_id(), stories_hidden); + td_->user_manager_->on_update_user_stories_hidden(owner_dialog_id.get_user_id(), stories_hidden); break; case DialogType::Channel: - td_->contacts_manager_->on_update_channel_stories_hidden(owner_dialog_id.get_channel_id(), stories_hidden); + td_->chat_manager_->on_update_channel_stories_hidden(owner_dialog_id.get_channel_id(), stories_hidden); break; case DialogType::Chat: case DialogType::SecretChat: @@ -4077,7 +4114,7 @@ bool StoryManager::update_active_stories_order(DialogId owner_dialog_id, ActiveS int64 new_private_order = 0; new_private_order += last_story->date_; if (owner_dialog_id.get_type() == DialogType::User && - td_->contacts_manager_->is_user_premium(owner_dialog_id.get_user_id())) { + td_->user_manager_->is_user_premium(owner_dialog_id.get_user_id())) { new_private_order += static_cast(1) << 33; } if (owner_dialog_id == get_changelog_story_dialog_id()) { @@ -4367,7 +4404,7 @@ void StoryManager::update_stealth_mode() { DialogId StoryManager::get_changelog_story_dialog_id() const { return DialogId(UserId(td_->option_manager_->get_option_integer( - "stories_changelog_user_id", ContactsManager::get_service_notifications_user_id().get()))); + "stories_changelog_user_id", UserManager::get_service_notifications_user_id().get()))); } bool StoryManager::is_subscribed_to_dialog_stories(DialogId owner_dialog_id) const { @@ -4379,9 +4416,9 @@ bool StoryManager::is_subscribed_to_dialog_stories(DialogId owner_dialog_id) con if (is_my_story(owner_dialog_id)) { return true; } - return td_->contacts_manager_->is_user_contact(owner_dialog_id.get_user_id()); + return td_->user_manager_->is_user_contact(owner_dialog_id.get_user_id()); case DialogType::Channel: - return td_->contacts_manager_->get_channel_status(owner_dialog_id.get_channel_id()).is_member(); + return td_->chat_manager_->get_channel_status(owner_dialog_id.get_channel_id()).is_member(); case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: @@ -4396,13 +4433,12 @@ StoryListId StoryManager::get_dialog_story_list_id(DialogId owner_dialog_id) con } switch (owner_dialog_id.get_type()) { case DialogType::User: - if (!is_my_story(owner_dialog_id) && - td_->contacts_manager_->get_user_stories_hidden(owner_dialog_id.get_user_id())) { + if (!is_my_story(owner_dialog_id) && td_->user_manager_->get_user_stories_hidden(owner_dialog_id.get_user_id())) { return StoryListId::archive(); } return StoryListId::main(); case DialogType::Channel: - if (td_->contacts_manager_->get_channel_stories_hidden(owner_dialog_id.get_channel_id())) { + if (td_->chat_manager_->get_channel_stories_hidden(owner_dialog_id.get_channel_id())) { return StoryListId::archive(); } return StoryListId::main(); @@ -4430,7 +4466,7 @@ void StoryManager::on_dialog_active_stories_order_updated(DialogId owner_dialog_ void StoryManager::on_get_story_views(DialogId owner_dialog_id, const vector &story_ids, telegram_api::object_ptr &&story_views) { schedule_interaction_info_update(); - td_->contacts_manager_->on_get_users(std::move(story_views->users_), "on_get_story_views"); + td_->user_manager_->on_get_users(std::move(story_views->users_), "on_get_story_views"); if (story_ids.size() != story_views->views_.size()) { LOG(ERROR) << "Receive invalid views for " << story_ids << ": " << to_string(story_views); return; @@ -4481,9 +4517,9 @@ void StoryManager::on_view_dialog_active_stories(vector dialog_ids) { bool need_poll = [&] { switch (dialog_id.get_type()) { case DialogType::User: - return td_->contacts_manager_->can_poll_user_active_stories(dialog_id.get_user_id()); + return td_->user_manager_->can_poll_user_active_stories(dialog_id.get_user_id()); case DialogType::Channel: - return td_->contacts_manager_->can_poll_channel_active_stories(dialog_id.get_channel_id()); + return td_->chat_manager_->can_poll_channel_active_stories(dialog_id.get_channel_id()); case DialogType::Chat: case DialogType::SecretChat: case DialogType::None: @@ -4528,9 +4564,9 @@ void StoryManager::on_get_dialog_max_active_story_ids(const vector &di auto dialog_id = dialog_ids[i]; if (max_story_id == StoryId() || max_story_id.is_server()) { if (dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_story_ids(dialog_id.get_user_id(), max_story_id, StoryId()); + td_->user_manager_->on_update_user_story_ids(dialog_id.get_user_id(), max_story_id, StoryId()); } else { - td_->contacts_manager_->on_update_channel_story_ids(dialog_id.get_channel_id(), max_story_id, StoryId()); + td_->chat_manager_->on_update_channel_story_ids(dialog_id.get_channel_id(), max_story_id, StoryId()); } } else { LOG(ERROR) << "Receive " << max_story_id << " as maximum active story for " << dialog_id; @@ -4605,12 +4641,8 @@ void StoryManager::on_reload_story(StoryFullId story_full_id, Result &&res void StoryManager::get_story(DialogId owner_dialog_id, StoryId story_id, bool only_local, Promise> &&promise) { - if (!td_->dialog_manager_->have_dialog_force(owner_dialog_id, "get_story")) { - return promise.set_error(Status::Error(400, "Story sender not found")); - } - if (!td_->dialog_manager_->have_input_peer(owner_dialog_id, AccessRights::Read)) { - return promise.set_error(Status::Error(400, "Can't access the story sender")); - } + TRY_STATUS_PROMISE( + promise, td_->dialog_manager_->check_dialog_access(owner_dialog_id, false, AccessRights::Read, "get_story")); if (!story_id.is_valid()) { return promise.set_error(Status::Error(400, "Invalid story identifier specified")); } @@ -4700,7 +4732,7 @@ void StoryManager::get_dialogs_to_send_stories(Promisetd_db()->get_binlog_pmc()->erase(pmc_key); } else { for (auto channel_id : channel_ids) { - if (td_->contacts_manager_->get_channel_status(channel_id).can_post_stories()) { + if (td_->chat_manager_->get_channel_status(channel_id).can_post_stories()) { channels_to_send_stories_.push_back(channel_id); } } @@ -4765,14 +4797,14 @@ void StoryManager::update_dialogs_to_send_stories(ChannelId channel_id, bool can } void StoryManager::on_get_dialogs_to_send_stories(vector> &&chats) { - auto channel_ids = td_->contacts_manager_->get_channel_ids(std::move(chats), "on_get_dialogs_to_send_stories"); + auto channel_ids = td_->chat_manager_->get_channel_ids(std::move(chats), "on_get_dialogs_to_send_stories"); if (channels_to_send_stories_inited_ && channels_to_send_stories_ == channel_ids) { return; } channels_to_send_stories_.clear(); for (auto channel_id : channel_ids) { td_->dialog_manager_->force_create_dialog(DialogId(channel_id), "on_get_dialogs_to_send_stories"); - if (td_->contacts_manager_->get_channel_status(channel_id).can_post_stories()) { + if (td_->chat_manager_->get_channel_status(channel_id).can_post_stories()) { channels_to_send_stories_.push_back(channel_id); } } @@ -4867,7 +4899,7 @@ void StoryManager::send_story(DialogId dialog_id, td_api::object_ptr(); if (dialog_id.get_type() == DialogType::Channel && - td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id())) { + td_->chat_manager_->is_megagroup_channel(dialog_id.get_channel_id())) { story->sender_dialog_id_ = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id); if (story->sender_dialog_id_ == DialogId() && !td_->dialog_manager_->is_anonymous_administrator(dialog_id, nullptr)) { diff --git a/lib/tgchat/ext/td/td/telegram/StoryManager.h b/lib/tgchat/ext/td/td/telegram/StoryManager.h index f9463732..57ac879d 100644 --- a/lib/tgchat/ext/td/td/telegram/StoryManager.h +++ b/lib/tgchat/ext/td/td/telegram/StoryManager.h @@ -253,6 +253,8 @@ class StoryManager final : public Actor { void reload_dialog_expiring_stories(DialogId dialog_id); + void set_pinned_stories(DialogId owner_dialog_id, vector story_ids, Promise &&promise); + void open_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise); void close_story(DialogId owner_dialog_id, StoryId story_id, Promise &&promise); @@ -337,8 +339,8 @@ class StoryManager final : public Actor { td_api::object_ptr get_story_object(StoryFullId story_full_id) const; - td_api::object_ptr get_stories_object(int32 total_count, - const vector &story_full_ids) const; + td_api::object_ptr get_stories_object(int32 total_count, const vector &story_full_ids, + const vector &pinned_story_ids) const; FileSourceId get_story_file_source_id(StoryFullId story_full_id); diff --git a/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp b/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp index 196145e0..e0a4bd0c 100644 --- a/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp +++ b/lib/tgchat/ext/td/td/telegram/SuggestedAction.cpp @@ -8,7 +8,7 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/Td.h" @@ -46,6 +46,10 @@ SuggestedAction::SuggestedAction(Slice action_str) { init(Type::RestorePremium); } else if (action_str == Slice("PREMIUM_CHRISTMAS")) { init(Type::GiftPremiumForChristmas); + } else if (action_str == Slice("BIRTHDAY_SETUP")) { + init(Type::BirthdaySetup); + } else if (action_str == Slice("PREMIUM_GRACE")) { + init(Type::PremiumGrace); } } @@ -101,6 +105,12 @@ SuggestedAction::SuggestedAction(const td_api::object_ptr SuggestedAction::get_suggested_actio return td_api::make_object(); case Type::GiftPremiumForChristmas: return td_api::make_object(); + case Type::BirthdaySetup: + 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")); default: UNREACHABLE(); return nullptr; @@ -173,11 +192,11 @@ td_api::object_ptr get_update_suggested_actions_ transform(removed_actions, get_object)); } -void update_suggested_actions(vector &suggested_actions, +bool update_suggested_actions(vector &suggested_actions, vector &&new_suggested_actions) { td::unique(new_suggested_actions); if (new_suggested_actions == suggested_actions) { - return; + return false; } vector added_actions; @@ -198,13 +217,16 @@ void update_suggested_actions(vector &suggested_actions, suggested_actions = std::move(new_suggested_actions); send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object(added_actions, removed_actions, "update_suggested_actions")); + return true; } -void remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action) { +bool remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action) { if (td::remove(suggested_actions, suggested_action)) { send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object({}, {suggested_action}, "remove_suggested_action")); + return true; } + return false; } void dismiss_suggested_action(SuggestedAction action, Promise &&promise) { @@ -219,10 +241,12 @@ void dismiss_suggested_action(SuggestedAction action, Promise &&promise) { case SuggestedAction::Type::SubscribeToAnnualPremium: case SuggestedAction::Type::RestorePremium: case SuggestedAction::Type::GiftPremiumForChristmas: + case SuggestedAction::Type::BirthdaySetup: + case SuggestedAction::Type::PremiumGrace: return send_closure_later(G()->config_manager(), &ConfigManager::dismiss_suggested_action, std::move(action), std::move(promise)); case SuggestedAction::Type::ConvertToGigagroup: - return send_closure_later(G()->contacts_manager(), &ContactsManager::dismiss_dialog_suggested_action, + return send_closure_later(G()->dialog_manager(), &DialogManager::dismiss_dialog_suggested_action, std::move(action), std::move(promise)); case SuggestedAction::Type::SetPassword: { if (action.otherwise_relogin_days_ < 0) { diff --git a/lib/tgchat/ext/td/td/telegram/SuggestedAction.h b/lib/tgchat/ext/td/td/telegram/SuggestedAction.h index 2e3a1175..ac25eaf3 100644 --- a/lib/tgchat/ext/td/td/telegram/SuggestedAction.h +++ b/lib/tgchat/ext/td/td/telegram/SuggestedAction.h @@ -27,7 +27,9 @@ struct SuggestedAction { UpgradePremium, SubscribeToAnnualPremium, RestorePremium, - GiftPremiumForChristmas + GiftPremiumForChristmas, + BirthdaySetup, + PremiumGrace }; Type type_ = Type::Empty; DialogId dialog_id_; @@ -54,6 +56,12 @@ struct SuggestedAction { string get_suggested_action_str() const; td_api::object_ptr get_suggested_action_object() const; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); }; inline bool operator==(const SuggestedAction &lhs, const SuggestedAction &rhs) { @@ -73,10 +81,10 @@ inline bool operator<(const SuggestedAction &lhs, const SuggestedAction &rhs) { td_api::object_ptr get_update_suggested_actions_object( const vector &added_actions, const vector &removed_actions, const char *source); -void update_suggested_actions(vector &suggested_actions, +bool update_suggested_actions(vector &suggested_actions, vector &&new_suggested_actions); -void remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action); +bool remove_suggested_action(vector &suggested_actions, SuggestedAction suggested_action); void dismiss_suggested_action(SuggestedAction action, Promise &&promise); diff --git a/lib/tgchat/ext/td/td/telegram/SuggestedAction.hpp b/lib/tgchat/ext/td/td/telegram/SuggestedAction.hpp new file mode 100644 index 00000000..a168227d --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/SuggestedAction.hpp @@ -0,0 +1,50 @@ +// +// 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/SuggestedAction.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +template +void SuggestedAction::store(StorerT &storer) const { + bool has_dialog_id = dialog_id_ != DialogId(); + bool has_otherwise_relogin_days = otherwise_relogin_days_ != 0; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_dialog_id); + STORE_FLAG(has_otherwise_relogin_days); + END_STORE_FLAGS(); + td::store(type_, storer); + if (has_dialog_id) { + td::store(dialog_id_, storer); + } + if (has_otherwise_relogin_days) { + td::store(otherwise_relogin_days_, storer); + } +} + +template +void SuggestedAction::parse(ParserT &parser) { + bool has_dialog_id; + bool has_otherwise_relogin_days; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_dialog_id); + PARSE_FLAG(has_otherwise_relogin_days); + END_PARSE_FLAGS(); + td::parse(type_, parser); + if (has_dialog_id) { + td::parse(dialog_id_, parser); + } + if (has_otherwise_relogin_days) { + td::parse(otherwise_relogin_days_, parser); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/Support.cpp b/lib/tgchat/ext/td/td/telegram/Support.cpp index db6d41e6..a43d6717 100644 --- a/lib/tgchat/ext/td/td/telegram/Support.cpp +++ b/lib/tgchat/ext/td/td/telegram/Support.cpp @@ -6,12 +6,12 @@ // #include "td/telegram/Support.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" #include "td/telegram/MessageEntity.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/utils/buffer.h" #include "td/utils/Status.h" @@ -25,7 +25,7 @@ static td_api::object_ptr get_user_support_info_object( FormattedText message; if (user_info->get_id() == telegram_api::help_userInfo::ID) { auto info = telegram_api::move_object_as(user_info); - message = get_message_text(td->contacts_manager_.get(), std::move(info->message_), std::move(info->entities_), true, + message = get_message_text(td->user_manager_.get(), std::move(info->message_), std::move(info->entities_), true, true, info->date_, false, "get_user_support_info_object"); result->author_ = std::move(info->author_); result->date_ = info->date_; @@ -43,7 +43,7 @@ class GetUserInfoQuery final : public Td::ResultHandler { } void send(UserId user_id) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } @@ -74,14 +74,14 @@ class EditUserInfoQuery final : public Td::ResultHandler { } void send(UserId user_id, FormattedText &&formatted_text) { - auto r_input_user = td_->contacts_manager_->get_input_user(user_id); + auto r_input_user = td_->user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return on_error(r_input_user.move_as_error()); } send_query(G()->net_query_creator().create(telegram_api::help_editUserInfo( r_input_user.move_as_ok(), formatted_text.text, - get_input_message_entities(td_->contacts_manager_.get(), &formatted_text, "EditUserInfoQuery")))); + get_input_message_entities(td_->user_manager_.get(), &formatted_text, "EditUserInfoQuery")))); } void on_result(BufferSlice packet) final { diff --git a/lib/tgchat/ext/td/td/telegram/Td.cpp b/lib/tgchat/ext/td/td/telegram/Td.cpp index 4d361f23..ddbe2354 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.cpp +++ b/lib/tgchat/ext/td/td/telegram/Td.cpp @@ -18,23 +18,28 @@ #include "td/telegram/BackgroundId.h" #include "td/telegram/BackgroundManager.h" #include "td/telegram/BackgroundType.h" +#include "td/telegram/Birthdate.h" #include "td/telegram/BoostManager.h" #include "td/telegram/BotCommand.h" #include "td/telegram/BotInfoManager.h" #include "td/telegram/BotMenuButton.h" #include "td/telegram/BusinessAwayMessage.h" +#include "td/telegram/BusinessConnectionId.h" +#include "td/telegram/BusinessConnectionManager.h" #include "td/telegram/BusinessGreetingMessage.h" +#include "td/telegram/BusinessIntro.h" #include "td/telegram/BusinessManager.h" #include "td/telegram/BusinessWorkHours.h" #include "td/telegram/CallbackQueriesManager.h" #include "td/telegram/CallId.h" #include "td/telegram/CallManager.h" #include "td/telegram/ChannelId.h" +#include "td/telegram/ChannelRecommendationManager.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/CommonDialogManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/CountryInfoManager.h" #include "td/telegram/CustomEmojiId.h" #include "td/telegram/DeviceTokenManager.h" @@ -67,7 +72,6 @@ #include "td/telegram/files/FileSourceId.h" #include "td/telegram/files/FileStats.h" #include "td/telegram/files/FileType.h" -#include "td/telegram/FolderId.h" #include "td/telegram/ForumTopicManager.h" #include "td/telegram/GameManager.h" #include "td/telegram/Global.h" @@ -87,6 +91,7 @@ #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" @@ -114,6 +119,7 @@ #include "td/telegram/OptionManager.h" #include "td/telegram/PasswordManager.h" #include "td/telegram/Payments.h" +#include "td/telegram/PeopleNearbyManager.h" #include "td/telegram/PhoneNumberManager.h" #include "td/telegram/PhotoSizeSource.h" #include "td/telegram/PollManager.h" @@ -122,6 +128,7 @@ #include "td/telegram/PublicDialogType.h" #include "td/telegram/QuickReplyManager.h" #include "td/telegram/ReactionManager.h" +#include "td/telegram/ReactionNotificationSettings.h" #include "td/telegram/ReactionType.h" #include "td/telegram/ReportReason.h" #include "td/telegram/RequestActor.h" @@ -158,6 +165,7 @@ #include "td/telegram/TranslationManager.h" #include "td/telegram/UpdatesManager.h" #include "td/telegram/UserId.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Version.h" #include "td/telegram/VideoNotesManager.h" #include "td/telegram/VideosManager.h" @@ -260,8 +268,8 @@ class GetRecentMeUrlsQuery final : public Td::ResultHandler { } auto urls_full = result_ptr.move_as_ok(); - td_->contacts_manager_->on_get_users(std::move(urls_full->users_), "GetRecentMeUrlsQuery"); - td_->contacts_manager_->on_get_chats(std::move(urls_full->chats_), "GetRecentMeUrlsQuery"); + td_->user_manager_->on_get_users(std::move(urls_full->users_), "GetRecentMeUrlsQuery"); + td_->chat_manager_->on_get_chats(std::move(urls_full->chats_), "GetRecentMeUrlsQuery"); auto urls = std::move(urls_full->urls_); auto results = make_tl_object(); @@ -279,8 +287,8 @@ class GetRecentMeUrlsQuery final : public Td::ResultHandler { result = nullptr; break; } - result->type_ = make_tl_object( - td_->contacts_manager_->get_user_id_object(user_id, "tMeUrlTypeUser")); + result->type_ = + make_tl_object(td_->user_manager_->get_user_id_object(user_id, "tMeUrlTypeUser")); break; } case telegram_api::recentMeUrlChat::ID: { @@ -293,7 +301,7 @@ class GetRecentMeUrlsQuery final : public Td::ResultHandler { break; } result->type_ = make_tl_object( - td_->contacts_manager_->get_supergroup_id_object(channel_id, "tMeUrlTypeSupergroup")); + td_->chat_manager_->get_supergroup_id_object(channel_id, "tMeUrlTypeSupergroup")); break; } case telegram_api::recentMeUrlChatInvite::ID: { @@ -594,11 +602,11 @@ class GetMeRequest final : public RequestActor<> { UserId user_id_; void do_run(Promise &&promise) final { - user_id_ = td_->contacts_manager_->get_me(std::move(promise)); + user_id_ = td_->user_manager_->get_me(std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_user_object(user_id_)); + send_result(td_->user_manager_->get_user_object(user_id_)); } public: @@ -610,11 +618,11 @@ class GetUserRequest final : public RequestActor<> { UserId user_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->get_user(user_id_, get_tries(), std::move(promise)); + td_->user_manager_->get_user(user_id_, get_tries(), std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_user_object(user_id_)); + send_result(td_->user_manager_->get_user_object(user_id_)); } public: @@ -628,11 +636,11 @@ class GetUserFullInfoRequest final : public RequestActor<> { UserId user_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->load_user_full(user_id_, get_tries() < 2, std::move(promise), "GetUserFullInfoRequest"); + td_->user_manager_->load_user_full(user_id_, get_tries() < 2, std::move(promise), "GetUserFullInfoRequest"); } void do_send_result() final { - send_result(td_->contacts_manager_->get_user_full_info_object(user_id_)); + send_result(td_->user_manager_->get_user_full_info_object(user_id_)); } public: @@ -645,11 +653,11 @@ class GetGroupRequest final : public RequestActor<> { ChatId chat_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->get_chat(chat_id_, get_tries(), std::move(promise)); + td_->chat_manager_->get_chat(chat_id_, get_tries(), std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_basic_group_object(chat_id_)); + send_result(td_->chat_manager_->get_basic_group_object(chat_id_)); } public: @@ -663,11 +671,11 @@ class GetGroupFullInfoRequest final : public RequestActor<> { ChatId chat_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->load_chat_full(chat_id_, get_tries() < 2, std::move(promise), "getBasicGroupFullInfo"); + td_->chat_manager_->load_chat_full(chat_id_, get_tries() < 2, std::move(promise), "getBasicGroupFullInfo"); } void do_send_result() final { - send_result(td_->contacts_manager_->get_basic_group_full_info_object(chat_id_)); + send_result(td_->chat_manager_->get_basic_group_full_info_object(chat_id_)); } public: @@ -680,11 +688,11 @@ class GetSupergroupRequest final : public RequestActor<> { ChannelId channel_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->get_channel(channel_id_, get_tries(), std::move(promise)); + td_->chat_manager_->get_channel(channel_id_, get_tries(), std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_supergroup_object(channel_id_)); + send_result(td_->chat_manager_->get_supergroup_object(channel_id_)); } public: @@ -698,12 +706,12 @@ class GetSupergroupFullInfoRequest final : public RequestActor<> { ChannelId channel_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->load_channel_full(channel_id_, get_tries() < 2, std::move(promise), - "GetSupergroupFullInfoRequest"); + td_->chat_manager_->load_channel_full(channel_id_, get_tries() < 2, std::move(promise), + "GetSupergroupFullInfoRequest"); } void do_send_result() final { - send_result(td_->contacts_manager_->get_supergroup_full_info_object(channel_id_)); + send_result(td_->chat_manager_->get_supergroup_full_info_object(channel_id_)); } public: @@ -716,11 +724,11 @@ class GetSecretChatRequest final : public RequestActor<> { SecretChatId secret_chat_id_; void do_run(Promise &&promise) final { - td_->contacts_manager_->get_secret_chat(secret_chat_id_, get_tries() < 2, std::move(promise)); + td_->user_manager_->get_secret_chat(secret_chat_id_, get_tries() < 2, std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_secret_chat_object(secret_chat_id_)); + send_result(td_->user_manager_->get_secret_chat_object(secret_chat_id_)); } public: @@ -742,7 +750,7 @@ class GetChatRequest final : public RequestActor<> { if (!dialog_found_) { send_error(Status::Error(400, "Chat is not accessible")); } else { - send_result(td_->messages_manager_->get_chat_object(dialog_id_)); + send_result(td_->messages_manager_->get_chat_object(dialog_id_, "GetChatRequest")); } } @@ -755,20 +763,21 @@ class GetChatRequest final : public RequestActor<> { class SearchUserByPhoneNumberRequest final : public RequestActor<> { string phone_number_; + bool only_local_; UserId user_id_; void do_run(Promise &&promise) final { - user_id_ = td_->contacts_manager_->search_user_by_phone_number(phone_number_, std::move(promise)); + user_id_ = td_->user_manager_->search_user_by_phone_number(phone_number_, only_local_, std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_user_object(user_id_)); + send_result(td_->user_manager_->get_user_object(user_id_)); } public: - SearchUserByPhoneNumberRequest(ActorShared td, uint64 request_id, string &&phone_number) - : RequestActor(std::move(td), request_id), phone_number_(std::move(phone_number)) { + SearchUserByPhoneNumberRequest(ActorShared td, uint64 request_id, string &&phone_number, bool only_local) + : RequestActor(std::move(td), request_id), phone_number_(std::move(phone_number)), only_local_(only_local) { } }; @@ -803,7 +812,7 @@ class SearchPublicChatRequest final : public RequestActor<> { } void do_send_result() final { - send_result(td_->messages_manager_->get_chat_object(dialog_id_)); + send_result(td_->messages_manager_->get_chat_object(dialog_id_, "SearchPublicChatRequest")); } public: @@ -898,7 +907,7 @@ class GetSuitableDiscussionChatsRequest final : public RequestActor<> { vector dialog_ids_; void do_run(Promise &&promise) final { - dialog_ids_ = td_->contacts_manager_->get_dialogs_for_discussion(std::move(promise)); + dialog_ids_ = td_->chat_manager_->get_dialogs_for_discussion(std::move(promise)); } void do_send_result() final { @@ -914,7 +923,7 @@ class GetInactiveSupergroupChatsRequest final : public RequestActor<> { vector dialog_ids_; void do_run(Promise &&promise) final { - dialog_ids_ = td_->contacts_manager_->get_inactive_channels(std::move(promise)); + dialog_ids_ = td_->chat_manager_->get_inactive_channels(std::move(promise)); } void do_send_result() final { @@ -1203,12 +1212,14 @@ class EditMessageLiveLocationRequest final : public RequestOnceActor { MessageFullId message_full_id_; tl_object_ptr reply_markup_; tl_object_ptr location_; + int32 live_period_; int32 heading_; int32 proximity_alert_radius_; void do_run(Promise &&promise) final { td_->messages_manager_->edit_message_live_location(message_full_id_, std::move(reply_markup_), std::move(location_), - heading_, proximity_alert_radius_, std::move(promise)); + live_period_, heading_, proximity_alert_radius_, + std::move(promise)); } void do_send_result() final { @@ -1218,11 +1229,13 @@ class EditMessageLiveLocationRequest final : public RequestOnceActor { public: EditMessageLiveLocationRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 message_id, tl_object_ptr reply_markup, - tl_object_ptr location, int32 heading, int32 proximity_alert_radius) + tl_object_ptr location, int32 live_period, int32 heading, + int32 proximity_alert_radius) : RequestOnceActor(std::move(td), request_id) , message_full_id_(DialogId(dialog_id), MessageId(message_id)) , reply_markup_(std::move(reply_markup)) , location_(std::move(location)) + , live_period_(live_period) , heading_(heading) , proximity_alert_radius_(proximity_alert_radius) { } @@ -1257,10 +1270,11 @@ class EditMessageCaptionRequest final : public RequestOnceActor { MessageFullId message_full_id_; tl_object_ptr reply_markup_; tl_object_ptr caption_; + bool invert_media_; void do_run(Promise &&promise) final { td_->messages_manager_->edit_message_caption(message_full_id_, std::move(reply_markup_), std::move(caption_), - std::move(promise)); + invert_media_, std::move(promise)); } void do_send_result() final { @@ -1270,11 +1284,12 @@ class EditMessageCaptionRequest final : public RequestOnceActor { public: EditMessageCaptionRequest(ActorShared td, uint64 request_id, int64 dialog_id, int64 message_id, tl_object_ptr reply_markup, - tl_object_ptr caption) + tl_object_ptr caption, bool invert_media) : RequestOnceActor(std::move(td), request_id) , message_full_id_(DialogId(dialog_id), MessageId(message_id)) , reply_markup_(std::move(reply_markup)) - , caption_(std::move(caption)) { + , caption_(std::move(caption)) + , invert_media_(invert_media) { } }; @@ -1454,85 +1469,6 @@ class SearchChatMessagesRequest final : public RequestActor<> { } }; -class SearchSecretMessagesRequest final : public RequestActor<> { - DialogId dialog_id_; - string query_; - string offset_; - int32 limit_; - MessageSearchFilter filter_; - int64 random_id_; - - MessagesManager::FoundMessages found_messages_; - - void do_run(Promise &&promise) final { - found_messages_ = td_->messages_manager_->offline_search_messages(dialog_id_, query_, offset_, limit_, filter_, - random_id_, std::move(promise)); - } - - void do_send_result() final { - send_result(td_->messages_manager_->get_found_messages_object(found_messages_, "SearchSecretMessagesRequest")); - } - - public: - SearchSecretMessagesRequest(ActorShared td, uint64 request_id, int64 dialog_id, string query, string offset, - int32 limit, tl_object_ptr filter) - : RequestActor(std::move(td), request_id) - , dialog_id_(dialog_id) - , query_(std::move(query)) - , offset_(std::move(offset)) - , limit_(limit) - , filter_(get_message_search_filter(filter)) - , random_id_(0) { - } -}; - -class SearchMessagesRequest final : public RequestActor<> { - FolderId folder_id_; - bool ignore_folder_id_; - string query_; - string offset_; - int32 limit_; - MessageSearchFilter filter_; - int32 min_date_; - int32 max_date_; - int64 random_id_; - - MessagesManager::FoundMessages messages_; - - void do_run(Promise &&promise) final { - messages_ = td_->messages_manager_->search_messages(folder_id_, ignore_folder_id_, query_, offset_, limit_, filter_, - min_date_, max_date_, random_id_, std::move(promise)); - } - - void do_send_result() final { - send_result(td_->messages_manager_->get_found_messages_object(messages_, "SearchMessagesRequest")); - } - - void do_send_error(Status &&status) final { - if (status.message() == "SEARCH_QUERY_EMPTY") { - messages_ = {}; - return do_send_result(); - } - send_error(std::move(status)); - } - - public: - SearchMessagesRequest(ActorShared td, uint64 request_id, FolderId folder_id, bool ignore_folder_id, string query, - string offset, int32 limit, tl_object_ptr &&filter, - int32 min_date, int32 max_date) - : RequestActor(std::move(td), request_id) - , folder_id_(folder_id) - , ignore_folder_id_(ignore_folder_id) - , query_(std::move(query)) - , offset_(std::move(offset)) - , limit_(limit) - , filter_(get_message_search_filter(filter)) - , min_date_(min_date) - , max_date_(max_date) - , random_id_(0) { - } -}; - class SearchCallMessagesRequest final : public RequestActor<> { string offset_; int32 limit_; @@ -1579,26 +1515,6 @@ class GetActiveLiveLocationMessagesRequest final : public RequestActor<> { } }; -class GetChatMessageByDateRequest final : public RequestOnceActor { - DialogId dialog_id_; - int32 date_; - - int64 random_id_; - - void do_run(Promise &&promise) final { - random_id_ = td_->messages_manager_->get_dialog_message_by_date(dialog_id_, date_, std::move(promise)); - } - - void do_send_result() final { - send_result(td_->messages_manager_->get_dialog_message_by_date_object(random_id_)); - } - - public: - GetChatMessageByDateRequest(ActorShared td, uint64 request_id, int64 dialog_id, int32 date) - : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), date_(date), random_id_(0) { - } -}; - class GetChatScheduledMessagesRequest final : public RequestActor<> { DialogId dialog_id_; @@ -1658,7 +1574,7 @@ class CreateChatRequest final : public RequestActor<> { } void do_send_result() final { - send_result(td_->messages_manager_->get_chat_object(dialog_id_)); + send_result(td_->messages_manager_->get_chat_object(dialog_id_, "CreateChatRequest")); } public: @@ -1706,7 +1622,7 @@ class JoinChatByInviteLinkRequest final : public RequestActor { void do_send_result() final { CHECK(dialog_id_.is_valid()); td_->dialog_manager_->force_create_dialog(dialog_id_, "join chat via an invite link"); - send_result(td_->messages_manager_->get_chat_object(dialog_id_)); + send_result(td_->messages_manager_->get_chat_object(dialog_id_, "JoinChatByInviteLinkRequest")); } public: @@ -1722,7 +1638,7 @@ class ImportContactsRequest final : public RequestActor<> { std::pair, vector> imported_contacts_; void do_run(Promise &&promise) final { - imported_contacts_ = td_->contacts_manager_->import_contacts(contacts_, random_id_, std::move(promise)); + imported_contacts_ = td_->user_manager_->import_contacts(contacts_, random_id_, std::move(promise)); } void do_send_result() final { @@ -1730,7 +1646,7 @@ class ImportContactsRequest final : public RequestActor<> { CHECK(imported_contacts_.second.size() == contacts_.size()); send_result(make_tl_object(transform(imported_contacts_.first, [this](UserId user_id) { - return td_->contacts_manager_->get_user_id_object( + return td_->user_manager_->get_user_id_object( user_id, "ImportContactsRequest"); }), std::move(imported_contacts_.second))); @@ -1750,11 +1666,11 @@ class SearchContactsRequest final : public RequestActor<> { std::pair> user_ids_; void do_run(Promise &&promise) final { - user_ids_ = td_->contacts_manager_->search_contacts(query_, limit_, std::move(promise)); + user_ids_ = td_->user_manager_->search_contacts(query_, limit_, std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_users_object(user_ids_.first, user_ids_.second)); + send_result(td_->user_manager_->get_users_object(user_ids_.first, user_ids_.second)); } public: @@ -1767,7 +1683,7 @@ class RemoveContactsRequest final : public RequestActor<> { vector user_ids_; void do_run(Promise &&promise) final { - td_->contacts_manager_->remove_contacts(user_ids_, std::move(promise)); + td_->user_manager_->remove_contacts(user_ids_, std::move(promise)); } public: @@ -1781,7 +1697,7 @@ class GetImportedContactCountRequest final : public RequestActor<> { int32 imported_contact_count_ = 0; void do_run(Promise &&promise) final { - imported_contact_count_ = td_->contacts_manager_->get_imported_contact_count(std::move(promise)); + imported_contact_count_ = td_->user_manager_->get_imported_contact_count(std::move(promise)); } void do_send_result() final { @@ -1801,7 +1717,7 @@ class ChangeImportedContactsRequest final : public RequestActor<> { std::pair, vector> imported_contacts_; void do_run(Promise &&promise) final { - imported_contacts_ = td_->contacts_manager_->change_imported_contacts(contacts_, random_id_, std::move(promise)); + imported_contacts_ = td_->user_manager_->change_imported_contacts(contacts_, random_id_, std::move(promise)); } void do_send_result() final { @@ -1809,7 +1725,7 @@ class ChangeImportedContactsRequest final : public RequestActor<> { CHECK(imported_contacts_.second.size() == contacts_size_); send_result(make_tl_object(transform(imported_contacts_.first, [this](UserId user_id) { - return td_->contacts_manager_->get_user_id_object( + return td_->user_manager_->get_user_id_object( user_id, "ChangeImportedContactsRequest"); }), std::move(imported_contacts_.second))); @@ -1829,11 +1745,11 @@ class GetCloseFriendsRequest final : public RequestActor<> { vector user_ids_; void do_run(Promise &&promise) final { - user_ids_ = td_->contacts_manager_->get_close_friends(std::move(promise)); + user_ids_ = td_->user_manager_->get_close_friends(std::move(promise)); } void do_send_result() final { - send_result(td_->contacts_manager_->get_users_object(-1, user_ids_)); + send_result(td_->user_manager_->get_users_object(-1, user_ids_)); } public: @@ -1849,7 +1765,7 @@ class GetRecentInlineBotsRequest final : public RequestActor<> { } void do_send_result() final { - send_result(td_->contacts_manager_->get_users_object(-1, user_ids_)); + send_result(td_->user_manager_->get_users_object(-1, user_ids_)); } public: @@ -2590,7 +2506,7 @@ void Td::on_online_updated(bool force, bool send_update) { return; } if (force || is_online_) { - contacts_manager_->set_my_online_status(is_online_, send_update, true); + 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_); @@ -2610,7 +2526,7 @@ void Td::on_update_status_success(bool is_online) { if (!update_status_query_.empty()) { update_status_query_ = NetQueryRef(); } - contacts_manager_->set_my_online_status(is_online_, true, false); + user_manager_->set_my_online_status(is_online_, true, false); } } @@ -2751,6 +2667,7 @@ bool Td::is_authentication_request(int32 id) { case td_api::getAuthorizationState::ID: case td_api::setAuthenticationPhoneNumber::ID: case td_api::sendAuthenticationFirebaseSms::ID: + case td_api::reportAuthenticationCodeMissing::ID: case td_api::setAuthenticationEmailAddress::ID: case td_api::resendAuthenticationCode::ID: case td_api::checkAuthenticationEmailCode::ID: @@ -2853,6 +2770,7 @@ bool Td::is_preauthentication_request(int32 id) { case td_api::getNetworkStatistics::ID: case td_api::addNetworkStatistics::ID: case td_api::resetNetworkStatistics::ID: + case td_api::setApplicationVerificationToken::ID: case td_api::getCountries::ID: case td_api::getCountryCode::ID: case td_api::getPhoneNumberInfo::ID: @@ -3206,10 +3124,12 @@ void Td::dec_actor_refcnt() { reset_manager(background_manager_, "BackgroundManager"); reset_manager(boost_manager_, "BoostManager"); reset_manager(bot_info_manager_, "BotInfoManager"); + reset_manager(business_connection_manager_, "BusinessConnectionManager"); 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(contacts_manager_, "ContactsManager"); + reset_manager(chat_manager_, "ChatManager"); reset_manager(country_info_manager_, "CountryInfoManager"); reset_manager(dialog_action_manager_, "DialogActionManager"); reset_manager(dialog_filter_manager_, "DialogFilterManager"); @@ -3229,6 +3149,8 @@ void Td::dec_actor_refcnt() { reset_manager(messages_manager_, "MessagesManager"); reset_manager(notification_manager_, "NotificationManager"); reset_manager(notification_settings_manager_, "NotificationSettingsManager"); + reset_manager(people_nearby_manager_, "PeopleNearbyManager"); + reset_manager(phone_number_manager_, "PhoneNumberManager"); reset_manager(poll_manager_, "PollManager"); reset_manager(privacy_manager_, "PrivacyManager"); reset_manager(quick_reply_manager_, "QuickReplyManager"); @@ -3244,6 +3166,7 @@ void Td::dec_actor_refcnt() { reset_manager(transcription_manager_, "TranscriptionManager"); reset_manager(translation_manager_, "TranslationManager"); reset_manager(updates_manager_, "UpdatesManager"); + reset_manager(user_manager_, "UserManager"); reset_manager(video_notes_manager_, "VideoNotesManager"); reset_manager(videos_manager_, "VideosManager"); reset_manager(voice_notes_manager_, "VoiceNotesManager"); @@ -3353,18 +3276,16 @@ void Td::clear() { // close all pure actors reset_actor(ActorOwn(std::move(call_manager_))); - reset_actor(ActorOwn(std::move(change_phone_number_manager_))); reset_actor(ActorOwn(std::move(config_manager_))); - reset_actor(ActorOwn(std::move(confirm_phone_number_manager_))); reset_actor(ActorOwn(std::move(device_token_manager_))); reset_actor(ActorOwn(std::move(hashtag_hints_))); + reset_actor(ActorOwn(std::move(hashtag_search_hints_))); reset_actor(ActorOwn(std::move(language_pack_manager_))); reset_actor(ActorOwn(std::move(net_stats_manager_))); reset_actor(ActorOwn(std::move(password_manager_))); reset_actor(ActorOwn(std::move(secure_manager_))); reset_actor(ActorOwn(std::move(secret_chats_manager_))); reset_actor(ActorOwn(std::move(storage_manager_))); - reset_actor(ActorOwn(std::move(verify_phone_number_manager_))); G()->set_connection_creator(ActorOwn()); LOG(DEBUG) << "ConnectionCreator was cleared" << timer; @@ -3380,9 +3301,11 @@ void Td::clear() { reset_actor(ActorOwn(std::move(background_manager_actor_))); reset_actor(ActorOwn(std::move(boost_manager_actor_))); reset_actor(ActorOwn(std::move(bot_info_manager_actor_))); + 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(contacts_manager_actor_))); + reset_actor(ActorOwn(std::move(chat_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_))); @@ -3401,6 +3324,8 @@ 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(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(quick_reply_manager_actor_))); @@ -3416,6 +3341,7 @@ void Td::clear() { reset_actor(ActorOwn(std::move(transcription_manager_actor_))); reset_actor(ActorOwn(std::move(translation_manager_actor_))); reset_actor(ActorOwn(std::move(updates_manager_actor_))); + reset_actor(ActorOwn(std::move(user_manager_actor_))); reset_actor(ActorOwn(std::move(video_notes_manager_actor_))); reset_actor(ActorOwn(std::move(voice_notes_manager_actor_))); reset_actor(ActorOwn(std::move(web_pages_manager_actor_))); @@ -3660,20 +3586,20 @@ void Td::init(Parameters parameters, Result r_opened_datab void Td::process_binlog_events(TdDb::OpenedDatabase &&events) { VLOG(td_init) << "Send binlog events"; for (auto &event : events.user_events) { - contacts_manager_->on_binlog_user_event(std::move(event)); + user_manager_->on_binlog_user_event(std::move(event)); } for (auto &event : events.channel_events) { - contacts_manager_->on_binlog_channel_event(std::move(event)); + chat_manager_->on_binlog_channel_event(std::move(event)); } // chats may contain links to channels, so should be inited after for (auto &event : events.chat_events) { - contacts_manager_->on_binlog_chat_event(std::move(event)); + chat_manager_->on_binlog_chat_event(std::move(event)); } for (auto &event : events.secret_chat_events) { - contacts_manager_->on_binlog_secret_chat_event(std::move(event)); + user_manager_->on_binlog_secret_chat_event(std::move(event)); } for (auto &event : events.web_page_events) { @@ -3871,14 +3797,20 @@ void Td::init_managers() { G()->set_boost_manager(boost_manager_actor_.get()); bot_info_manager_ = make_unique(this, create_reference()); bot_info_manager_actor_ = register_actor("BotInfoManager", bot_info_manager_.get()); + business_connection_manager_ = make_unique(this, create_reference()); + business_connection_manager_actor_ = register_actor("BusinessConnectionManager", business_connection_manager_.get()); + G()->set_business_connection_manager(business_connection_manager_actor_.get()); business_manager_ = make_unique(this, create_reference()); business_manager_actor_ = register_actor("BusinessManager", business_manager_.get()); G()->set_business_manager(business_manager_actor_.get()); + 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()); - contacts_manager_ = make_unique(this, create_reference()); - contacts_manager_actor_ = register_actor("ContactsManager", contacts_manager_.get()); - G()->set_contacts_manager(contacts_manager_actor_.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()); 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()); @@ -3926,6 +3858,11 @@ void Td::init_managers() { notification_settings_manager_actor_ = register_actor("NotificationSettingsManager", notification_settings_manager_.get()); G()->set_notification_settings_manager(notification_settings_manager_actor_.get()); + people_nearby_manager_ = make_unique(this, create_reference()); + people_nearby_manager_actor_ = register_actor("PeopleNearbyManager", people_nearby_manager_.get()); + G()->set_people_nearby_manager(people_nearby_manager_actor_.get()); + phone_number_manager_ = make_unique(this, create_reference()); + phone_number_manager_actor_ = register_actor("PhoneNumberManager", phone_number_manager_.get()); poll_manager_ = make_unique(this, create_reference()); poll_manager_actor_ = register_actor("PollManager", poll_manager_.get()); privacy_manager_ = make_unique(this, create_reference()); @@ -3967,6 +3904,9 @@ void Td::init_managers() { updates_manager_ = make_unique(this, create_reference()); updates_manager_actor_ = register_actor("UpdatesManager", updates_manager_.get()); G()->set_updates_manager(updates_manager_actor_.get()); + user_manager_ = make_unique(this, create_reference()); + user_manager_actor_ = register_actor("UserManager", user_manager_.get()); + G()->set_user_manager(user_manager_actor_.get()); video_notes_manager_ = make_unique(this, create_reference()); video_notes_manager_actor_ = register_actor("VideoNotesManager", video_notes_manager_.get()); voice_notes_manager_ = make_unique(this, create_reference()); @@ -3979,19 +3919,14 @@ void Td::init_managers() { void Td::init_pure_actor_managers() { call_manager_ = create_actor("CallManager", create_reference()); G()->set_call_manager(call_manager_.get()); - change_phone_number_manager_ = create_actor( - "ChangePhoneNumberManager", PhoneNumberManager::Type::ChangePhone, create_reference()); - confirm_phone_number_manager_ = create_actor( - "ConfirmPhoneNumberManager", PhoneNumberManager::Type::ConfirmPhone, create_reference()); device_token_manager_ = create_actor("DeviceTokenManager", create_reference()); hashtag_hints_ = create_actor("HashtagHints", "text", create_reference()); + hashtag_search_hints_ = create_actor("HashtagSearchHints", "search", create_reference()); language_pack_manager_ = create_actor("LanguagePackManager", create_reference()); G()->set_language_pack_manager(language_pack_manager_.get()); password_manager_ = create_actor("PasswordManager", create_reference()); G()->set_password_manager(password_manager_.get()); secure_manager_ = create_actor("SecureManager", create_reference()); - verify_phone_number_manager_ = create_actor( - "VerifyPhoneNumberManager", PhoneNumberManager::Type::VerifyPhone, create_reference()); } void Td::send_update(tl_object_ptr &&object) { @@ -4209,13 +4144,18 @@ void Td::on_request(uint64 id, td_api::sendAuthenticationFirebaseSms &request) { send_closure(auth_manager_actor_, &AuthManager::set_firebase_token, id, std::move(request.token_)); } +void Td::on_request(uint64 id, td_api::reportAuthenticationCodeMissing &request) { + CLEAN_INPUT_STRING(request.mobile_network_code_); + send_closure(auth_manager_actor_, &AuthManager::report_missing_code, id, std::move(request.mobile_network_code_)); +} + void Td::on_request(uint64 id, td_api::setAuthenticationEmailAddress &request) { CLEAN_INPUT_STRING(request.email_address_); send_closure(auth_manager_actor_, &AuthManager::set_email_address, id, std::move(request.email_address_)); } -void Td::on_request(uint64 id, const td_api::resendAuthenticationCode &request) { - send_closure(auth_manager_actor_, &AuthManager::resend_authentication_code, id); +void Td::on_request(uint64 id, td_api::resendAuthenticationCode &request) { + send_closure(auth_manager_actor_, &AuthManager::resend_authentication_code, id, std::move(request.reason_)); } void Td::on_request(uint64 id, td_api::checkAuthenticationEmailCode &request) { @@ -4306,7 +4246,9 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { updates.push_back(get_update_connection_state_object(connection_state_)); if (auth_manager_->is_authorized()) { - contacts_manager_->get_current_state(updates); + user_manager_->get_current_state(updates); + + chat_manager_->get_current_state(updates); background_manager_->get_current_state(updates); @@ -4342,6 +4284,8 @@ void Td::on_request(uint64 id, const td_api::getCurrentState &request) { account_manager_->get_current_state(updates); + business_connection_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; @@ -4513,9 +4457,6 @@ void Td::on_request(uint64 id, td_api::processPushNotification &request) { void Td::on_request(uint64 id, td_api::registerDevice &request) { CHECK_IS_USER(); - if (request.device_token_ == nullptr) { - return send_error_raw(id, 400, "Device token must be non-empty"); - } CREATE_REQUEST_PROMISE(); send_closure(device_token_manager_, &DeviceTokenManager::register_device, std::move(request.device_token_), UserId::get_user_ids(request.other_user_ids_), std::move(promise)); @@ -4583,22 +4524,39 @@ void Td::on_request(uint64 id, td_api::deleteAccount &request) { send_closure(auth_manager_actor_, &AuthManager::delete_account, id, request.reason_, request.password_); } -void Td::on_request(uint64 id, td_api::changePhoneNumber &request) { +void Td::on_request(uint64 id, td_api::sendPhoneNumberCode &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.phone_number_); - send_closure(change_phone_number_manager_, &PhoneNumberManager::set_phone_number, id, - std::move(request.phone_number_), std::move(request.settings_)); + CREATE_REQUEST_PROMISE(); + phone_number_manager_->set_phone_number(std::move(request.phone_number_), std::move(request.settings_), + std::move(request.type_), std::move(promise)); } -void Td::on_request(uint64 id, td_api::checkChangePhoneNumberCode &request) { +void Td::on_request(uint64 id, td_api::sendPhoneNumberFirebaseSms &request) { CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.code_); - send_closure(change_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_)); + CLEAN_INPUT_STRING(request.token_); + CREATE_OK_REQUEST_PROMISE(); + phone_number_manager_->send_firebase_sms(std::move(request.token_), std::move(promise)); } -void Td::on_request(uint64 id, td_api::resendChangePhoneNumberCode &request) { +void Td::on_request(uint64 id, td_api::reportPhoneNumberCodeMissing &request) { CHECK_IS_USER(); - send_closure(change_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id); + CLEAN_INPUT_STRING(request.mobile_network_code_); + CREATE_OK_REQUEST_PROMISE(); + phone_number_manager_->report_missing_code(std::move(request.mobile_network_code_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::resendPhoneNumberCode &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + phone_number_manager_->resend_authentication_code(std::move(request.reason_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::checkPhoneNumberCode &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.code_); + CREATE_OK_REQUEST_PROMISE(); + phone_number_manager_->check_code(std::move(request.code_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getUserLink &request) { @@ -4751,6 +4709,13 @@ void Td::on_request(uint64 id, const td_api::clickChatSponsoredMessage &request) std::move(promise)); } +void Td::on_request(uint64 id, const td_api::reportChatSponsoredMessage &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + sponsored_message_manager_->report_sponsored_message(DialogId(request.chat_id_), MessageId(request.message_id_), + request.option_id_, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getMessageThread &request) { CHECK_IS_USER(); CREATE_REQUEST(GetMessageThreadRequest, request.chat_id_, request.message_id_); @@ -5026,24 +4991,31 @@ void Td::on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &re autosave_manager_->clear_autosave_settings_exceptions(std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getRecommendedChats &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + channel_recommendation_manager_->get_recommended_channels(std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getChatSimilarChats &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise), Auto()); + channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), false, std::move(promise), + Auto()); } void Td::on_request(uint64 id, const td_api::getChatSimilarChatCount &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_, Auto(), - std::move(promise)); + channel_recommendation_manager_->get_channel_recommendations(DialogId(request.chat_id_), request.return_local_, + Auto(), std::move(promise)); } void Td::on_request(uint64 id, const td_api::openChatSimilarChat &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->open_channel_recommended_channel(DialogId(request.chat_id_), DialogId(request.opened_chat_id_), - std::move(promise)); + channel_recommendation_manager_->open_channel_recommended_channel( + DialogId(request.chat_id_), DialogId(request.opened_chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getTopChats &request) { @@ -5158,7 +5130,7 @@ void Td::on_request(uint64 id, td_api::searchChatsOnServer &request) { void Td::on_request(uint64 id, const td_api::searchChatsNearby &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->search_dialogs_nearby(Location(request.location_), std::move(promise)); + people_nearby_manager_->search_dialogs_nearby(Location(request.location_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getGroupsInCommon &request) { @@ -5184,13 +5156,13 @@ void Td::on_request(uint64 id, td_api::checkChatUsername &request) { void Td::on_request(uint64 id, const td_api::getCreatedPublicChats &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_created_public_dialogs(get_public_dialog_type(request.type_), std::move(promise), false); + chat_manager_->get_created_public_dialogs(get_public_dialog_type(request.type_), std::move(promise), false); } void Td::on_request(uint64 id, const td_api::checkCreatedPublicChatsLimit &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->check_created_public_dialogs_limit(get_public_dialog_type(request.type_), std::move(promise)); + chat_manager_->check_created_public_dialogs_limit(get_public_dialog_type(request.type_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getSuitableDiscussionChats &request) { @@ -5203,6 +5175,12 @@ void Td::on_request(uint64 id, const td_api::getInactiveSupergroupChats &request CREATE_NO_ARGS_REQUEST(GetInactiveSupergroupChatsRequest); } +void Td::on_request(uint64 id, const td_api::getSuitablePersonalChats &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + chat_manager_->get_created_public_dialogs(PublicDialogType::ForPersonalDialog, std::move(promise), false); +} + void Td::on_request(uint64 id, td_api::searchRecentlyFoundChats &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); @@ -5342,21 +5320,21 @@ void Td::on_request(uint64 id, td_api::searchSecretMessages &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); CLEAN_INPUT_STRING(request.offset_); - CREATE_REQUEST(SearchSecretMessagesRequest, request.chat_id_, std::move(request.query_), std::move(request.offset_), - request.limit_, std::move(request.filter_)); + CREATE_REQUEST_PROMISE(); + messages_manager_->offline_search_messages(DialogId(request.chat_id_), std::move(request.query_), + std::move(request.offset_), request.limit_, + get_message_search_filter(request.filter_), std::move(promise)); } void Td::on_request(uint64 id, td_api::searchMessages &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.query_); CLEAN_INPUT_STRING(request.offset_); - DialogListId dialog_list_id(request.chat_list_); - if (!dialog_list_id.is_folder()) { - return send_error_raw(id, 400, "Wrong chat list specified"); - } - CREATE_REQUEST(SearchMessagesRequest, dialog_list_id.get_folder_id(), request.chat_list_ == nullptr, - std::move(request.query_), std::move(request.offset_), request.limit_, std::move(request.filter_), - request.min_date_, request.max_date_); + CREATE_REQUEST_PROMISE(); + messages_manager_->search_messages(DialogListId(request.chat_list_), request.chat_list_ == nullptr, + request.only_in_channels_, std::move(request.query_), std::move(request.offset_), + request.limit_, get_message_search_filter(request.filter_), request.min_date_, + request.max_date_, std::move(promise)); } void Td::on_request(uint64 id, td_api::searchSavedMessages &request) { @@ -5379,6 +5357,43 @@ void Td::on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request) messages_manager_->search_outgoing_document_messages(request.query_, request.limit_, std::move(promise)); } +void Td::on_request(uint64 id, td_api::searchPublicHashtagMessages &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.hashtag_); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + messages_manager_->search_hashtag_posts(std::move(request.hashtag_), std::move(request.offset_), request.limit_, + std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getSearchedForHashtags &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.prefix_); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result> result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(td_api::make_object(result.move_as_ok())); + } + }); + send_closure(hashtag_search_hints_, &HashtagHints::query, std::move(request.prefix_), request.limit_, + std::move(query_promise)); +} + +void Td::on_request(uint64 id, td_api::removeSearchedForHashtag &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.hashtag_); + CREATE_OK_REQUEST_PROMISE(); + send_closure(hashtag_search_hints_, &HashtagHints::remove_hashtag, std::move(request.hashtag_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::clearSearchedForHashtags &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + send_closure(hashtag_search_hints_, &HashtagHints::clear, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::deleteAllCallMessages &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -5398,7 +5413,9 @@ void Td::on_request(uint64 id, const td_api::getActiveLiveLocationMessages &requ } void Td::on_request(uint64 id, const td_api::getChatMessageByDate &request) { - CREATE_REQUEST(GetChatMessageByDateRequest, request.chat_id_, request.date_); + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + messages_manager_->get_dialog_message_by_date(DialogId(request.chat_id_), request.date_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getChatSparseMessagePositions &request) { @@ -5527,6 +5544,12 @@ void Td::on_request(uint64 id, td_api::setSavedMessagesTagLabel &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getMessageEffect &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + reaction_manager_->get_message_effect(request.effect_id_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.offset_); @@ -5701,7 +5724,7 @@ void Td::on_request(uint64 id, td_api::editMessageText &request) { void Td::on_request(uint64 id, td_api::editMessageLiveLocation &request) { CREATE_REQUEST(EditMessageLiveLocationRequest, request.chat_id_, request.message_id_, - std::move(request.reply_markup_), std::move(request.location_), request.heading_, + std::move(request.reply_markup_), std::move(request.location_), request.live_period_, request.heading_, request.proximity_alert_radius_); } @@ -5712,7 +5735,7 @@ void Td::on_request(uint64 id, td_api::editMessageMedia &request) { void Td::on_request(uint64 id, td_api::editMessageCaption &request) { CREATE_REQUEST(EditMessageCaptionRequest, request.chat_id_, request.message_id_, std::move(request.reply_markup_), - std::move(request.caption_)); + std::move(request.caption_), request.show_caption_above_media_); } void Td::on_request(uint64 id, td_api::editMessageReplyMarkup &request) { @@ -5733,9 +5756,9 @@ void Td::on_request(uint64 id, td_api::editInlineMessageLiveLocation &request) { CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.inline_message_id_); CREATE_OK_REQUEST_PROMISE(); - messages_manager_->edit_inline_message_live_location(request.inline_message_id_, std::move(request.reply_markup_), - std::move(request.location_), request.heading_, - request.proximity_alert_radius_, std::move(promise)); + messages_manager_->edit_inline_message_live_location( + request.inline_message_id_, std::move(request.reply_markup_), std::move(request.location_), request.live_period_, + request.heading_, request.proximity_alert_radius_, std::move(promise)); } void Td::on_request(uint64 id, td_api::editInlineMessageMedia &request) { @@ -5751,7 +5774,8 @@ void Td::on_request(uint64 id, td_api::editInlineMessageCaption &request) { CLEAN_INPUT_STRING(request.inline_message_id_); CREATE_OK_REQUEST_PROMISE(); messages_manager_->edit_inline_message_caption(request.inline_message_id_, std::move(request.reply_markup_), - std::move(request.caption_), std::move(promise)); + std::move(request.caption_), request.show_caption_above_media_, + std::move(promise)); } void Td::on_request(uint64 id, td_api::editInlineMessageReplyMarkup &request) { @@ -5769,6 +5793,31 @@ void Td::on_request(uint64 id, td_api::editMessageSchedulingState &request) { std::move(request.scheduling_state_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::setMessageFactCheck &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + messages_manager_->set_message_fact_check({DialogId(request.chat_id_), MessageId(request.message_id_)}, + std::move(request.text_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::sendBusinessMessage &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->send_message( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + std::move(request.reply_to_), request.disable_notification_, request.protect_content_, request.effect_id_, + std::move(request.reply_markup_), std::move(request.input_message_content_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::sendBusinessMessageAlbum &request) { + CHECK_IS_BOT(); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->send_message_album( + BusinessConnectionId(std::move(request.business_connection_id_)), DialogId(request.chat_id_), + std::move(request.reply_to_), request.disable_notification_, request.protect_content_, request.effect_id_, + std::move(request.input_message_contents_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -5809,6 +5858,63 @@ void Td::on_request(uint64 id, const td_api::deleteQuickReplyShortcutMessages &r QuickReplyShortcutId(request.shortcut_id_), MessageId::get_message_ids(request.message_ids_), std::move(promise)); } +void Td::on_request(uint64 id, td_api::addQuickReplyShortcutMessage &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.shortcut_name_); + auto r_sent_message = quick_reply_manager_->send_message( + request.shortcut_name_, MessageId(request.reply_to_message_id_), std::move(request.input_message_content_)); + if (r_sent_message.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, r_sent_message.move_as_ok()); + } +} + +void Td::on_request(uint64 id, td_api::addQuickReplyShortcutInlineQueryResultMessage &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.shortcut_name_); + CLEAN_INPUT_STRING(request.result_id_); + auto r_sent_message = quick_reply_manager_->send_inline_query_result_message( + request.shortcut_name_, MessageId(request.reply_to_message_id_), request.query_id_, request.result_id_, + request.hide_via_bot_); + if (r_sent_message.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, r_sent_message.move_as_ok()); + } +} + +void Td::on_request(uint64 id, td_api::addQuickReplyShortcutMessageAlbum &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.shortcut_name_); + auto r_messages = quick_reply_manager_->send_message_group( + request.shortcut_name_, MessageId(request.reply_to_message_id_), std::move(request.input_message_contents_)); + if (r_messages.is_error()) { + send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error()); + } else { + send_closure(actor_id(this), &Td::send_result, id, r_messages.move_as_ok()); + } +} + +void Td::on_request(uint64 id, td_api::readdQuickReplyShortcutMessages &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.shortcut_name_); + auto r_messages = + quick_reply_manager_->resend_messages(request.shortcut_name_, MessageId::get_message_ids(request.message_ids_)); + if (r_messages.is_error()) { + return send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error()); + } + send_closure(actor_id(this), &Td::send_result, id, r_messages.move_as_ok()); +} + +void Td::on_request(uint64 id, td_api::editQuickReplyMessage &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + quick_reply_manager_->edit_quick_reply_message(QuickReplyShortcutId(request.shortcut_id_), + MessageId(request.message_id_), + std::move(request.input_message_content_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getStory &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -5833,8 +5939,8 @@ void Td::on_request(uint64 id, td_api::sendStory &request) { CREATE_REQUEST_PROMISE(); story_manager_->send_story(DialogId(request.chat_id_), std::move(request.content_), std::move(request.areas_), std::move(request.caption_), std::move(request.privacy_settings_), request.active_period_, - std::move(request.from_story_full_id_), request.is_pinned_, request.protect_content_, - std::move(promise)); + std::move(request.from_story_full_id_), request.is_posted_to_chat_page_, + request.protect_content_, std::move(promise)); } void Td::on_request(uint64 id, td_api::editStory &request) { @@ -5852,11 +5958,11 @@ void Td::on_request(uint64 id, td_api::setStoryPrivacySettings &request) { std::move(promise)); } -void Td::on_request(uint64 id, const td_api::toggleStoryIsPinned &request) { +void Td::on_request(uint64 id, const td_api::toggleStoryIsPostedToChatPage &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); story_manager_->toggle_story_is_pinned(DialogId(request.story_sender_chat_id_), StoryId(request.story_id_), - request.is_pinned_, std::move(promise)); + request.is_posted_to_chat_page_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::deleteStory &request) { @@ -5988,8 +6094,10 @@ void Td::on_request(uint64 id, const td_api::deleteChatReplyMarkup &request) { } void Td::on_request(uint64 id, td_api::sendChatAction &request) { + CLEAN_INPUT_STRING(request.business_connection_id_); CREATE_OK_REQUEST_PROMISE(); dialog_action_manager_->send_dialog_action(DialogId(request.chat_id_), MessageId(request.message_thread_id_), + BusinessConnectionId(std::move(request.business_connection_id_)), DialogAction(std::move(request.action_)), std::move(promise)); } @@ -6063,8 +6171,8 @@ void Td::on_request(uint64 id, td_api::createNewBasicGroupChat &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.title_); CREATE_REQUEST_PROMISE(); - contacts_manager_->create_new_chat(UserId::get_user_ids(request.user_ids_), std::move(request.title_), - MessageTtl(request.message_auto_delete_time_), std::move(promise)); + chat_manager_->create_new_chat(UserId::get_user_ids(request.user_ids_), std::move(request.title_), + MessageTtl(request.message_auto_delete_time_), std::move(promise)); } void Td::on_request(uint64 id, td_api::createNewSupergroupChat &request) { @@ -6072,16 +6180,16 @@ void Td::on_request(uint64 id, td_api::createNewSupergroupChat &request) { CLEAN_INPUT_STRING(request.title_); CLEAN_INPUT_STRING(request.description_); CREATE_REQUEST_PROMISE(); - contacts_manager_->create_new_channel(std::move(request.title_), request.is_forum_, !request.is_channel_, - std::move(request.description_), DialogLocation(std::move(request.location_)), - request.for_import_, MessageTtl(request.message_auto_delete_time_), - std::move(promise)); + chat_manager_->create_new_channel(std::move(request.title_), request.is_forum_, !request.is_channel_, + std::move(request.description_), DialogLocation(std::move(request.location_)), + request.for_import_, MessageTtl(request.message_auto_delete_time_), + std::move(promise)); } void Td::on_request(uint64 id, const td_api::createNewSecretChat &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->create_new_secret_chat(UserId(request.user_id_), std::move(promise)); + user_manager_->create_new_secret_chat(UserId(request.user_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::createCall &request) { @@ -6092,7 +6200,7 @@ void Td::on_request(uint64 id, const td_api::createCall &request) { } UserId user_id(request.user_id_); - auto r_input_user = contacts_manager_->get_input_user(user_id); + auto r_input_user = user_manager_->get_input_user(user_id); if (r_input_user.is_error()) { return send_error_raw(id, r_input_user.error().code(), r_input_user.error().message()); } @@ -6415,7 +6523,7 @@ void Td::on_request(uint64 id, td_api::getGroupCallStreamSegment &request) { void Td::on_request(uint64 id, const td_api::upgradeBasicGroupChatToSupergroupChat &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->migrate_dialog_to_megagroup(DialogId(request.chat_id_), std::move(promise)); + dialog_manager_->migrate_dialog_to_megagroup(DialogId(request.chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getChatListsToAddChat &request) { @@ -6633,7 +6741,7 @@ void Td::on_request(uint64 id, td_api::setNewChatPrivacySettings &request) { void Td::on_request(uint64 id, const td_api::canSendMessageToUser &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->can_send_message_to_user(UserId(request.user_id_), request.only_local_, std::move(promise)); + user_manager_->can_send_message_to_user(UserId(request.user_id_), request.only_local_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setChatTitle &request) { @@ -6775,7 +6883,7 @@ void Td::on_request(uint64 id, const td_api::getChatActiveStories &request) { story_manager_->get_dialog_expiring_stories(DialogId(request.chat_id_), std::move(promise)); } -void Td::on_request(uint64 id, const td_api::getChatPinnedStories &request) { +void Td::on_request(uint64 id, const td_api::getChatPostedToChatPageStories &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); story_manager_->get_dialog_pinned_stories(DialogId(request.chat_id_), StoryId(request.from_story_id_), request.limit_, @@ -6789,6 +6897,13 @@ void Td::on_request(uint64 id, const td_api::getChatArchivedStories &request) { std::move(promise)); } +void Td::on_request(uint64 id, const td_api::setChatPinnedStories &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + story_manager_->set_pinned_stories(DialogId(request.chat_id_), StoryId::get_story_ids(request.story_ids_), + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::openStory &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); @@ -6941,8 +7056,8 @@ void Td::on_request(uint64 id, td_api::setChatDescription &request) { void Td::on_request(uint64 id, const td_api::setChatDiscussionGroup &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_discussion_group(DialogId(request.chat_id_), DialogId(request.discussion_chat_id_), - std::move(promise)); + chat_manager_->set_channel_discussion_group(DialogId(request.chat_id_), DialogId(request.discussion_chat_id_), + std::move(promise)); } void Td::on_request(uint64 id, td_api::setChatLocation &request) { @@ -6955,8 +7070,7 @@ void Td::on_request(uint64 id, td_api::setChatLocation &request) { void Td::on_request(uint64 id, const td_api::setChatSlowModeDelay &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_slow_mode_delay(DialogId(request.chat_id_), request.slow_mode_delay_, - std::move(promise)); + chat_manager_->set_channel_slow_mode_delay(DialogId(request.chat_id_), request.slow_mode_delay_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::pinChatMessage &request) { @@ -6989,8 +7103,9 @@ void Td::on_request(uint64 id, const td_api::unpinAllMessageThreadMessages &requ void Td::on_request(uint64 id, const td_api::joinChat &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - dialog_participant_manager_->add_dialog_participant(DialogId(request.chat_id_), contacts_manager_->get_my_id(), 0, - std::move(promise)); + dialog_participant_manager_->add_dialog_participant( + DialogId(request.chat_id_), user_manager_->get_my_id(), 0, + DialogParticipantManager::wrap_failed_to_add_members_promise(std::move(promise))); } void Td::on_request(uint64 id, const td_api::leaveChat &request) { @@ -6998,7 +7113,7 @@ void Td::on_request(uint64 id, const td_api::leaveChat &request) { DialogId dialog_id(request.chat_id_); td_api::object_ptr new_status = td_api::make_object(); if (dialog_id.get_type() == DialogType::Channel && dialog_manager_->have_dialog_force(dialog_id, "leaveChat")) { - auto status = contacts_manager_->get_channel_status(dialog_id.get_channel_id()); + auto status = chat_manager_->get_channel_status(dialog_id.get_channel_id()); if (status.is_creator()) { if (!status.is_member()) { return promise.set_value(Unit()); @@ -7014,14 +7129,14 @@ void Td::on_request(uint64 id, const td_api::leaveChat &request) { void Td::on_request(uint64 id, const td_api::addChatMember &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); + CREATE_REQUEST_PROMISE(); dialog_participant_manager_->add_dialog_participant(DialogId(request.chat_id_), UserId(request.user_id_), request.forward_limit_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::addChatMembers &request) { CHECK_IS_USER(); - CREATE_OK_REQUEST_PROMISE(); + CREATE_REQUEST_PROMISE(); dialog_participant_manager_->add_dialog_participants(DialogId(request.chat_id_), UserId::get_user_ids(request.user_ids_), std::move(promise)); } @@ -7434,6 +7549,14 @@ void Td::on_request(uint64 id, td_api::searchFileDownloads &request) { request.only_completed_, std::move(request.offset_), request.limit_, std::move(promise)); } +void Td::on_request(uint64 id, td_api::setApplicationVerificationToken &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.token_); + CREATE_OK_REQUEST_PROMISE(); + G()->net_query_dispatcher().set_verification_token(request.verification_id_, std::move(request.token_), + std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getMessageFileType &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.message_file_head_); @@ -7477,12 +7600,12 @@ void Td::on_request(uint64 id, const td_api::getBlockedMessageSenders &request) void Td::on_request(uint64 id, td_api::addContact &request) { CHECK_IS_USER(); - auto r_contact = get_contact(std::move(request.contact_)); + auto r_contact = get_contact(this, std::move(request.contact_)); if (r_contact.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error()); } CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->add_contact(r_contact.move_as_ok(), request.share_phone_number_, std::move(promise)); + user_manager_->add_contact(r_contact.move_as_ok(), request.share_phone_number_, std::move(promise)); } void Td::on_request(uint64 id, td_api::importContacts &request) { @@ -7490,7 +7613,7 @@ void Td::on_request(uint64 id, td_api::importContacts &request) { vector contacts; contacts.reserve(request.contacts_.size()); for (auto &contact : request.contacts_) { - auto r_contact = get_contact(std::move(contact)); + auto r_contact = get_contact(this, std::move(contact)); if (r_contact.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error()); } @@ -7525,7 +7648,7 @@ void Td::on_request(uint64 id, td_api::changeImportedContacts &request) { vector contacts; contacts.reserve(request.contacts_.size()); for (auto &contact : request.contacts_) { - auto r_contact = get_contact(std::move(contact)); + auto r_contact = get_contact(this, std::move(contact)); if (r_contact.is_error()) { return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error()); } @@ -7537,7 +7660,7 @@ void Td::on_request(uint64 id, td_api::changeImportedContacts &request) { void Td::on_request(uint64 id, const td_api::clearImportedContacts &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->clear_imported_contacts(std::move(promise)); + user_manager_->clear_imported_contacts(std::move(promise)); } void Td::on_request(uint64 id, const td_api::getCloseFriends &request) { @@ -7548,31 +7671,31 @@ void Td::on_request(uint64 id, const td_api::getCloseFriends &request) { void Td::on_request(uint64 id, const td_api::setCloseFriends &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_close_friends(UserId::get_user_ids(request.user_ids_), std::move(promise)); + user_manager_->set_close_friends(UserId::get_user_ids(request.user_ids_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setUserPersonalProfilePhoto &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_user_profile_photo(UserId(request.user_id_), request.photo_, false, std::move(promise)); + user_manager_->set_user_profile_photo(UserId(request.user_id_), request.photo_, false, std::move(promise)); } void Td::on_request(uint64 id, td_api::suggestUserProfilePhoto &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_user_profile_photo(UserId(request.user_id_), request.photo_, true, std::move(promise)); + user_manager_->set_user_profile_photo(UserId(request.user_id_), request.photo_, true, std::move(promise)); } void Td::on_request(uint64 id, td_api::searchUserByPhoneNumber &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.phone_number_); - CREATE_REQUEST(SearchUserByPhoneNumberRequest, std::move(request.phone_number_)); + CREATE_REQUEST(SearchUserByPhoneNumberRequest, std::move(request.phone_number_), request.only_local_); } void Td::on_request(uint64 id, const td_api::sharePhoneNumber &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->share_phone_number(UserId(request.user_id_), std::move(promise)); + user_manager_->share_phone_number(UserId(request.user_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::getRecentInlineBots &request) { @@ -7585,28 +7708,28 @@ void Td::on_request(uint64 id, td_api::setName &request) { CLEAN_INPUT_STRING(request.first_name_); CLEAN_INPUT_STRING(request.last_name_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_name(request.first_name_, request.last_name_, std::move(promise)); + user_manager_->set_name(request.first_name_, request.last_name_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setBio &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.bio_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_bio(request.bio_, std::move(promise)); + user_manager_->set_bio(request.bio_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setUsername &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_username(request.username_, std::move(promise)); + user_manager_->set_username(request.username_, std::move(promise)); } void Td::on_request(uint64 id, td_api::toggleUsernameIsActive &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_username_is_active(std::move(request.username_), request.is_active_, std::move(promise)); + user_manager_->toggle_username_is_active(std::move(request.username_), request.is_active_, std::move(promise)); } void Td::on_request(uint64 id, td_api::reorderActiveUsernames &request) { @@ -7615,13 +7738,31 @@ void Td::on_request(uint64 id, td_api::reorderActiveUsernames &request) { CLEAN_INPUT_STRING(username); } CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->reorder_usernames(std::move(request.usernames_), std::move(promise)); + user_manager_->reorder_usernames(std::move(request.usernames_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::setBirthdate &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + user_manager_->set_birthdate(Birthdate(std::move(request.birthdate_)), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::setPersonalChat &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + user_manager_->set_personal_channel(DialogId(request.chat_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::setEmojiStatus &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_emoji_status(EmojiStatus(request.emoji_status_), std::move(promise)); + user_manager_->set_emoji_status(EmojiStatus(request.emoji_status_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::toggleHasSponsoredMessagesEnabled &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + user_manager_->toggle_sponsored_messages(request.has_sponsored_messages_enabled_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getThemedEmojiStatuses &request) { @@ -7747,15 +7888,15 @@ void Td::on_request(uint64 id, const td_api::getBotName &request) { void Td::on_request(uint64 id, td_api::setBotProfilePhoto &request) { CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_bot_profile_photo(UserId(request.bot_user_id_), request.photo_, std::move(promise)); + user_manager_->set_bot_profile_photo(UserId(request.bot_user_id_), request.photo_, std::move(promise)); } void Td::on_request(uint64 id, td_api::toggleBotUsernameIsActive &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_bot_username_is_active(UserId(request.bot_user_id_), std::move(request.username_), - request.is_active_, std::move(promise)); + user_manager_->toggle_bot_username_is_active(UserId(request.bot_user_id_), std::move(request.username_), + request.is_active_, std::move(promise)); } void Td::on_request(uint64 id, td_api::reorderBotActiveUsernames &request) { @@ -7764,8 +7905,7 @@ void Td::on_request(uint64 id, td_api::reorderBotActiveUsernames &request) { CLEAN_INPUT_STRING(username); } CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->reorder_bot_usernames(UserId(request.bot_user_id_), std::move(request.usernames_), - std::move(promise)); + user_manager_->reorder_bot_usernames(UserId(request.bot_user_id_), std::move(request.usernames_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setBotInfoDescription &request) { @@ -7810,7 +7950,7 @@ void Td::on_request(uint64 id, const td_api::getBotInfoShortDescription &request void Td::on_request(uint64 id, const td_api::setLocation &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_location(Location(request.location_), std::move(promise)); + people_nearby_manager_->set_location(Location(request.location_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setBusinessLocation &request) { @@ -7839,37 +7979,42 @@ void Td::on_request(uint64 id, td_api::setBusinessAwayMessageSettings &request) std::move(promise)); } +void Td::on_request(uint64 id, td_api::setBusinessStartPage &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + business_manager_->set_business_intro(BusinessIntro(this, std::move(request.start_page_)), std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setProfilePhoto &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_profile_photo(request.photo_, request.is_public_, std::move(promise)); + user_manager_->set_profile_photo(request.photo_, request.is_public_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::deleteProfilePhoto &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->delete_profile_photo(request.profile_photo_id_, false, std::move(promise)); + user_manager_->delete_profile_photo(request.profile_photo_id_, false, std::move(promise)); } void Td::on_request(uint64 id, const td_api::getUserProfilePhotos &request) { CREATE_REQUEST_PROMISE(); - contacts_manager_->get_user_profile_photos(UserId(request.user_id_), request.offset_, request.limit_, - std::move(promise)); + user_manager_->get_user_profile_photos(UserId(request.user_id_), request.offset_, request.limit_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::setAccentColor &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_accent_color(AccentColorId(request.accent_color_id_), - CustomEmojiId(request.background_custom_emoji_id_), std::move(promise)); + user_manager_->set_accent_color(AccentColorId(request.accent_color_id_), + CustomEmojiId(request.background_custom_emoji_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::setProfileAccentColor &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_profile_accent_color(AccentColorId(request.profile_accent_color_id_), - CustomEmojiId(request.profile_background_custom_emoji_id_), - std::move(promise)); + user_manager_->set_profile_accent_color(AccentColorId(request.profile_accent_color_id_), + CustomEmojiId(request.profile_background_custom_emoji_id_), + std::move(promise)); } void Td::on_request(uint64 id, const td_api::getBusinessConnectedBot &request) { @@ -7890,25 +8035,71 @@ void Td::on_request(uint64 id, const td_api::deleteBusinessConnectedBot &request business_manager_->delete_business_connected_bot(UserId(request.bot_user_id_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::toggleBusinessConnectedBotChatIsPaused &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + business_manager_->toggle_business_connected_bot_dialog_is_paused(DialogId(request.chat_id_), request.is_paused_, + std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::removeBusinessConnectedBotFromChat &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + business_manager_->remove_business_connected_bot_from_dialog(DialogId(request.chat_id_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getBusinessChatLinks &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + business_manager_->get_business_chat_links(std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::createBusinessChatLink &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + business_manager_->create_business_chat_link(std::move(request.link_info_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::editBusinessChatLink &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.link_); + CREATE_REQUEST_PROMISE(); + business_manager_->edit_business_chat_link(request.link_, std::move(request.link_info_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::deleteBusinessChatLink &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.link_); + CREATE_OK_REQUEST_PROMISE(); + business_manager_->delete_business_chat_link(request.link_, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getBusinessChatLinkInfo &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.link_name_); + CREATE_REQUEST_PROMISE(); + business_manager_->get_business_chat_link_info(request.link_name_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::setSupergroupUsername &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_username(ChannelId(request.supergroup_id_), request.username_, std::move(promise)); + chat_manager_->set_channel_username(ChannelId(request.supergroup_id_), request.username_, std::move(promise)); } void Td::on_request(uint64 id, td_api::toggleSupergroupUsernameIsActive &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.username_); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_username_is_active(ChannelId(request.supergroup_id_), std::move(request.username_), - request.is_active_, std::move(promise)); + chat_manager_->toggle_channel_username_is_active(ChannelId(request.supergroup_id_), std::move(request.username_), + request.is_active_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::disableAllSupergroupUsernames &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->disable_all_channel_usernames(ChannelId(request.supergroup_id_), std::move(promise)); + chat_manager_->disable_all_channel_usernames(ChannelId(request.supergroup_id_), std::move(promise)); } void Td::on_request(uint64 id, td_api::reorderSupergroupActiveUsernames &request) { @@ -7917,94 +8108,101 @@ void Td::on_request(uint64 id, td_api::reorderSupergroupActiveUsernames &request CLEAN_INPUT_STRING(username); } CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->reorder_channel_usernames(ChannelId(request.supergroup_id_), std::move(request.usernames_), - std::move(promise)); + chat_manager_->reorder_channel_usernames(ChannelId(request.supergroup_id_), std::move(request.usernames_), + std::move(promise)); } void Td::on_request(uint64 id, const td_api::setSupergroupStickerSet &request) { CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_sticker_set(ChannelId(request.supergroup_id_), StickerSetId(request.sticker_set_id_), - std::move(promise)); + chat_manager_->set_channel_sticker_set(ChannelId(request.supergroup_id_), StickerSetId(request.sticker_set_id_), + std::move(promise)); } void Td::on_request(uint64 id, const td_api::setSupergroupCustomEmojiStickerSet &request) { CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_emoji_sticker_set( - ChannelId(request.supergroup_id_), StickerSetId(request.custom_emoji_sticker_set_id_), std::move(promise)); + chat_manager_->set_channel_emoji_sticker_set(ChannelId(request.supergroup_id_), + StickerSetId(request.custom_emoji_sticker_set_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::setSupergroupUnrestrictBoostCount &request) { CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->set_channel_unrestrict_boost_count(ChannelId(request.supergroup_id_), - request.unrestrict_boost_count_, std::move(promise)); + chat_manager_->set_channel_unrestrict_boost_count(ChannelId(request.supergroup_id_), request.unrestrict_boost_count_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupSignMessages &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_sign_messages(ChannelId(request.supergroup_id_), request.sign_messages_, - std::move(promise)); + chat_manager_->toggle_channel_sign_messages(ChannelId(request.supergroup_id_), request.sign_messages_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_join_to_send(ChannelId(request.supergroup_id_), request.join_to_send_messages_, - std::move(promise)); + chat_manager_->toggle_channel_join_to_send(ChannelId(request.supergroup_id_), request.join_to_send_messages_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinByRequest &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_join_request(ChannelId(request.supergroup_id_), request.join_by_request_, - std::move(promise)); + chat_manager_->toggle_channel_join_request(ChannelId(request.supergroup_id_), request.join_by_request_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_is_all_history_available(ChannelId(request.supergroup_id_), - request.is_all_history_available_, std::move(promise)); + chat_manager_->toggle_channel_is_all_history_available(ChannelId(request.supergroup_id_), + request.is_all_history_available_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::toggleSupergroupCanHaveSponsoredMessages &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + chat_manager_->toggle_channel_can_have_sponsored_messages(ChannelId(request.supergroup_id_), + request.can_have_sponsored_messages_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupHasHiddenMembers &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_has_hidden_participants(ChannelId(request.supergroup_id_), - request.has_hidden_members_, std::move(promise)); + chat_manager_->toggle_channel_has_hidden_participants(ChannelId(request.supergroup_id_), request.has_hidden_members_, + std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupHasAggressiveAntiSpamEnabled &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_has_aggressive_anti_spam_enabled( + chat_manager_->toggle_channel_has_aggressive_anti_spam_enabled( ChannelId(request.supergroup_id_), request.has_aggressive_anti_spam_enabled_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupIsForum &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->toggle_channel_is_forum(ChannelId(request.supergroup_id_), request.is_forum_, std::move(promise)); + chat_manager_->toggle_channel_is_forum(ChannelId(request.supergroup_id_), request.is_forum_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->convert_channel_to_gigagroup(ChannelId(request.supergroup_id_), std::move(promise)); + chat_manager_->convert_channel_to_gigagroup(ChannelId(request.supergroup_id_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::reportSupergroupSpam &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->report_channel_spam(ChannelId(request.supergroup_id_), - MessageId::get_message_ids(request.message_ids_), std::move(promise)); + chat_manager_->report_channel_spam(ChannelId(request.supergroup_id_), + MessageId::get_message_ids(request.message_ids_), std::move(promise)); } void Td::on_request(uint64 id, const td_api::reportSupergroupAntiSpamFalsePositive &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); - contacts_manager_->report_channel_anti_spam_false_positive(ChannelId(request.supergroup_id_), - MessageId(request.message_id_), std::move(promise)); + chat_manager_->report_channel_anti_spam_false_positive(ChannelId(request.supergroup_id_), + MessageId(request.message_id_), std::move(promise)); } void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) { @@ -8053,11 +8251,19 @@ void Td::on_request(uint64 id, td_api::searchStickers &request) { request.emojis_ = "⭐️"; } else if (request.emojis_ == "📂⭐️") { request.emojis_ = "📂"; + } else if (request.emojis_ == "👋⭐️") { + request.emojis_ = "👋"; } } stickers_manager_->search_stickers(sticker_type, std::move(request.emojis_), request.limit_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getGreetingStickers &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->search_stickers(StickerType::Regular, "👋⭐️", 100, std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getPremiumStickers &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -8162,30 +8368,39 @@ void Td::on_request(uint64 id, td_api::createNewStickerSet &request) { CLEAN_INPUT_STRING(request.name_); CLEAN_INPUT_STRING(request.source_); CREATE_REQUEST_PROMISE(); - stickers_manager_->create_new_sticker_set( - UserId(request.user_id_), std::move(request.title_), std::move(request.name_), - get_sticker_format(request.sticker_format_), get_sticker_type(request.sticker_type_), request.needs_repainting_, - std::move(request.stickers_), std::move(request.source_), std::move(promise)); + stickers_manager_->create_new_sticker_set(UserId(request.user_id_), std::move(request.title_), + std::move(request.name_), get_sticker_type(request.sticker_type_), + request.needs_repainting_, std::move(request.stickers_), + std::move(request.source_), std::move(promise)); } void Td::on_request(uint64 id, td_api::addStickerToSet &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->add_sticker_to_set(UserId(request.user_id_), std::move(request.name_), std::move(request.sticker_), + nullptr, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::replaceStickerInSet &request) { + CLEAN_INPUT_STRING(request.name_); + if (request.old_sticker_ == nullptr) { + return send_error_raw(id, 400, "Old sticker must be non-empty"); + } + CREATE_OK_REQUEST_PROMISE(); + stickers_manager_->add_sticker_to_set(UserId(request.user_id_), std::move(request.name_), + std::move(request.new_sticker_), std::move(request.old_sticker_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setStickerSetThumbnail &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->set_sticker_set_thumbnail(UserId(request.user_id_), std::move(request.name_), - std::move(request.thumbnail_), std::move(promise)); + std::move(request.thumbnail_), get_sticker_format(request.format_), + std::move(promise)); } void Td::on_request(uint64 id, td_api::setCustomEmojiStickerSetThumbnail &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->set_custom_emoji_sticker_set_thumbnail( @@ -8193,7 +8408,6 @@ void Td::on_request(uint64 id, td_api::setCustomEmojiStickerSetThumbnail &reques } void Td::on_request(uint64 id, td_api::setStickerSetTitle &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.name_); CLEAN_INPUT_STRING(request.title_); CREATE_OK_REQUEST_PROMISE(); @@ -8201,33 +8415,28 @@ void Td::on_request(uint64 id, td_api::setStickerSetTitle &request) { } void Td::on_request(uint64 id, td_api::deleteStickerSet &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.name_); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->delete_sticker_set(std::move(request.name_), std::move(promise)); } void Td::on_request(uint64 id, td_api::setStickerPositionInSet &request) { - CHECK_IS_BOT(); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->set_sticker_position_in_set(request.sticker_, request.position_, std::move(promise)); } void Td::on_request(uint64 id, const td_api::removeStickerFromSet &request) { - CHECK_IS_BOT(); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->remove_sticker_from_set(request.sticker_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setStickerEmojis &request) { - CHECK_IS_BOT(); CLEAN_INPUT_STRING(request.emojis_); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->set_sticker_emojis(request.sticker_, request.emojis_, std::move(promise)); } void Td::on_request(uint64 id, td_api::setStickerKeywords &request) { - CHECK_IS_BOT(); for (auto &keyword : request.keywords_) { CLEAN_INPUT_STRING(keyword); } @@ -8236,11 +8445,17 @@ void Td::on_request(uint64 id, td_api::setStickerKeywords &request) { } void Td::on_request(uint64 id, td_api::setStickerMaskPosition &request) { - CHECK_IS_BOT(); CREATE_OK_REQUEST_PROMISE(); stickers_manager_->set_sticker_mask_position(request.sticker_, std::move(request.mask_position_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getOwnedStickerSets &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + stickers_manager_->get_created_sticker_sets(StickerSetId(request.offset_sticker_set_id_), request.limit_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getRecentStickers &request) { CHECK_IS_USER(); CREATE_REQUEST(GetRecentStickersRequest, request.is_attached_); @@ -8446,6 +8661,33 @@ void Td::on_request(uint64 id, const td_api::getChatStatistics &request) { statistics_manager_->get_channel_statistics(DialogId(request.chat_id_), request.is_dark_, std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getChatRevenueStatistics &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + statistics_manager_->get_channel_revenue_statistics(DialogId(request.chat_id_), request.is_dark_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getChatRevenueWithdrawalUrl &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + promise.set_value(td_api::make_object(result.move_as_ok())); + } + }); + statistics_manager_->get_channel_revenue_withdrawal_url(DialogId(request.chat_id_), request.password_, + std::move(query_promise)); +} + +void Td::on_request(uint64 id, const td_api::getChatRevenueTransactions &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + statistics_manager_->get_channel_revenue_transactions(DialogId(request.chat_id_), request.offset_, request.limit_, + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -8490,6 +8732,12 @@ void Td::on_request(uint64 id, td_api::setScopeNotificationSettings &request) { get_notification_settings_scope(request.scope_), std::move(request.notification_settings_))); } +void Td::on_request(uint64 id, td_api::setReactionNotificationSettings &request) { + CHECK_IS_USER(); + answer_ok_query(id, notification_settings_manager_->set_reaction_notification_settings( + ReactionNotificationSettings(std::move(request.notification_settings_)))); +} + void Td::on_request(uint64 id, const td_api::resetAllNotificationSettings &request) { CHECK_IS_USER(); messages_manager_->reset_all_notification_settings(); @@ -8620,6 +8868,21 @@ void Td::on_request(uint64 id, const td_api::hideSuggestedAction &request) { dismiss_suggested_action(SuggestedAction(request.action_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::hideContactCloseBirthdays &request) { + CHECK_IS_USER(); + CREATE_OK_REQUEST_PROMISE(); + option_manager_->set_option_boolean("dismiss_birthday_contact_today", true); + user_manager_->hide_contact_birthdays(std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getBusinessConnection &request) { + CHECK_IS_BOT(); + CLEAN_INPUT_STRING(request.connection_id_); + CREATE_REQUEST_PROMISE(); + business_connection_manager_->get_business_connection(BusinessConnectionId(std::move(request.connection_id_)), + std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getLoginUrlInfo &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -8843,6 +9106,13 @@ void Td::on_request(uint64 id, td_api::createInvoiceLink &request) { export_invoice(this, std::move(request.invoice_), std::move(query_promise)); } +void Td::on_request(uint64 id, td_api::refundStarPayment &request) { + CHECK_IS_BOT(); + CLEAN_INPUT_STRING(request.telegram_payment_charge_id_); + CREATE_OK_REQUEST_PROMISE(); + refund_star_payment(this, UserId(request.user_id_), request.telegram_payment_charge_id_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::getPassportElement &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); @@ -8886,7 +9156,7 @@ void Td::on_request(uint64 id, const td_api::deletePassportElement &request) { void Td::on_request(uint64 id, td_api::setPassportElementErrors &request) { CHECK_IS_BOT(); - auto r_input_user = contacts_manager_->get_input_user(UserId(request.user_id_)); + auto r_input_user = user_manager_->get_input_user(UserId(request.user_id_)); if (r_input_user.is_error()) { return send_error_raw(id, r_input_user.error().code(), r_input_user.error().message()); } @@ -8903,24 +9173,6 @@ void Td::on_request(uint64 id, td_api::getPreferredCountryLanguage &request) { std::move(promise)); } -void Td::on_request(uint64 id, td_api::sendPhoneNumberVerificationCode &request) { - CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.phone_number_); - send_closure(verify_phone_number_manager_, &PhoneNumberManager::set_phone_number, id, - std::move(request.phone_number_), std::move(request.settings_)); -} - -void Td::on_request(uint64 id, const td_api::resendPhoneNumberVerificationCode &request) { - CHECK_IS_USER(); - send_closure(verify_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id); -} - -void Td::on_request(uint64 id, td_api::checkPhoneNumberVerificationCode &request) { - CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.code_); - send_closure(verify_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_)); -} - void Td::on_request(uint64 id, td_api::sendEmailAddressVerificationCode &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.email_address_); @@ -8995,29 +9247,10 @@ void Td::on_request(uint64 id, td_api::sendPassportAuthorizationForm &request) { get_secure_value_types_td_api(request.types_), std::move(promise)); } -void Td::on_request(uint64 id, td_api::sendPhoneNumberConfirmationCode &request) { - CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.phone_number_); - CLEAN_INPUT_STRING(request.hash_); - send_closure(confirm_phone_number_manager_, &PhoneNumberManager::set_phone_number_and_hash, id, - std::move(request.hash_), std::move(request.phone_number_), std::move(request.settings_)); -} - -void Td::on_request(uint64 id, const td_api::resendPhoneNumberConfirmationCode &request) { - CHECK_IS_USER(); - send_closure(confirm_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id); -} - -void Td::on_request(uint64 id, td_api::checkPhoneNumberConfirmationCode &request) { - CHECK_IS_USER(); - CLEAN_INPUT_STRING(request.code_); - send_closure(confirm_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_)); -} - void Td::on_request(uint64 id, const td_api::getSupportUser &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); - contacts_manager_->get_support_user(std::move(promise)); + user_manager_->get_support_user(std::move(promise)); } void Td::on_request(uint64 id, const td_api::getInstalledBackgrounds &request) { @@ -9198,7 +9431,20 @@ void Td::on_request(uint64 id, const td_api::getPremiumGiveawayInfo &request) { get_premium_giveaway_info(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(promise)); } -void Td::on_request(uint64 id, td_api::canPurchasePremium &request) { +void Td::on_request(uint64 id, const td_api::getStarPaymentOptions &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_star_payment_options(this, std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::getStarTransactions &request) { + CHECK_IS_USER(); + CLEAN_INPUT_STRING(request.offset_); + CREATE_REQUEST_PROMISE(); + get_star_transactions(this, request.offset_, std::move(request.direction_), std::move(promise)); +} + +void Td::on_request(uint64 id, td_api::canPurchaseFromStore &request) { CHECK_IS_USER(); CREATE_OK_REQUEST_PROMISE(); can_purchase_premium(this, std::move(request.purpose_), std::move(promise)); @@ -9220,6 +9466,12 @@ void Td::on_request(uint64 id, td_api::assignGooglePlayTransaction &request) { std::move(request.purpose_), std::move(promise)); } +void Td::on_request(uint64 id, const td_api::getBusinessFeatures &request) { + CHECK_IS_USER(); + CREATE_REQUEST_PROMISE(); + get_business_features(this, request.source_, std::move(promise)); +} + void Td::on_request(uint64 id, td_api::acceptTermsOfService &request) { CHECK_IS_USER(); CLEAN_INPUT_STRING(request.terms_of_service_id_); @@ -9256,6 +9508,11 @@ void Td::on_request(uint64 id, const td_api::getPhoneNumberInfo &request) { country_info_manager_->get_phone_number_info(request.phone_number_prefix_, std::move(promise)); } +void Td::on_request(uint64 id, td_api::getCollectibleItemInfo &request) { + CREATE_REQUEST_PROMISE(); + get_collectible_info(this, std::move(request.type_), std::move(promise)); +} + void Td::on_request(uint64 id, const td_api::getApplicationDownloadLink &request) { CHECK_IS_USER(); CREATE_REQUEST_PROMISE(); @@ -9495,9 +9752,9 @@ td_api::object_ptr Td::do_static_request(td_api::searchQuote &re if (r_quote_entities.is_error()) { return make_error(400, r_quote_entities.error().message()); } - auto position = - search_quote({std::move(request.text_->text_), r_text_entities.move_as_ok()}, - {std::move(request.quote_->text_), r_quote_entities.move_as_ok()}, request.quote_position_); + 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"); } diff --git a/lib/tgchat/ext/td/td/telegram/Td.h b/lib/tgchat/ext/td/td/telegram/Td.h index ac0ccb06..ed7e0051 100644 --- a/lib/tgchat/ext/td/td/telegram/Td.h +++ b/lib/tgchat/ext/td/td/telegram/Td.h @@ -46,12 +46,14 @@ class AutosaveManager; class BackgroundManager; class BoostManager; class BotInfoManager; +class BusinessConnectionManager; class BusinessManager; class CallManager; class CallbackQueriesManager; +class ChannelRecommendationManager; +class ChatManager; class CommonDialogManager; class ConfigManager; -class ContactsManager; class CountryInfoManager; class DeviceTokenManager; class DialogActionManager; @@ -77,6 +79,7 @@ class NotificationManager; class NotificationSettingsManager; class OptionManager; class PasswordManager; +class PeopleNearbyManager; class PhoneNumberManager; class PollManager; class PrivacyManager; @@ -97,6 +100,7 @@ class TopDialogManager; class TranscriptionManager; class TranslationManager; class UpdatesManager; +class UserManager; class VideoNotesManager; class VideosManager; class VoiceNotesManager; @@ -178,12 +182,16 @@ class Td final : public Actor { ActorOwn boost_manager_actor_; unique_ptr bot_info_manager_; ActorOwn bot_info_manager_actor_; + unique_ptr business_connection_manager_; + ActorOwn business_connection_manager_actor_; unique_ptr business_manager_; ActorOwn business_manager_actor_; + unique_ptr channel_recommendation_manager_; + ActorOwn channel_recommendation_manager_actor_; + unique_ptr chat_manager_; + ActorOwn chat_manager_actor_; unique_ptr common_dialog_manager_; ActorOwn common_dialog_manager_actor_; - unique_ptr contacts_manager_; - ActorOwn contacts_manager_actor_; unique_ptr country_info_manager_; ActorOwn country_info_manager_actor_; unique_ptr dialog_action_manager_; @@ -224,6 +232,10 @@ class Td final : public Actor { ActorOwn poll_manager_actor_; unique_ptr privacy_manager_; ActorOwn privacy_manager_actor_; + unique_ptr people_nearby_manager_; + ActorOwn people_nearby_manager_actor_; + unique_ptr phone_number_manager_; + ActorOwn phone_number_manager_actor_; unique_ptr quick_reply_manager_; ActorOwn quick_reply_manager_actor_; unique_ptr reaction_manager_; @@ -250,6 +262,8 @@ class Td final : public Actor { ActorOwn translation_manager_actor_; unique_ptr updates_manager_; ActorOwn updates_manager_actor_; + unique_ptr user_manager_; + ActorOwn user_manager_actor_; unique_ptr video_notes_manager_; ActorOwn video_notes_manager_actor_; unique_ptr voice_notes_manager_; @@ -258,11 +272,10 @@ class Td final : public Actor { ActorOwn web_pages_manager_actor_; ActorOwn call_manager_; - ActorOwn change_phone_number_manager_; ActorOwn config_manager_; - ActorOwn confirm_phone_number_manager_; ActorOwn device_token_manager_; ActorOwn hashtag_hints_; + ActorOwn hashtag_search_hints_; ActorOwn language_pack_manager_; ActorOwn net_stats_manager_; ActorOwn password_manager_; @@ -270,7 +283,6 @@ class Td final : public Actor { ActorOwn secure_manager_; ActorOwn state_manager_; ActorOwn storage_manager_; - ActorOwn verify_phone_number_manager_; class ResultHandler : public std::enable_shared_from_this { public: @@ -410,9 +422,8 @@ class Td final : public Actor { void on_get_promo_data(Result> r_promo_data, bool dummy); template - friend class RequestActor; // uses send_result/send_error - friend class AuthManager; // uses send_result/send_error, TODO pass Promise<> - friend class PhoneNumberManager; // uses send_result/send_error, TODO pass Promise<> + friend class RequestActor; // uses send_result/send_error + friend class AuthManager; // uses send_result/send_error, TODO pass Promise<> void add_handler(uint64 id, std::shared_ptr handler); std::shared_ptr extract_handler(uint64 id); @@ -469,9 +480,11 @@ class Td final : public Actor { void on_request(uint64 id, td_api::sendAuthenticationFirebaseSms &request); + void on_request(uint64 id, td_api::reportAuthenticationCodeMissing &request); + void on_request(uint64 id, td_api::setAuthenticationEmailAddress &request); - void on_request(uint64 id, const td_api::resendAuthenticationCode &request); + void on_request(uint64 id, td_api::resendAuthenticationCode &request); void on_request(uint64 id, td_api::checkAuthenticationEmailCode &request); @@ -557,11 +570,15 @@ class Td final : public Actor { void on_request(uint64 id, td_api::deleteAccount &request); - void on_request(uint64 id, td_api::changePhoneNumber &request); + void on_request(uint64 id, td_api::sendPhoneNumberCode &request); + + void on_request(uint64 id, td_api::sendPhoneNumberFirebaseSms &request); - void on_request(uint64 id, td_api::checkChangePhoneNumberCode &request); + void on_request(uint64 id, td_api::reportPhoneNumberCodeMissing &request); - void on_request(uint64 id, td_api::resendChangePhoneNumberCode &request); + void on_request(uint64 id, td_api::resendPhoneNumberCode &request); + + void on_request(uint64 id, td_api::checkPhoneNumberCode &request); void on_request(uint64 id, const td_api::getUserLink &request); @@ -627,6 +644,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::clickChatSponsoredMessage &request); + void on_request(uint64 id, const td_api::reportChatSponsoredMessage &request); + void on_request(uint64 id, const td_api::getMessageLink &request); void on_request(uint64 id, const td_api::getMessageEmbeddingCode &request); @@ -671,6 +690,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::clearAutosaveSettingsExceptions &request); + void on_request(uint64 id, const td_api::getRecommendedChats &request); + void on_request(uint64 id, const td_api::getChatSimilarChats &request); void on_request(uint64 id, const td_api::getChatSimilarChatCount &request); @@ -731,6 +752,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getInactiveSupergroupChats &request); + void on_request(uint64 id, const td_api::getSuitablePersonalChats &request); + void on_request(uint64 id, const td_api::openChat &request); void on_request(uint64 id, const td_api::closeChat &request); @@ -771,6 +794,14 @@ class Td final : public Actor { void on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request); + void on_request(uint64 id, td_api::searchPublicHashtagMessages &request); + + void on_request(uint64 id, td_api::getSearchedForHashtags &request); + + void on_request(uint64 id, td_api::removeSearchedForHashtag &request); + + void on_request(uint64 id, td_api::clearSearchedForHashtags &request); + void on_request(uint64 id, const td_api::deleteAllCallMessages &request); void on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request); @@ -809,6 +840,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setSavedMessagesTagLabel &request); + void on_request(uint64 id, const td_api::getMessageEffect &request); + void on_request(uint64 id, td_api::getMessagePublicForwards &request); void on_request(uint64 id, td_api::getStoryPublicForwards &request); @@ -867,6 +900,12 @@ class Td final : public Actor { void on_request(uint64 id, td_api::editMessageSchedulingState &request); + void on_request(uint64 id, td_api::setMessageFactCheck &request); + + void on_request(uint64 id, td_api::sendBusinessMessage &request); + + void on_request(uint64 id, td_api::sendBusinessMessageAlbum &request); + void on_request(uint64 id, const td_api::loadQuickReplyShortcuts &request); void on_request(uint64 id, const td_api::setQuickReplyShortcutName &request); @@ -879,6 +918,16 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::deleteQuickReplyShortcutMessages &request); + void on_request(uint64 id, td_api::addQuickReplyShortcutMessage &request); + + void on_request(uint64 id, td_api::addQuickReplyShortcutInlineQueryResultMessage &request); + + void on_request(uint64 id, td_api::addQuickReplyShortcutMessageAlbum &request); + + void on_request(uint64 id, td_api::readdQuickReplyShortcutMessages &request); + + void on_request(uint64 id, td_api::editQuickReplyMessage &request); + void on_request(uint64 id, const td_api::getStory &request); void on_request(uint64 id, const td_api::getChatsToSendStories &request); @@ -891,7 +940,7 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setStoryPrivacySettings &request); - void on_request(uint64 id, const td_api::toggleStoryIsPinned &request); + void on_request(uint64 id, const td_api::toggleStoryIsPostedToChatPage &request); void on_request(uint64 id, const td_api::deleteStory &request); @@ -1131,10 +1180,12 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatActiveStories &request); - void on_request(uint64 id, const td_api::getChatPinnedStories &request); + void on_request(uint64 id, const td_api::getChatPostedToChatPageStories &request); void on_request(uint64 id, const td_api::getChatArchivedStories &request); + void on_request(uint64 id, const td_api::setChatPinnedStories &request); + void on_request(uint64 id, const td_api::openStory &request); void on_request(uint64 id, const td_api::closeStory &request); @@ -1285,6 +1336,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::searchFileDownloads &request); + void on_request(uint64 id, td_api::setApplicationVerificationToken &request); + void on_request(uint64 id, td_api::getMessageFileType &request); void on_request(uint64 id, const td_api::getMessageImportConfirmationText &request); @@ -1335,8 +1388,14 @@ class Td final : public Actor { void on_request(uint64 id, td_api::reorderActiveUsernames &request); + void on_request(uint64 id, td_api::setBirthdate &request); + + void on_request(uint64 id, const td_api::setPersonalChat &request); + void on_request(uint64 id, const td_api::setEmojiStatus &request); + void on_request(uint64 id, const td_api::toggleHasSponsoredMessagesEnabled &request); + void on_request(uint64 id, const td_api::getThemedEmojiStatuses &request); void on_request(uint64 id, const td_api::getThemedChatEmojiStatuses &request); @@ -1397,6 +1456,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setBusinessAwayMessageSettings &request); + void on_request(uint64 id, td_api::setBusinessStartPage &request); + void on_request(uint64 id, td_api::setProfilePhoto &request); void on_request(uint64 id, const td_api::deleteProfilePhoto &request); @@ -1413,6 +1474,20 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::deleteBusinessConnectedBot &request); + void on_request(uint64 id, const td_api::toggleBusinessConnectedBotChatIsPaused &request); + + void on_request(uint64 id, const td_api::removeBusinessConnectedBotFromChat &request); + + void on_request(uint64 id, const td_api::getBusinessChatLinks &request); + + void on_request(uint64 id, td_api::createBusinessChatLink &request); + + void on_request(uint64 id, td_api::editBusinessChatLink &request); + + void on_request(uint64 id, td_api::deleteBusinessChatLink &request); + + void on_request(uint64 id, td_api::getBusinessChatLinkInfo &request); + void on_request(uint64 id, td_api::setSupergroupUsername &request); void on_request(uint64 id, td_api::toggleSupergroupUsernameIsActive &request); @@ -1435,6 +1510,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request); + void on_request(uint64 id, const td_api::toggleSupergroupCanHaveSponsoredMessages &request); + void on_request(uint64 id, const td_api::toggleSupergroupHasHiddenMembers &request); void on_request(uint64 id, const td_api::toggleSupergroupHasAggressiveAntiSpamEnabled &request); @@ -1457,6 +1534,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::searchStickers &request); + void on_request(uint64 id, const td_api::getGreetingStickers &request); + void on_request(uint64 id, const td_api::getPremiumStickers &request); void on_request(uint64 id, const td_api::getInstalledStickerSets &request); @@ -1491,6 +1570,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::addStickerToSet &request); + void on_request(uint64 id, td_api::replaceStickerInSet &request); + void on_request(uint64 id, td_api::setStickerSetThumbnail &request); void on_request(uint64 id, td_api::setCustomEmojiStickerSetThumbnail &request); @@ -1509,6 +1590,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setStickerMaskPosition &request); + void on_request(uint64 id, const td_api::getOwnedStickerSets &request); + void on_request(uint64 id, const td_api::getRecentStickers &request); void on_request(uint64 id, td_api::addRecentSticker &request); @@ -1569,6 +1652,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::setScopeNotificationSettings &request); + void on_request(uint64 id, td_api::setReactionNotificationSettings &request); + void on_request(uint64 id, const td_api::resetAllNotificationSettings &request); void on_request(uint64 id, const td_api::removeChatActionBar &request); @@ -1581,6 +1666,12 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getChatStatistics &request); + void on_request(uint64 id, const td_api::getChatRevenueStatistics &request); + + void on_request(uint64 id, const td_api::getChatRevenueWithdrawalUrl &request); + + void on_request(uint64 id, const td_api::getChatRevenueTransactions &request); + void on_request(uint64 id, const td_api::getMessageStatistics &request); void on_request(uint64 id, const td_api::getStoryStatistics &request); @@ -1619,6 +1710,10 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::hideSuggestedAction &request); + void on_request(uint64 id, const td_api::hideContactCloseBirthdays &request); + + void on_request(uint64 id, td_api::getBusinessConnection &request); + void on_request(uint64 id, const td_api::getLoginUrlInfo &request); void on_request(uint64 id, const td_api::getLoginUrl &request); @@ -1671,6 +1766,8 @@ class Td final : public Actor { void on_request(uint64 id, td_api::createInvoiceLink &request); + void on_request(uint64 id, td_api::refundStarPayment &request); + void on_request(uint64 id, td_api::getPassportElement &request); void on_request(uint64 id, td_api::getAllPassportElements &request); @@ -1683,12 +1780,6 @@ class Td final : public Actor { void on_request(uint64 id, td_api::getPreferredCountryLanguage &request); - void on_request(uint64 id, td_api::sendPhoneNumberVerificationCode &request); - - void on_request(uint64 id, const td_api::resendPhoneNumberVerificationCode &request); - - void on_request(uint64 id, td_api::checkPhoneNumberVerificationCode &request); - void on_request(uint64 id, td_api::sendEmailAddressVerificationCode &request); void on_request(uint64 id, const td_api::resendEmailAddressVerificationCode &request); @@ -1701,12 +1792,6 @@ class Td final : public Actor { void on_request(uint64 id, td_api::sendPassportAuthorizationForm &request); - void on_request(uint64 id, td_api::sendPhoneNumberConfirmationCode &request); - - void on_request(uint64 id, const td_api::resendPhoneNumberConfirmationCode &request); - - void on_request(uint64 id, td_api::checkPhoneNumberConfirmationCode &request); - void on_request(uint64 id, const td_api::getSupportUser &request); void on_request(uint64 id, const td_api::getInstalledBackgrounds &request); @@ -1759,12 +1844,18 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getPremiumGiveawayInfo &request); - void on_request(uint64 id, td_api::canPurchasePremium &request); + void on_request(uint64 id, const td_api::getStarPaymentOptions &request); + + void on_request(uint64 id, td_api::getStarTransactions &request); + + void on_request(uint64 id, td_api::canPurchaseFromStore &request); void on_request(uint64 id, td_api::assignAppStoreTransaction &request); void on_request(uint64 id, td_api::assignGooglePlayTransaction &request); + void on_request(uint64 id, const td_api::getBusinessFeatures &request); + void on_request(uint64 id, td_api::acceptTermsOfService &request); void on_request(uint64 id, const td_api::getCountries &request); @@ -1773,6 +1864,8 @@ class Td final : public Actor { void on_request(uint64 id, const td_api::getPhoneNumberInfo &request); + void on_request(uint64 id, td_api::getCollectibleItemInfo &request); + void on_request(uint64 id, const td_api::getApplicationDownloadLink &request); void on_request(uint64 id, td_api::getDeepLinkInfo &request); diff --git a/lib/tgchat/ext/td/td/telegram/TdDb.cpp b/lib/tgchat/ext/td/td/telegram/TdDb.cpp index 167bfdeb..3cf446ca 100644 --- a/lib/tgchat/ext/td/td/telegram/TdDb.cpp +++ b/lib/tgchat/ext/td/td/telegram/TdDb.cpp @@ -126,6 +126,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p case LogEvent::HandlerType::DeleteTopicHistoryOnServer: case LogEvent::HandlerType::ToggleDialogIsTranslatableOnServer: case LogEvent::HandlerType::ToggleDialogViewAsMessagesOnServer: + case LogEvent::HandlerType::SendQuickReplyShortcutMessages: events.to_messages_manager.push_back(event.clone()); break; case LogEvent::HandlerType::DeleteStoryOnServer: @@ -136,6 +137,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_p events.to_story_manager.push_back(event.clone()); break; case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer: + case LogEvent::HandlerType::UpdateReactionNotificationSettingsOnServer: events.to_notification_settings_manager.push_back(event.clone()); break; case LogEvent::HandlerType::AddMessagePushNotification: diff --git a/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp b/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp index afd410ab..0e88221f 100644 --- a/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp +++ b/lib/tgchat/ext/td/td/telegram/TermsOfService.cpp @@ -7,7 +7,6 @@ #include "td/telegram/TermsOfService.h" #include "td/telegram/Global.h" -#include "td/telegram/misc.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/Td.h" #include "td/telegram/telegram_api.h" @@ -95,18 +94,11 @@ TermsOfService::TermsOfService(telegram_api::object_ptrid_->data_); - auto entities = get_message_entities(nullptr, std::move(terms->entities_), "TermsOfService"); - auto status = fix_formatted_text(terms->text_, entities, true, true, true, true, false); - if (status.is_error()) { - if (!clean_input_string(terms->text_)) { - terms->text_.clear(); - } - entities = find_entities(terms->text_, true, true); - } - if (terms->text_.empty()) { + text_ = + get_formatted_text(nullptr, std::move(terms->text_), std::move(terms->entities_), true, false, "TermsOfService"); + if (text_.text.empty()) { id_.clear(); } - text_ = FormattedText{std::move(terms->text_), std::move(entities)}; min_user_age_ = terms->min_age_confirm_; show_popup_ = terms->popup_; } diff --git a/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp b/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp index ee5a934e..da1f571e 100644 --- a/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/ThemeManager.cpp @@ -678,9 +678,8 @@ string ThemeManager::get_theme_parameters_json_string(const td_api::object_ptrauth_manager_->is_bot() || accent_color_id.is_built_in() || - accent_colors_.light_colors_.count(accent_color_id) != 0) { + if (accent_color_id.is_valid() && (td_->auth_manager_->is_bot() || accent_color_id.is_built_in() || + accent_colors_.light_colors_.count(accent_color_id) != 0)) { return accent_color_id.get(); } if (!fallback_accent_color_id.is_valid()) { diff --git a/lib/tgchat/ext/td/td/telegram/ThemeManager.h b/lib/tgchat/ext/td/td/telegram/ThemeManager.h index f6af7272..c98dffbf 100644 --- a/lib/tgchat/ext/td/td/telegram/ThemeManager.h +++ b/lib/tgchat/ext/td/td/telegram/ThemeManager.h @@ -55,7 +55,7 @@ class ThemeManager final : public Actor { void get_current_state(vector> &updates) const; private: - // apeend-only + // append-only enum class BaseTheme : int32 { Classic, Day, Night, Tinted, Arctic }; struct ThemeSettings { diff --git a/lib/tgchat/ext/td/td/telegram/TimeZoneManager.cpp b/lib/tgchat/ext/td/td/telegram/TimeZoneManager.cpp index db3e6188..c977f5ae 100644 --- a/lib/tgchat/ext/td/td/telegram/TimeZoneManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/TimeZoneManager.cpp @@ -15,6 +15,7 @@ #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" #include "td/utils/tl_helpers.h" namespace td { @@ -109,6 +110,16 @@ void TimeZoneManager::tear_down() { parent_.reset(); } +int32 TimeZoneManager::get_time_zone_offset(const string &time_zone_id) { + load_time_zones(); + for (auto &time_zone : time_zones_.time_zones_) { + if (time_zone.id_ == time_zone_id) { + return time_zone.utc_offset_; + } + } + return narrow_cast(G()->get_option_integer("utc_time_offset")); +} + void TimeZoneManager::get_time_zones(Promise> &&promise) { load_time_zones(); if (time_zones_.hash_ != 0) { diff --git a/lib/tgchat/ext/td/td/telegram/TimeZoneManager.h b/lib/tgchat/ext/td/td/telegram/TimeZoneManager.h index c73fbbfc..4cc23c34 100644 --- a/lib/tgchat/ext/td/td/telegram/TimeZoneManager.h +++ b/lib/tgchat/ext/td/td/telegram/TimeZoneManager.h @@ -28,6 +28,8 @@ class TimeZoneManager final : public Actor { TimeZoneManager &operator=(TimeZoneManager &&) = delete; ~TimeZoneManager() final; + int32 get_time_zone_offset(const string &time_zone_id); + void get_time_zones(Promise> &&promise); void reload_time_zones(Promise> &&promise); diff --git a/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp b/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp index 3ad6a7cc..6c5c7f25 100644 --- a/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/TopDialogManager.cpp @@ -8,7 +8,7 @@ #include "td/telegram/AccessRights.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Global.h" @@ -21,6 +21,7 @@ #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/actor/PromiseFuture.h" @@ -102,12 +103,10 @@ class ResetTopPeerRatingQuery final : public Td::ResultHandler { public: void send(TopDialogCategory category, DialogId dialog_id) { - auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); - if (input_peer == nullptr) { - return; - } - dialog_id_ = dialog_id; + + auto input_peer = td_->dialog_manager_->get_input_peer(dialog_id, AccessRights::Read); + CHECK(input_peer != nullptr); send_query(G()->net_query_creator().create( telegram_api::contacts_resetTopPeerRating(get_input_top_peer_category(category), std::move(input_peer)))); } @@ -239,9 +238,8 @@ void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog if (category == TopDialogCategory::Size) { return promise.set_error(Status::Error(400, "Top chat category must be non-empty")); } - if (!td_->dialog_manager_->have_dialog_force(dialog_id, "remove_dialog")) { - return promise.set_error(Status::Error(400, "Chat not found")); - } + TRY_STATUS_PROMISE(promise, + td_->dialog_manager_->check_dialog_access(dialog_id, false, AccessRights::Read, "remove_dialog")); CHECK(!td_->auth_manager_->is_bot()); if (!is_enabled_) { return promise.set_value(Unit()); @@ -408,16 +406,16 @@ void TopDialogManager::on_load_dialogs(GetTopDialogsQuery &&query, vectorcontacts_manager_->is_user_deleted(user_id)) { + if (td_->user_manager_->is_user_deleted(user_id)) { LOG(INFO) << "Skip deleted " << user_id; continue; } - if (td_->contacts_manager_->get_my_id() == user_id) { + if (td_->user_manager_->get_my_id() == user_id) { LOG(INFO) << "Skip self " << user_id; continue; } if (query.category == TopDialogCategory::BotInline || query.category == TopDialogCategory::BotPM) { - auto r_bot_info = td_->contacts_manager_->get_bot_data(user_id); + auto r_bot_info = td_->user_manager_->get_bot_data(user_id); if (r_bot_info.is_error()) { LOG(INFO) << "Skip not a bot " << user_id; continue; @@ -495,8 +493,8 @@ void TopDialogManager::on_get_top_peers(Result(std::move(top_peers_parent)); - td_->contacts_manager_->on_get_users(std::move(top_peers->users_), "on get top chats"); - td_->contacts_manager_->on_get_chats(std::move(top_peers->chats_), "on get top chats"); + td_->user_manager_->on_get_users(std::move(top_peers->users_), "on get top chats"); + td_->chat_manager_->on_get_chats(std::move(top_peers->chats_), "on get top chats"); for (auto &category : top_peers->categories_) { auto dialog_category = get_top_dialog_category(category->category_); auto pos = static_cast(dialog_category); diff --git a/lib/tgchat/ext/td/td/telegram/TranscriptionManager.cpp b/lib/tgchat/ext/td/td/telegram/TranscriptionManager.cpp index 8dd5c642..1c362db5 100644 --- a/lib/tgchat/ext/td/td/telegram/TranscriptionManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/TranscriptionManager.cpp @@ -357,7 +357,7 @@ void TranscriptionManager::on_transcription_updated(FileId file_id) { auto it = voice_messages_.find(file_id); if (it != voice_messages_.end()) { for (const auto &message_full_id : it->second) { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_transcription_updated"); } } } diff --git a/lib/tgchat/ext/td/td/telegram/TranslationManager.cpp b/lib/tgchat/ext/td/td/telegram/TranslationManager.cpp index 7e443ea3..250de4af 100644 --- a/lib/tgchat/ext/td/td/telegram/TranslationManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/TranslationManager.cpp @@ -28,10 +28,9 @@ class TranslateTextQuery final : public Td::ResultHandler { void send(vector &&texts, const string &to_language_code) { int flags = telegram_api::messages_translateText::TEXT_MASK; - auto input_texts = - transform(std::move(texts), [contacts_manager = td_->contacts_manager_.get()](FormattedText &&text) { - return get_input_text_with_entities(contacts_manager, std::move(text), "TranslateTextQuery"); - }); + auto input_texts = transform(std::move(texts), [user_manager = td_->user_manager_.get()](FormattedText &&text) { + return get_input_text_with_entities(user_manager, std::move(text), "TranslateTextQuery"); + }); send_query(G()->net_query_creator().create(telegram_api::messages_translateText( flags, nullptr, vector{}, std::move(input_texts), to_language_code))); } @@ -48,6 +47,11 @@ class TranslateTextQuery final : public Td::ResultHandler { } void on_error(Status status) final { + if (status.message() == "INPUT_TEXT_EMPTY") { + vector> result; + result.push_back(telegram_api::make_object(string(), Auto())); + return promise_.set_value(std::move(result)); + } promise_.set_error(std::move(status)); } }; @@ -87,7 +91,7 @@ void TranslationManager::translate_text(td_api::object_ptrcontacts_manager_.get(), std::move(text->entities_))); + TRY_RESULT_PROMISE(promise, entities, get_message_entities(td_->user_manager_.get(), std::move(text->entities_))); TRY_STATUS_PROMISE(promise, fix_formatted_text(text->text_, entities, true, true, true, true, true)); translate_text(FormattedText{std::move(text->text_), std::move(entities)}, skip_bot_commands, max_media_timestamp, @@ -120,9 +124,8 @@ void TranslationManager::on_get_translated_texts(vectorcontacts_manager_.get(), std::move(texts[0]), true, true, skip_bot_commands, - max_media_timestamp == -1, true, "on_get_translated_texts"); + auto formatted_text = get_formatted_text(td_->user_manager_.get(), std::move(texts[0]), max_media_timestamp == -1, + true, "on_get_translated_texts"); promise.set_value(get_formatted_text_object(formatted_text, skip_bot_commands, max_media_timestamp)); } diff --git a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp index 0e5a12bd..455dc217 100644 --- a/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/UpdatesManager.cpp @@ -12,13 +12,15 @@ #include "td/telegram/AuthManager.h" #include "td/telegram/AutosaveManager.h" #include "td/telegram/BoostManager.h" +#include "td/telegram/BusinessConnectionId.h" +#include "td/telegram/BusinessConnectionManager.h" #include "td/telegram/CallbackQueriesManager.h" #include "td/telegram/CallManager.h" #include "td/telegram/ChannelId.h" #include "td/telegram/ChannelType.h" #include "td/telegram/ChatId.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/ConfigManager.h" -#include "td/telegram/ContactsManager.h" #include "td/telegram/DialogAction.h" #include "td/telegram/DialogActionManager.h" #include "td/telegram/DialogFilterManager.h" @@ -48,6 +50,7 @@ #include "td/telegram/NotificationSettingsScope.h" #include "td/telegram/OptionManager.h" #include "td/telegram/OrderInfo.h" +#include "td/telegram/PeopleNearbyManager.h" #include "td/telegram/PollId.h" #include "td/telegram/PollManager.h" #include "td/telegram/PrivacyManager.h" @@ -64,6 +67,7 @@ #include "td/telegram/ServerMessageId.h" #include "td/telegram/SpecialStickerSetType.h" #include "td/telegram/StateManager.h" +#include "td/telegram/StatisticsManager.h" #include "td/telegram/StickerListType.h" #include "td/telegram/StickerSetId.h" #include "td/telegram/StickersManager.h" @@ -78,6 +82,7 @@ #include "td/telegram/ThemeManager.h" #include "td/telegram/TimeZoneManager.h" #include "td/telegram/TranscriptionManager.h" +#include "td/telegram/UserManager.h" #include "td/telegram/Usernames.h" #include "td/telegram/WebPagesManager.h" @@ -390,11 +395,10 @@ void UpdatesManager::fill_pts_gap(void *td) { } max_pts = max(max_pts, updates_manager->postponed_pts_updates_.rbegin()->pts); } - string source = PSTRING() << "PTS from " << updates_manager->get_pts() << " to " << min_pts << "(-" << min_pts_count - << ")-" << max_pts << ' ' - << (first_update == nullptr ? string() : oneline(to_string(*first_update))); updates_manager->pts_gap_++; - fill_gap(td, source.c_str()); + fill_gap(td, PSTRING() << "PTS from " << updates_manager->get_pts() << " to " << min_pts << "(-" << min_pts_count + << ")-" << max_pts << ' ' + << (first_update == nullptr ? string() : oneline(to_string(*first_update)))); } void UpdatesManager::fill_seq_gap(void *td) { @@ -410,8 +414,7 @@ void UpdatesManager::fill_seq_gap(void *td) { min_seq = updates_manager->pending_seq_updates_.begin()->seq_begin; max_seq = updates_manager->pending_seq_updates_.rbegin()->seq_end; } - string source = PSTRING() << "seq from " << updates_manager->seq_ << " to " << min_seq << '-' << max_seq; - fill_gap(td, source.c_str()); + fill_gap(td, PSTRING() << "seq from " << updates_manager->seq_ << " to " << min_seq << '-' << max_seq); } void UpdatesManager::fill_qts_gap(void *td) { @@ -427,16 +430,15 @@ void UpdatesManager::fill_qts_gap(void *td) { min_qts = updates_manager->pending_qts_updates_.begin()->first; max_qts = updates_manager->pending_qts_updates_.rbegin()->first; } - string source = PSTRING() << "QTS from " << updates_manager->get_qts() << " to " << min_qts << '-' << max_qts; updates_manager->qts_gap_++; - fill_gap(td, source.c_str()); + fill_gap(td, PSTRING() << "QTS from " << updates_manager->get_qts() << " to " << min_qts << '-' << max_qts); } void UpdatesManager::fill_get_difference_gap(void *td) { - fill_gap(td, nullptr); + fill_gap(td, string()); } -void UpdatesManager::fill_gap(void *td, const char *source) { +void UpdatesManager::fill_gap(void *td, const string &source) { if (G()->close_flag()) { return; } @@ -446,7 +448,7 @@ void UpdatesManager::fill_gap(void *td, const char *source) { } auto updates_manager = static_cast(td)->updates_manager_.get(); - if (source != nullptr && !updates_manager->running_get_difference_) { + if (!source.empty() && !updates_manager->running_get_difference_) { auto auth_key_id = updates_manager->get_most_unused_auth_key_id(); uint64 update_count = 0; double active_time = 0.0; @@ -700,16 +702,15 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source) } bool UpdatesManager::is_acceptable_user(UserId user_id) const { - return td_->contacts_manager_->have_user_force(user_id, "is_acceptable_user") && - td_->contacts_manager_->have_user(user_id); + return td_->user_manager_->have_user_force(user_id, "is_acceptable_user") && td_->user_manager_->have_user(user_id); } bool UpdatesManager::is_acceptable_chat(ChatId chat_id) const { - return td_->contacts_manager_->have_chat_force(chat_id, "is_acceptable_chat"); + return td_->chat_manager_->have_chat_force(chat_id, "is_acceptable_chat"); } bool UpdatesManager::is_acceptable_channel(ChannelId channel_id) const { - return td_->contacts_manager_->have_channel_force(channel_id, "is_acceptable_channel"); + return td_->chat_manager_->have_channel_force(channel_id, "is_acceptable_channel"); } bool UpdatesManager::is_acceptable_peer(const tl_object_ptr &peer) const { @@ -750,7 +751,7 @@ bool UpdatesManager::is_acceptable_message_entities( if (entity->get_id() == telegram_api::messageEntityMentionName::ID) { auto entity_mention_name = static_cast(entity.get()); UserId user_id(entity_mention_name->user_id_); - if (!is_acceptable_user(user_id) || td_->contacts_manager_->get_input_user(user_id).is_error()) { + if (!is_acceptable_user(user_id) || td_->user_manager_->get_input_user(user_id).is_error()) { return false; } } @@ -767,7 +768,7 @@ bool UpdatesManager::is_acceptable_reply_markup(const tl_object_ptrget_id() == telegram_api::keyboardButtonUserProfile::ID) { auto user_profile_button = static_cast(button.get()); UserId user_id(user_profile_button->user_id_); - if (!is_acceptable_user(user_id) || td_->contacts_manager_->get_input_user(user_id).is_error()) { + if (!is_acceptable_user(user_id) || td_->user_manager_->get_input_user(user_id).is_error()) { return false; } } @@ -875,7 +876,7 @@ bool UpdatesManager::is_acceptable_message_media( for (auto &page_block : *page_blocks) { if (page_block->get_id() == telegram_api::pageBlockChannel::ID) { auto page_block_channel = static_cast(page_block.get()); - auto channel_id = ContactsManager::get_channel_id(page_block_channel->channel_); + auto channel_id = ChatManager::get_channel_id(page_block_channel->channel_); if (channel_id.is_valid()) { if (!is_acceptable_channel(channel_id)) { return false; @@ -989,6 +990,7 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ case telegram_api::messageActionGiveawayLaunch::ID: case telegram_api::messageActionGiveawayResults::ID: case telegram_api::messageActionBoostApply::ID: + case telegram_api::messageActionRequestedPeerSentMe::ID: break; case telegram_api::messageActionChatCreate::ID: { auto chat_create = static_cast(action); @@ -1185,14 +1187,14 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up break; case telegram_api::updateShortMessage::ID: { auto update = move_tl_object_as(updates_ptr); - auto from_id = update->out_ ? td_->contacts_manager_->get_my_id().get() : update->user_id_; + auto from_id = update->out_ ? td_->user_manager_->get_my_id().get() : update->user_id_; auto message = make_tl_object( fix_short_message_flags(update->flags_), update->out_, update->mentioned_, update->media_unread_, - update->silent_, false, false, false, false, false, false, false, update->id_, + update->silent_, false, false, false, false, false, false, false, 0, false, update->id_, make_tl_object(from_id), 0, make_tl_object(update->user_id_), - nullptr, std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_, + nullptr, std::move(update->fwd_from_), update->via_bot_id_, 0, std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr, - Auto(), update->ttl_period_, 0); + Auto(), update->ttl_period_, 0, 0, nullptr); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, std::move(promise), "telegram_api::updateShortMessage"); @@ -1202,11 +1204,12 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up auto update = move_tl_object_as(updates_ptr); auto message = make_tl_object( fix_short_message_flags(update->flags_), update->out_, update->mentioned_, update->media_unread_, - update->silent_, false, false, false, false, false, false, false, update->id_, + update->silent_, false, false, false, false, false, false, false, 0, false, update->id_, make_tl_object(update->from_id_), 0, make_tl_object(update->chat_id_), nullptr, std::move(update->fwd_from_), - update->via_bot_id_, std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, - std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr, Auto(), update->ttl_period_, 0); + update->via_bot_id_, 0, std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, + std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr, Auto(), update->ttl_period_, 0, 0, + nullptr); on_pending_update( make_tl_object(std::move(message), update->pts_, update->pts_count_), 0, std::move(promise), "telegram_api::updateShortChatMessage"); @@ -1227,8 +1230,8 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up } case telegram_api::updatesCombined::ID: { auto updates = move_tl_object_as(updates_ptr); - td_->contacts_manager_->on_get_users(std::move(updates->users_), "updatesCombined"); - td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updatesCombined"); + td_->user_manager_->on_get_users(std::move(updates->users_), "updatesCombined"); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), "updatesCombined"); on_pending_updates(std::move(updates->updates_), updates->seq_start_, updates->seq_, updates->date_, Time::now(), std::move(promise), "telegram_api::updatesCombined"); break; @@ -1241,8 +1244,8 @@ void UpdatesManager::on_get_updates_impl(tl_object_ptr up source_str = PSTRING() << "update " << updates->updates_[0]->get_id(); source = source_str.c_str(); } - td_->contacts_manager_->on_get_users(std::move(updates->users_), source); - td_->contacts_manager_->on_get_chats(std::move(updates->chats_), source); + td_->user_manager_->on_get_users(std::move(updates->users_), source); + td_->chat_manager_->on_get_chats(std::move(updates->chats_), source); on_pending_updates(std::move(updates->updates_), updates->seq_, updates->seq_, updates->date_, Time::now(), std::move(promise), "telegram_api::updates"); break; @@ -1381,33 +1384,6 @@ bool UpdatesManager::are_empty_updates(const telegram_api::Updates *updates_ptr) } } -vector UpdatesManager::extract_group_invite_privacy_forbidden_updates( - tl_object_ptr &updates_ptr) { - auto updates = get_updates(updates_ptr.get()); - if (updates == nullptr) { - LOG(ERROR) << "Can't find updateGroupInvitePrivacyForbidden updates"; - return {}; - } - LOG(INFO) << "Extract updateGroupInvitePrivacyForbidden updates from " << updates->size() << " updates"; - vector user_ids; - for (auto &update_ptr : *updates) { - if (update_ptr->get_id() != telegram_api::updateGroupInvitePrivacyForbidden::ID) { - continue; - } - auto update = telegram_api::move_object_as(update_ptr); - UserId user_id(update->user_id_); - if (!user_id.is_valid()) { - LOG(ERROR) << "Receive " << to_string(update); - continue; - } - user_ids.push_back(user_id); - } - if (!user_ids.empty()) { - td::remove_if(*updates, [](auto &update) { return update == nullptr; }); - } - return user_ids; -} - FlatHashSet UpdatesManager::get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr) { FlatHashSet random_ids; auto updates = get_updates(updates_ptr); @@ -1639,7 +1615,7 @@ vector UpdatesManager::get_chat_dialog_ids(const telegram_api::Updates vector dialog_ids; dialog_ids.reserve(chats->size()); for (const auto &chat : *chats) { - auto dialog_id = ContactsManager::get_dialog_id(chat); + auto dialog_id = ChatManager::get_dialog_id(chat); if (dialog_id.is_valid()) { dialog_ids.push_back(dialog_id); } else { @@ -1647,7 +1623,7 @@ vector UpdatesManager::get_chat_dialog_ids(const telegram_api::Updates } } if (dialog_ids.size() > 1) { - td::remove(dialog_ids, DialogId(ContactsManager::get_unsupported_channel_id())); + td::remove(dialog_ids, DialogId(ChatManager::get_unsupported_channel_id())); } return dialog_ids; } @@ -1807,6 +1783,16 @@ void UpdatesManager::on_server_pong(tl_object_ptr & } } +void UpdatesManager::notify_speed_limited(bool is_upload) { + if (Time::now() < next_notify_speed_limited_[is_upload]) { + return; + } + next_notify_speed_limited_[is_upload] = + Time::now() + + static_cast(td_->option_manager_->get_option_integer("upload_premium_speedup_notify_period")); + send_closure(G()->td(), &Td::send_update, td_api::make_object(is_upload)); +} + void UpdatesManager::process_get_difference_updates( vector> &&new_messages, vector> &&new_encrypted_messages, @@ -1930,8 +1916,8 @@ void UpdatesManager::on_get_difference(tl_object_ptr(difference_ptr); VLOG(get_difference) << "In get difference receive " << difference->users_.size() << " users and " << difference->chats_.size() << " chats"; - td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.difference"); - td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.difference"); + td_->user_manager_->on_get_users(std::move(difference->users_), "updates.difference"); + td_->chat_manager_->on_get_chats(std::move(difference->chats_), "updates.difference"); if (get_difference_retry_count_ <= 5) { for (const auto &message : difference->new_messages_) { @@ -1966,8 +1952,8 @@ void UpdatesManager::on_get_difference(tl_object_ptrusers_.size() << " users and " << difference->chats_.size() << " chats"; - td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.differenceSlice"); - td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.differenceSlice"); + td_->user_manager_->on_get_users(std::move(difference->users_), "updates.differenceSlice"); + td_->chat_manager_->on_get_chats(std::move(difference->chats_), "updates.differenceSlice"); if (get_difference_retry_count_ <= 5) { for (const auto &message : difference->new_messages_) { @@ -2063,8 +2049,8 @@ void UpdatesManager::on_get_pts_update(int32 pts, return; } - td_->contacts_manager_->on_get_users(std::move(difference->users_), "on_get_pts_update"); - td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "on_get_pts_update"); + td_->user_manager_->on_get_users(std::move(difference->users_), "on_get_pts_update"); + td_->chat_manager_->on_get_chats(std::move(difference->chats_), "on_get_pts_update"); for (auto &message : difference->new_messages_) { difference->other_updates_.push_back( @@ -2259,11 +2245,13 @@ void UpdatesManager::try_reload_data() { LOG(INFO) << "Reload data"; td_->animations_manager_->reload_saved_animations(true); td_->autosave_manager_->reload_autosave_settings(); - td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::HasUsername, std::move(promise)); - td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::IsLocationBased, Auto()); + td_->chat_manager_->reload_created_public_dialogs(PublicDialogType::HasUsername, std::move(promise)); + td_->chat_manager_->reload_created_public_dialogs(PublicDialogType::IsLocationBased, Auto()); + td_->chat_manager_->reload_created_public_dialogs(PublicDialogType::ForPersonalDialog, Auto()); get_default_emoji_statuses(td_, Auto()); get_default_channel_emoji_statuses(td_, Auto()); td_->notification_settings_manager_->reload_saved_ringtones(Auto()); + td_->notification_settings_manager_->send_get_reaction_notification_settings_query(Auto()); td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Private, Auto()); td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Group, @@ -2272,10 +2260,11 @@ void UpdatesManager::try_reload_data() { Auto()); td_->quick_reply_manager_->reload_quick_reply_shortcuts(); td_->reaction_manager_->reload_reactions(); + td_->reaction_manager_->reload_message_effects(); for (int32 type = 0; type < MAX_REACTION_LIST_TYPE; type++) { auto reaction_list_type = static_cast(type); - td_->reaction_manager_->reload_reaction_list(reaction_list_type); + td_->reaction_manager_->reload_reaction_list(reaction_list_type, "try_reload_data"); } for (int32 type = 0; type < MAX_STICKER_TYPE; type++) { @@ -2303,6 +2292,7 @@ void UpdatesManager::try_reload_data() { td_->theme_manager_->reload_chat_themes(); td_->theme_manager_->reload_profile_accent_colors(); td_->time_zone_manager_->reload_time_zones(Auto()); + td_->user_manager_->reload_contact_birthdates(false); schedule_data_reload(); } @@ -2391,7 +2381,7 @@ void UpdatesManager::on_pending_updates(vectorcontacts_manager_->have_channel_force(channel_id, source)) { + if (td_->chat_manager_->have_channel_force(channel_id, source)) { if (td_->messages_manager_->is_old_channel_update(dialog_id, pts)) { // the update will be ignored anyway, so there is no reason to replace it or force get_difference LOG(INFO) << "Allow an outdated unacceptable update from " << source; @@ -2423,7 +2413,7 @@ void UpdatesManager::on_pending_updates(vectorcontacts_manager_->on_update_user_local_was_online(user_id, date); + td_->user_manager_->on_update_user_local_was_online(user_id, date); } } } @@ -3004,18 +2994,22 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up } case telegram_api::updateChatParticipant::ID: { auto update = move_tl_object_as(update_ptr); + bool via_join_request = + 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"), + DialogInviteLink(std::move(update->invite_), true, "updateChatParticipant"), via_join_request, std::move(update->prev_participant_), std::move(update->new_participant_)); break; } case telegram_api::updateChannelParticipant::ID: { auto update = move_tl_object_as(update_ptr); + bool via_join_request = + 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"), update->via_chatlist_, - std::move(update->prev_participant_), std::move(update->new_participant_)); + DialogInviteLink(std::move(update->invite_), true, "updateChannelParticipant"), via_join_request, + update->via_chatlist_, std::move(update->prev_participant_), std::move(update->new_participant_)); break; } case telegram_api::updateBotChatInviteRequester::ID: { @@ -3082,6 +3076,32 @@ void UpdatesManager::process_qts_update(tl_object_ptr &&up message_id.get(), date, std::move(message_reactions))); break; } + case telegram_api::updateBotBusinessConnect::ID: { + auto update = move_tl_object_as(update_ptr); + td_->business_connection_manager_->on_update_bot_business_connect(std::move(update->connection_)); + break; + } + case telegram_api::updateBotNewBusinessMessage::ID: { + auto update = move_tl_object_as(update_ptr); + td_->business_connection_manager_->on_update_bot_new_business_message( + BusinessConnectionId(std::move(update->connection_id_)), std::move(update->message_), + std::move(update->reply_to_message_)); + break; + } + case telegram_api::updateBotEditBusinessMessage::ID: { + auto update = move_tl_object_as(update_ptr); + td_->business_connection_manager_->on_update_bot_edit_business_message( + BusinessConnectionId(std::move(update->connection_id_)), std::move(update->message_), + std::move(update->reply_to_message_)); + break; + } + case telegram_api::updateBotDeleteBusinessMessage::ID: { + auto update = move_tl_object_as(update_ptr); + td_->business_connection_manager_->on_update_bot_delete_business_messages( + BusinessConnectionId(std::move(update->connection_id_)), DialogId(update->peer_), + std::move(update->messages_)); + break; + } default: UNREACHABLE(); break; @@ -3511,7 +3531,7 @@ void UpdatesManager::on_update(tl_object_ptr } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->invalidate_channel_full(ChannelId(update->channel_id_), false, "updateChannel"); + td_->chat_manager_->invalidate_channel_full(ChannelId(update->channel_id_), false, "updateChannel"); promise.set_value(Unit()); } @@ -3693,7 +3713,7 @@ void UpdatesManager::on_update(tl_object_ptr } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_peer_located(std::move(update->peers_), true); + td_->people_nearby_manager_->on_update_peer_located(std::move(update->peers_), true); promise.set_value(Unit()); } @@ -3719,7 +3739,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent); + td_->reaction_manager_->reload_reaction_list(ReactionListType::Recent, "updateRecentReactions"); promise.set_value(Unit()); } @@ -3870,6 +3890,10 @@ bool UpdatesManager::is_qts_update(const telegram_api::Update *update) { case telegram_api::updateBotChatBoost::ID: case telegram_api::updateBotMessageReaction::ID: case telegram_api::updateBotMessageReactions::ID: + case telegram_api::updateBotBusinessConnect::ID: + case telegram_api::updateBotNewBusinessMessage::ID: + case telegram_api::updateBotEditBusinessMessage::ID: + case telegram_api::updateBotDeleteBusinessMessage::ID: return true; default: return false; @@ -3896,6 +3920,14 @@ int32 UpdatesManager::get_update_qts(const telegram_api::Update *update) { return static_cast(update)->qts_; case telegram_api::updateBotMessageReactions::ID: return static_cast(update)->qts_; + case telegram_api::updateBotBusinessConnect::ID: + return static_cast(update)->qts_; + case telegram_api::updateBotNewBusinessMessage::ID: + return static_cast(update)->qts_; + case telegram_api::updateBotEditBusinessMessage::ID: + return static_cast(update)->qts_; + case telegram_api::updateBotDeleteBusinessMessage::ID: + return static_cast(update)->qts_; default: return 0; } @@ -3936,36 +3968,36 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { SecretChatId secret_chat_id(update->chat_id_); - UserId user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id); + UserId user_id = td_->user_manager_->get_secret_chat_user_id(secret_chat_id); td_->dialog_action_manager_->on_dialog_action(DialogId(secret_chat_id), MessageId(), DialogId(user_id), DialogAction::get_typing_action(), get_short_update_date()); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_user_online(UserId(update->user_id_), std::move(update->status_)); + td_->user_manager_->on_update_user_online(UserId(update->user_id_), std::move(update->status_)); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_user_name(UserId(update->user_id_), std::move(update->first_name_), - std::move(update->last_name_), - Usernames{string(), std::move(update->usernames_)}); + td_->user_manager_->on_update_user_name(UserId(update->user_id_), std::move(update->first_name_), + std::move(update->last_name_), + Usernames{string(), std::move(update->usernames_)}); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_user_phone_number(UserId(update->user_id_), std::move(update->phone_)); + td_->user_manager_->on_update_user_phone_number(UserId(update->user_id_), std::move(update->phone_)); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->invalidate_user_full(UserId(update->user_id_)); + td_->user_manager_->invalidate_user_full(UserId(update->user_id_)); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_user_emoji_status(UserId(update->user_id_), std::move(update->emoji_status_)); + td_->user_manager_->on_update_user_emoji_status(UserId(update->user_id_), std::move(update->emoji_status_)); promise.set_value(Unit()); } @@ -3981,38 +4013,37 @@ void UpdatesManager::on_update(tl_object_ptr up } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_bot_commands(DialogId(update->peer_), UserId(update->bot_id_), - std::move(update->commands_)); + td_->dialog_manager_->on_update_dialog_bot_commands(DialogId(update->peer_), UserId(update->bot_id_), + std::move(update->commands_)); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_bot_menu_button(UserId(update->bot_id_), std::move(update->button_)); + td_->user_manager_->on_update_bot_menu_button(UserId(update->bot_id_), std::move(update->button_)); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_get_chat_participants(std::move(update->participants_), true); + td_->chat_manager_->on_get_chat_participants(std::move(update->participants_), true); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_chat_add_user(ChatId(update->chat_id_), UserId(update->inviter_id_), - UserId(update->user_id_), update->date_, update->version_); + td_->chat_manager_->on_update_chat_add_user(ChatId(update->chat_id_), UserId(update->inviter_id_), + UserId(update->user_id_), update->date_, update->version_); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_chat_edit_administrator(ChatId(update->chat_id_), UserId(update->user_id_), - update->is_admin_, update->version_); + td_->chat_manager_->on_update_chat_edit_administrator(ChatId(update->chat_id_), UserId(update->user_id_), + update->is_admin_, update->version_); promise.set_value(Unit()); } void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_chat_delete_user(ChatId(update->chat_id_), UserId(update->user_id_), - update->version_); + td_->chat_manager_->on_update_chat_delete_user(ChatId(update->chat_id_), UserId(update->user_id_), update->version_); promise.set_value(Unit()); } @@ -4022,12 +4053,12 @@ void UpdatesManager::on_update(tl_object_ptrversion_; switch (dialog_id.get_type()) { case DialogType::Chat: - td_->contacts_manager_->on_update_chat_default_permissions( + td_->chat_manager_->on_update_chat_default_permissions( dialog_id.get_chat_id(), RestrictedRights(update->default_banned_rights_, ChannelType::Unknown), version); break; case DialogType::Channel: LOG_IF(ERROR, version != 0) << "Receive version " << version << " in " << dialog_id; - td_->contacts_manager_->on_update_channel_default_permissions( + td_->chat_manager_->on_update_channel_default_permissions( dialog_id.get_channel_id(), RestrictedRights(update->default_banned_rights_, ChannelType::Megagroup)); break; case DialogType::None: @@ -4222,7 +4253,7 @@ void UpdatesManager::on_update(tl_object_ptrtd(), &Td::send_update, make_tl_object( - update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewShippingQuery"), + update->query_id_, td_->user_manager_->get_user_id_object(user_id, "updateNewShippingQuery"), update->payload_.as_slice().str(), get_address_object(get_address(std::move(update->shipping_address_))))); // TODO use convert_address } @@ -4236,12 +4267,11 @@ void UpdatesManager::on_update(tl_object_ptrtotal_amount_ <= 0 || !check_currency_amount(update->total_amount_)) { LOG(ERROR) << "Receive pre-checkout query with invalid total amount " << update->total_amount_; } else { - send_closure( - G()->td(), &Td::send_update, - make_tl_object( - update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewPreCheckoutQuery"), - update->currency_, update->total_amount_, update->payload_.as_slice().str(), update->shipping_option_id_, - get_order_info_object(get_order_info(std::move(update->info_))))); + send_closure(G()->td(), &Td::send_update, + make_tl_object( + update->query_id_, td_->user_manager_->get_user_id_object(user_id, "updateNewPreCheckoutQuery"), + update->currency_, update->total_amount_, update->payload_.as_slice().str(), + update->shipping_option_id_, get_order_info_object(get_order_info(std::move(update->info_))))); } promise.set_value(Unit()); } @@ -4299,7 +4329,7 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { - td_->contacts_manager_->on_update_contacts_reset(); + td_->user_manager_->on_update_contacts_reset(); promise.set_value(Unit()); } @@ -4402,7 +4432,7 @@ void UpdatesManager::on_update(tl_object_ptr update, void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { auto dialog_id = DialogId(update->peer_); if (dialog_id.get_type() == DialogType::User) { - td_->contacts_manager_->on_update_user_wallpaper_overridden(dialog_id.get_user_id(), update->wallpaper_overridden_); + td_->user_manager_->on_update_user_wallpaper_overridden(dialog_id.get_user_id(), update->wallpaper_overridden_); } td_->messages_manager_->on_update_dialog_background(dialog_id, std::move(update->wallpaper_)); promise.set_value(Unit()); @@ -4423,12 +4453,6 @@ void UpdatesManager::on_update(tl_object_ptr update, - Promise &&promise) { - LOG(ERROR) << "Receive " << to_string(update); - promise.set_value(Unit()); -} - void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { td_->autosave_manager_->reload_autosave_settings(); promise.set_value(Unit()); @@ -4506,6 +4530,49 @@ void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + +void UpdatesManager::on_update(tl_object_ptr update, + Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + +void UpdatesManager::on_update(tl_object_ptr update, + Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + +void UpdatesManager::on_update(tl_object_ptr update, + Promise &&promise) { + auto qts = update->qts_; + add_pending_qts_update(std::move(update), qts, std::move(promise)); +} + +void UpdatesManager::on_update(tl_object_ptr update, + Promise &&promise) { + td_->statistics_manager_->on_update_dialog_revenue_transactions(DialogId(update->peer_), + std::move(update->balances_)); + promise.set_value(Unit()); +} + +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + if (update->balance_ < 0) { + LOG(ERROR) << "Receive " << update->balance_ << " stars"; + update->balance_ = 0; + } + send_closure(G()->td(), &Td::send_update, td_api::make_object(update->balance_)); + promise.set_value(Unit()); +} + // unsupported updates +void UpdatesManager::on_update(tl_object_ptr update, Promise &&promise) { + promise.set_value(Unit()); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/UpdatesManager.h b/lib/tgchat/ext/td/td/telegram/UpdatesManager.h index e608b3c6..a9880111 100644 --- a/lib/tgchat/ext/td/td/telegram/UpdatesManager.h +++ b/lib/tgchat/ext/td/td/telegram/UpdatesManager.h @@ -104,9 +104,6 @@ class UpdatesManager final : public Actor { static bool are_empty_updates(const telegram_api::Updates *updates_ptr); - static vector extract_group_invite_privacy_forbidden_updates( - tl_object_ptr &updates_ptr); - static FlatHashSet get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr); static const telegram_api::Message *get_message_by_random_id(const telegram_api::Updates *updates_ptr, @@ -134,6 +131,8 @@ class UpdatesManager final : public Actor { void ping_server(); + void notify_speed_limited(bool is_upload); + bool running_get_difference() const { return running_get_difference_; } @@ -283,6 +282,8 @@ class UpdatesManager final : public Actor { }; FlatHashMap session_infos_; + double next_notify_speed_limited_[2] = {0.0, 0.0}; + void start_up() final; void tear_down() final; @@ -383,7 +384,7 @@ class UpdatesManager final : public Actor { static void fill_get_difference_gap(void *td); - static void fill_gap(void *td, const char *source); + static void fill_gap(void *td, const string &source); void repair_pts_gap(); @@ -634,8 +635,6 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); - void on_update(tl_object_ptr update, Promise &&promise); - void on_update(tl_object_ptr update, Promise &&promise); void on_update(tl_object_ptr update, Promise &&promise); @@ -662,7 +661,21 @@ class UpdatesManager final : public Actor { void on_update(tl_object_ptr update, Promise &&promise); + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + + void on_update(tl_object_ptr update, Promise &&promise); + // unsupported updates + + void on_update(tl_object_ptr update, Promise &&promise); }; } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/UserManager.cpp b/lib/tgchat/ext/td/td/telegram/UserManager.cpp new file mode 100644 index 00000000..4217808a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/UserManager.cpp @@ -0,0 +1,8082 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/UserManager.h" + +#include "td/telegram/AnimationsManager.h" +#include "td/telegram/AuthManager.h" +#include "td/telegram/Birthdate.hpp" +#include "td/telegram/BlockListId.h" +#include "td/telegram/BotMenuButton.h" +#include "td/telegram/BusinessAwayMessage.h" +#include "td/telegram/BusinessGreetingMessage.h" +#include "td/telegram/BusinessInfo.h" +#include "td/telegram/BusinessInfo.hpp" +#include "td/telegram/BusinessIntro.h" +#include "td/telegram/BusinessWorkHours.h" +#include "td/telegram/ChannelType.h" +#include "td/telegram/ChatManager.h" +#include "td/telegram/CommonDialogManager.h" +#include "td/telegram/ConfigManager.h" +#include "td/telegram/Dependencies.h" +#include "td/telegram/DialogLocation.h" +#include "td/telegram/DialogManager.h" +#include "td/telegram/DialogParticipantManager.h" +#include "td/telegram/Document.h" +#include "td/telegram/DocumentsManager.h" +#include "td/telegram/FileReferenceManager.h" +#include "td/telegram/files/FileManager.h" +#include "td/telegram/files/FileType.h" +#include "td/telegram/FolderId.h" +#include "td/telegram/Global.h" +#include "td/telegram/GroupCallManager.h" +#include "td/telegram/InlineQueriesManager.h" +#include "td/telegram/LinkManager.h" +#include "td/telegram/logevent/LogEvent.h" +#include "td/telegram/logevent/LogEventHelper.h" +#include "td/telegram/MessageId.h" +#include "td/telegram/MessagesManager.h" +#include "td/telegram/MessageTtl.h" +#include "td/telegram/misc.h" +#include "td/telegram/net/NetQuery.h" +#include "td/telegram/NotificationManager.h" +#include "td/telegram/OptionManager.h" +#include "td/telegram/PeerColor.h" +#include "td/telegram/PeopleNearbyManager.h" +#include "td/telegram/Photo.h" +#include "td/telegram/Photo.hpp" +#include "td/telegram/PhotoSize.h" +#include "td/telegram/PremiumGiftOption.hpp" +#include "td/telegram/ReactionListType.h" +#include "td/telegram/ReactionManager.h" +#include "td/telegram/SecretChatLayer.h" +#include "td/telegram/SecretChatsManager.h" +#include "td/telegram/ServerMessageId.h" +#include "td/telegram/StickerPhotoSize.h" +#include "td/telegram/StoryManager.h" +#include "td/telegram/SuggestedAction.h" +#include "td/telegram/Td.h" +#include "td/telegram/TdDb.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/ThemeManager.h" +#include "td/telegram/UpdatesManager.h" +#include "td/telegram/Version.h" + +#include "td/db/binlog/BinlogEvent.h" +#include "td/db/binlog/BinlogHelper.h" +#include "td/db/SqliteKeyValue.h" +#include "td/db/SqliteKeyValueAsync.h" + +#include "td/utils/algorithm.h" +#include "td/utils/buffer.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/Random.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" +#include "td/utils/utf8.h" + +#include +#include +#include +#include +#include +#include + +namespace td { + +class GetContactsQuery final : public Td::ResultHandler { + public: + void send(int64 hash) { + send_query(G()->net_query_creator().create(telegram_api::contacts_getContacts(hash))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for GetContactsQuery: " << to_string(ptr); + td_->user_manager_->on_get_contacts(std::move(ptr)); + } + + void on_error(Status status) final { + td_->user_manager_->on_get_contacts_failed(std::move(status)); + } +}; + +class GetContactsBirthdaysQuery final : public Td::ResultHandler { + public: + void send() { + send_query(G()->net_query_creator().create(telegram_api::contacts_getBirthdays())); + } + + 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 GetContactsBirthdaysQuery: " << to_string(ptr); + td_->user_manager_->on_get_contact_birthdates(std::move(ptr)); + } + + void on_error(Status status) final { + td_->user_manager_->on_get_contact_birthdates(nullptr); + } +}; + +class DismissContactBirthdaysSuggestionQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit DismissContactBirthdaysSuggestionQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::help_dismissSuggestion( + telegram_api::make_object(), "BIRTHDAY_CONTACTS_TODAY"))); + } + + 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 GetContactsStatusesQuery final : public Td::ResultHandler { + public: + void send() { + send_query(G()->net_query_creator().create(telegram_api::contacts_getStatuses())); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_get_contacts_statuses(result_ptr.move_as_ok()); + } + + void on_error(Status status) final { + if (!G()->is_expected_error(status)) { + LOG(ERROR) << "Receive error for GetContactsStatusesQuery: " << status; + } + } +}; + +class AddContactQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + + public: + explicit AddContactQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, telegram_api::object_ptr &&input_user, const Contact &contact, + bool share_phone_number) { + user_id_ = user_id; + int32 flags = 0; + if (share_phone_number) { + flags |= telegram_api::contacts_addContact::ADD_PHONE_PRIVACY_EXCEPTION_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::contacts_addContact(flags, false /*ignored*/, std::move(input_user), contact.get_first_name(), + contact.get_last_name(), contact.get_phone_number()), + {{DialogId(user_id)}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for AddContactQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + td_->user_manager_->reload_contacts(true); + td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AddContactQuery"); + } +}; + +class EditCloseFriendsQuery final : public Td::ResultHandler { + Promise promise_; + vector user_ids_; + + public: + explicit EditCloseFriendsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector user_ids) { + user_ids_ = std::move(user_ids); + send_query(G()->net_query_creator().create( + telegram_api::contacts_editCloseFriends(UserId::get_input_user_ids(user_ids_)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_set_close_friends(user_ids_, std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class ResolvePhoneQuery final : public Td::ResultHandler { + Promise promise_; + string phone_number_; + + public: + explicit ResolvePhoneQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &phone_number) { + phone_number_ = phone_number; + send_query(G()->net_query_creator().create(telegram_api::contacts_resolvePhone(phone_number))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for ResolvePhoneQuery: " << to_string(ptr); + td_->user_manager_->on_get_users(std::move(ptr->users_), "ResolvePhoneQuery"); + // on_get_chats(std::move(ptr->chats_), "ResolvePhoneQuery"); + + DialogId dialog_id(ptr->peer_); + if (dialog_id.get_type() != DialogType::User) { + LOG(ERROR) << "Receive " << dialog_id << " by " << phone_number_; + return on_error(Status::Error(500, "Receive invalid response")); + } + + td_->user_manager_->on_resolved_phone_number(phone_number_, dialog_id.get_user_id()); + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == Slice("PHONE_NOT_OCCUPIED")) { + td_->user_manager_->on_resolved_phone_number(phone_number_, UserId()); + return promise_.set_value(Unit()); + } + promise_.set_error(std::move(status)); + } +}; + +class AcceptContactQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + + public: + explicit AcceptContactQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, telegram_api::object_ptr &&input_user) { + user_id_ = user_id; + send_query(G()->net_query_creator().create(telegram_api::contacts_acceptContact(std::move(input_user)))); + } + + 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 AcceptContactQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + td_->user_manager_->reload_contacts(true); + td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AcceptContactQuery"); + } +}; + +class ImportContactsQuery final : public Td::ResultHandler { + int64 random_id_ = 0; + size_t sent_size_ = 0; + + public: + void send(vector> &&input_phone_contacts, int64 random_id) { + random_id_ = random_id; + sent_size_ = input_phone_contacts.size(); + send_query(G()->net_query_creator().create(telegram_api::contacts_importContacts(std::move(input_phone_contacts)))); + } + + 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 ImportContactsQuery: " << to_string(ptr); + if (sent_size_ == ptr->retry_contacts_.size()) { + return on_error(Status::Error(429, "Too Many Requests: retry after 3600")); + } + td_->user_manager_->on_imported_contacts(random_id_, std::move(ptr)); + } + + void on_error(Status status) final { + td_->user_manager_->on_imported_contacts(random_id_, std::move(status)); + } +}; + +class DeleteContactsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit DeleteContactsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector> &&input_users) { + send_query(G()->net_query_creator().create(telegram_api::contacts_deleteContacts(std::move(input_users)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for DeleteContactsQuery: " << to_string(ptr); + td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + td_->user_manager_->reload_contacts(true); + } +}; + +class DeleteContactsByPhoneNumberQuery final : public Td::ResultHandler { + Promise promise_; + vector user_ids_; + + public: + explicit DeleteContactsByPhoneNumberQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector &&user_phone_numbers, vector &&user_ids) { + if (user_phone_numbers.empty()) { + return promise_.set_value(Unit()); + } + user_ids_ = std::move(user_ids); + send_query(G()->net_query_creator().create(telegram_api::contacts_deleteByPhones(std::move(user_phone_numbers)))); + } + + 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(); + if (!result) { + return on_error(Status::Error(500, "Some contacts can't be deleted")); + } + + td_->user_manager_->on_deleted_contacts(user_ids_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + td_->user_manager_->reload_contacts(true); + } +}; + +class ResetContactsQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ResetContactsQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::contacts_resetSaved())); + } + + 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(); + if (!result) { + LOG(ERROR) << "Failed to delete imported contacts"; + td_->user_manager_->reload_contacts(true); + } else { + td_->user_manager_->on_update_contacts_reset(); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + td_->user_manager_->reload_contacts(true); + } +}; + +class UploadProfilePhotoQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + FileId file_id_; + bool is_fallback_; + bool only_suggest_; + + public: + explicit UploadProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, FileId file_id, telegram_api::object_ptr &&input_file, + bool is_fallback, bool only_suggest, bool is_animation, double main_frame_timestamp) { + CHECK(input_file != nullptr); + CHECK(file_id.is_valid()); + + user_id_ = user_id; + file_id_ = file_id; + is_fallback_ = is_fallback; + only_suggest_ = only_suggest; + + static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_MASK) == + static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_MASK), + ""); + static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK) == + static_cast(telegram_api::photos_uploadContactProfilePhoto::VIDEO_START_TS_MASK), + ""); + static_assert(static_cast(telegram_api::photos_uploadProfilePhoto::FILE_MASK) == + static_cast(telegram_api::photos_uploadContactProfilePhoto::FILE_MASK), + ""); + + int32 flags = 0; + telegram_api::object_ptr photo_input_file; + telegram_api::object_ptr video_input_file; + if (is_animation) { + flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_MASK; + video_input_file = std::move(input_file); + + if (main_frame_timestamp != 0.0) { + flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK; + } + } else { + flags |= telegram_api::photos_uploadProfilePhoto::FILE_MASK; + photo_input_file = std::move(input_file); + } + if (td_->user_manager_->is_user_bot(user_id)) { + auto r_input_user = td_->user_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), + std::move(photo_input_file), std::move(video_input_file), + main_frame_timestamp, nullptr), + {{user_id}})); + } else if (user_id == td_->user_manager_->get_my_id()) { + if (is_fallback) { + flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, std::move(photo_input_file), + std::move(video_input_file), main_frame_timestamp, nullptr), + {{"me"}})); + } else { + if (only_suggest) { + flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; + } else { + flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; + } + auto r_input_user = td_->user_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, + r_input_user.move_as_ok(), std::move(photo_input_file), + std::move(video_input_file), main_frame_timestamp, nullptr), + {{user_id}})); + } + } + + void send(UserId user_id, unique_ptr sticker_photo_size, bool is_fallback, bool only_suggest) { + CHECK(sticker_photo_size != nullptr); + user_id_ = user_id; + file_id_ = FileId(); + is_fallback_ = is_fallback; + only_suggest_ = only_suggest; + + if (td_->user_manager_->is_user_bot(user_id)) { + auto r_input_user = td_->user_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; + flags |= telegram_api::photos_uploadProfilePhoto::BOT_MASK; + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), nullptr, nullptr, + 0.0, sticker_photo_size->get_input_video_size_object(td_)), + {{user_id}})); + } else if (user_id == td_->user_manager_->get_my_id()) { + int32 flags = telegram_api::photos_uploadProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; + if (is_fallback) { + flags |= telegram_api::photos_uploadProfilePhoto::FALLBACK_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadProfilePhoto(flags, false /*ignored*/, nullptr, nullptr, nullptr, 0.0, + sticker_photo_size->get_input_video_size_object(td_)), + {{"me"}})); + } else { + int32 flags = telegram_api::photos_uploadContactProfilePhoto::VIDEO_EMOJI_MARKUP_MASK; + if (only_suggest) { + flags |= telegram_api::photos_uploadContactProfilePhoto::SUGGEST_MASK; + } else { + flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; + } + auto r_input_user = td_->user_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, + r_input_user.move_as_ok(), nullptr, nullptr, 0.0, + sticker_photo_size->get_input_video_size_object(td_)), + {{user_id}})); + } + } + + void on_result(BufferSlice packet) final { + static_assert(std::is_same::value, + ""); + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + if (!only_suggest_) { + td_->user_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, 0, std::move(promise_)); + } else { + promise_.set_value(Unit()); + } + + if (file_id_.is_valid()) { + td_->file_manager_->delete_partial_remote_location(file_id_); + } + } + + void on_error(Status status) final { + if (file_id_.is_valid()) { + td_->file_manager_->delete_partial_remote_location(file_id_); + } + promise_.set_error(std::move(status)); + } +}; + +class UpdateProfilePhotoQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + FileId file_id_; + int64 old_photo_id_; + bool is_fallback_; + string file_reference_; + + public: + explicit UpdateProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, + telegram_api::object_ptr &&input_photo) { + CHECK(input_photo != nullptr); + user_id_ = user_id; + file_id_ = file_id; + old_photo_id_ = old_photo_id; + is_fallback_ = is_fallback; + file_reference_ = FileManager::extract_file_reference(input_photo); + int32 flags = 0; + if (is_fallback) { + flags |= telegram_api::photos_updateProfilePhoto::FALLBACK_MASK; + } + if (td_->user_manager_->is_user_bot(user_id)) { + auto r_input_user = td_->user_manager_->get_input_user(user_id); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + flags |= telegram_api::photos_updateProfilePhoto::BOT_MASK; + send_query(G()->net_query_creator().create( + telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, r_input_user.move_as_ok(), + std::move(input_photo)), + {{user_id}})); + } else { + send_query(G()->net_query_creator().create( + telegram_api::photos_updateProfilePhoto(flags, false /*ignored*/, nullptr, std::move(input_photo)), + {{"me"}})); + } + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_set_profile_photo(user_id_, result_ptr.move_as_ok(), is_fallback_, old_photo_id_, + std::move(promise_)); + } + + void on_error(Status status) final { + if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) { + if (file_id_.is_valid()) { + VLOG(file_references) << "Receive " << status << " for " << file_id_; + td_->file_manager_->delete_file_reference(file_id_, file_reference_); + td_->file_reference_manager_->repair_file_reference( + file_id_, PromiseCreator::lambda([user_id = user_id_, file_id = file_id_, is_fallback = is_fallback_, + old_photo_id = old_photo_id_, + promise = std::move(promise_)](Result result) mutable { + if (result.is_error()) { + return promise.set_error(Status::Error(400, "Can't find the photo")); + } + + send_closure(G()->user_manager(), &UserManager::send_update_profile_photo_query, user_id, file_id, + old_photo_id, is_fallback, std::move(promise)); + })); + return; + } else { + LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_; + } + } + + promise_.set_error(std::move(status)); + } +}; + +class DeleteContactProfilePhotoQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + + public: + explicit DeleteContactProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, telegram_api::object_ptr &&input_user) { + CHECK(input_user != nullptr); + user_id_ = user_id; + + int32 flags = 0; + flags |= telegram_api::photos_uploadContactProfilePhoto::SAVE_MASK; + send_query(G()->net_query_creator().create( + telegram_api::photos_uploadContactProfilePhoto(flags, false /*ignored*/, false /*ignored*/, + std::move(input_user), nullptr, nullptr, 0, nullptr), + {{user_id}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + ptr->photo_ = nullptr; + td_->user_manager_->on_set_profile_photo(user_id_, std::move(ptr), false, 0, std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class DeleteProfilePhotoQuery final : public Td::ResultHandler { + Promise promise_; + int64 profile_photo_id_; + + public: + explicit DeleteProfilePhotoQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(int64 profile_photo_id) { + profile_photo_id_ = profile_photo_id; + vector> input_photo_ids; + input_photo_ids.push_back(telegram_api::make_object(profile_photo_id, 0, BufferSlice())); + send_query(G()->net_query_creator().create(telegram_api::photos_deletePhotos(std::move(input_photo_ids)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto result = result_ptr.move_as_ok(); + LOG(INFO) << "Receive result for DeleteProfilePhotoQuery: " << format::as_array(result); + if (result.size() != 1u) { + LOG(WARNING) << "Photo can't be deleted"; + return on_error(Status::Error(400, "Photo can't be deleted")); + } + + td_->user_manager_->on_delete_profile_photo(profile_photo_id_, std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateColorQuery final : public Td::ResultHandler { + Promise promise_; + bool for_profile_; + AccentColorId accent_color_id_; + CustomEmojiId background_custom_emoji_id_; + + public: + explicit UpdateColorQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(bool for_profile, AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id) { + for_profile_ = for_profile; + accent_color_id_ = accent_color_id; + background_custom_emoji_id_ = background_custom_emoji_id; + int32 flags = 0; + if (for_profile) { + flags |= telegram_api::account_updateColor::FOR_PROFILE_MASK; + } + if (accent_color_id.is_valid()) { + flags |= telegram_api::account_updateColor::COLOR_MASK; + } + if (background_custom_emoji_id.is_valid()) { + flags |= telegram_api::account_updateColor::BACKGROUND_EMOJI_ID_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::account_updateColor(flags, false /*ignored*/, accent_color_id.get(), + background_custom_emoji_id.get()), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateColorQuery: " << result_ptr.ok(); + td_->user_manager_->on_update_accent_color_success(for_profile_, accent_color_id_, background_custom_emoji_id_); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateProfileQuery final : public Td::ResultHandler { + Promise promise_; + int32 flags_; + string first_name_; + string last_name_; + string about_; + + public: + explicit UpdateProfileQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(int32 flags, const string &first_name, const string &last_name, const string &about) { + flags_ = flags; + first_name_ = first_name; + last_name_ = last_name; + about_ = about; + send_query(G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateProfileQuery: " << to_string(result_ptr.ok()); + td_->user_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateProfileQuery"); + td_->user_manager_->on_update_profile_success(flags_, first_name_, last_name_, about_); + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateUsernameQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdateUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const string &username) { + send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateUsernameQuery: " << to_string(result_ptr.ok()); + td_->user_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateUsernameQuery"); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) { + promise_.set_value(Unit()); + return; + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleUsernameQuery final : public Td::ResultHandler { + Promise promise_; + string username_; + bool is_active_; + + public: + explicit ToggleUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(string &&username, bool is_active) { + username_ = std::move(username); + is_active_ = is_active; + send_query(G()->net_query_creator().create(telegram_api::account_toggleUsername(username_, is_active_), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ToggleUsernameQuery: " << result; + td_->user_manager_->on_update_username_is_active(td_->user_manager_->get_my_id(), std::move(username_), is_active_, + std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED") { + td_->user_manager_->on_update_username_is_active(td_->user_manager_->get_my_id(), std::move(username_), + is_active_, std::move(promise_)); + return; + } + promise_.set_error(std::move(status)); + } +}; + +class ReorderUsernamesQuery final : public Td::ResultHandler { + Promise promise_; + vector usernames_; + + public: + explicit ReorderUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector &&usernames) { + usernames_ = usernames; + send_query(G()->net_query_creator().create(telegram_api::account_reorderUsernames(std::move(usernames)), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ReorderUsernamesQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Usernames weren't updated")); + } + + td_->user_manager_->on_update_active_usernames_order(td_->user_manager_->get_my_id(), std::move(usernames_), + std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED") { + td_->user_manager_->on_update_active_usernames_order(td_->user_manager_->get_my_id(), std::move(usernames_), + std::move(promise_)); + return; + } + promise_.set_error(std::move(status)); + } +}; + +class ToggleBotUsernameQuery final : public Td::ResultHandler { + Promise promise_; + UserId bot_user_id_; + string username_; + bool is_active_; + + public: + explicit ToggleBotUsernameQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId bot_user_id, string &&username, bool is_active) { + bot_user_id_ = bot_user_id; + username_ = std::move(username); + is_active_ = is_active; + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id_); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::bots_toggleUsername(r_input_user.move_as_ok(), username_, is_active_), {{bot_user_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ToggleBotUsernameQuery: " << result; + td_->user_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, + std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED") { + td_->user_manager_->on_update_username_is_active(bot_user_id_, std::move(username_), is_active_, + std::move(promise_)); + return; + } + promise_.set_error(std::move(status)); + } +}; + +class ReorderBotUsernamesQuery final : public Td::ResultHandler { + Promise promise_; + UserId bot_user_id_; + vector usernames_; + + public: + explicit ReorderBotUsernamesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId bot_user_id, vector &&usernames) { + bot_user_id_ = bot_user_id; + usernames_ = usernames; + auto r_input_user = td_->user_manager_->get_input_user(bot_user_id_); + if (r_input_user.is_error()) { + return on_error(r_input_user.move_as_error()); + } + send_query(G()->net_query_creator().create( + telegram_api::bots_reorderUsernames(r_input_user.move_as_ok(), std::move(usernames)), {{bot_user_id_}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + bool result = result_ptr.ok(); + LOG(DEBUG) << "Receive result for ReorderBotUsernamesQuery: " << result; + if (!result) { + return on_error(Status::Error(500, "Usernames weren't updated")); + } + + td_->user_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), std::move(promise_)); + } + + void on_error(Status status) final { + if (status.message() == "USERNAME_NOT_MODIFIED") { + td_->user_manager_->on_update_active_usernames_order(bot_user_id_, std::move(usernames_), std::move(promise_)); + return; + } + promise_.set_error(std::move(status)); + } +}; + +class UpdateBirthdayQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdateBirthdayQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const Birthdate &birthdate) { + int32 flags = 0; + if (!birthdate.is_empty()) { + flags |= telegram_api::account_updateBirthday::BIRTHDAY_MASK; + } + send_query(G()->net_query_creator().create( + telegram_api::account_updateBirthday(flags, birthdate.get_input_birthday()), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateBirthdayQuery: " << result_ptr.ok(); + if (result_ptr.ok()) { + promise_.set_value(Unit()); + } else { + promise_.set_error(Status::Error(400, "Failed to change birthdate")); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdatePersonalChannelQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdatePersonalChannelQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(ChannelId channel_id) { + telegram_api::object_ptr input_channel; + if (channel_id == ChannelId()) { + input_channel = telegram_api::make_object(); + } else { + input_channel = td_->chat_manager_->get_input_channel(channel_id); + CHECK(input_channel != nullptr); + } + send_query(G()->net_query_creator().create(telegram_api::account_updatePersonalChannel(std::move(input_channel)), + {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdatePersonalChannelQuery: " << result_ptr.ok(); + if (result_ptr.ok()) { + promise_.set_value(Unit()); + } else { + promise_.set_error(Status::Error(400, "Failed to change personal chat")); + } + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class UpdateEmojiStatusQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit UpdateEmojiStatusQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(const EmojiStatus &emoji_status) { + send_query(G()->net_query_creator().create( + telegram_api::account_updateEmojiStatus(emoji_status.get_input_emoji_status()), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for UpdateEmojiStatusQuery: " << result_ptr.ok(); + if (result_ptr.ok()) { + promise_.set_value(Unit()); + } else { + promise_.set_error(Status::Error(400, "Failed to change Premium badge")); + } + } + + void on_error(Status status) final { + get_recent_emoji_statuses(td_, Auto()); + promise_.set_error(std::move(status)); + } +}; + +class ToggleSponsoredMessagesQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit ToggleSponsoredMessagesQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(bool sponsored_enabled) { + send_query( + G()->net_query_creator().create(telegram_api::account_toggleSponsoredMessages(sponsored_enabled), {{"me"}})); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + LOG(DEBUG) << "Receive result for ToggleSponsoredMessagesQuery: " << result_ptr.ok(); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetUsersQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetUsersQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector> &&input_users) { + send_query(G()->net_query_creator().create(telegram_api::users_getUsers(std::move(input_users)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_get_users(result_ptr.move_as_ok(), "GetUsersQuery"); + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetFullUserQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetFullUserQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(telegram_api::object_ptr &&input_user) { + send_query(G()->net_query_creator().create(telegram_api::users_getFullUser(std::move(input_user)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + LOG(DEBUG) << "Receive result for GetFullUserQuery: " << to_string(ptr); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetFullUserQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetFullUserQuery"); + td_->user_manager_->on_get_user_full(std::move(ptr->full_user_)); + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetUserPhotosQuery final : public Td::ResultHandler { + Promise promise_; + UserId user_id_; + int32 offset_; + int32 limit_; + + public: + explicit GetUserPhotosQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(UserId user_id, telegram_api::object_ptr &&input_user, int32 offset, int32 limit, + int64 photo_id) { + user_id_ = user_id; + offset_ = offset; + limit_ = limit; + send_query(G()->net_query_creator().create( + telegram_api::photos_getUserPhotos(std::move(input_user), offset, photo_id, limit))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + auto ptr = result_ptr.move_as_ok(); + + LOG(INFO) << "Receive result for GetUserPhotosQuery: " << to_string(ptr); + int32 constructor_id = ptr->get_id(); + if (constructor_id == telegram_api::photos_photos::ID) { + auto photos = move_tl_object_as(ptr); + + td_->user_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery"); + auto photos_size = narrow_cast(photos->photos_.size()); + td_->user_manager_->on_get_user_photos(user_id_, offset_, limit_, photos_size, std::move(photos->photos_)); + } else { + CHECK(constructor_id == telegram_api::photos_photosSlice::ID); + auto photos = move_tl_object_as(ptr); + + td_->user_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery slice"); + td_->user_manager_->on_get_user_photos(user_id_, offset_, limit_, photos->count_, std::move(photos->photos_)); + } + + promise_.set_value(Unit()); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetSupportUserQuery final : public Td::ResultHandler { + Promise promise_; + + public: + explicit GetSupportUserQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send() { + send_query(G()->net_query_creator().create(telegram_api::help_getSupport())); + } + + 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 GetSupportUserQuery: " << to_string(ptr); + + auto user_id = UserManager::get_user_id(ptr->user_); + td_->user_manager_->on_get_user(std::move(ptr->user_), "GetSupportUserQuery"); + + promise_.set_value(std::move(user_id)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +class GetIsPremiumRequiredToContactQuery final : public Td::ResultHandler { + Promise promise_; + vector user_ids_; + + public: + explicit GetIsPremiumRequiredToContactQuery(Promise &&promise) : promise_(std::move(promise)) { + } + + void send(vector &&user_ids, vector> &&input_users) { + user_ids_ = std::move(user_ids); + send_query( + G()->net_query_creator().create(telegram_api::users_getIsPremiumRequiredToContact(std::move(input_users)))); + } + + void on_result(BufferSlice packet) final { + auto result_ptr = fetch_result(packet); + if (result_ptr.is_error()) { + return on_error(result_ptr.move_as_error()); + } + + td_->user_manager_->on_get_is_premium_required_to_contact_users(std::move(user_ids_), result_ptr.move_as_ok(), + std::move(promise_)); + } + + void on_error(Status status) final { + promise_.set_error(std::move(status)); + } +}; + +template +void UserManager::User::store(StorerT &storer) const { + using td::store; + bool has_last_name = !last_name.empty(); + bool legacy_has_username = false; + bool has_photo = photo.small_file_id.is_valid(); + bool has_language_code = !language_code.empty(); + bool have_access_hash = access_hash != -1; + bool has_cache_version = cache_version != 0; + bool has_is_contact = true; + bool has_restriction_reasons = !restriction_reasons.empty(); + bool has_emoji_status = !emoji_status.is_empty(); + bool has_usernames = !usernames.is_empty(); + bool has_flags2 = true; + bool has_max_active_story_id = max_active_story_id.is_valid(); + bool has_max_read_story_id = max_read_story_id.is_valid(); + bool has_max_active_story_id_next_reload_time = max_active_story_id_next_reload_time > Time::now(); + bool has_accent_color_id = accent_color_id.is_valid(); + bool has_background_custom_emoji_id = background_custom_emoji_id.is_valid(); + bool has_profile_accent_color_id = profile_accent_color_id.is_valid(); + bool has_profile_background_custom_emoji_id = profile_background_custom_emoji_id.is_valid(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_received); + STORE_FLAG(is_verified); + STORE_FLAG(is_deleted); + STORE_FLAG(is_bot); + STORE_FLAG(can_join_groups); + STORE_FLAG(can_read_all_group_messages); + STORE_FLAG(is_inline_bot); + STORE_FLAG(need_location_bot); + STORE_FLAG(has_last_name); + STORE_FLAG(legacy_has_username); + STORE_FLAG(has_photo); + STORE_FLAG(false); // legacy is_restricted + STORE_FLAG(has_language_code); + STORE_FLAG(have_access_hash); + STORE_FLAG(is_support); + STORE_FLAG(is_min_access_hash); + STORE_FLAG(is_scam); + STORE_FLAG(has_cache_version); + STORE_FLAG(has_is_contact); + STORE_FLAG(is_contact); + STORE_FLAG(is_mutual_contact); + STORE_FLAG(has_restriction_reasons); + STORE_FLAG(need_apply_min_photo); + STORE_FLAG(is_fake); + STORE_FLAG(can_be_added_to_attach_menu); + STORE_FLAG(is_premium); + STORE_FLAG(attach_menu_enabled); + STORE_FLAG(has_emoji_status); + STORE_FLAG(has_usernames); + STORE_FLAG(can_be_edited_bot); + END_STORE_FLAGS(); + if (has_flags2) { + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_close_friend); + STORE_FLAG(stories_hidden); + STORE_FLAG(false); + STORE_FLAG(has_max_active_story_id); + STORE_FLAG(has_max_read_story_id); + STORE_FLAG(has_max_active_story_id_next_reload_time); + STORE_FLAG(has_accent_color_id); + STORE_FLAG(has_background_custom_emoji_id); + STORE_FLAG(has_profile_accent_color_id); + STORE_FLAG(has_profile_background_custom_emoji_id); + STORE_FLAG(contact_require_premium); + STORE_FLAG(is_business_bot); + END_STORE_FLAGS(); + } + store(first_name, storer); + if (has_last_name) { + store(last_name, storer); + } + store(phone_number, storer); + if (have_access_hash) { + store(access_hash, storer); + } + if (has_photo) { + store(photo, storer); + } + store(was_online, storer); + if (has_restriction_reasons) { + store(restriction_reasons, storer); + } + if (is_inline_bot) { + store(inline_query_placeholder, storer); + } + if (is_bot) { + store(bot_info_version, storer); + } + if (has_language_code) { + store(language_code, storer); + } + if (has_cache_version) { + store(cache_version, storer); + } + if (has_emoji_status) { + store(emoji_status, storer); + } + if (has_usernames) { + store(usernames, storer); + } + if (has_max_active_story_id) { + store(max_active_story_id, storer); + } + if (has_max_read_story_id) { + store(max_read_story_id, storer); + } + if (has_max_active_story_id_next_reload_time) { + store_time(max_active_story_id_next_reload_time, storer); + } + if (has_accent_color_id) { + store(accent_color_id, storer); + } + if (has_background_custom_emoji_id) { + store(background_custom_emoji_id, storer); + } + if (has_profile_accent_color_id) { + store(profile_accent_color_id, storer); + } + if (has_profile_background_custom_emoji_id) { + store(profile_background_custom_emoji_id, storer); + } +} + +template +void UserManager::User::parse(ParserT &parser) { + using td::parse; + bool has_last_name; + bool legacy_has_username; + bool has_photo; + bool legacy_is_restricted; + bool has_language_code; + bool have_access_hash; + bool has_cache_version; + bool has_is_contact; + bool has_restriction_reasons; + bool has_emoji_status; + bool has_usernames; + bool has_flags2 = parser.version() >= static_cast(Version::AddUserFlags2); + bool legacy_has_stories = false; + bool has_max_active_story_id = false; + bool has_max_read_story_id = false; + bool has_max_active_story_id_next_reload_time = false; + bool has_accent_color_id = false; + bool has_background_custom_emoji_id = false; + bool has_profile_accent_color_id = false; + bool has_profile_background_custom_emoji_id = false; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_received); + PARSE_FLAG(is_verified); + PARSE_FLAG(is_deleted); + PARSE_FLAG(is_bot); + PARSE_FLAG(can_join_groups); + PARSE_FLAG(can_read_all_group_messages); + PARSE_FLAG(is_inline_bot); + PARSE_FLAG(need_location_bot); + PARSE_FLAG(has_last_name); + PARSE_FLAG(legacy_has_username); + PARSE_FLAG(has_photo); + PARSE_FLAG(legacy_is_restricted); + PARSE_FLAG(has_language_code); + PARSE_FLAG(have_access_hash); + PARSE_FLAG(is_support); + PARSE_FLAG(is_min_access_hash); + PARSE_FLAG(is_scam); + PARSE_FLAG(has_cache_version); + PARSE_FLAG(has_is_contact); + PARSE_FLAG(is_contact); + PARSE_FLAG(is_mutual_contact); + PARSE_FLAG(has_restriction_reasons); + PARSE_FLAG(need_apply_min_photo); + PARSE_FLAG(is_fake); + PARSE_FLAG(can_be_added_to_attach_menu); + PARSE_FLAG(is_premium); + PARSE_FLAG(attach_menu_enabled); + PARSE_FLAG(has_emoji_status); + PARSE_FLAG(has_usernames); + PARSE_FLAG(can_be_edited_bot); + END_PARSE_FLAGS(); + if (has_flags2) { + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_close_friend); + PARSE_FLAG(stories_hidden); + PARSE_FLAG(legacy_has_stories); + PARSE_FLAG(has_max_active_story_id); + PARSE_FLAG(has_max_read_story_id); + PARSE_FLAG(has_max_active_story_id_next_reload_time); + PARSE_FLAG(has_accent_color_id); + PARSE_FLAG(has_background_custom_emoji_id); + PARSE_FLAG(has_profile_accent_color_id); + PARSE_FLAG(has_profile_background_custom_emoji_id); + PARSE_FLAG(contact_require_premium); + PARSE_FLAG(is_business_bot); + END_PARSE_FLAGS(); + } + parse(first_name, parser); + if (has_last_name) { + parse(last_name, parser); + } + if (legacy_has_username) { + CHECK(!has_usernames); + string username; + parse(username, parser); + usernames = Usernames(std::move(username), vector>()); + } + parse(phone_number, parser); + if (parser.version() < static_cast(Version::FixMinUsers)) { + have_access_hash = is_received; + } + if (have_access_hash) { + parse(access_hash, parser); + } else { + is_min_access_hash = true; + } + if (has_photo) { + parse(photo, parser); + } + if (!has_is_contact) { + // enum class LinkState : uint8 { Unknown, None, KnowsPhoneNumber, Contact }; + + uint32 link_state_inbound; + uint32 link_state_outbound; + parse(link_state_inbound, parser); + parse(link_state_outbound, parser); + + is_contact = link_state_outbound == 3; + is_mutual_contact = is_contact && link_state_inbound == 3; + is_close_friend = false; + } + parse(was_online, parser); + if (legacy_is_restricted) { + string restriction_reason; + parse(restriction_reason, parser); + restriction_reasons = get_restriction_reasons(restriction_reason); + } else if (has_restriction_reasons) { + parse(restriction_reasons, parser); + } + if (is_inline_bot) { + parse(inline_query_placeholder, parser); + } + if (is_bot) { + parse(bot_info_version, parser); + } + if (has_language_code) { + parse(language_code, parser); + } + if (has_cache_version) { + parse(cache_version, parser); + } + if (has_emoji_status) { + parse(emoji_status, parser); + } + if (has_usernames) { + CHECK(!legacy_has_username); + parse(usernames, parser); + } + if (has_max_active_story_id) { + parse(max_active_story_id, parser); + } + if (has_max_read_story_id) { + parse(max_read_story_id, parser); + } + if (has_max_active_story_id_next_reload_time) { + parse_time(max_active_story_id_next_reload_time, parser); + } + if (has_accent_color_id) { + parse(accent_color_id, parser); + } + if (has_background_custom_emoji_id) { + parse(background_custom_emoji_id, parser); + } + if (has_profile_accent_color_id) { + parse(profile_accent_color_id, parser); + } + if (has_profile_background_custom_emoji_id) { + parse(profile_background_custom_emoji_id, parser); + } + + if (!check_utf8(first_name)) { + LOG(ERROR) << "Have invalid first name \"" << first_name << '"'; + first_name.clear(); + cache_version = 0; + } + if (!check_utf8(last_name)) { + LOG(ERROR) << "Have invalid last name \"" << last_name << '"'; + last_name.clear(); + cache_version = 0; + } + + clean_phone_number(phone_number); + if (first_name.empty() && last_name.empty()) { + first_name = phone_number; + } + if (!is_contact && is_mutual_contact) { + LOG(ERROR) << "Have invalid flag is_mutual_contact"; + is_mutual_contact = false; + cache_version = 0; + } + if (!is_contact && is_close_friend) { + LOG(ERROR) << "Have invalid flag is_close_friend"; + is_close_friend = false; + cache_version = 0; + } +} + +template +void UserManager::UserFull::store(StorerT &storer) const { + using td::store; + bool has_about = !about.empty(); + bool has_photo = !photo.is_empty(); + bool has_description = !description.empty(); + bool has_commands = !commands.empty(); + bool has_private_forward_name = !private_forward_name.empty(); + bool has_group_administrator_rights = group_administrator_rights != AdministratorRights(); + bool has_broadcast_administrator_rights = broadcast_administrator_rights != AdministratorRights(); + bool has_menu_button = menu_button != nullptr; + bool has_description_photo = !description_photo.is_empty(); + bool has_description_animation = description_animation_file_id.is_valid(); + bool has_premium_gift_options = !premium_gift_options.empty(); + bool has_personal_photo = !personal_photo.is_empty(); + bool has_fallback_photo = !fallback_photo.is_empty(); + bool has_business_info = business_info != nullptr && !business_info->is_empty(); + bool has_birthdate = !birthdate.is_empty(); + bool has_personal_channel_id = personal_channel_id.is_valid(); + bool has_flags2 = true; + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_about); + STORE_FLAG(is_blocked); + STORE_FLAG(can_be_called); + STORE_FLAG(has_private_calls); + STORE_FLAG(can_pin_messages); + STORE_FLAG(need_phone_number_privacy_exception); + STORE_FLAG(has_photo); + STORE_FLAG(supports_video_calls); + STORE_FLAG(has_description); + STORE_FLAG(has_commands); + STORE_FLAG(has_private_forward_name); + STORE_FLAG(has_group_administrator_rights); + STORE_FLAG(has_broadcast_administrator_rights); + STORE_FLAG(has_menu_button); + STORE_FLAG(has_description_photo); + STORE_FLAG(has_description_animation); + STORE_FLAG(has_premium_gift_options); + STORE_FLAG(voice_messages_forbidden); + STORE_FLAG(has_personal_photo); + STORE_FLAG(has_fallback_photo); + STORE_FLAG(has_pinned_stories); + STORE_FLAG(is_blocked_for_stories); + STORE_FLAG(wallpaper_overridden); + STORE_FLAG(read_dates_private); + STORE_FLAG(contact_require_premium); + STORE_FLAG(has_business_info); + STORE_FLAG(has_birthdate); + STORE_FLAG(has_personal_channel_id); + STORE_FLAG(sponsored_enabled); + STORE_FLAG(has_flags2); + END_STORE_FLAGS(); + if (has_flags2) { + BEGIN_STORE_FLAGS(); + END_STORE_FLAGS(); + } + if (has_about) { + store(about, storer); + } + store(common_chat_count, storer); + store_time(expires_at, storer); + if (has_photo) { + store(photo, storer); + } + if (has_description) { + store(description, storer); + } + if (has_commands) { + store(commands, storer); + } + if (has_private_forward_name) { + store(private_forward_name, storer); + } + if (has_group_administrator_rights) { + store(group_administrator_rights, storer); + } + if (has_broadcast_administrator_rights) { + store(broadcast_administrator_rights, storer); + } + if (has_menu_button) { + store(menu_button, storer); + } + if (has_description_photo) { + store(description_photo, storer); + } + if (has_description_animation) { + storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(description_animation_file_id, + storer); + } + if (has_premium_gift_options) { + store(premium_gift_options, storer); + } + if (has_personal_photo) { + store(personal_photo, storer); + } + if (has_fallback_photo) { + store(fallback_photo, storer); + } + if (has_business_info) { + store(business_info, storer); + } + if (has_birthdate) { + store(birthdate, storer); + } + if (has_personal_channel_id) { + store(personal_channel_id, storer); + } +} + +template +void UserManager::UserFull::parse(ParserT &parser) { + using td::parse; + bool has_about; + bool has_photo; + bool has_description; + bool has_commands; + bool has_private_forward_name; + bool has_group_administrator_rights; + bool has_broadcast_administrator_rights; + bool has_menu_button; + bool has_description_photo; + bool has_description_animation; + bool has_premium_gift_options; + bool has_personal_photo; + bool has_fallback_photo; + bool has_business_info; + bool has_birthdate; + bool has_personal_channel_id; + bool has_flags2; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_about); + PARSE_FLAG(is_blocked); + PARSE_FLAG(can_be_called); + PARSE_FLAG(has_private_calls); + PARSE_FLAG(can_pin_messages); + PARSE_FLAG(need_phone_number_privacy_exception); + PARSE_FLAG(has_photo); + PARSE_FLAG(supports_video_calls); + PARSE_FLAG(has_description); + PARSE_FLAG(has_commands); + PARSE_FLAG(has_private_forward_name); + PARSE_FLAG(has_group_administrator_rights); + PARSE_FLAG(has_broadcast_administrator_rights); + PARSE_FLAG(has_menu_button); + PARSE_FLAG(has_description_photo); + PARSE_FLAG(has_description_animation); + PARSE_FLAG(has_premium_gift_options); + PARSE_FLAG(voice_messages_forbidden); + PARSE_FLAG(has_personal_photo); + PARSE_FLAG(has_fallback_photo); + PARSE_FLAG(has_pinned_stories); + PARSE_FLAG(is_blocked_for_stories); + PARSE_FLAG(wallpaper_overridden); + PARSE_FLAG(read_dates_private); + PARSE_FLAG(contact_require_premium); + PARSE_FLAG(has_business_info); + PARSE_FLAG(has_birthdate); + PARSE_FLAG(has_personal_channel_id); + PARSE_FLAG(sponsored_enabled); + PARSE_FLAG(has_flags2); + END_PARSE_FLAGS(); + if (has_flags2) { + BEGIN_PARSE_FLAGS(); + END_PARSE_FLAGS(); + } + if (has_about) { + parse(about, parser); + } + parse(common_chat_count, parser); + parse_time(expires_at, parser); + if (has_photo) { + parse(photo, parser); + } + if (has_description) { + parse(description, parser); + } + if (has_commands) { + parse(commands, parser); + } + if (has_private_forward_name) { + parse(private_forward_name, parser); + } + if (has_group_administrator_rights) { + parse(group_administrator_rights, parser); + } + if (has_broadcast_administrator_rights) { + parse(broadcast_administrator_rights, parser); + } + if (has_menu_button) { + parse(menu_button, parser); + } + if (has_description_photo) { + parse(description_photo, parser); + } + if (has_description_animation) { + description_animation_file_id = + parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser); + } + if (has_premium_gift_options) { + parse(premium_gift_options, parser); + } + if (has_personal_photo) { + parse(personal_photo, parser); + } + if (has_fallback_photo) { + parse(fallback_photo, parser); + } + if (has_business_info) { + parse(business_info, parser); + } + if (has_birthdate) { + parse(birthdate, parser); + } + if (has_personal_channel_id) { + parse(personal_channel_id, parser); + } +} + +template +void UserManager::SecretChat::store(StorerT &storer) const { + using td::store; + bool has_layer = layer > static_cast(SecretChatLayer::Default); + bool has_initial_folder_id = initial_folder_id != FolderId(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(is_outbound); + STORE_FLAG(has_layer); + STORE_FLAG(has_initial_folder_id); + END_STORE_FLAGS(); + + store(access_hash, storer); + store(user_id, storer); + store(state, storer); + store(ttl, storer); + store(date, storer); + store(key_hash, storer); + if (has_layer) { + store(layer, storer); + } + if (has_initial_folder_id) { + store(initial_folder_id, storer); + } +} + +template +void UserManager::SecretChat::parse(ParserT &parser) { + using td::parse; + bool has_layer; + bool has_initial_folder_id; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(is_outbound); + PARSE_FLAG(has_layer); + PARSE_FLAG(has_initial_folder_id); + END_PARSE_FLAGS(); + + if (parser.version() >= static_cast(Version::AddAccessHashToSecretChat)) { + parse(access_hash, parser); + } + parse(user_id, parser); + parse(state, parser); + parse(ttl, parser); + parse(date, parser); + if (parser.version() >= static_cast(Version::AddKeyHashToSecretChat)) { + parse(key_hash, parser); + } + if (has_layer) { + parse(layer, parser); + } else { + layer = static_cast(SecretChatLayer::Default); + } + if (has_initial_folder_id) { + parse(initial_folder_id, parser); + } +} + +class UserManager::UserLogEvent { + public: + UserId user_id; + const User *u_in = nullptr; + unique_ptr u_out; + + UserLogEvent() = default; + + UserLogEvent(UserId user_id, const User *u) : user_id(user_id), u_in(u) { + } + + template + void store(StorerT &storer) const { + td::store(user_id, storer); + td::store(*u_in, storer); + } + + template + void parse(ParserT &parser) { + td::parse(user_id, parser); + td::parse(u_out, parser); + } +}; + +class UserManager::SecretChatLogEvent { + public: + SecretChatId secret_chat_id; + const SecretChat *c_in = nullptr; + unique_ptr c_out; + + SecretChatLogEvent() = default; + + SecretChatLogEvent(SecretChatId secret_chat_id, const SecretChat *c) : secret_chat_id(secret_chat_id), c_in(c) { + } + + template + void store(StorerT &storer) const { + td::store(secret_chat_id, storer); + td::store(*c_in, storer); + } + + template + void parse(ParserT &parser) { + td::parse(secret_chat_id, parser); + td::parse(c_out, parser); + } +}; + +class UserManager::UploadProfilePhotoCallback final : public FileManager::UploadCallback { + public: + void on_upload_ok(FileId file_id, telegram_api::object_ptr input_file) final { + send_closure_later(G()->user_manager(), &UserManager::on_upload_profile_photo, file_id, std::move(input_file)); + } + void on_upload_encrypted_ok(FileId file_id, + telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, telegram_api::object_ptr input_file) final { + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) final { + send_closure_later(G()->user_manager(), &UserManager::on_upload_profile_photo_error, file_id, std::move(error)); + } +}; + +UserManager::UserManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { + upload_profile_photo_callback_ = std::make_shared(); + + my_id_ = load_my_id(); + + if (G()->use_chat_info_database()) { + auto next_contacts_sync_date_string = G()->td_db()->get_binlog_pmc()->get("next_contacts_sync_date"); + if (!next_contacts_sync_date_string.empty()) { + next_contacts_sync_date_ = min(to_integer(next_contacts_sync_date_string), G()->unix_time() + 100000); + } + + auto saved_contact_count_string = G()->td_db()->get_binlog_pmc()->get("saved_contact_count"); + if (!saved_contact_count_string.empty()) { + saved_contact_count_ = to_integer(saved_contact_count_string); + } + } else if (!td_->auth_manager_->is_bot()) { + G()->td_db()->get_binlog_pmc()->erase("next_contacts_sync_date"); + G()->td_db()->get_binlog_pmc()->erase("saved_contact_count"); + } + if (G()->use_sqlite_pmc()) { + G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto()); + } + + if (!td_->auth_manager_->is_bot()) { + 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()) { + was_online_local_ = unix_time - 1; + } + } + + user_online_timeout_.set_callback(on_user_online_timeout_callback); + user_online_timeout_.set_callback_data(static_cast(this)); + + user_emoji_status_timeout_.set_callback(on_user_emoji_status_timeout_callback); + user_emoji_status_timeout_.set_callback_data(static_cast(this)); + + get_user_queries_.set_merge_function([this](vector query_ids, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + auto input_users = transform(query_ids, [this](int64 query_id) { return get_input_user_force(UserId(query_id)); }); + td_->create_handler(std::move(promise))->send(std::move(input_users)); + }); + get_is_premium_required_to_contact_queries_.set_merge_function( + [this](vector query_ids, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + auto user_ids = UserId::get_user_ids(query_ids); + auto input_users = transform(user_ids, [this](UserId user_id) { return get_input_user_force(user_id); }); + td_->create_handler(std::move(promise)) + ->send(std::move(user_ids), std::move(input_users)); + }); +} + +UserManager::~UserManager() { + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), users_, users_full_, user_photos_, + unknown_users_, pending_user_photos_, user_profile_photo_file_source_ids_, + my_photo_file_id_, user_full_file_source_ids_, secret_chats_, + unknown_secret_chats_, secret_chats_with_user_); + Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), loaded_from_database_users_, + unavailable_user_fulls_, loaded_from_database_secret_chats_, + resolved_phone_numbers_, all_imported_contacts_, restricted_user_ids_); +} + +void UserManager::tear_down() { + parent_.reset(); + + LOG(DEBUG) << "Have " << users_.calc_size() << " users and " << secret_chats_.calc_size() << " secret chats to free"; + LOG(DEBUG) << "Have " << users_full_.calc_size() << " full users to free"; +} + +void UserManager::on_user_online_timeout_callback(void *user_manager_ptr, int64 user_id_long) { + if (G()->close_flag()) { + return; + } + + auto user_manager = static_cast(user_manager_ptr); + send_closure_later(user_manager->actor_id(user_manager), &UserManager::on_user_online_timeout, UserId(user_id_long)); +} + +void UserManager::on_user_online_timeout(UserId user_id) { + if (G()->close_flag()) { + return; + } + + auto u = get_user(user_id); + CHECK(u != nullptr); + CHECK(u->is_update_user_sent); + + LOG(INFO) << "Update " << user_id << " online status to offline"; + send_closure(G()->td(), &Td::send_update, + td_api::make_object(user_id.get(), + get_user_status_object(user_id, u, G()->unix_time()))); + + td_->dialog_participant_manager_->update_user_online_member_count(user_id); +} + +void UserManager::on_user_emoji_status_timeout_callback(void *user_manager_ptr, int64 user_id_long) { + if (G()->close_flag()) { + return; + } + + auto user_manager = static_cast(user_manager_ptr); + send_closure_later(user_manager->actor_id(user_manager), &UserManager::on_user_emoji_status_timeout, + UserId(user_id_long)); +} + +void UserManager::on_user_emoji_status_timeout(UserId user_id) { + if (G()->close_flag()) { + return; + } + + auto u = get_user(user_id); + CHECK(u != nullptr); + CHECK(u->is_update_user_sent); + + update_user(u, user_id); +} + +UserId UserManager::get_user_id(const telegram_api::object_ptr &user) { + CHECK(user != nullptr); + switch (user->get_id()) { + case telegram_api::userEmpty::ID: + return UserId(static_cast(user.get())->id_); + case telegram_api::user::ID: + return UserId(static_cast(user.get())->id_); + default: + UNREACHABLE(); + return UserId(); + } +} + +UserId UserManager::load_my_id() { + auto id_string = G()->td_db()->get_binlog_pmc()->get("my_id"); + if (!id_string.empty()) { + UserId my_id(to_integer(id_string)); + if (my_id.is_valid()) { + return my_id; + } + + my_id = UserId(to_integer(Slice(id_string).substr(5))); + if (my_id.is_valid()) { + G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); + return my_id; + } + + LOG(ERROR) << "Wrong my ID = \"" << id_string << "\" stored in database"; + } + return UserId(); +} + +UserId UserManager::get_my_id() const { + LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my ID returned"; + return my_id_; +} + +void UserManager::set_my_id(UserId my_id) { + UserId my_old_id = my_id_; + if (my_old_id.is_valid() && my_old_id != my_id) { + LOG(ERROR) << "Already know that me is " << my_old_id << " but received userSelf with " << my_id; + return; + } + if (!my_id.is_valid()) { + LOG(ERROR) << "Receive invalid my ID " << my_id; + return; + } + if (my_old_id != my_id) { + my_id_ = my_id; + G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get())); + td_->option_manager_->set_option_integer("my_id", my_id_.get()); + if (!td_->auth_manager_->is_bot()) { + G()->td_db()->get_binlog_pmc()->force_sync(Promise(), "set_my_id"); + } + } +} + +UserId UserManager::get_service_notifications_user_id() { + return UserId(static_cast(777000)); +} + +UserId UserManager::add_service_notifications_user() { + auto user_id = get_service_notifications_user_id(); + if (!have_user_force(user_id, "add_service_notifications_user")) { + LOG(FATAL) << "Failed to load service notification user"; + } + return user_id; +} + +UserId UserManager::get_replies_bot_user_id() { + return UserId(static_cast(G()->is_test_dc() ? 708513 : 1271266957)); +} + +UserId UserManager::get_anonymous_bot_user_id() { + return UserId(static_cast(G()->is_test_dc() ? 552888 : 1087968824)); +} + +UserId UserManager::get_channel_bot_user_id() { + return UserId(static_cast(G()->is_test_dc() ? 936174 : 136817688)); +} + +UserId UserManager::get_anti_spam_bot_user_id() { + return UserId(static_cast(G()->is_test_dc() ? 2200583762ll : 5434988373ll)); +} + +UserId UserManager::add_anonymous_bot_user() { + auto user_id = get_anonymous_bot_user_id(); + if (!have_user_force(user_id, "add_anonymous_bot_user")) { + LOG(FATAL) << "Failed to load anonymous bot user"; + } + return user_id; +} + +UserId UserManager::add_channel_bot_user() { + auto user_id = get_channel_bot_user_id(); + if (!have_user_force(user_id, "add_channel_bot_user")) { + LOG(FATAL) << "Failed to load channel bot user"; + } + return user_id; +} + +UserManager::MyOnlineStatusInfo UserManager::get_my_online_status() const { + MyOnlineStatusInfo status_info; + status_info.is_online_local = td_->is_online(); + status_info.is_online_remote = was_online_remote_ > G()->unix_time(); + status_info.was_online_local = was_online_local_; + status_info.was_online_remote = was_online_remote_; + + return status_info; +} + +void UserManager::set_my_online_status(bool is_online, bool send_update, bool is_local) { + if (td_->auth_manager_->is_bot()) { + return; // just in case + } + + auto my_id = get_my_id(); + User *u = get_user_force(my_id, "set_my_online_status"); + if (u != nullptr) { + int32 new_online; + int32 unix_time = G()->unix_time(); + if (is_online) { + new_online = unix_time + 300; + } else { + new_online = unix_time - 1; + } + + auto old_was_online = get_user_was_online(u, my_id, unix_time); + if (is_local) { + LOG(INFO) << "Update my local online from " << my_was_online_local_ << " to " << new_online; + if (!is_online) { + new_online = min(new_online, u->was_online); + } + if (new_online != my_was_online_local_) { + my_was_online_local_ = new_online; + } + } else { + if (my_was_online_local_ != 0 || new_online != u->was_online) { + LOG(INFO) << "Update my online from " << u->was_online << " to " << new_online; + my_was_online_local_ = 0; + u->was_online = new_online; + u->need_save_to_database = true; + } + } + if (old_was_online != get_user_was_online(u, my_id, unix_time)) { + u->is_status_changed = true; + u->is_online_status_changed = true; + } + + if (was_online_local_ != new_online) { + was_online_local_ = new_online; + VLOG(notifications) << "Set was_online_local to " << was_online_local_; + G()->td_db()->get_binlog_pmc()->set("my_was_online_local", to_string(was_online_local_)); + } + + if (send_update) { + update_user(u, my_id); + } + } +} + +void UserManager::on_get_user(telegram_api::object_ptr &&user_ptr, const char *source) { + LOG(DEBUG) << "Receive from " << source << ' ' << to_string(user_ptr); + int32 constructor_id = user_ptr->get_id(); + if (constructor_id == telegram_api::userEmpty::ID) { + auto user = move_tl_object_as(user_ptr); + UserId user_id(user->id_); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id << " from " << source; + return; + } + LOG(INFO) << "Receive empty " << user_id << " from " << source; + + User *u = get_user_force(user_id, source); + if (u == nullptr && Slice(source) != Slice("GetUsersQuery")) { + // userEmpty should be received only through getUsers for nonexistent users + LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty from " << source; + } + return; + } + + CHECK(constructor_id == telegram_api::user::ID); + auto user = move_tl_object_as(user_ptr); + UserId user_id(user->id_); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + int32 flags = user->flags_; + int32 flags2 = user->flags2_; + LOG(INFO) << "Receive " << user_id << " with flags " << flags << ' ' << flags2 << " from " << source; + + // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used + bool is_bot = (flags & USER_FLAG_IS_BOT) != 0; + if (flags & USER_FLAG_IS_ME) { + set_my_id(user_id); + if (!is_bot) { + td_->option_manager_->set_option_string("my_phone_number", user->phone_); + } + } + + bool have_access_hash = (flags & USER_FLAG_HAS_ACCESS_HASH) != 0; + bool is_received = (flags & USER_FLAG_IS_INACCESSIBLE) == 0; + bool is_contact = (flags & USER_FLAG_IS_CONTACT) != 0; + + User *u = get_user(user_id); + if (u == nullptr) { + if (!is_received) { + // we must preload received inaccessible users from database in order to not save + // the min-user to the database and to not override access_hash and other data + u = get_user_force(user_id, "on_get_user 2"); + if (u == nullptr) { + LOG(INFO) << "Receive inaccessible " << user_id; + u = add_user(user_id); + } + } else if (is_contact && !are_contacts_loaded_) { + // preload contact users from database to know that is_contact didn't changed + // and the list of contacts doesn't need to be saved to the database + u = get_user_force(user_id, "on_get_user 3"); + if (u == nullptr) { + LOG(INFO) << "Receive contact " << user_id << " for the first time"; + u = add_user(user_id); + } + } else { + u = add_user(user_id); + } + CHECK(u != nullptr); + } + + if (have_access_hash) { // access_hash must be updated before photo + auto access_hash = user->access_hash_; + bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty()); + if (u->access_hash != access_hash && (!is_min_access_hash || u->is_min_access_hash || u->access_hash == -1)) { + LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << "/" + << u->is_min_access_hash << " to " << access_hash << "/" << is_min_access_hash; + u->access_hash = access_hash; + u->is_min_access_hash = is_min_access_hash; + u->need_save_to_database = true; + } + } + + bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0; + bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0; + bool is_support = (flags & USER_FLAG_IS_SUPPORT) != 0; + bool is_deleted = (flags & USER_FLAG_IS_DELETED) != 0; + bool can_join_groups = (flags & USER_FLAG_IS_PRIVATE_BOT) == 0; + bool can_read_all_group_messages = (flags & USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED) != 0; + bool can_be_added_to_attach_menu = (flags & USER_FLAG_IS_ATTACH_MENU_BOT) != 0; + bool attach_menu_enabled = (flags & USER_FLAG_ATTACH_MENU_ENABLED) != 0; + bool is_scam = (flags & USER_FLAG_IS_SCAM) != 0; + bool can_be_edited_bot = (flags2 & USER_FLAG_CAN_BE_EDITED_BOT) != 0; + bool is_inline_bot = (flags & USER_FLAG_IS_INLINE_BOT) != 0; + bool is_business_bot = user->bot_business_; + string inline_query_placeholder = std::move(user->bot_inline_placeholder_); + bool need_location_bot = (flags & USER_FLAG_NEED_LOCATION_BOT) != 0; + bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0; + bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0; + bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0; + bool stories_available = user->stories_max_id_ > 0; + bool stories_unavailable = user->stories_unavailable_; + bool stories_hidden = user->stories_hidden_; + bool contact_require_premium = user->contact_require_premium_; + + if (!is_bot && (!can_join_groups || can_read_all_group_messages || can_be_added_to_attach_menu || can_be_edited_bot || + is_inline_bot || is_business_bot)) { + LOG(ERROR) << "Receive not bot " << user_id << " with bot properties from " << source; + can_join_groups = true; + can_read_all_group_messages = false; + can_be_added_to_attach_menu = false; + can_be_edited_bot = false; + is_inline_bot = false; + is_business_bot = false; + } + if (need_location_bot && !is_inline_bot) { + LOG(ERROR) << "Receive not inline bot " << user_id << " which needs user location from " << source; + need_location_bot = false; + } + + if (is_deleted) { + // just in case + is_verified = false; + is_premium = false; + is_support = false; + is_bot = false; + can_join_groups = false; + can_read_all_group_messages = false; + can_be_added_to_attach_menu = false; + can_be_edited_bot = false; + is_inline_bot = false; + is_business_bot = false; + inline_query_placeholder = string(); + need_location_bot = false; + has_bot_info_version = false; + need_apply_min_photo = false; + } + + LOG_IF(ERROR, has_bot_info_version && !is_bot) + << "Receive not bot " << user_id << " which has bot info version from " << source; + + int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1; + if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot || + can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages || + is_scam != u->is_scam || is_fake != u->is_fake || is_inline_bot != u->is_inline_bot || + is_business_bot != u->is_business_bot || inline_query_placeholder != u->inline_query_placeholder || + need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu) { + if (is_bot != u->is_bot) { + LOG_IF(ERROR, !is_deleted && !u->is_deleted && u->is_received) + << "User.is_bot has changed for " << user_id << "/" << u->usernames << " from " << source << " from " + << u->is_bot << " to " << is_bot; + u->is_full_info_changed = true; + } + u->is_verified = is_verified; + u->is_support = is_support; + u->is_bot = is_bot; + u->can_join_groups = can_join_groups; + u->can_read_all_group_messages = can_read_all_group_messages; + u->is_scam = is_scam; + u->is_fake = is_fake; + u->is_inline_bot = is_inline_bot; + u->is_business_bot = is_business_bot; + u->inline_query_placeholder = std::move(inline_query_placeholder); + u->need_location_bot = need_location_bot; + u->can_be_added_to_attach_menu = can_be_added_to_attach_menu; + + LOG(DEBUG) << "Info has changed for " << user_id; + u->is_changed = true; + } + if (u->contact_require_premium != contact_require_premium) { + u->contact_require_premium = contact_require_premium; + u->is_changed = true; + user_full_contact_require_premium_.erase(user_id); + } + if (is_received && attach_menu_enabled != u->attach_menu_enabled) { + u->attach_menu_enabled = attach_menu_enabled; + u->is_changed = true; + } + if (is_premium != u->is_premium) { + u->is_premium = is_premium; + u->is_is_premium_changed = true; + u->is_changed = true; + u->is_full_info_changed = true; + } + if (is_received && can_be_edited_bot != u->can_be_edited_bot) { + u->can_be_edited_bot = can_be_edited_bot; + u->is_changed = true; + u->is_full_info_changed = true; + } + + if (u->bot_info_version != bot_info_version) { + u->bot_info_version = bot_info_version; + LOG(DEBUG) << "Bot info version has changed for " << user_id; + u->need_save_to_database = true; + } + if (is_received && u->need_apply_min_photo != need_apply_min_photo) { + LOG(DEBUG) << "Need apply min photo has changed for " << user_id; + u->need_apply_min_photo = need_apply_min_photo; + u->need_save_to_database = true; + } + + if (is_received && !u->is_received) { + u->is_received = true; + + LOG(DEBUG) << "Receive " << user_id; + u->is_changed = true; + } + + if (is_deleted != u->is_deleted) { + u->is_deleted = is_deleted; + + LOG(DEBUG) << "User.is_deleted has changed for " << user_id << " to " << u->is_deleted; + u->is_is_deleted_changed = true; + u->is_changed = true; + } + + bool has_language_code = (flags & USER_FLAG_HAS_LANGUAGE_CODE) != 0; + LOG_IF(ERROR, has_language_code && !td_->auth_manager_->is_bot()) + << "Receive language code for " << user_id << " from " << source; + if (u->language_code != user->lang_code_ && !user->lang_code_.empty()) { + u->language_code = user->lang_code_; + + LOG(DEBUG) << "Language code has changed for " << user_id << " to " << u->language_code; + u->is_changed = true; + } + + bool is_me_regular_user = !td_->auth_manager_->is_bot(); + if (is_received || u->need_apply_min_photo || !u->is_received) { + on_update_user_photo(u, user_id, std::move(user->photo_), source); + } + if (is_me_regular_user) { + if (is_received || !u->is_received) { + on_update_user_phone_number(u, user_id, std::move(user->phone_)); + } + if (is_received || !u->is_received || u->was_online == 0) { + on_update_user_online(u, user_id, std::move(user->status_)); + } + if (is_received) { + auto is_mutual_contact = (flags & USER_FLAG_IS_MUTUAL_CONTACT) != 0; + auto is_close_friend = (flags2 & USER_FLAG_IS_CLOSE_FRIEND) != 0; + on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact, is_close_friend); + } + } + + if (is_received || !u->is_received) { + on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_)); + on_update_user_usernames(u, user_id, Usernames{std::move(user->username_), std::move(user->usernames_)}); + } + on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_))); + PeerColor peer_color(user->color_); + on_update_user_accent_color_id(u, user_id, peer_color.accent_color_id_); + on_update_user_background_custom_emoji_id(u, user_id, peer_color.background_custom_emoji_id_); + PeerColor profile_peer_color(user->profile_color_); + on_update_user_profile_accent_color_id(u, user_id, profile_peer_color.accent_color_id_); + on_update_user_profile_background_custom_emoji_id(u, user_id, profile_peer_color.background_custom_emoji_id_); + if (is_me_regular_user) { + if (is_received) { + on_update_user_stories_hidden(u, user_id, stories_hidden); + } + if (stories_available || stories_unavailable) { + // update at the end, because it calls need_poll_user_active_stories + on_update_user_story_ids_impl(u, user_id, StoryId(user->stories_max_id_), StoryId()); + } + auto restriction_reasons = get_restriction_reasons(std::move(user->restriction_reason_)); + if (restriction_reasons != u->restriction_reasons) { + u->restriction_reasons = std::move(restriction_reasons); + u->is_changed = true; + } + } + + if (u->cache_version != User::CACHE_VERSION && u->is_received) { + u->cache_version = User::CACHE_VERSION; + u->need_save_to_database = true; + } + u->is_received_from_server = true; + update_user(u, user_id); +} + +void UserManager::on_get_users(vector> &&users, const char *source) { + for (auto &user : users) { + on_get_user(std::move(user), source); + } +} + +void UserManager::on_binlog_user_event(BinlogEvent &&event) { + if (!G()->use_chat_info_database()) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + UserLogEvent log_event; + if (log_event_parse(log_event, event.get_data()).is_error()) { + LOG(ERROR) << "Failed to load a user from binlog"; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + auto user_id = log_event.user_id; + if (have_min_user(user_id) || !user_id.is_valid()) { + LOG(ERROR) << "Skip adding already added " << user_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + LOG(INFO) << "Add " << user_id << " from binlog"; + users_.set(user_id, std::move(log_event.u_out)); + + User *u = get_user(user_id); + CHECK(u != nullptr); + u->log_event_id = event.id_; + + update_user(u, user_id, true, false); +} + +void UserManager::on_binlog_secret_chat_event(BinlogEvent &&event) { + if (!G()->use_chat_info_database()) { + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + SecretChatLogEvent log_event; + if (log_event_parse(log_event, event.get_data()).is_error()) { + LOG(ERROR) << "Failed to load a secret chat from binlog"; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + auto secret_chat_id = log_event.secret_chat_id; + if (have_secret_chat(secret_chat_id) || !secret_chat_id.is_valid()) { + LOG(ERROR) << "Skip adding already added " << secret_chat_id; + binlog_erase(G()->td_db()->get_binlog(), event.id_); + return; + } + + LOG(INFO) << "Add " << secret_chat_id << " from binlog"; + secret_chats_.set(secret_chat_id, std::move(log_event.c_out)); + + SecretChat *c = get_secret_chat(secret_chat_id); + CHECK(c != nullptr); + c->log_event_id = event.id_; + + update_secret_chat(c, secret_chat_id, true, false); +} + +void UserManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_name"); + if (u != nullptr) { + on_update_user_name(u, user_id, std::move(first_name), std::move(last_name)); + on_update_user_usernames(u, user_id, std::move(usernames)); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user name about unknown " << user_id; + } +} + +void UserManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name) { + if (first_name.empty() && last_name.empty()) { + first_name = u->phone_number; + } + if (u->first_name != first_name || u->last_name != last_name) { + u->first_name = std::move(first_name); + u->last_name = std::move(last_name); + u->is_name_changed = true; + LOG(DEBUG) << "Name has changed for " << user_id; + u->is_changed = true; + } +} + +void UserManager::on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames) { + if (u->usernames != usernames) { + td_->dialog_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); + td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames); + if (u->can_be_edited_bot && u->usernames.get_editable_username() != usernames.get_editable_username()) { + u->is_full_info_changed = true; + } + u->usernames = std::move(usernames); + u->is_username_changed = true; + LOG(DEBUG) << "Usernames have changed for " << user_id; + u->is_changed = true; + } else if (u->is_bot || !td_->auth_manager_->is_bot()) { + td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), usernames, false); + } +} + +void UserManager::on_update_user_phone_number(UserId user_id, string &&phone_number) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_phone_number"); + if (u != nullptr) { + on_update_user_phone_number(u, user_id, std::move(phone_number)); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user phone number about unknown " << user_id; + } +} + +void UserManager::on_update_user_phone_number(User *u, UserId user_id, string &&phone_number) { + if (td_->auth_manager_->is_bot()) { + return; + } + + clean_phone_number(phone_number); + if (u->phone_number != phone_number) { + if (!u->phone_number.empty()) { + auto it = resolved_phone_numbers_.find(u->phone_number); + if (it != resolved_phone_numbers_.end() && it->second == user_id) { + resolved_phone_numbers_.erase(it); + } + } + + u->phone_number = std::move(phone_number); + u->is_phone_number_changed = true; + LOG(DEBUG) << "Phone number has changed for " << user_id; + u->is_changed = true; + } +} + +void UserManager::on_update_user_photo(User *u, UserId user_id, + telegram_api::object_ptr &&photo, + const char *source) { + if (td_->auth_manager_->is_bot() && !G()->use_chat_info_database()) { + if (!u->is_photo_inited) { + auto new_photo_id = get_profile_photo_id(photo); + auto &old_photo = pending_user_photos_[user_id]; + if (new_photo_id == get_profile_photo_id(old_photo)) { + return; + } + if (photo != nullptr && photo->get_id() == telegram_api::userProfilePhoto::ID) { + auto *profile_photo = static_cast(photo.get()); + if ((profile_photo->flags_ & telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK) != 0) { + profile_photo->flags_ -= telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK; + profile_photo->stripped_thumb_ = BufferSlice(); + } + } + + old_photo = std::move(photo); + + drop_user_photos(user_id, new_photo_id == 0, "on_update_user_photo"); + auto user_full = get_user_full(user_id); // must not load UserFull + if (user_full != nullptr && new_photo_id != get_user_full_profile_photo_id(user_full)) { + // we didn't sent updateUser yet, so we must not sent updateUserFull with new_photo_id yet + drop_user_full_photos(user_full, user_id, 0, "on_update_user_photo"); + } + return; + } + if (u->is_received) { + auto new_photo_id = get_profile_photo_id(photo); + if (new_photo_id == u->photo.id) { + return; + } + } + } + + do_update_user_photo(u, user_id, std::move(photo), source); +} + +void UserManager::do_update_user_photo(User *u, UserId user_id, + telegram_api::object_ptr &&photo, + const char *source) { + ProfilePhoto new_photo = get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(photo)); + if (td_->auth_manager_->is_bot()) { + new_photo.minithumbnail.clear(); + } + do_update_user_photo(u, user_id, std::move(new_photo), true, source); +} + +void UserManager::do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, bool invalidate_photo_cache, + const char *source) { + u->is_photo_inited = true; + if (need_update_profile_photo(u->photo, new_photo)) { + LOG_IF(ERROR, u->access_hash == -1 && new_photo.small_file_id.is_valid()) + << "Update profile photo of " << user_id << " without access hash from " << source; + u->photo = new_photo; + u->is_photo_changed = true; + LOG(DEBUG) << "Photo has changed for " << user_id << " to " << u->photo + << ", invalidate_photo_cache = " << invalidate_photo_cache << " from " << source; + u->is_changed = true; + + if (invalidate_photo_cache) { + drop_user_photos(user_id, u->photo.id == 0, source); + } + auto user_full = get_user_full(user_id); // must not load UserFull + if (user_full != nullptr && u->photo.id != get_user_full_profile_photo_id(user_full)) { + // we didn't sent updateUser yet, so we must not sent updateUserFull with u->photo.id yet + drop_user_full_photos(user_full, user_id, 0, "do_update_user_photo"); + } + } else if (need_update_dialog_photo_minithumbnail(u->photo.minithumbnail, new_photo.minithumbnail)) { + LOG(DEBUG) << "Photo minithumbnail has changed for " << user_id << " from " << source; + u->photo.minithumbnail = std::move(new_photo.minithumbnail); + u->is_photo_changed = true; + u->is_changed = true; + } +} + +void UserManager::register_suggested_profile_photo(const Photo &photo) { + auto photo_file_ids = photo_get_file_ids(photo); + if (photo.is_empty() || photo_file_ids.empty()) { + return; + } + auto first_file_id = photo_file_ids[0]; + auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); + if (file_type == FileType::ProfilePhoto) { + return; + } + CHECK(file_type == FileType::Photo); + auto photo_id = photo.id.get(); + if (photo_id != 0) { + my_photo_file_id_[photo_id] = first_file_id; + } +} + +void UserManager::register_user_photo(User *u, UserId user_id, const Photo &photo) { + auto photo_file_ids = photo_get_file_ids(photo); + if (photo.is_empty() || photo_file_ids.empty()) { + return; + } + auto first_file_id = photo_file_ids[0]; + auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type(); + if (file_type == FileType::ProfilePhoto) { + return; + } + CHECK(file_type == FileType::Photo); + CHECK(u != nullptr); + auto photo_id = photo.id.get(); + if (photo_id != 0 && u->photo_ids.emplace(photo_id).second) { + VLOG(file_references) << "Register photo " << photo_id << " of " << user_id; + if (user_id == get_my_id()) { + my_photo_file_id_[photo_id] = first_file_id; + } + auto file_source_id = user_profile_photo_file_source_ids_.get(std::make_pair(user_id, photo_id)); + if (file_source_id.is_valid()) { + VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; + user_profile_photo_file_source_ids_.erase(std::make_pair(user_id, photo_id)); + } else { + VLOG(file_references) << "Need to create new file source for photo " << photo_id << " of " << user_id; + file_source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); + } + for (auto &file_id : photo_file_ids) { + td_->file_manager_->add_file_source(file_id, file_source_id); + } + } +} + +void UserManager::on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { + if (accent_color_id == AccentColorId(user_id) || !accent_color_id.is_valid()) { + accent_color_id = AccentColorId(); + } + if (u->accent_color_id != accent_color_id) { + u->accent_color_id = accent_color_id; + u->is_accent_color_changed = true; + u->is_changed = true; + } +} + +void UserManager::on_update_user_background_custom_emoji_id(User *u, UserId user_id, + CustomEmojiId background_custom_emoji_id) { + if (u->background_custom_emoji_id != background_custom_emoji_id) { + u->background_custom_emoji_id = background_custom_emoji_id; + u->is_accent_color_changed = true; + u->is_changed = true; + } +} + +void UserManager::on_update_user_profile_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id) { + if (!accent_color_id.is_valid()) { + accent_color_id = AccentColorId(); + } + if (u->profile_accent_color_id != accent_color_id) { + u->profile_accent_color_id = accent_color_id; + u->is_accent_color_changed = true; + u->is_changed = true; + } +} + +void UserManager::on_update_user_profile_background_custom_emoji_id(User *u, UserId user_id, + CustomEmojiId background_custom_emoji_id) { + if (u->profile_background_custom_emoji_id != background_custom_emoji_id) { + u->profile_background_custom_emoji_id = background_custom_emoji_id; + u->is_accent_color_changed = true; + u->is_changed = true; + } +} + +void UserManager::on_update_user_emoji_status(UserId user_id, + telegram_api::object_ptr &&emoji_status) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_emoji_status"); + if (u != nullptr) { + on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status))); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user emoji status about unknown " << user_id; + } +} + +void UserManager::on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status) { + if (u->emoji_status != emoji_status) { + LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status; + u->emoji_status = std::move(emoji_status); + u->is_emoji_status_changed = true; + // effective emoji status might not be changed; checked in update_user + // u->is_changed = true; + } +} + +void UserManager::on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_story_ids"); + if (u != nullptr) { + on_update_user_story_ids_impl(u, user_id, max_active_story_id, max_read_story_id); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user story identifiers about unknown " << user_id; + } +} + +void UserManager::on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, + StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot()) { + return; + } + if (max_active_story_id != StoryId() && !max_active_story_id.is_server()) { + LOG(ERROR) << "Receive max active " << max_active_story_id << " for " << user_id; + return; + } + if (max_read_story_id != StoryId() && !max_read_story_id.is_server()) { + LOG(ERROR) << "Receive max read " << max_read_story_id << " for " << user_id; + return; + } + + auto has_unread_stories = get_user_has_unread_stories(u); + if (u->max_active_story_id != max_active_story_id) { + LOG(DEBUG) << "Change last active story of " << user_id << " from " << u->max_active_story_id << " to " + << max_active_story_id; + u->max_active_story_id = max_active_story_id; + u->need_save_to_database = true; + } + if (need_poll_user_active_stories(u, user_id)) { + auto max_active_story_id_next_reload_time = Time::now() + MAX_ACTIVE_STORY_ID_RELOAD_TIME; + if (max_active_story_id_next_reload_time > + u->max_active_story_id_next_reload_time + MAX_ACTIVE_STORY_ID_RELOAD_TIME / 5) { + LOG(DEBUG) << "Change max_active_story_id_next_reload_time of " << user_id; + u->max_active_story_id_next_reload_time = max_active_story_id_next_reload_time; + u->need_save_to_database = true; + } + } + if (!max_active_story_id.is_valid()) { + CHECK(max_read_story_id == StoryId()); + if (u->max_read_story_id != StoryId()) { + LOG(DEBUG) << "Drop last read " << u->max_read_story_id << " of " << user_id; + u->max_read_story_id = StoryId(); + u->need_save_to_database = true; + } + } else if (max_read_story_id.get() > u->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " + << max_read_story_id; + u->max_read_story_id = max_read_story_id; + u->need_save_to_database = true; + } + if (has_unread_stories != get_user_has_unread_stories(u)) { + LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; + u->is_changed = true; + } +} + +void UserManager::on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id) { + CHECK(user_id.is_valid()); + + User *u = get_user(user_id); + if (u != nullptr) { + on_update_user_max_read_story_id(u, user_id, max_read_story_id); + update_user(u, user_id); + } +} + +void UserManager::on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id) { + if (td_->auth_manager_->is_bot() || !u->is_received) { + return; + } + + auto has_unread_stories = get_user_has_unread_stories(u); + if (max_read_story_id.get() > u->max_read_story_id.get()) { + LOG(DEBUG) << "Change last read story of " << user_id << " from " << u->max_read_story_id << " to " + << max_read_story_id; + u->max_read_story_id = max_read_story_id; + u->need_save_to_database = true; + } + if (has_unread_stories != get_user_has_unread_stories(u)) { + LOG(DEBUG) << "Change has_unread_stories of " << user_id << " to " << !has_unread_stories; + u->is_changed = true; + } +} + +void UserManager::on_update_user_stories_hidden(UserId user_id, bool stories_hidden) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_stories_hidden"); + if (u != nullptr) { + on_update_user_stories_hidden(u, user_id, stories_hidden); + update_user(u, user_id); + } else { + LOG(INFO) << "Ignore update user stories are archived about unknown " << user_id; + } +} + +void UserManager::on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (u->stories_hidden != stories_hidden) { + LOG(DEBUG) << "Change stories are archived of " << user_id << " to " << stories_hidden; + u->stories_hidden = stories_hidden; + u->is_stories_hidden_changed = true; + u->need_save_to_database = true; + } +} + +void UserManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, + bool is_close_friend) { + if (td_->auth_manager_->is_bot()) { + return; + } + + UserId my_id = get_my_id(); + if (user_id == my_id) { + is_mutual_contact = is_contact; + is_close_friend = false; + } + if (!is_contact && (is_mutual_contact || is_close_friend)) { + LOG(ERROR) << "Receive is_mutual_contact = " << is_mutual_contact << ", and is_close_friend = " << is_close_friend + << " for non-contact " << user_id; + is_mutual_contact = false; + is_close_friend = false; + } + + if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact || + u->is_close_friend != is_close_friend) { + LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact << ", " + << u->is_close_friend << ") to (" << is_contact << ", " << is_mutual_contact << ", " << is_close_friend + << ")"; + if (u->is_contact != is_contact) { + u->is_contact = is_contact; + u->is_is_contact_changed = true; + } + if (u->is_mutual_contact != is_mutual_contact) { + u->is_mutual_contact = is_mutual_contact; + u->is_is_mutual_contact_changed = true; + + reload_contact_birthdates(true); + } + u->is_close_friend = is_close_friend; + u->is_changed = true; + } +} + +void UserManager::on_update_user_online(UserId user_id, telegram_api::object_ptr &&status) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + User *u = get_user_force(user_id, "on_update_user_online"); + if (u != nullptr) { + if (u->is_bot) { + LOG(ERROR) << "Receive updateUserStatus about bot " << user_id; + return; + } + on_update_user_online(u, user_id, std::move(status)); + update_user(u, user_id); + + if (user_id == get_my_id() && + was_online_remote_ != u->was_online) { // only update was_online_remote_ from updateUserStatus + was_online_remote_ = u->was_online; + VLOG(notifications) << "Set was_online_remote to " << was_online_remote_; + G()->td_db()->get_binlog_pmc()->set("my_was_online_remote", to_string(was_online_remote_)); + } + } else { + LOG(INFO) << "Ignore update user online about unknown " << user_id; + } +} + +void UserManager::on_update_user_online(User *u, UserId user_id, + telegram_api::object_ptr &&status) { + if (td_->auth_manager_->is_bot()) { + return; + } + + int32 id = status == nullptr ? telegram_api::userStatusEmpty::ID : status->get_id(); + int32 new_online; + bool is_offline = false; + if (id == telegram_api::userStatusOnline::ID) { + int32 now = G()->unix_time(); + + auto st = move_tl_object_as(status); + new_online = st->expires_; + LOG_IF(ERROR, new_online < now - 86400) + << "Receive userStatusOnline expired more than one day in past " << new_online; + } else if (id == telegram_api::userStatusOffline::ID) { + int32 now = G()->unix_time(); + + auto st = move_tl_object_as(status); + new_online = st->was_online_; + if (new_online >= now) { + LOG_IF(ERROR, new_online > now + 10) + << "Receive userStatusOffline but was online points to future time " << new_online << ", now is " << now; + new_online = now - 1; + } + is_offline = true; + } else if (id == telegram_api::userStatusRecently::ID) { + auto st = telegram_api::move_object_as(status); + new_online = st->by_me_ ? -4 : -1; + } else if (id == telegram_api::userStatusLastWeek::ID) { + auto st = telegram_api::move_object_as(status); + new_online = st->by_me_ ? -5 : -2; + } else if (id == telegram_api::userStatusLastMonth::ID) { + auto st = telegram_api::move_object_as(status); + new_online = st->by_me_ ? -6 : -3; + } else { + CHECK(id == telegram_api::userStatusEmpty::ID); + new_online = 0; + } + + if (new_online != u->was_online && !(new_online < 0 && user_id == get_my_id())) { + LOG(DEBUG) << "Update " << user_id << " online from " << u->was_online << " to " << new_online; + auto unix_time = G()->unix_time(); + bool old_is_online = u->was_online > unix_time; + bool new_is_online = new_online > unix_time; + u->was_online = new_online; + u->is_status_changed = true; + if (u->was_online > 0) { + u->local_was_online = 0; + } + + if (user_id == get_my_id()) { + if (my_was_online_local_ != 0 || old_is_online != new_is_online) { + my_was_online_local_ = 0; + u->is_online_status_changed = true; + } + if (is_offline) { + td_->on_online_updated(false, false); + } + } else if (old_is_online != new_is_online) { + u->is_online_status_changed = true; + } + } +} + +void UserManager::on_update_user_local_was_online(UserId user_id, int32 local_was_online) { + CHECK(user_id.is_valid()); + if (td_->auth_manager_->is_bot()) { + return; + } + + User *u = get_user_force(user_id, "on_update_user_local_was_online"); + if (u == nullptr) { + return; + } + + on_update_user_local_was_online(u, user_id, local_was_online); + update_user(u, user_id); +} + +void UserManager::on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online) { + CHECK(u != nullptr); + if (u->is_deleted || u->is_bot || u->is_support || user_id == get_my_id()) { + return; + } + int32 unix_time = G()->unix_time(); + if (u->was_online > unix_time) { + // if user is currently online, ignore local online + return; + } + + // bring users online for 30 seconds + local_was_online += 30; + if (local_was_online < unix_time + 2 || local_was_online <= u->local_was_online || + local_was_online <= u->was_online) { + return; + } + + LOG(DEBUG) << "Update " << user_id << " local online from " << u->local_was_online << " to " << local_was_online; + bool old_is_online = u->local_was_online > unix_time; + u->local_was_online = local_was_online; + u->is_status_changed = true; + + if (!old_is_online) { + u->is_online_status_changed = true; + } +} + +void UserManager::on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories) { + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_is_blocked"); + if (user_full == nullptr) { + return; + } + on_update_user_full_is_blocked(user_full, user_id, is_blocked, is_blocked_for_stories); + update_user_full(user_full, user_id, "on_update_user_is_blocked"); +} + +void UserManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, + bool is_blocked_for_stories) { + CHECK(user_full != nullptr); + if (user_full->is_blocked != is_blocked || user_full->is_blocked_for_stories != is_blocked_for_stories) { + LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked << '/' + << is_blocked_for_stories; + user_full->is_blocked = is_blocked; + user_full->is_blocked_for_stories = is_blocked_for_stories; + user_full->is_changed = true; + } +} + +void UserManager::on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories) { + if (td_->auth_manager_->is_bot()) { + return; + } + + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_has_pinned_stories"); + if (user_full == nullptr || user_full->has_pinned_stories == has_pinned_stories) { + return; + } + user_full->has_pinned_stories = has_pinned_stories; + user_full->is_changed = true; + update_user_full(user_full, user_id, "on_update_user_has_pinned_stories"); +} + +void UserManager::on_update_user_common_chat_count(UserId user_id, int32 common_chat_count) { + LOG(INFO) << "Receive " << common_chat_count << " common chat count with " << user_id; + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_common_chat_count"); + if (user_full == nullptr) { + return; + } + on_update_user_full_common_chat_count(user_full, user_id, common_chat_count); + update_user_full(user_full, user_id, "on_update_user_common_chat_count"); +} + +void UserManager::on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count) { + CHECK(user_full != nullptr); + if (common_chat_count < 0) { + LOG(ERROR) << "Receive " << common_chat_count << " as common group count with " << user_id; + common_chat_count = 0; + } + if (user_full->common_chat_count != common_chat_count) { + user_full->common_chat_count = common_chat_count; + user_full->is_common_chat_count_changed = true; + user_full->is_changed = true; + } +} + +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; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_location"); + if (user_full == nullptr) { + return; + } + on_update_user_full_location(user_full, user_id, std::move(location)); + update_user_full(user_full, user_id, "on_update_user_location"); +} + +void UserManager::on_update_user_full_location(UserFull *user_full, UserId user_id, DialogLocation &&location) { + CHECK(user_full != nullptr); + if (BusinessInfo::set_location(user_full->business_info, std::move(location))) { + user_full->is_changed = true; + } +} + +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; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_work_hours"); + if (user_full == nullptr) { + return; + } + on_update_user_full_work_hours(user_full, user_id, std::move(work_hours)); + update_user_full(user_full, user_id, "on_update_user_work_hours"); +} + +void UserManager::on_update_user_full_work_hours(UserFull *user_full, UserId user_id, BusinessWorkHours &&work_hours) { + CHECK(user_full != nullptr); + if (BusinessInfo::set_work_hours(user_full->business_info, std::move(work_hours))) { + user_full->is_changed = true; + } +} + +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; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_away_message"); + if (user_full == nullptr) { + return; + } + on_update_user_full_away_message(user_full, user_id, std::move(away_message)); + update_user_full(user_full, user_id, "on_update_user_away_message"); +} + +void UserManager::on_update_user_full_away_message(UserFull *user_full, UserId user_id, + BusinessAwayMessage &&away_message) const { + CHECK(user_full != nullptr); + if (away_message.is_valid() && user_id != get_my_id()) { + LOG(ERROR) << "Receive " << away_message << " for " << user_id; + return; + } + if (BusinessInfo::set_away_message(user_full->business_info, std::move(away_message))) { + user_full->is_changed = true; + } +} + +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; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_greeting_message"); + if (user_full == nullptr) { + return; + } + on_update_user_full_greeting_message(user_full, user_id, std::move(greeting_message)); + update_user_full(user_full, user_id, "on_update_user_greeting_message"); +} + +void UserManager::on_update_user_full_greeting_message(UserFull *user_full, UserId user_id, + BusinessGreetingMessage &&greeting_message) const { + CHECK(user_full != nullptr); + if (greeting_message.is_valid() && user_id != get_my_id()) { + LOG(ERROR) << "Receive " << greeting_message << " for " << user_id; + return; + } + if (BusinessInfo::set_greeting_message(user_full->business_info, std::move(greeting_message))) { + user_full->is_changed = true; + } +} + +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; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_intro"); + if (user_full == nullptr) { + return; + } + on_update_user_full_intro(user_full, user_id, std::move(intro)); + update_user_full(user_full, user_id, "on_update_user_intro"); +} + +void UserManager::on_update_user_full_intro(UserFull *user_full, UserId user_id, BusinessIntro &&intro) { + CHECK(user_full != nullptr); + if (BusinessInfo::set_intro(user_full->business_info, std::move(intro))) { + user_full->is_changed = true; + } +} + +void UserManager::on_update_user_commands(UserId user_id, + vector> &&bot_commands) { + UserFull *user_full = get_user_full_force(user_id, "on_update_user_commands"); + if (user_full != nullptr) { + on_update_user_full_commands(user_full, user_id, std::move(bot_commands)); + update_user_full(user_full, user_id, "on_update_user_commands"); + } +} + +void UserManager::on_update_user_full_commands( + UserFull *user_full, UserId user_id, vector> &&bot_commands) { + CHECK(user_full != nullptr); + auto commands = + transform(std::move(bot_commands), [](telegram_api::object_ptr &&bot_command) { + return BotCommand(std::move(bot_command)); + }); + if (user_full->commands != commands) { + user_full->commands = std::move(commands); + user_full->is_changed = true; + } +} + +void UserManager::on_update_user_need_phone_number_privacy_exception(UserId user_id, + bool need_phone_number_privacy_exception) { + LOG(INFO) << "Receive " << need_phone_number_privacy_exception << " need phone number privacy exception with " + << user_id; + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_need_phone_number_privacy_exception"); + if (user_full == nullptr) { + return; + } + on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, need_phone_number_privacy_exception); + update_user_full(user_full, user_id, "on_update_user_need_phone_number_privacy_exception"); +} + +void UserManager::on_update_user_full_need_phone_number_privacy_exception( + UserFull *user_full, UserId user_id, bool need_phone_number_privacy_exception) const { + CHECK(user_full != nullptr); + if (need_phone_number_privacy_exception) { + const User *u = get_user(user_id); + if (u == nullptr || u->is_contact || user_id == get_my_id()) { + need_phone_number_privacy_exception = false; + } + } + if (user_full->need_phone_number_privacy_exception != need_phone_number_privacy_exception) { + user_full->need_phone_number_privacy_exception = need_phone_number_privacy_exception; + user_full->is_changed = true; + } +} + +void UserManager::on_update_user_wallpaper_overridden(UserId user_id, bool wallpaper_overridden) { + LOG(INFO) << "Receive " << wallpaper_overridden << " set chat background for " << user_id; + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + return; + } + + UserFull *user_full = get_user_full_force(user_id, "on_update_user_wallpaper_overridden"); + if (user_full == nullptr) { + return; + } + on_update_user_full_wallpaper_overridden(user_full, user_id, wallpaper_overridden); + update_user_full(user_full, user_id, "on_update_user_wallpaper_overridden"); +} + +void UserManager::on_update_user_full_wallpaper_overridden(UserFull *user_full, UserId user_id, + bool wallpaper_overridden) const { + CHECK(user_full != nullptr); + if (user_full->wallpaper_overridden != wallpaper_overridden) { + user_full->wallpaper_overridden = wallpaper_overridden; + user_full->is_changed = true; + } +} + +void UserManager::on_update_bot_menu_button(UserId bot_user_id, + telegram_api::object_ptr &&bot_menu_button) { + if (!bot_user_id.is_valid()) { + LOG(ERROR) << "Receive updateBotMenuButton about invalid " << bot_user_id; + return; + } + if (!have_user_force(bot_user_id, "on_update_bot_menu_button") || !is_user_bot(bot_user_id)) { + return; + } + if (td_->auth_manager_->is_bot()) { + return; + } + + auto user_full = get_user_full_force(bot_user_id, "on_update_bot_menu_button"); + if (user_full != nullptr) { + on_update_user_full_menu_button(user_full, bot_user_id, std::move(bot_menu_button)); + update_user_full(user_full, bot_user_id, "on_update_bot_menu_button"); + } +} + +void UserManager::on_update_user_full_menu_button( + UserFull *user_full, UserId user_id, telegram_api::object_ptr &&bot_menu_button) { + CHECK(user_full != nullptr); + auto new_button = get_bot_menu_button(std::move(bot_menu_button)); + bool is_changed; + if (user_full->menu_button == nullptr) { + is_changed = (new_button != nullptr); + } else { + is_changed = (new_button == nullptr || *user_full->menu_button != *new_button); + } + if (is_changed) { + user_full->menu_button = std::move(new_button); + user_full->is_changed = true; + } +} + +void UserManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, + SecretChatState state, bool is_outbound, int32 ttl, int32 date, string key_hash, + int32 layer, FolderId initial_folder_id) { + LOG(INFO) << "Update " << secret_chat_id << " with " << user_id << " and access_hash " << access_hash; + auto *secret_chat = add_secret_chat(secret_chat_id); + if (access_hash != secret_chat->access_hash) { + secret_chat->access_hash = access_hash; + secret_chat->need_save_to_database = true; + } + if (user_id.is_valid() && user_id != secret_chat->user_id) { + if (secret_chat->user_id.is_valid()) { + LOG(ERROR) << "Secret chat user has changed from " << secret_chat->user_id << " to " << user_id; + auto &old_secret_chat_ids = secret_chats_with_user_[secret_chat->user_id]; + td::remove(old_secret_chat_ids, secret_chat_id); + } + secret_chat->user_id = user_id; + secret_chats_with_user_[secret_chat->user_id].push_back(secret_chat_id); + secret_chat->is_changed = true; + } + if (state != SecretChatState::Unknown && state != secret_chat->state) { + secret_chat->state = state; + secret_chat->is_changed = true; + secret_chat->is_state_changed = true; + } + if (is_outbound != secret_chat->is_outbound) { + secret_chat->is_outbound = is_outbound; + secret_chat->is_changed = true; + } + + if (ttl != -1 && ttl != secret_chat->ttl) { + secret_chat->ttl = ttl; + secret_chat->need_save_to_database = true; + secret_chat->is_ttl_changed = true; + } + if (date != 0 && date != secret_chat->date) { + secret_chat->date = date; + secret_chat->need_save_to_database = true; + } + if (!key_hash.empty() && key_hash != secret_chat->key_hash) { + secret_chat->key_hash = std::move(key_hash); + secret_chat->is_changed = true; + } + if (layer != 0 && layer != secret_chat->layer) { + secret_chat->layer = layer; + secret_chat->is_changed = true; + } + if (initial_folder_id != FolderId() && initial_folder_id != secret_chat->initial_folder_id) { + secret_chat->initial_folder_id = initial_folder_id; + secret_chat->is_changed = true; + } + + update_secret_chat(secret_chat, secret_chat_id); +} + +void UserManager::on_update_online_status_privacy() { + td_->create_handler()->send(); +} + +void UserManager::on_update_phone_number_privacy() { + // all UserFull.need_phone_number_privacy_exception can be outdated now, + // so mark all of them as expired + users_full_.foreach([&](const UserId &user_id, unique_ptr &user_full) { user_full->expires_at = 0.0; }); +} + +void UserManager::on_ignored_restriction_reasons_changed() { + restricted_user_ids_.foreach([&](const UserId &user_id) { + send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, get_user(user_id))); + }); +} + +void UserManager::invalidate_user_full(UserId user_id) { + auto user_full = get_user_full_force(user_id, "invalidate_user_full"); + if (user_full != nullptr) { + td_->dialog_manager_->on_dialog_info_full_invalidated(DialogId(user_id)); + + if (!user_full->is_expired()) { + user_full->expires_at = 0.0; + user_full->need_save_to_database = true; + + update_user_full(user_full, user_id, "invalidate_user_full"); + } + } +} + +bool UserManager::have_user(UserId user_id) const { + auto u = get_user(user_id); + return u != nullptr && u->is_received; +} + +bool UserManager::have_min_user(UserId user_id) const { + return users_.count(user_id) > 0; +} + +const UserManager::User *UserManager::get_user(UserId user_id) const { + return users_.get_pointer(user_id); +} + +UserManager::User *UserManager::get_user(UserId user_id) { + return users_.get_pointer(user_id); +} + +UserManager::User *UserManager::add_user(UserId user_id) { + CHECK(user_id.is_valid()); + auto &user_ptr = users_[user_id]; + if (user_ptr == nullptr) { + user_ptr = make_unique(); + } + return user_ptr.get(); +} + +void UserManager::save_user(User *u, UserId user_id, bool from_binlog) { + if (!G()->use_chat_info_database()) { + return; + } + CHECK(u != nullptr); + if (!u->is_saved || !u->is_status_saved) { // TODO more effective handling of !u->is_status_saved + if (!from_binlog) { + auto log_event = UserLogEvent(user_id, u); + auto storer = get_log_event_storer(log_event); + if (u->log_event_id == 0) { + u->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Users, storer); + } else { + binlog_rewrite(G()->td_db()->get_binlog(), u->log_event_id, LogEvent::HandlerType::Users, storer); + } + } + + save_user_to_database(u, user_id); + } +} + +string UserManager::get_user_database_key(UserId user_id) { + return PSTRING() << "us" << user_id.get(); +} + +string UserManager::get_user_database_value(const User *u) { + return log_event_store(*u).as_slice().str(); +} + +void UserManager::save_user_to_database(User *u, UserId user_id) { + CHECK(u != nullptr); + if (u->is_being_saved) { + return; + } + if (loaded_from_database_users_.count(user_id)) { + save_user_to_database_impl(u, user_id, get_user_database_value(u)); + return; + } + if (load_user_from_database_queries_.count(user_id) != 0) { + return; + } + + load_user_from_database_impl(user_id, Auto()); +} + +void UserManager::save_user_to_database_impl(User *u, UserId user_id, string value) { + CHECK(u != nullptr); + CHECK(load_user_from_database_queries_.count(user_id) == 0); + CHECK(!u->is_being_saved); + u->is_being_saved = true; + u->is_saved = true; + u->is_status_saved = true; + LOG(INFO) << "Trying to save to database " << user_id; + G()->td_db()->get_sqlite_pmc()->set( + get_user_database_key(user_id), std::move(value), PromiseCreator::lambda([user_id](Result<> result) { + send_closure(G()->user_manager(), &UserManager::on_save_user_to_database, user_id, result.is_ok()); + })); +} + +void UserManager::on_save_user_to_database(UserId user_id, bool success) { + if (G()->close_flag()) { + return; + } + + User *u = get_user(user_id); + CHECK(u != nullptr); + LOG_CHECK(u->is_being_saved) << success << ' ' << user_id << ' ' << u->is_saved << ' ' << u->is_status_saved << ' ' + << load_user_from_database_queries_.count(user_id) << ' ' << u->is_received << ' ' + << u->is_deleted << ' ' << u->is_bot << ' ' << u->need_save_to_database << ' ' + << u->is_changed << ' ' << u->is_status_changed << ' ' << u->is_name_changed << ' ' + << u->is_username_changed << ' ' << u->is_photo_changed << ' ' + << u->is_is_contact_changed << ' ' << u->is_is_deleted_changed << ' ' + << u->is_stories_hidden_changed << ' ' << u->log_event_id; + CHECK(load_user_from_database_queries_.count(user_id) == 0); + u->is_being_saved = false; + + if (!success) { + LOG(ERROR) << "Failed to save " << user_id << " to database"; + u->is_saved = false; + u->is_status_saved = false; + } else { + LOG(INFO) << "Successfully saved " << user_id << " to database"; + } + if (u->is_saved && u->is_status_saved) { + if (u->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); + u->log_event_id = 0; + } + } else { + save_user(u, user_id, u->log_event_id != 0); + } +} + +void UserManager::load_user_from_database(User *u, UserId user_id, Promise promise) { + if (loaded_from_database_users_.count(user_id)) { + promise.set_value(Unit()); + return; + } + + CHECK(u == nullptr || !u->is_being_saved); + load_user_from_database_impl(user_id, std::move(promise)); +} + +void UserManager::load_user_from_database_impl(UserId user_id, Promise promise) { + LOG(INFO) << "Load " << user_id << " from database"; + auto &load_user_queries = load_user_from_database_queries_[user_id]; + load_user_queries.push_back(std::move(promise)); + if (load_user_queries.size() == 1u) { + G()->td_db()->get_sqlite_pmc()->get(get_user_database_key(user_id), PromiseCreator::lambda([user_id](string value) { + send_closure(G()->user_manager(), &UserManager::on_load_user_from_database, + user_id, std::move(value), false); + })); + } +} + +void UserManager::on_load_user_from_database(UserId user_id, string value, bool force) { + if (G()->close_flag() && !force) { + // the user is in Binlog and will be saved after restart + return; + } + + CHECK(user_id.is_valid()); + if (!loaded_from_database_users_.insert(user_id).second) { + return; + } + + auto it = load_user_from_database_queries_.find(user_id); + vector> promises; + if (it != load_user_from_database_queries_.end()) { + promises = std::move(it->second); + CHECK(!promises.empty()); + load_user_from_database_queries_.erase(it); + } + + LOG(INFO) << "Successfully loaded " << user_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_user_database_key(user_id), Auto()); + // return; + + User *u = get_user(user_id); + if (u == nullptr) { + if (!value.empty()) { + u = add_user(user_id); + + if (log_event_parse(*u, value).is_error()) { + LOG(ERROR) << "Failed to load " << user_id << " from database"; + users_.erase(user_id); + } else { + u->is_saved = true; + u->is_status_saved = true; + update_user(u, user_id, true, true); + } + } + } else { + CHECK(!u->is_saved); // user can't be saved before load completes + CHECK(!u->is_being_saved); + auto new_value = get_user_database_value(u); + if (value != new_value) { + save_user_to_database_impl(u, user_id, std::move(new_value)); + } else if (u->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), u->log_event_id); + u->log_event_id = 0; + } + } + + set_promises(promises); +} + +bool UserManager::have_user_force(UserId user_id, const char *source) { + return get_user_force(user_id, source) != nullptr; +} + +UserManager::User *UserManager::get_user_force(UserId user_id, const char *source) { + auto u = get_user_force_impl(user_id, source); + if ((u == nullptr || !u->is_received) && + (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || + user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || + user_id == get_anti_spam_bot_user_id())) { + int32 flags = USER_FLAG_HAS_ACCESS_HASH | USER_FLAG_HAS_FIRST_NAME | USER_FLAG_NEED_APPLY_MIN_PHOTO; + int64 profile_photo_id = 0; + int32 profile_photo_dc_id = 1; + string first_name; + string last_name; + string username; + string phone_number; + int32 bot_info_version = 0; + + if (user_id == get_service_notifications_user_id()) { + flags |= USER_FLAG_HAS_PHONE_NUMBER | USER_FLAG_IS_VERIFIED | USER_FLAG_IS_SUPPORT; + first_name = "Telegram"; + if (G()->is_test_dc()) { + flags |= USER_FLAG_HAS_LAST_NAME; + last_name = "Notifications"; + } + phone_number = "42777"; + profile_photo_id = 3337190045231023; + } else if (user_id == get_replies_bot_user_id()) { + flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; + if (!G()->is_test_dc()) { + flags |= USER_FLAG_IS_PRIVATE_BOT; + } + first_name = "Replies"; + username = "replies"; + bot_info_version = G()->is_test_dc() ? 1 : 3; + } else if (user_id == get_anonymous_bot_user_id()) { + flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; + if (!G()->is_test_dc()) { + flags |= USER_FLAG_IS_PRIVATE_BOT; + } + first_name = "Group"; + username = G()->is_test_dc() ? "izgroupbot" : "GroupAnonymousBot"; + bot_info_version = G()->is_test_dc() ? 1 : 3; + profile_photo_id = 5159307831025969322; + } else if (user_id == get_channel_bot_user_id()) { + flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; + if (!G()->is_test_dc()) { + flags |= USER_FLAG_IS_PRIVATE_BOT; + } + first_name = G()->is_test_dc() ? "Channels" : "Channel"; + username = G()->is_test_dc() ? "channelsbot" : "Channel_Bot"; + bot_info_version = G()->is_test_dc() ? 1 : 4; + profile_photo_id = 587627495930570665; + } else if (user_id == get_service_notifications_user_id()) { + flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT; + if (G()->is_test_dc()) { + first_name = "antispambot"; + username = "tantispambot"; + } else { + flags |= USER_FLAG_IS_VERIFIED; + first_name = "Telegram Anti-Spam"; + username = "tgsantispambot"; + profile_photo_id = 5170408289966598902; + } + } + + telegram_api::object_ptr profile_photo; + if (!G()->is_test_dc() && profile_photo_id != 0) { + profile_photo = telegram_api::make_object(0, false, false, profile_photo_id, + BufferSlice(), profile_photo_dc_id); + } + + auto user = telegram_api::make_object( + flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, false /*ignored*/, false /*ignored*/, + false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, user_id.get(), 1, first_name, + string(), username, phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), + string(), nullptr, vector>(), 0, nullptr, nullptr); + on_get_user(std::move(user), "get_user_force"); + u = get_user(user_id); + CHECK(u != nullptr && u->is_received); + + reload_user(user_id, Promise(), "get_user_force"); + } + return u; +} + +UserManager::User *UserManager::get_user_force_impl(UserId user_id, const char *source) { + if (!user_id.is_valid()) { + return nullptr; + } + + User *u = get_user(user_id); + if (u != nullptr) { + return u; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (loaded_from_database_users_.count(user_id)) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << user_id << " from database from " << source; + on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)), true); + return get_user(user_id); +} + +void UserManager::send_get_me_query(Td *td, Promise &&promise) { + vector> users; + users.push_back(telegram_api::make_object()); + td->create_handler(std::move(promise))->send(std::move(users)); +} + +UserId UserManager::get_me(Promise &&promise) { + auto my_id = get_my_id(); + if (!have_user_force(my_id, "get_me")) { + get_user_queries_.add_query(my_id.get(), std::move(promise), "get_me"); + return UserId(); + } + + promise.set_value(Unit()); + return my_id; +} + +bool UserManager::get_user(UserId user_id, int left_tries, Promise &&promise) { + if (!user_id.is_valid()) { + promise.set_error(Status::Error(400, "Invalid user identifier")); + return false; + } + + if (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() || + user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id() || + user_id == get_anti_spam_bot_user_id()) { + get_user_force(user_id, "get_user"); + } + + if (td_->auth_manager_->is_bot() ? !have_user(user_id) : !have_min_user(user_id)) { + if (left_tries > 2 && G()->use_chat_info_database()) { + send_closure_later(actor_id(this), &UserManager::load_user_from_database, nullptr, user_id, std::move(promise)); + return false; + } + auto r_input_user = get_input_user(user_id); + if (left_tries == 1 || r_input_user.is_error()) { + if (r_input_user.is_error()) { + promise.set_error(r_input_user.move_as_error()); + } else { + promise.set_error(Status::Error(400, "User not found")); + } + return false; + } + + get_user_queries_.add_query(user_id.get(), std::move(promise), "get_user"); + return false; + } + + promise.set_value(Unit()); + return true; +} + +void UserManager::reload_user(UserId user_id, Promise &&promise, const char *source) { + if (!user_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid user identifier")); + } + + have_user_force(user_id, source); + + TRY_STATUS_PROMISE(promise, get_input_user(user_id)); + + get_user_queries_.add_query(user_id.get(), std::move(promise), source); +} + +Result> UserManager::get_input_user(UserId user_id) const { + if (user_id == get_my_id()) { + return telegram_api::make_object(); + } + + const User *u = get_user(user_id); + if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { + if (td_->auth_manager_->is_bot() && user_id.is_valid()) { + return telegram_api::make_object(user_id.get(), 0); + } + auto it = user_messages_.find(user_id); + if (it != user_messages_.end()) { + CHECK(!it->second.empty()); + auto message_full_id = *it->second.begin(); + return telegram_api::make_object( + td_->chat_manager_->get_simple_input_peer(message_full_id.get_dialog_id()), + message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); + } + if (u == nullptr) { + return Status::Error(400, "User not found"); + } else { + return Status::Error(400, "Have no access to the user"); + } + } + + return telegram_api::make_object(user_id.get(), u->access_hash); +} + +telegram_api::object_ptr UserManager::get_input_user_force(UserId user_id) const { + auto r_input_user = get_input_user(user_id); + if (r_input_user.is_error()) { + CHECK(user_id.is_valid()); + return telegram_api::make_object(user_id.get(), 0); + } + return r_input_user.move_as_ok(); +} + +bool UserManager::have_input_peer_user(UserId user_id, AccessRights access_rights) const { + if (user_id == get_my_id()) { + return true; + } + return have_input_peer_user(get_user(user_id), user_id, access_rights); +} + +bool UserManager::have_input_peer_user(const User *u, UserId user_id, AccessRights access_rights) const { + if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { + if (u == nullptr) { + LOG(DEBUG) << "Have no user"; + } else { + LOG(DEBUG) << "Have user without access hash"; + } + if (td_->auth_manager_->is_bot() && user_id.is_valid()) { + return true; + } + if (user_messages_.count(user_id) != 0) { + return true; + } + return false; + } + if (access_rights == AccessRights::Know) { + return true; + } + if (access_rights == AccessRights::Read) { + return true; + } + if (u->is_deleted) { + LOG(DEBUG) << "Have a deleted user"; + return false; + } + return true; +} + +telegram_api::object_ptr UserManager::get_input_peer_user(UserId user_id, + AccessRights access_rights) const { + if (user_id == get_my_id()) { + return telegram_api::make_object(); + } + const User *u = get_user(user_id); + if (!have_input_peer_user(u, user_id, access_rights)) { + return nullptr; + } + if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { + if (td_->auth_manager_->is_bot() && user_id.is_valid()) { + return telegram_api::make_object(user_id.get(), 0); + } + auto it = user_messages_.find(user_id); + CHECK(it != user_messages_.end()); + CHECK(!it->second.empty()); + auto message_full_id = *it->second.begin(); + return telegram_api::make_object( + td_->chat_manager_->get_simple_input_peer(message_full_id.get_dialog_id()), + message_full_id.get_message_id().get_server_message_id().get(), user_id.get()); + } + + return telegram_api::make_object(user_id.get(), u->access_hash); +} + +bool UserManager::have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const { + return have_input_encrypted_peer(get_secret_chat(secret_chat_id), access_rights); +} + +bool UserManager::have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights) { + if (secret_chat == nullptr) { + LOG(DEBUG) << "Have no secret chat"; + return false; + } + if (access_rights == AccessRights::Know) { + return true; + } + if (access_rights == AccessRights::Read) { + return true; + } + return secret_chat->state == SecretChatState::Active; +} + +telegram_api::object_ptr UserManager::get_input_encrypted_chat( + SecretChatId secret_chat_id, AccessRights access_rights) const { + auto sc = get_secret_chat(secret_chat_id); + if (!have_input_encrypted_peer(sc, access_rights)) { + return nullptr; + } + + return telegram_api::make_object(secret_chat_id.get(), sc->access_hash); +} + +bool UserManager::is_user_contact(UserId user_id, bool is_mutual) const { + return is_user_contact(get_user(user_id), user_id, is_mutual); +} + +bool UserManager::is_user_contact(const User *u, UserId user_id, bool is_mutual) const { + return u != nullptr && (is_mutual ? u->is_mutual_contact : u->is_contact) && user_id != get_my_id(); +} + +bool UserManager::is_user_premium(UserId user_id) const { + return is_user_premium(get_user(user_id)); +} + +bool UserManager::is_user_premium(const User *u) { + return u != nullptr && u->is_premium; +} + +bool UserManager::is_user_deleted(UserId user_id) const { + return is_user_deleted(get_user(user_id)); +} + +bool UserManager::is_user_deleted(const User *u) { + return u == nullptr || u->is_deleted; +} + +bool UserManager::is_user_support(UserId user_id) const { + return is_user_support(get_user(user_id)); +} + +bool UserManager::is_user_support(const User *u) { + return u != nullptr && !u->is_deleted && u->is_support; +} + +bool UserManager::is_user_bot(UserId user_id) const { + return is_user_bot(get_user(user_id)); +} + +bool UserManager::is_user_bot(const User *u) { + return u != nullptr && !u->is_deleted && u->is_bot; +} + +Result UserManager::get_bot_data(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return Status::Error(400, "Bot not found"); + } + if (!u->is_bot) { + return Status::Error(400, "User is not a bot"); + } + if (u->is_deleted) { + return Status::Error(400, "Bot is deleted"); + } + if (!u->is_received) { + return Status::Error(400, "Bot is inaccessible"); + } + + BotData bot_data; + bot_data.username = u->usernames.get_first_username(); + bot_data.can_be_edited = u->can_be_edited_bot; + bot_data.can_join_groups = u->can_join_groups; + bot_data.can_read_all_group_messages = u->can_read_all_group_messages; + bot_data.is_inline = u->is_inline_bot; + bot_data.is_business = u->is_business_bot; + bot_data.need_location = u->need_location_bot; + bot_data.can_be_added_to_attach_menu = u->can_be_added_to_attach_menu; + return bot_data; +} + +bool UserManager::is_user_online(UserId user_id, int32 tolerance, int32 unix_time) const { + if (unix_time <= 0) { + unix_time = G()->unix_time(); + } + int32 was_online = get_user_was_online(get_user(user_id), user_id, unix_time); + return was_online > unix_time - tolerance; +} + +int32 UserManager::get_user_was_online(UserId user_id, int32 unix_time) const { + if (unix_time <= 0) { + unix_time = G()->unix_time(); + } + return get_user_was_online(get_user(user_id), user_id, unix_time); +} + +int32 UserManager::get_user_was_online(const User *u, UserId user_id, int32 unix_time) const { + if (u == nullptr || u->is_deleted) { + return 0; + } + + int32 was_online = u->was_online; + if (user_id == get_my_id()) { + if (my_was_online_local_ != 0) { + was_online = my_was_online_local_; + } + } else { + if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > unix_time) { + was_online = u->local_was_online; + } + } + return was_online; +} + +bool UserManager::is_user_status_exact(UserId user_id) const { + auto u = get_user(user_id); + return u != nullptr && !u->is_deleted && !u->is_bot && u->was_online > 0; +} + +bool UserManager::is_user_received_from_server(UserId user_id) const { + const auto *u = get_user(user_id); + return u != nullptr && u->is_received_from_server; +} + +bool UserManager::can_report_user(UserId user_id) const { + auto u = get_user(user_id); + return u != nullptr && !u->is_deleted && !u->is_support && + (u->is_bot || td_->people_nearby_manager_->is_user_nearby(user_id)); +} + +const DialogPhoto *UserManager::get_user_dialog_photo(UserId user_id) { + auto u = get_user(user_id); + if (u == nullptr) { + return nullptr; + } + + apply_pending_user_photo(u, user_id); + return &u->photo; +} + +const DialogPhoto *UserManager::get_secret_chat_dialog_photo(SecretChatId secret_chat_id) { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return nullptr; + } + return get_user_dialog_photo(c->user_id); +} + +int32 UserManager::get_user_accent_color_id_object(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr || !u->accent_color_id.is_valid()) { + return td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)); + } + + return td_->theme_manager_->get_accent_color_id_object(u->accent_color_id, AccentColorId(user_id)); +} + +int32 UserManager::get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return 5; + } + return get_user_accent_color_id_object(c->user_id); +} + +CustomEmojiId UserManager::get_user_background_custom_emoji_id(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return CustomEmojiId(); + } + + return u->background_custom_emoji_id; +} + +CustomEmojiId UserManager::get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return CustomEmojiId(); + } + return get_user_background_custom_emoji_id(c->user_id); +} + +int32 UserManager::get_user_profile_accent_color_id_object(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return -1; + } + + return td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id); +} + +int32 UserManager::get_secret_chat_profile_accent_color_id_object(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return -1; + } + return get_user_profile_accent_color_id_object(c->user_id); +} + +CustomEmojiId UserManager::get_user_profile_background_custom_emoji_id(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return CustomEmojiId(); + } + + return u->profile_background_custom_emoji_id; +} + +CustomEmojiId UserManager::get_secret_chat_profile_background_custom_emoji_id(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return CustomEmojiId(); + } + return get_user_profile_background_custom_emoji_id(c->user_id); +} + +string UserManager::get_user_title(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return string(); + } + if (u->last_name.empty()) { + return u->first_name; + } + if (u->first_name.empty()) { + return u->last_name; + } + return PSTRING() << u->first_name << ' ' << u->last_name; +} + +string UserManager::get_secret_chat_title(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return string(); + } + return get_user_title(c->user_id); +} + +RestrictedRights UserManager::get_user_default_permissions(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr || user_id == get_replies_bot_user_id()) { + return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, u != nullptr, false, ChannelType::Unknown); + } + return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, + true, false, ChannelType::Unknown); +} + +RestrictedRights UserManager::get_secret_chat_default_permissions(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false, false, + false, false, false, false, ChannelType::Unknown); + } + return RestrictedRights(true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, + false, false, ChannelType::Unknown); +} + +td_api::object_ptr UserManager::get_user_emoji_status_object(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return nullptr; + } + return u->last_sent_emoji_status.get_emoji_status_object(); +} + +td_api::object_ptr UserManager::get_secret_chat_emoji_status_object( + SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return nullptr; + } + return get_user_emoji_status_object(c->user_id); +} + +bool UserManager::get_user_stories_hidden(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return false; + } + return u->stories_hidden; +} + +bool UserManager::can_poll_user_active_stories(UserId user_id) const { + const User *u = get_user(user_id); + return need_poll_user_active_stories(u, user_id) && Time::now() >= u->max_active_story_id_next_reload_time; +} + +bool UserManager::need_poll_user_active_stories(const User *u, UserId user_id) const { + return u != nullptr && user_id != get_my_id() && !is_user_contact(u, user_id, false) && !is_user_bot(u) && + !is_user_support(u) && !is_user_deleted(u) && u->was_online != 0; +} + +string UserManager::get_user_about(UserId user_id) { + auto user_full = get_user_full_force(user_id, "get_user_about"); + if (user_full != nullptr) { + return user_full->about; + } + return string(); +} + +string UserManager::get_secret_chat_about(SecretChatId secret_chat_id) { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return string(); + } + return get_user_about(c->user_id); +} + +string UserManager::get_user_private_forward_name(UserId user_id) { + auto user_full = get_user_full_force(user_id, "get_user_private_forward_name"); + if (user_full != nullptr) { + return user_full->private_forward_name; + } + return string(); +} + +bool UserManager::get_user_voice_messages_forbidden(UserId user_id) const { + if (!is_user_premium(user_id)) { + return false; + } + auto user_full = get_user_full(user_id); + if (user_full != nullptr) { + return user_full->voice_messages_forbidden; + } + return false; +} + +bool UserManager::get_user_read_dates_private(UserId user_id) { + auto user_full = get_user_full_force(user_id, "get_user_read_dates_private"); + if (user_full != nullptr) { + return user_full->read_dates_private; + } + return false; +} + +string UserManager::get_user_search_text(UserId user_id) const { + auto u = get_user(user_id); + if (u == nullptr) { + return string(); + } + return get_user_search_text(u); +} + +string UserManager::get_user_search_text(const User *u) { + CHECK(u != nullptr); + return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << implode(u->usernames.get_active_usernames()); +} + +void UserManager::for_each_secret_chat_with_user(UserId user_id, const std::function &f) { + auto it = secret_chats_with_user_.find(user_id); + if (it != secret_chats_with_user_.end()) { + for (auto secret_chat_id : it->second) { + f(secret_chat_id); + } + } +} + +string UserManager::get_user_first_username(UserId user_id) const { + if (!user_id.is_valid()) { + return string(); + } + + auto u = get_user(user_id); + if (u == nullptr) { + return string(); + } + return u->usernames.get_first_username(); +} + +int32 UserManager::get_secret_chat_date(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return 0; + } + return c->date; +} + +int32 UserManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return 0; + } + return c->ttl; +} + +UserId UserManager::get_secret_chat_user_id(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return UserId(); + } + return c->user_id; +} + +bool UserManager::get_secret_chat_is_outbound(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return false; + } + return c->is_outbound; +} + +SecretChatState UserManager::get_secret_chat_state(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return SecretChatState::Unknown; + } + return c->state; +} + +int32 UserManager::get_secret_chat_layer(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return 0; + } + return c->layer; +} + +FolderId UserManager::get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const { + auto c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + return FolderId::main(); + } + return c->initial_folder_id; +} + +vector UserManager::get_bot_commands(vector> &&bot_infos, + const vector *participants) { + vector result; + if (td_->auth_manager_->is_bot()) { + return result; + } + for (auto &bot_info : bot_infos) { + if (bot_info->commands_.empty()) { + continue; + } + + auto user_id = UserId(bot_info->user_id_); + const User *u = get_user_force(user_id, "get_bot_commands"); + if (u == nullptr) { + LOG(ERROR) << "Receive unknown " << user_id; + continue; + } + if (!is_user_bot(u)) { + if (!is_user_deleted(u)) { + LOG(ERROR) << "Receive non-bot " << user_id; + } + continue; + } + if (participants != nullptr) { + bool is_participant = false; + for (auto &participant : *participants) { + if (participant.dialog_id_ == DialogId(user_id)) { + is_participant = true; + break; + } + } + if (!is_participant) { + LOG(ERROR) << "Skip commands of non-member bot " << user_id; + continue; + } + } + result.emplace_back(user_id, std::move(bot_info->commands_)); + } + return result; +} + +void UserManager::set_name(const string &first_name, const string &last_name, Promise &&promise) { + auto new_first_name = clean_name(first_name, MAX_NAME_LENGTH); + auto new_last_name = clean_name(last_name, MAX_NAME_LENGTH); + if (new_first_name.empty()) { + return promise.set_error(Status::Error(400, "First name must be non-empty")); + } + + const User *u = get_user(get_my_id()); + int32 flags = 0; + // TODO we can already send request for changing first_name and last_name and wanting to set initial values + // TODO need to be rewritten using invoke after and cancelling previous request + if (u == nullptr || u->first_name != new_first_name) { + flags |= ACCOUNT_UPDATE_FIRST_NAME; + } + if (u == nullptr || u->last_name != new_last_name) { + flags |= ACCOUNT_UPDATE_LAST_NAME; + } + if (flags == 0) { + return promise.set_value(Unit()); + } + + td_->create_handler(std::move(promise))->send(flags, new_first_name, new_last_name, ""); +} + +void UserManager::set_bio(const string &bio, Promise &&promise) { + auto max_bio_length = static_cast(td_->option_manager_->get_option_integer("bio_length_max")); + auto new_bio = strip_empty_characters(bio, max_bio_length); + for (auto &c : new_bio) { + if (c == '\n') { + c = ' '; + } + } + + const UserFull *user_full = get_user_full(get_my_id()); + int32 flags = 0; + // TODO we can already send request for changing bio and wanting to set initial values + // TODO need to be rewritten using invoke after and cancelling previous request + if (user_full == nullptr || user_full->about != new_bio) { + flags |= ACCOUNT_UPDATE_ABOUT; + } + if (flags == 0) { + return promise.set_value(Unit()); + } + + td_->create_handler(std::move(promise))->send(flags, "", "", new_bio); +} + +void UserManager::on_update_profile_success(int32 flags, const string &first_name, const string &last_name, + const string &about) { + CHECK(flags != 0); + + auto my_user_id = get_my_id(); + const User *u = get_user(my_user_id); + if (u == nullptr) { + LOG(ERROR) << "Doesn't receive info about me during update profile"; + return; + } + LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_FIRST_NAME) != 0 && u->first_name != first_name) + << "Wrong first name \"" << u->first_name << "\", expected \"" << first_name << '"'; + LOG_IF(ERROR, (flags & ACCOUNT_UPDATE_LAST_NAME) != 0 && u->last_name != last_name) + << "Wrong last name \"" << u->last_name << "\", expected \"" << last_name << '"'; + + if ((flags & ACCOUNT_UPDATE_ABOUT) != 0) { + UserFull *user_full = get_user_full_force(my_user_id, "on_update_profile_success"); + if (user_full != nullptr) { + user_full->about = about; + user_full->is_changed = true; + update_user_full(user_full, my_user_id, "on_update_profile_success"); + td_->group_call_manager_->on_update_dialog_about(DialogId(my_user_id), user_full->about, true); + } + } +} + +FileId UserManager::get_profile_photo_file_id(int64 photo_id) const { + auto it = my_photo_file_id_.find(photo_id); + if (it == my_photo_file_id_.end()) { + return FileId(); + } + return it->second; +} + +void UserManager::set_bot_profile_photo(UserId bot_user_id, + const td_api::object_ptr &input_photo, + Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + if (bot_user_id != UserId() && bot_user_id != get_my_id()) { + return promise.set_error(Status::Error(400, "Invalid bot user identifier specified")); + } + bot_user_id = get_my_id(); + } else { + TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); + if (!bot_data.can_be_edited) { + return promise.set_error(Status::Error(400, "The bot can't be edited")); + } + } + if (input_photo == nullptr) { + td_->create_handler(std::move(promise)) + ->send(bot_user_id, FileId(), 0, false, telegram_api::make_object()); + return; + } + set_profile_photo_impl(bot_user_id, input_photo, false, false, std::move(promise)); +} + +void UserManager::set_profile_photo(const td_api::object_ptr &input_photo, bool is_fallback, + Promise &&promise) { + set_profile_photo_impl(get_my_id(), input_photo, is_fallback, false, std::move(promise)); +} + +void UserManager::set_profile_photo_impl(UserId user_id, const td_api::object_ptr &input_photo, + bool is_fallback, bool only_suggest, Promise &&promise) { + if (input_photo == nullptr) { + return promise.set_error(Status::Error(400, "New profile photo must be non-empty")); + } + + const td_api::object_ptr *input_file = nullptr; + double main_frame_timestamp = 0.0; + bool is_animation = false; + switch (input_photo->get_id()) { + case td_api::inputChatPhotoPrevious::ID: { + if (user_id != get_my_id() || td_->auth_manager_->is_bot()) { + return promise.set_error(Status::Error(400, "Can't use inputChatPhotoPrevious")); + } + auto photo = static_cast(input_photo.get()); + auto photo_id = photo->chat_photo_id_; + auto *u = get_user(user_id); + if (u != nullptr && u->photo.id > 0 && photo_id == u->photo.id) { + // it is possible that u->photo.is_fallback != is_fallback, so we need to set the photo anyway + // return promise.set_value(Unit()); + } + + auto file_id = get_profile_photo_file_id(photo_id); + if (!file_id.is_valid()) { + return promise.set_error(Status::Error(400, "Unknown profile photo ID specified")); + } + return send_update_profile_photo_query(user_id, + td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), + photo_id, is_fallback, std::move(promise)); + } + case td_api::inputChatPhotoStatic::ID: { + auto photo = static_cast(input_photo.get()); + input_file = &photo->photo_; + break; + } + case td_api::inputChatPhotoAnimation::ID: { + auto photo = static_cast(input_photo.get()); + input_file = &photo->animation_; + main_frame_timestamp = photo->main_frame_timestamp_; + is_animation = true; + break; + } + case td_api::inputChatPhotoSticker::ID: { + auto photo = static_cast(input_photo.get()); + TRY_RESULT_PROMISE(promise, sticker_photo_size, StickerPhotoSize::get_sticker_photo_size(td_, photo->sticker_)); + + td_->create_handler(std::move(promise)) + ->send(user_id, std::move(sticker_photo_size), is_fallback, only_suggest); + return; + } + default: + UNREACHABLE(); + break; + } + + const double MAX_ANIMATION_DURATION = 10.0; + if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) { + return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified")); + } + + auto file_type = is_animation ? FileType::Animation : FileType::Photo; + TRY_RESULT_PROMISE(promise, file_id, + td_->file_manager_->get_input_file_id(file_type, *input_file, DialogId(user_id), false, false)); + CHECK(file_id.is_valid()); + + upload_profile_photo(user_id, td_->file_manager_->dup_file_id(file_id, "set_profile_photo_impl"), is_fallback, + only_suggest, is_animation, main_frame_timestamp, std::move(promise)); +} + +void UserManager::set_user_profile_photo(UserId user_id, const td_api::object_ptr &input_photo, + bool only_suggest, Promise &&promise) { + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + if (!only_suggest && !is_user_contact(user_id)) { + return promise.set_error(Status::Error(400, "User isn't a contact")); + } + if (user_id == get_my_id()) { + return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to self")); + } + if (is_user_bot(user_id)) { + return promise.set_error(Status::Error(400, "Can't set personal or suggest photo to bots")); + } + if (input_photo == nullptr) { + td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); + return; + } + + set_profile_photo_impl(user_id, input_photo, false, only_suggest, std::move(promise)); +} + +void UserManager::send_update_profile_photo_query(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, + Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + FileView file_view = td_->file_manager_->get_file_view(file_id); + td_->create_handler(std::move(promise)) + ->send(user_id, file_id, old_photo_id, is_fallback, file_view.main_remote_location().as_input_photo()); +} + +void UserManager::upload_profile_photo(UserId user_id, FileId file_id, bool is_fallback, bool only_suggest, + bool is_animation, double main_frame_timestamp, Promise &&promise, + int reupload_count, vector bad_parts) { + CHECK(file_id.is_valid()); + bool is_inserted = + uploaded_profile_photos_ + .emplace(file_id, UploadedProfilePhoto{user_id, is_fallback, only_suggest, main_frame_timestamp, is_animation, + reupload_count, std::move(promise)}) + .second; + CHECK(is_inserted); + LOG(INFO) << "Ask to upload " << (is_animation ? "animated" : "static") << " profile photo " << file_id + << " for user " << user_id << " with bad parts " << bad_parts; + // TODO use force_reupload if reupload_count >= 1, replace reupload_count with is_reupload + td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_profile_photo_callback_, 32, 0); +} + +void UserManager::on_upload_profile_photo(FileId file_id, + telegram_api::object_ptr input_file) { + auto it = uploaded_profile_photos_.find(file_id); + CHECK(it != uploaded_profile_photos_.end()); + + UserId user_id = it->second.user_id; + bool is_fallback = it->second.is_fallback; + bool only_suggest = it->second.only_suggest; + double main_frame_timestamp = it->second.main_frame_timestamp; + bool is_animation = it->second.is_animation; + int32 reupload_count = it->second.reupload_count; + auto promise = std::move(it->second.promise); + + uploaded_profile_photos_.erase(it); + + LOG(INFO) << "Uploaded " << (is_animation ? "animated" : "static") << " profile photo " << file_id << " for " + << user_id << " with reupload_count = " << reupload_count; + FileView file_view = td_->file_manager_->get_file_view(file_id); + if (file_view.has_remote_location() && input_file == nullptr) { + if (file_view.main_remote_location().is_web()) { + return promise.set_error(Status::Error(400, "Can't use web photo as profile photo")); + } + if (reupload_count == 3) { // upload, ForceReupload repair file reference, reupload + return promise.set_error(Status::Error(400, "Failed to reupload the file")); + } + + // delete file reference and forcely reupload the file + if (is_animation) { + CHECK(file_view.get_type() == FileType::Animation); + LOG_CHECK(file_view.main_remote_location().is_common()) << file_view.main_remote_location(); + } else { + CHECK(file_view.get_type() == FileType::Photo); + LOG_CHECK(file_view.main_remote_location().is_photo()) << file_view.main_remote_location(); + } + auto file_reference = + is_animation ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_document()) + : FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo()); + td_->file_manager_->delete_file_reference(file_id, file_reference); + upload_profile_photo(user_id, file_id, is_fallback, only_suggest, is_animation, main_frame_timestamp, + std::move(promise), reupload_count + 1, {-1}); + return; + } + CHECK(input_file != nullptr); + + td_->create_handler(std::move(promise)) + ->send(user_id, file_id, std::move(input_file), is_fallback, only_suggest, is_animation, main_frame_timestamp); +} + +void UserManager::on_upload_profile_photo_error(FileId file_id, Status status) { + LOG(INFO) << "File " << file_id << " has upload error " << status; + CHECK(status.is_error()); + + auto it = uploaded_profile_photos_.find(file_id); + CHECK(it != uploaded_profile_photos_.end()); + + auto promise = std::move(it->second.promise); + + uploaded_profile_photos_.erase(it); + + promise.set_error(std::move(status)); // TODO check that status has valid error code +} + +void UserManager::on_set_profile_photo(UserId user_id, telegram_api::object_ptr &&photo, + bool is_fallback, int64 old_photo_id, Promise &&promise) { + LOG(INFO) << "Changed profile photo to " << to_string(photo); + + bool is_bot = is_user_bot(user_id); + bool is_my = (user_id == get_my_id()); + if (is_my && !is_fallback) { + delete_my_profile_photo_from_cache(old_photo_id); + } + bool have_user = false; + for (const auto &user : photo->users_) { + if (get_user_id(user) == user_id) { + have_user = true; + } + } + on_get_users(std::move(photo->users_), "on_set_profile_photo"); + if (!is_bot) { + add_set_profile_photo_to_cache(user_id, get_photo(td_, std::move(photo->photo_), DialogId(user_id)), is_fallback); + } + if (have_user) { + promise.set_value(Unit()); + } else { + reload_user(user_id, std::move(promise), "on_set_profile_photo"); + } +} + +void UserManager::add_set_profile_photo_to_cache(UserId user_id, Photo &&photo, bool is_fallback) { + // we have subsequence of user photos in user_photos_ + // ProfilePhoto in User and Photo in UserFull + + User *u = get_user_force(user_id, "add_set_profile_photo_to_cache"); + if (u == nullptr) { + return; + } + + LOG(INFO) << "Add profile photo " << photo.id.get() << " to cache"; + + bool is_me = user_id == get_my_id(); + + // update photo list + auto user_photos = user_photos_.get_pointer(user_id); + if (is_me && !is_fallback && user_photos != nullptr && user_photos->count != -1 && !photo.is_empty()) { + if (user_photos->offset == 0) { + if (user_photos->photos.empty() || user_photos->photos[0].id.get() != photo.id.get()) { + user_photos->photos.insert(user_photos->photos.begin(), photo); + user_photos->count++; + register_user_photo(u, user_id, user_photos->photos[0]); + } + } else { + user_photos->count++; + user_photos->offset++; + } + } + + // update ProfilePhoto in User + if ((!is_fallback || u->photo.id == 0) && !photo.is_empty()) { + do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, photo, !is_me), + false, "add_set_profile_photo_to_cache"); + update_user(u, user_id); + } + + // update Photo in UserFull + auto user_full = get_user_full_force(user_id, "add_set_profile_photo_to_cache"); + if (user_full != nullptr) { + Photo *current_photo = nullptr; + // don't update the changed photo if other photos aren't known to avoid having only some photos known + bool need_apply = get_user_full_profile_photo_id(user_full) > 0; + if (!is_me) { + current_photo = &user_full->personal_photo; + if (photo.is_empty()) { + // always can apply empty personal photo + need_apply = true; + } + } else if (!is_fallback) { + current_photo = &user_full->photo; + if (photo.is_empty()) { + // never can apply empty photo + need_apply = false; + } + } else { + current_photo = &user_full->fallback_photo; + if (photo.is_empty()) { + // always can apply empty fallback photo + need_apply = true; + } + } + if (*current_photo != photo && need_apply) { + LOG(INFO) << "Update full photo of " << user_id << " to " << photo; + *current_photo = photo; + user_full->is_changed = true; + if (is_me && !photo.is_empty()) { + if (!is_fallback) { + register_user_photo(u, user_id, photo); + } else { + register_suggested_profile_photo(photo); + } + } + drop_user_full_photos(user_full, user_id, u->photo.id, "add_set_profile_photo_to_cache"); // just in case + } + if (user_full->expires_at > 0.0) { + user_full->expires_at = 0.0; + user_full->need_save_to_database = true; + } + update_user_full(user_full, user_id, "add_set_profile_photo_to_cache"); + reload_user_full(user_id, Auto(), "add_set_profile_photo_to_cache"); + } +} + +bool UserManager::delete_my_profile_photo_from_cache(int64 profile_photo_id) { + if (profile_photo_id == 0 || profile_photo_id == -2) { + return false; + } + + // we have subsequence of user photos in user_photos_ + // ProfilePhoto in User and Photo in UserFull + + LOG(INFO) << "Delete profile photo " << profile_photo_id << " from cache"; + + auto user_id = get_my_id(); + User *u = get_user_force(user_id, "delete_my_profile_photo_from_cache"); + bool is_main_photo_deleted = u != nullptr && u->photo.id == profile_photo_id; + + // update photo list + auto user_photos = user_photos_.get_pointer(user_id); + if (user_photos != nullptr && user_photos->count > 0) { + auto old_size = user_photos->photos.size(); + if (td::remove_if(user_photos->photos, + [profile_photo_id](const auto &photo) { return photo.id.get() == profile_photo_id; })) { + auto removed_photos = old_size - user_photos->photos.size(); + CHECK(removed_photos > 0); + LOG_IF(ERROR, removed_photos != 1) << "Had " << removed_photos << " photos with ID " << profile_photo_id; + user_photos->count -= narrow_cast(removed_photos); + // offset was not changed + CHECK(user_photos->count >= 0); + } else { + // failed to find photo to remove from cache + // don't know how to adjust user_photos->offset, so drop photos cache + LOG(INFO) << "Drop photos of " << user_id; + user_photos->photos.clear(); + user_photos->count = -1; + user_photos->offset = -1; + } + } + bool have_new_photo = + user_photos != nullptr && user_photos->count != -1 && user_photos->offset == 0 && !user_photos->photos.empty(); + + auto user_full = get_user_full_force(user_id, "delete_my_profile_photo_from_cache"); + + // update ProfilePhoto in User + bool need_reget_user = false; + if (is_main_photo_deleted) { + if (have_new_photo) { + do_update_user_photo( + u, user_id, + as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, user_photos->photos[0], false), false, + "delete_my_profile_photo_from_cache"); + } else { + do_update_user_photo(u, user_id, ProfilePhoto(), false, "delete_my_profile_photo_from_cache 2"); + need_reget_user = user_photos == nullptr || user_photos->count != 0; + } + update_user(u, user_id); + + // update Photo in UserFull + if (user_full != nullptr) { + if (user_full->fallback_photo.id.get() == profile_photo_id) { + LOG(INFO) << "Drop full public photo of " << user_id; + user_full->photo = Photo(); + user_full->is_changed = true; + } else if (have_new_photo) { + if (user_full->photo.id.get() == profile_photo_id && user_photos->photos[0] != user_full->photo) { + LOG(INFO) << "Update full photo of " << user_id << " to " << user_photos->photos[0]; + user_full->photo = user_photos->photos[0]; + user_full->is_changed = true; + } + } else { + // repair UserFull photo + if (!user_full->photo.is_empty()) { + user_full->photo = Photo(); + user_full->is_changed = true; + } + if (!user_full->fallback_photo.is_empty()) { + user_full->fallback_photo = Photo(); + user_full->is_changed = true; + } + } + if (user_full->expires_at > 0.0) { + user_full->expires_at = 0.0; + user_full->need_save_to_database = true; + } + reload_user_full(user_id, Auto(), "delete_my_profile_photo_from_cache"); + update_user_full(user_full, user_id, "delete_my_profile_photo_from_cache"); + } + } + + return need_reget_user; +} + +void UserManager::delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + const UserFull *user_full = get_user_full_force(get_my_id(), "delete_profile_photo"); + if (user_full == nullptr) { + // must load UserFull first, because fallback photo can't be deleted via DeleteProfilePhotoQuery + if (is_recursive) { + return promise.set_error(Status::Error(500, "Failed to load UserFullInfo")); + } + auto reload_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), profile_photo_id, promise = std::move(promise)](Result result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &UserManager::delete_profile_photo, profile_photo_id, true, std::move(promise)); + }); + reload_user_full(get_my_id(), std::move(reload_promise), "delete_profile_photo"); + return; + } + if (user_full->photo.id.get() == profile_photo_id || user_full->fallback_photo.id.get() == profile_photo_id) { + td_->create_handler(std::move(promise)) + ->send(get_my_id(), FileId(), profile_photo_id, user_full->fallback_photo.id.get() == profile_photo_id, + telegram_api::make_object()); + return; + } + + td_->create_handler(std::move(promise))->send(profile_photo_id); +} + +void UserManager::on_delete_profile_photo(int64 profile_photo_id, Promise promise) { + bool need_reget_user = delete_my_profile_photo_from_cache(profile_photo_id); + if (need_reget_user && !G()->close_flag()) { + return reload_user(get_my_id(), std::move(promise), "on_delete_profile_photo"); + } + + promise.set_value(Unit()); +} + +void UserManager::set_username(const string &username, Promise &&promise) { + if (!username.empty() && !is_allowed_username(username)) { + return promise.set_error(Status::Error(400, "Username is invalid")); + } + td_->create_handler(std::move(promise))->send(username); +} + +void UserManager::toggle_username_is_active(string &&username, bool is_active, Promise &&promise) { + get_me(PromiseCreator::lambda([actor_id = actor_id(this), username = std::move(username), is_active, + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &UserManager::toggle_username_is_active_impl, std::move(username), is_active, + std::move(promise)); + } + })); +} + +void UserManager::toggle_username_is_active_impl(string &&username, bool is_active, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + const User *u = get_user(get_my_id()); + CHECK(u != nullptr); + if (!u->usernames.can_toggle(username)) { + return promise.set_error(Status::Error(400, "Wrong username specified")); + } + td_->create_handler(std::move(promise))->send(std::move(username), is_active); +} + +void UserManager::reorder_usernames(vector &&usernames, Promise &&promise) { + get_me(PromiseCreator::lambda([actor_id = actor_id(this), usernames = std::move(usernames), + promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &UserManager::reorder_usernames_impl, std::move(usernames), std::move(promise)); + } + })); +} + +void UserManager::reorder_usernames_impl(vector &&usernames, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + const User *u = get_user(get_my_id()); + CHECK(u != nullptr); + if (!u->usernames.can_reorder_to(usernames)) { + return promise.set_error(Status::Error(400, "Invalid username order specified")); + } + if (usernames.size() <= 1) { + return promise.set_value(Unit()); + } + td_->create_handler(std::move(promise))->send(std::move(usernames)); +} + +void UserManager::on_update_username_is_active(UserId user_id, string &&username, bool is_active, + Promise &&promise) { + User *u = get_user(user_id); + CHECK(u != nullptr); + if (!u->usernames.can_toggle(username)) { + return reload_user(user_id, std::move(promise), "on_update_username_is_active"); + } + on_update_user_usernames(u, user_id, u->usernames.toggle(username, is_active)); + update_user(u, user_id); + promise.set_value(Unit()); +} + +void UserManager::on_update_active_usernames_order(UserId user_id, vector &&usernames, + Promise &&promise) { + User *u = get_user(user_id); + CHECK(u != nullptr); + if (!u->usernames.can_reorder_to(usernames)) { + return reload_user(user_id, std::move(promise), "on_update_active_usernames_order"); + } + on_update_user_usernames(u, user_id, u->usernames.reorder_to(std::move(usernames))); + update_user(u, user_id); + promise.set_value(Unit()); +} + +void UserManager::toggle_bot_username_is_active(UserId bot_user_id, string &&username, bool is_active, + Promise &&promise) { + TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); + if (!bot_data.can_be_edited) { + return promise.set_error(Status::Error(400, "The bot can't be edited")); + } + const User *u = get_user(bot_user_id); + CHECK(u != nullptr); + if (!u->usernames.can_toggle(username)) { + return promise.set_error(Status::Error(400, "Wrong username specified")); + } + td_->create_handler(std::move(promise))->send(bot_user_id, std::move(username), is_active); +} + +void UserManager::reorder_bot_usernames(UserId bot_user_id, vector &&usernames, Promise &&promise) { + TRY_RESULT_PROMISE(promise, bot_data, get_bot_data(bot_user_id)); + if (!bot_data.can_be_edited) { + return promise.set_error(Status::Error(400, "The bot can't be edited")); + } + const User *u = get_user(bot_user_id); + CHECK(u != nullptr); + if (!u->usernames.can_reorder_to(usernames)) { + return promise.set_error(Status::Error(400, "Invalid username order specified")); + } + if (usernames.size() <= 1) { + return promise.set_value(Unit()); + } + td_->create_handler(std::move(promise))->send(bot_user_id, std::move(usernames)); +} + +void UserManager::set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise) { + if (!accent_color_id.is_valid()) { + return promise.set_error(Status::Error(400, "Invalid accent color identifier specified")); + } + if (accent_color_id == AccentColorId(get_my_id())) { + accent_color_id = AccentColorId(); + } + + td_->create_handler(std::move(promise))->send(false, accent_color_id, background_custom_emoji_id); +} + +void UserManager::set_profile_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise) { + td_->create_handler(std::move(promise))->send(true, accent_color_id, background_custom_emoji_id); +} + +void UserManager::on_update_accent_color_success(bool for_profile, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id) { + auto user_id = get_my_id(); + User *u = get_user_force(user_id, "on_update_accent_color_success"); + if (u == nullptr) { + return; + } + if (for_profile) { + on_update_user_profile_accent_color_id(u, user_id, accent_color_id); + on_update_user_profile_background_custom_emoji_id(u, user_id, background_custom_emoji_id); + } else { + on_update_user_accent_color_id(u, user_id, accent_color_id); + on_update_user_background_custom_emoji_id(u, user_id, background_custom_emoji_id); + } + update_user(u, user_id); +} + +void UserManager::set_birthdate(Birthdate &&birthdate, Promise &&promise) { + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::BirthdaySetup}, Promise()); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), birthdate, promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &UserManager::on_set_birthdate, birthdate, std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + }); + td_->create_handler(std::move(query_promise))->send(birthdate); +} + +void UserManager::on_set_birthdate(Birthdate birthdate, Promise &&promise) { + auto my_user_id = get_my_id(); + UserFull *user_full = get_user_full_force(my_user_id, "on_set_birthdate"); + if (user_full != nullptr && user_full->birthdate != birthdate) { + user_full->birthdate = std::move(birthdate); + user_full->is_changed = true; + update_user_full(user_full, my_user_id, "on_set_birthdate"); + } + promise.set_value(Unit()); +} + +void UserManager::set_personal_channel(DialogId dialog_id, Promise &&promise) { + ChannelId channel_id; + if (dialog_id != DialogId()) { + if (!td_->dialog_manager_->have_dialog_force(dialog_id, "set_personal_channel")) { + return promise.set_error(Status::Error(400, "Chat not found")); + } + 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")); + } + } + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), channel_id, promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &UserManager::on_set_personal_channel, channel_id, std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + }); + td_->create_handler(std::move(query_promise))->send(channel_id); +} + +void UserManager::on_set_personal_channel(ChannelId channel_id, Promise &&promise) { + auto my_user_id = get_my_id(); + UserFull *user_full = get_user_full_force(my_user_id, "on_set_personal_channel"); + if (user_full != nullptr && user_full->personal_channel_id != channel_id) { + user_full->personal_channel_id = channel_id; + user_full->is_changed = true; + update_user_full(user_full, my_user_id, "on_set_personal_channel"); + } + promise.set_value(Unit()); +} + +void UserManager::set_emoji_status(const EmojiStatus &emoji_status, Promise &&promise) { + if (!td_->option_manager_->get_option_boolean("is_premium")) { + return promise.set_error(Status::Error(400, "The method is available only to Telegram Premium users")); + } + add_recent_emoji_status(td_, emoji_status); + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), emoji_status, promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &UserManager::on_set_emoji_status, emoji_status, std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + }); + td_->create_handler(std::move(query_promise))->send(emoji_status); +} + +void UserManager::on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise) { + auto user_id = get_my_id(); + User *u = get_user(user_id); + if (u != nullptr) { + on_update_user_emoji_status(u, user_id, emoji_status); + update_user(u, user_id); + } + promise.set_value(Unit()); +} + +void UserManager::toggle_sponsored_messages(bool sponsored_enabled, Promise &&promise) { + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), sponsored_enabled, promise = std::move(promise)](Result result) mutable { + if (result.is_ok()) { + send_closure(actor_id, &UserManager::on_toggle_sponsored_messages, sponsored_enabled, std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + }); + td_->create_handler(std::move(query_promise))->send(sponsored_enabled); +} + +void UserManager::on_toggle_sponsored_messages(bool sponsored_enabled, Promise &&promise) { + auto my_user_id = get_my_id(); + UserFull *user_full = get_user_full_force(my_user_id, "on_toggle_sponsored_messages"); + if (user_full != nullptr && user_full->sponsored_enabled != sponsored_enabled) { + user_full->sponsored_enabled = sponsored_enabled; + user_full->is_changed = true; + update_user_full(user_full, my_user_id, "on_toggle_sponsored_messages"); + } + promise.set_value(Unit()); +} + +void UserManager::get_support_user(Promise> &&promise) { + if (support_user_id_.is_valid()) { + return promise.set_value(get_user_object(support_user_id_)); + } + + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + promise.set_error(result.move_as_error()); + } else { + send_closure(actor_id, &UserManager::on_get_support_user, result.move_as_ok(), std::move(promise)); + } + }); + td_->create_handler(std::move(query_promise))->send(); +} + +void UserManager::on_get_support_user(UserId user_id, Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + const User *u = get_user(user_id); + if (u == nullptr) { + return promise.set_error(Status::Error(500, "Can't find support user")); + } + if (!u->is_support) { + LOG(ERROR) << "Receive non-support " << user_id << ", but expected a support user"; + } + + support_user_id_ = user_id; + promise.set_value(get_user_object(user_id, u)); +} + +void UserManager::get_user_profile_photos(UserId user_id, int32 offset, int32 limit, + Promise> &&promise) { + if (offset < 0) { + return promise.set_error(Status::Error(400, "Parameter offset must be non-negative")); + } + if (limit <= 0) { + return promise.set_error(Status::Error(400, "Parameter limit must be positive")); + } + if (limit > MAX_GET_PROFILE_PHOTOS) { + limit = MAX_GET_PROFILE_PHOTOS; + } + + TRY_STATUS_PROMISE(promise, get_input_user(user_id)); + + auto *u = get_user(user_id); + if (u == nullptr) { + return promise.set_error(Status::Error(400, "User not found")); + } + + apply_pending_user_photo(u, user_id); + + auto user_photos = add_user_photos(user_id); + if (user_photos->count != -1) { // know photo count + CHECK(user_photos->offset != -1); + LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; + vector> photo_objects; + + if (offset >= user_photos->count) { + // offset if too big + return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); + } + + if (limit > user_photos->count - offset) { + limit = user_photos->count - offset; + } + + int32 cache_begin = user_photos->offset; + int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); + if (cache_begin <= offset && offset + limit <= cache_end) { + // answer query from cache + for (int i = 0; i < limit; i++) { + photo_objects.push_back( + get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + offset - cache_begin])); + } + return promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); + } + } + + PendingGetPhotoRequest pending_request; + pending_request.offset = offset; + pending_request.limit = limit; + pending_request.promise = std::move(promise); + user_photos->pending_requests.push_back(std::move(pending_request)); + if (user_photos->pending_requests.size() != 1u) { + return; + } + + send_get_user_photos_query(user_id, user_photos); +} + +void UserManager::send_get_user_photos_query(UserId user_id, const UserPhotos *user_photos) { + CHECK(!user_photos->pending_requests.empty()); + auto offset = user_photos->pending_requests[0].offset; + auto limit = user_photos->pending_requests[0].limit; + + if (user_photos->count != -1 && offset >= user_photos->offset) { + int32 cache_end = user_photos->offset + narrow_cast(user_photos->photos.size()); + if (offset < cache_end) { + // adjust offset to the end of cache + CHECK(offset + limit > cache_end); // otherwise the request has already been answered + limit = offset + limit - cache_end; + offset = cache_end; + } + } + + auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), user_id](Result &&result) { + send_closure(actor_id, &UserManager::on_get_user_profile_photos, user_id, std::move(result)); + }); + + td_->create_handler(std::move(query_promise)) + ->send(user_id, get_input_user_force(user_id), offset, max(limit, MAX_GET_PROFILE_PHOTOS / 5), 0); +} + +void UserManager::on_get_user_profile_photos(UserId user_id, Result &&result) { + G()->ignore_result_if_closing(result); + auto user_photos = add_user_photos(user_id); + auto pending_requests = std::move(user_photos->pending_requests); + CHECK(!pending_requests.empty()); + if (result.is_error()) { + for (auto &request : pending_requests) { + request.promise.set_error(result.error().clone()); + } + return; + } + if (user_photos->count == -1) { + CHECK(have_user(user_id)); + // received result has just been dropped; resend request + if (++pending_requests[0].retry_count >= 3) { + pending_requests[0].promise.set_error(Status::Error(500, "Failed to return profile photos")); + pending_requests.erase(pending_requests.begin()); + if (pending_requests.empty()) { + return; + } + } + user_photos->pending_requests = std::move(pending_requests); + return send_get_user_photos_query(user_id, user_photos); + } + + CHECK(user_photos->offset != -1); + LOG(INFO) << "Have " << user_photos->count << " cached user profile photos at offset " << user_photos->offset; + vector left_requests; + for (size_t request_index = 0; request_index < pending_requests.size(); request_index++) { + auto &request = pending_requests[request_index]; + vector> photo_objects; + + if (request.offset >= user_photos->count) { + // offset if too big + request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); + continue; + } + + if (request.limit > user_photos->count - request.offset) { + request.limit = user_photos->count - request.offset; + } + + int32 cache_begin = user_photos->offset; + int32 cache_end = cache_begin + narrow_cast(user_photos->photos.size()); + if (cache_begin <= request.offset && request.offset + request.limit <= cache_end) { + // answer query from cache + for (int i = 0; i < request.limit; i++) { + photo_objects.push_back( + get_chat_photo_object(td_->file_manager_.get(), user_photos->photos[i + request.offset - cache_begin])); + } + request.promise.set_value(td_api::make_object(user_photos->count, std::move(photo_objects))); + continue; + } + + if (request_index == 0 && ++request.retry_count >= 3) { + request.promise.set_error(Status::Error(500, "Failed to get profile photos")); + continue; + } + + left_requests.push_back(std::move(request)); + } + + if (!left_requests.empty()) { + bool need_send = user_photos->pending_requests.empty(); + append(user_photos->pending_requests, std::move(left_requests)); + if (need_send) { + send_get_user_photos_query(user_id, user_photos); + } + } +} + +void UserManager::reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise) { + get_user_force(user_id, "reload_user_profile_photo"); + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + + // this request will be needed only to download the photo, + // so there is no reason to combine different requests for a photo into one request + td_->create_handler(std::move(promise))->send(user_id, std::move(input_user), -1, 1, photo_id); +} + +FileSourceId UserManager::get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id) { + if (!user_id.is_valid()) { + return FileSourceId(); + } + + auto u = get_user(user_id); + if (u != nullptr && u->photo_ids.count(photo_id) != 0) { + VLOG(file_references) << "Don't need to create file source for photo " << photo_id << " of " << user_id; + // photo was already added, source ID was registered and shouldn't be needed + return FileSourceId(); + } + + auto &source_id = user_profile_photo_file_source_ids_[std::make_pair(user_id, photo_id)]; + if (!source_id.is_valid()) { + source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id); + } + VLOG(file_references) << "Return " << source_id << " for photo " << photo_id << " of " << user_id; + return source_id; +} + +UserManager::UserPhotos *UserManager::add_user_photos(UserId user_id) { + CHECK(user_id.is_valid()); + auto &user_photos_ptr = user_photos_[user_id]; + if (user_photos_ptr == nullptr) { + user_photos_ptr = make_unique(); + } + return user_photos_ptr.get(); +} + +void UserManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, + vector> photos) { + auto photo_count = narrow_cast(photos.size()); + int32 min_total_count = (offset >= 0 && photo_count > 0 ? offset : 0) + photo_count; + if (total_count < min_total_count) { + LOG(ERROR) << "Receive wrong photos total_count " << total_count << " for user " << user_id << ": receive " + << photo_count << " photos with offset " << offset; + total_count = min_total_count; + } + LOG_IF(ERROR, limit < photo_count) << "Requested not more than " << limit << " photos, but " << photo_count + << " received"; + + User *u = get_user(user_id); + if (u == nullptr) { + LOG(ERROR) << "Can't find " << user_id; + return; + } + + if (offset == -1) { + // from reload_user_profile_photo + CHECK(limit == 1); + for (auto &photo_ptr : photos) { + if (photo_ptr->get_id() == telegram_api::photo::ID) { + auto server_photo = telegram_api::move_object_as(photo_ptr); + if (server_photo->id_ == u->photo.id) { + auto profile_photo = convert_photo_to_profile_photo(server_photo, u->photo.is_personal); + if (profile_photo) { + LOG_IF(ERROR, u->access_hash == -1) << "Receive profile photo of " << user_id << " without access hash"; + get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(profile_photo)); + } else { + LOG(ERROR) << "Failed to get profile photo from " << to_string(server_photo); + } + } + + auto photo = get_photo(td_, std::move(server_photo), DialogId(user_id)); + register_user_photo(u, user_id, photo); + } + } + return; + } + + LOG(INFO) << "Receive " << photo_count << " photos of " << user_id << " out of " << total_count << " with offset " + << offset << " and limit " << limit; + UserPhotos *user_photos = add_user_photos(user_id); + user_photos->count = total_count; + CHECK(!user_photos->pending_requests.empty()); + + if (user_photos->offset == -1) { + user_photos->offset = 0; + CHECK(user_photos->photos.empty()); + } + + if (offset != narrow_cast(user_photos->photos.size()) + user_photos->offset) { + LOG(INFO) << "Inappropriate offset to append " << user_id << " profile photos to cache: offset = " << offset + << ", current_offset = " << user_photos->offset << ", photo_count = " << user_photos->photos.size(); + user_photos->photos.clear(); + user_photos->offset = offset; + } + + for (auto &photo : photos) { + auto user_photo = get_photo(td_, std::move(photo), DialogId(user_id)); + if (user_photo.is_empty()) { + LOG(ERROR) << "Receive empty profile photo in getUserPhotos request for " << user_id << " with offset " << offset + << " and limit " << limit << ". Receive " << photo_count << " photos out of " << total_count + << " photos"; + user_photos->count--; + CHECK(user_photos->count >= 0); + continue; + } + + user_photos->photos.push_back(std::move(user_photo)); + register_user_photo(u, user_id, user_photos->photos.back()); + } + if (user_photos->offset > user_photos->count) { + user_photos->offset = user_photos->count; + user_photos->photos.clear(); + } + + auto known_photo_count = narrow_cast(user_photos->photos.size()); + if (user_photos->offset + known_photo_count > user_photos->count) { + user_photos->photos.resize(user_photos->count - user_photos->offset); + } +} + +void UserManager::apply_pending_user_photo(User *u, UserId user_id) { + if (u == nullptr || u->is_photo_inited) { + return; + } + + if (pending_user_photos_.count(user_id) > 0) { + do_update_user_photo(u, user_id, std::move(pending_user_photos_[user_id]), "apply_pending_user_photo"); + pending_user_photos_.erase(user_id); + update_user(u, user_id); + } +} + +void UserManager::register_message_users(MessageFullId message_full_id, vector user_ids) { + auto dialog_id = message_full_id.get_dialog_id(); + CHECK(dialog_id.get_type() == DialogType::Channel); + if (!td_->chat_manager_->have_channel(dialog_id.get_channel_id())) { + return; + } + for (auto user_id : user_ids) { + CHECK(user_id.is_valid()); + const User *u = get_user(user_id); + if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { + auto &user_messages = user_messages_[user_id]; + auto need_update = user_messages.empty(); + user_messages.insert(message_full_id); + if (need_update) { + send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); + } + } + } +} + +void UserManager::unregister_message_users(MessageFullId message_full_id, vector user_ids) { + if (user_messages_.empty()) { + // fast path + return; + } + for (auto user_id : user_ids) { + auto it = user_messages_.find(user_id); + if (it != user_messages_.end()) { + it->second.erase(message_full_id); + if (it->second.empty()) { + user_messages_.erase(it); + + const User *u = get_user(user_id); + if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) { + send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); + } + } + } + } +} + +void UserManager::can_send_message_to_user(UserId user_id, bool force, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + if (user_id == get_my_id()) { + return promise.set_value(td_api::make_object()); + } + const auto *u = get_user(user_id); + if (!have_input_peer_user(u, user_id, AccessRights::Write)) { + return promise.set_value(td_api::make_object()); + } + CHECK(user_id.is_valid()); + if ((u != nullptr && (!u->contact_require_premium || u->is_mutual_contact)) || + td_->option_manager_->get_option_boolean("is_premium")) { + return promise.set_value(td_api::make_object()); + } + + auto user_full = get_user_full_force(user_id, "can_send_message_to_user"); + if (user_full != nullptr) { + if (!user_full->contact_require_premium) { + return promise.set_value(td_api::make_object()); + } + return promise.set_value(td_api::make_object()); + } + + auto it = user_full_contact_require_premium_.find(user_id); + if (it != user_full_contact_require_premium_.end()) { + if (!it->second) { + return promise.set_value(td_api::make_object()); + } + return promise.set_value(td_api::make_object()); + } + + if (force) { + return promise.set_value(td_api::make_object()); + } + + auto query_promise = PromiseCreator::lambda( + [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&result) mutable { + if (result.is_error()) { + return promise.set_error(result.move_as_error()); + } + send_closure(actor_id, &UserManager::can_send_message_to_user, user_id, true, std::move(promise)); + }); + get_is_premium_required_to_contact_queries_.add_query(user_id.get(), std::move(query_promise), + "can_send_message_to_user"); +} + +void UserManager::on_get_is_premium_required_to_contact_users(vector &&user_ids, + vector &&is_premium_required, + Promise &&promise) { + if (user_ids.size() != is_premium_required.size()) { + LOG(ERROR) << "Receive " << is_premium_required.size() << " flags instead of " << user_ids.size(); + return promise.set_error(Status::Error(500, "Receive invalid response")); + } + for (size_t i = 0; i < user_ids.size(); i++) { + auto user_id = user_ids[i]; + CHECK(user_id.is_valid()); + if (get_user_full(user_id) == nullptr) { + user_full_contact_require_premium_[user_id] = is_premium_required[i]; + } + } + promise.set_value(Unit()); +} + +void UserManager::allow_send_message_to_user(UserId user_id) { + if (get_user_full(user_id) == nullptr) { + CHECK(user_id.is_valid()); + user_full_contact_require_premium_[user_id] = true; + } +} + +void UserManager::share_phone_number(UserId user_id, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!are_contacts_loaded_) { + load_contacts(PromiseCreator::lambda( + [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result &&) mutable { + send_closure(actor_id, &UserManager::share_phone_number, user_id, std::move(promise)); + })); + return; + } + + LOG(INFO) << "Share phone number with " << user_id; + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + + td_->messages_manager_->hide_dialog_action_bar(DialogId(user_id)); + + td_->create_handler(std::move(promise))->send(user_id, std::move(input_user)); +} + +void UserManager::load_contacts(Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + are_contacts_loaded_ = true; + saved_contact_count_ = 0; + } + if (are_contacts_loaded_ && saved_contact_count_ != -1) { + LOG(INFO) << "Contacts are already loaded"; + return promise.set_value(Unit()); + } + load_contacts_queries_.push_back(std::move(promise)); + if (load_contacts_queries_.size() == 1u) { + if (G()->use_chat_info_database() && next_contacts_sync_date_ > 0 && saved_contact_count_ != -1) { + LOG(INFO) << "Load contacts from database"; + G()->td_db()->get_sqlite_pmc()->get( + "user_contacts", PromiseCreator::lambda([](string value) { + send_closure(G()->user_manager(), &UserManager::on_load_contacts_from_database, std::move(value)); + })); + } else { + LOG(INFO) << "Load contacts from server"; + reload_contacts(true); + } + } else { + LOG(INFO) << "Load contacts request has already been sent"; + } +} + +int64 UserManager::get_contacts_hash() { + if (!are_contacts_loaded_) { + return 0; + } + + vector user_ids = contacts_hints_.search_empty(100000).second; + CHECK(std::is_sorted(user_ids.begin(), user_ids.end())); + auto my_id = get_my_id(); + const User *u = get_user_force(my_id, "get_contacts_hash"); + if (u != nullptr && u->is_contact) { + user_ids.insert(std::upper_bound(user_ids.begin(), user_ids.end(), my_id.get()), my_id.get()); + } + + vector numbers; + numbers.reserve(user_ids.size() + 1); + numbers.push_back(saved_contact_count_); + for (auto user_id : user_ids) { + numbers.push_back(user_id); + } + return get_vector_hash(numbers); +} + +void UserManager::reload_contacts(bool force) { + if (!G()->close_flag() && !td_->auth_manager_->is_bot() && + next_contacts_sync_date_ != std::numeric_limits::max() && + (next_contacts_sync_date_ < G()->unix_time() || force)) { + next_contacts_sync_date_ = std::numeric_limits::max(); + td_->create_handler()->send(get_contacts_hash()); + } +} + +void UserManager::save_next_contacts_sync_date() { + if (G()->close_flag()) { + return; + } + if (!G()->use_chat_info_database()) { + return; + } + G()->td_db()->get_binlog_pmc()->set("next_contacts_sync_date", to_string(next_contacts_sync_date_)); +} + +void UserManager::save_contacts_to_database() { + if (!G()->use_chat_info_database() || !are_contacts_loaded_) { + return; + } + + LOG(INFO) << "Schedule save contacts to database"; + vector user_ids = + transform(contacts_hints_.search_empty(100000).second, [](int64 key) { return UserId(key); }); + + G()->td_db()->get_binlog_pmc()->set("saved_contact_count", to_string(saved_contact_count_)); + G()->td_db()->get_binlog()->force_sync( + PromiseCreator::lambda([user_ids = std::move(user_ids)](Result<> result) { + if (result.is_ok()) { + LOG(INFO) << "Saved contacts to database"; + G()->td_db()->get_sqlite_pmc()->set( + "user_contacts", log_event_store(user_ids).as_slice().str(), PromiseCreator::lambda([](Result<> result) { + if (result.is_ok()) { + send_closure(G()->user_manager(), &UserManager::save_next_contacts_sync_date); + } + })); + } + }), + "save_contacts_to_database"); +} + +void UserManager::on_get_contacts(telegram_api::object_ptr &&new_contacts) { + next_contacts_sync_date_ = G()->unix_time() + Random::fast(70000, 100000); + + CHECK(new_contacts != nullptr); + if (new_contacts->get_id() == telegram_api::contacts_contactsNotModified::ID) { + if (saved_contact_count_ == -1) { + saved_contact_count_ = 0; + } + on_get_contacts_finished(contacts_hints_.size()); + td_->create_handler()->send(); + return; + } + + auto contacts = move_tl_object_as(new_contacts); + FlatHashSet contact_user_ids; + for (auto &user : contacts->users_) { + auto user_id = get_user_id(user); + if (!user_id.is_valid()) { + LOG(ERROR) << "Receive invalid " << user_id; + continue; + } + contact_user_ids.insert(user_id); + } + on_get_users(std::move(contacts->users_), "on_get_contacts"); + + UserId my_id = get_my_id(); + users_.foreach([&](const UserId &user_id, unique_ptr &user) { + User *u = user.get(); + bool should_be_contact = contact_user_ids.count(user_id) == 1; + if (u->is_contact != should_be_contact) { + if (u->is_contact) { + LOG(INFO) << "Drop contact with " << user_id; + if (user_id != my_id) { + LOG_CHECK(contacts_hints_.has_key(user_id.get())) + << my_id << " " << user_id << " " << to_string(get_user_object(user_id, u)); + } + on_update_user_is_contact(u, user_id, false, false, false); + CHECK(u->is_is_contact_changed); + u->cache_version = 0; + u->is_repaired = false; + update_user(u, user_id); + CHECK(!u->is_contact); + if (user_id != my_id) { + CHECK(!contacts_hints_.has_key(user_id.get())); + } + } else { + LOG(ERROR) << "Receive non-contact " << user_id << " in the list of contacts"; + } + } + }); + + saved_contact_count_ = contacts->saved_count_; + on_get_contacts_finished(std::numeric_limits::max()); +} + +void UserManager::on_get_contacts_failed(Status error) { + CHECK(error.is_error()); + next_contacts_sync_date_ = G()->unix_time() + Random::fast(5, 10); + fail_promises(load_contacts_queries_, std::move(error)); +} + +void UserManager::on_load_contacts_from_database(string value) { + if (G()->close_flag()) { + return; + } + if (value.empty()) { + reload_contacts(true); + return; + } + + vector user_ids; + if (log_event_parse(user_ids, value).is_error()) { + LOG(ERROR) << "Failed to load contacts from database"; + reload_contacts(true); + return; + } + + if (log_event_get_version(value) < static_cast(Version::AddUserFlags2)) { + next_contacts_sync_date_ = 0; + save_next_contacts_sync_date(); + reload_contacts(true); + } + + LOG(INFO) << "Successfully loaded " << user_ids.size() << " contacts from database"; + + load_contact_users_multipromise_.add_promise(PromiseCreator::lambda( + [actor_id = actor_id(this), expected_contact_count = user_ids.size()](Result result) { + if (result.is_ok()) { + send_closure(actor_id, &UserManager::on_get_contacts_finished, expected_contact_count); + } else { + LOG(INFO) << "Failed to load contact users from database: " << result.error(); + send_closure(actor_id, &UserManager::reload_contacts, true); + } + })); + + auto lock_promise = load_contact_users_multipromise_.get_promise(); + + for (auto user_id : user_ids) { + get_user(user_id, 3, load_contact_users_multipromise_.get_promise()); + } + + lock_promise.set_value(Unit()); +} + +void UserManager::on_get_contacts_finished(size_t expected_contact_count) { + LOG(INFO) << "Finished to get " << contacts_hints_.size() << " contacts out of expected " << expected_contact_count; + are_contacts_loaded_ = true; + set_promises(load_contacts_queries_); + if (expected_contact_count != contacts_hints_.size()) { + save_contacts_to_database(); + } +} + +void UserManager::on_get_contacts_statuses(vector> &&statuses) { + auto my_user_id = get_my_id(); + for (auto &status : statuses) { + UserId user_id(status->user_id_); + if (user_id != my_user_id) { + on_update_user_online(user_id, std::move(status->status_)); + } + } + save_next_contacts_sync_date(); +} + +void UserManager::add_contact(Contact contact, bool share_phone_number, Promise &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + + if (!are_contacts_loaded_) { + load_contacts(PromiseCreator::lambda([actor_id = actor_id(this), contact = std::move(contact), share_phone_number, + promise = std::move(promise)](Result &&) mutable { + send_closure(actor_id, &UserManager::add_contact, std::move(contact), share_phone_number, std::move(promise)); + })); + return; + } + + LOG(INFO) << "Add " << contact << " with share_phone_number = " << share_phone_number; + + auto user_id = contact.get_user_id(); + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + + td_->create_handler(std::move(promise)) + ->send(user_id, std::move(input_user), contact, share_phone_number); +} + +std::pair, vector> UserManager::import_contacts(const vector &contacts, int64 &random_id, + Promise &&promise) { + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return {}; + } + + LOG(INFO) << "Asked to import " << contacts.size() << " contacts with random_id = " << random_id; + if (random_id != 0) { + // request has already been sent before + auto it = imported_contacts_.find(random_id); + CHECK(it != imported_contacts_.end()); + auto result = std::move(it->second); + imported_contacts_.erase(it); + + promise.set_value(Unit()); + return result; + } + + do { + random_id = Random::secure_int64(); + } while (random_id == 0 || random_id == 1 || imported_contacts_.count(random_id) > 0); + imported_contacts_[random_id]; // reserve place for result + + do_import_contacts(contacts, random_id, std::move(promise)); + return {}; +} + +void UserManager::do_import_contacts(vector contacts, int64 random_id, Promise &&promise) { + size_t size = contacts.size(); + if (size == 0) { + on_import_contacts_finished(random_id, {}, {}); + return promise.set_value(Unit()); + } + + vector> input_phone_contacts; + input_phone_contacts.reserve(size); + for (size_t i = 0; i < size; i++) { + input_phone_contacts.push_back(contacts[i].get_input_phone_contact(static_cast(i))); + } + + auto task = make_unique(); + task->promise_ = std::move(promise); + task->input_contacts_ = std::move(contacts); + task->imported_user_ids_.resize(size); + task->unimported_contact_invites_.resize(size); + + bool is_added = import_contact_tasks_.emplace(random_id, std::move(task)).second; + CHECK(is_added); + + td_->create_handler()->send(std::move(input_phone_contacts), random_id); +} + +void UserManager::on_imported_contacts( + int64 random_id, Result> result) { + auto it = import_contact_tasks_.find(random_id); + CHECK(it != import_contact_tasks_.end()); + CHECK(it->second != nullptr); + + auto task = it->second.get(); + if (result.is_error()) { + auto promise = std::move(task->promise_); + import_contact_tasks_.erase(it); + return promise.set_error(result.move_as_error()); + } + + auto imported_contacts = result.move_as_ok(); + on_get_users(std::move(imported_contacts->users_), "on_imported_contacts"); + + for (auto &imported_contact : imported_contacts->imported_) { + int64 client_id = imported_contact->client_id_; + if (client_id < 0 || client_id >= static_cast(task->imported_user_ids_.size())) { + LOG(ERROR) << "Wrong client_id " << client_id << " returned"; + continue; + } + + task->imported_user_ids_[static_cast(client_id)] = UserId(imported_contact->user_id_); + } + for (auto &popular_contact : imported_contacts->popular_invites_) { + int64 client_id = popular_contact->client_id_; + if (client_id < 0 || client_id >= static_cast(task->unimported_contact_invites_.size())) { + LOG(ERROR) << "Wrong client_id " << client_id << " returned"; + continue; + } + if (popular_contact->importers_ < 0) { + LOG(ERROR) << "Wrong number of importers " << popular_contact->importers_ << " returned"; + continue; + } + + task->unimported_contact_invites_[static_cast(client_id)] = popular_contact->importers_; + } + + if (!imported_contacts->retry_contacts_.empty()) { + auto total_size = static_cast(task->input_contacts_.size()); + vector> input_phone_contacts; + input_phone_contacts.reserve(imported_contacts->retry_contacts_.size()); + for (auto &client_id : imported_contacts->retry_contacts_) { + if (client_id < 0 || client_id >= total_size) { + LOG(ERROR) << "Wrong client_id " << client_id << " returned"; + continue; + } + auto i = static_cast(client_id); + input_phone_contacts.push_back(task->input_contacts_[i].get_input_phone_contact(client_id)); + } + td_->create_handler()->send(std::move(input_phone_contacts), random_id); + return; + } + + auto promise = std::move(task->promise_); + on_import_contacts_finished(random_id, std::move(task->imported_user_ids_), + std::move(task->unimported_contact_invites_)); + import_contact_tasks_.erase(it); + promise.set_value(Unit()); +} + +void UserManager::on_import_contacts_finished(int64 random_id, vector imported_contact_user_ids, + vector unimported_contact_invites) { + LOG(INFO) << "Contacts import with random_id " << random_id + << " has finished: " << format::as_array(imported_contact_user_ids); + if (random_id == 1) { + // import from change_imported_contacts + all_imported_contacts_ = std::move(next_all_imported_contacts_); + next_all_imported_contacts_.clear(); + + auto result_size = imported_contacts_unique_id_.size(); + auto unique_size = all_imported_contacts_.size(); + auto add_size = imported_contacts_pos_.size(); + + imported_contact_user_ids_.resize(result_size); + unimported_contact_invites_.resize(result_size); + + CHECK(imported_contact_user_ids.size() == add_size); + CHECK(unimported_contact_invites.size() == add_size); + CHECK(imported_contacts_unique_id_.size() == result_size); + + std::unordered_map> unique_id_to_unimported_contact_invites; + for (size_t i = 0; i < add_size; i++) { + auto unique_id = imported_contacts_pos_[i]; + get_user_id_object(imported_contact_user_ids[i], "on_import_contacts_finished"); // to ensure updateUser + all_imported_contacts_[unique_id].set_user_id(imported_contact_user_ids[i]); + unique_id_to_unimported_contact_invites[narrow_cast(unique_id)] = unimported_contact_invites[i]; + } + + if (G()->use_chat_info_database()) { + G()->td_db()->get_binlog()->force_sync( + PromiseCreator::lambda( + [log_event = log_event_store(all_imported_contacts_).as_slice().str()](Result<> result) mutable { + if (result.is_ok()) { + LOG(INFO) << "Save imported contacts to database"; + G()->td_db()->get_sqlite_pmc()->set("user_imported_contacts", std::move(log_event), Auto()); + } + }), + "on_import_contacts_finished"); + } + + for (size_t i = 0; i < result_size; i++) { + auto unique_id = imported_contacts_unique_id_[i]; + CHECK(unique_id < unique_size); + imported_contact_user_ids_[i] = all_imported_contacts_[unique_id].get_user_id(); + auto it = unique_id_to_unimported_contact_invites.find(narrow_cast(unique_id)); + if (it == unique_id_to_unimported_contact_invites.end()) { + unimported_contact_invites_[i] = 0; + } else { + unimported_contact_invites_[i] = it->second; + } + } + return; + } + + auto it = imported_contacts_.find(random_id); + CHECK(it != imported_contacts_.end()); + CHECK(it->second.first.empty()); + CHECK(it->second.second.empty()); + imported_contacts_[random_id] = {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; +} + +void UserManager::remove_contacts(const vector &user_ids, Promise &&promise) { + LOG(INFO) << "Delete contacts: " << format::as_array(user_ids); + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return; + } + + vector to_delete_user_ids; + vector> input_users; + for (auto &user_id : user_ids) { + const User *u = get_user(user_id); + if (u != nullptr && u->is_contact) { + auto r_input_user = get_input_user(user_id); + if (r_input_user.is_ok()) { + to_delete_user_ids.push_back(user_id); + input_users.push_back(r_input_user.move_as_ok()); + } + } + } + + if (input_users.empty()) { + return promise.set_value(Unit()); + } + + td_->create_handler(std::move(promise))->send(std::move(input_users)); +} + +void UserManager::remove_contacts_by_phone_number(vector user_phone_numbers, vector user_ids, + Promise &&promise) { + LOG(INFO) << "Delete contacts by phone number: " << format::as_array(user_phone_numbers); + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return; + } + + td_->create_handler(std::move(promise)) + ->send(std::move(user_phone_numbers), std::move(user_ids)); +} + +void UserManager::on_deleted_contacts(const vector &deleted_contact_user_ids) { + LOG(INFO) << "Contacts deletion has finished for " << deleted_contact_user_ids; + + for (auto user_id : deleted_contact_user_ids) { + auto u = get_user(user_id); + CHECK(u != nullptr); + if (!u->is_contact) { + continue; + } + + LOG(INFO) << "Drop contact with " << user_id; + on_update_user_is_contact(u, user_id, false, false, false); + CHECK(u->is_is_contact_changed); + u->cache_version = 0; + u->is_repaired = false; + update_user(u, user_id); + CHECK(!u->is_contact); + CHECK(!contacts_hints_.has_key(user_id.get())); + } +} + +int32 UserManager::get_imported_contact_count(Promise &&promise) { + LOG(INFO) << "Get imported contact count"; + + if (!are_contacts_loaded_ || saved_contact_count_ == -1) { + load_contacts(std::move(promise)); + return 0; + } + reload_contacts(false); + + promise.set_value(Unit()); + return saved_contact_count_; +} + +void UserManager::load_imported_contacts(Promise &&promise) { + if (td_->auth_manager_->is_bot()) { + are_imported_contacts_loaded_ = true; + } + if (are_imported_contacts_loaded_) { + LOG(INFO) << "Imported contacts are already loaded"; + promise.set_value(Unit()); + return; + } + load_imported_contacts_queries_.push_back(std::move(promise)); + if (load_imported_contacts_queries_.size() == 1u) { + if (G()->use_chat_info_database()) { + LOG(INFO) << "Load imported contacts from database"; + G()->td_db()->get_sqlite_pmc()->get("user_imported_contacts", PromiseCreator::lambda([](string value) { + send_closure_later(G()->user_manager(), + &UserManager::on_load_imported_contacts_from_database, + std::move(value)); + })); + } else { + LOG(INFO) << "Have no previously imported contacts"; + send_closure_later(G()->user_manager(), &UserManager::on_load_imported_contacts_from_database, string()); + } + } else { + LOG(INFO) << "Load imported contacts request has already been sent"; + } +} + +void UserManager::on_load_imported_contacts_from_database(string value) { + if (G()->close_flag()) { + return; + } + + CHECK(!are_imported_contacts_loaded_); + if (need_clear_imported_contacts_) { + need_clear_imported_contacts_ = false; + value.clear(); + } + if (value.empty()) { + CHECK(all_imported_contacts_.empty()); + } else { + if (log_event_parse(all_imported_contacts_, value).is_error()) { + LOG(ERROR) << "Failed to load all imported contacts from database"; + all_imported_contacts_.clear(); + } else { + LOG(INFO) << "Successfully loaded " << all_imported_contacts_.size() << " imported contacts from database"; + } + } + + load_imported_contact_users_multipromise_.add_promise( + PromiseCreator::lambda([actor_id = actor_id(this)](Result result) { + if (result.is_ok()) { + send_closure_later(actor_id, &UserManager::on_load_imported_contacts_finished); + } + })); + + auto lock_promise = load_imported_contact_users_multipromise_.get_promise(); + + for (const auto &contact : all_imported_contacts_) { + auto user_id = contact.get_user_id(); + if (user_id.is_valid()) { + get_user(user_id, 3, load_imported_contact_users_multipromise_.get_promise()); + } + } + + lock_promise.set_value(Unit()); +} + +void UserManager::on_load_imported_contacts_finished() { + LOG(INFO) << "Finished to load " << all_imported_contacts_.size() << " imported contacts"; + + for (const auto &contact : all_imported_contacts_) { + get_user_id_object(contact.get_user_id(), "on_load_imported_contacts_finished"); // to ensure updateUser + } + + if (need_clear_imported_contacts_) { + need_clear_imported_contacts_ = false; + all_imported_contacts_.clear(); + } + are_imported_contacts_loaded_ = true; + set_promises(load_imported_contacts_queries_); +} + +std::pair, vector> UserManager::change_imported_contacts(vector &contacts, + int64 &random_id, + Promise &&promise) { + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return {}; + } + if (!are_imported_contacts_loaded_) { + load_imported_contacts(std::move(promise)); + return {}; + } + + LOG(INFO) << "Asked to change imported contacts to a list of " << contacts.size() + << " contacts with random_id = " << random_id; + if (random_id != 0) { + // request has already been sent before + if (need_clear_imported_contacts_) { + need_clear_imported_contacts_ = false; + all_imported_contacts_.clear(); + if (G()->use_chat_info_database()) { + G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); + } + reload_contacts(true); + } + + CHECK(are_imported_contacts_changing_); + are_imported_contacts_changing_ = false; + + auto unimported_contact_invites = std::move(unimported_contact_invites_); + unimported_contact_invites_.clear(); + + auto imported_contact_user_ids = std::move(imported_contact_user_ids_); + imported_contact_user_ids_.clear(); + + promise.set_value(Unit()); + return {std::move(imported_contact_user_ids), std::move(unimported_contact_invites)}; + } + + if (are_imported_contacts_changing_) { + promise.set_error(Status::Error(400, "ChangeImportedContacts can be called only once at the same time")); + return {}; + } + + vector new_contacts_unique_id(contacts.size()); + vector unique_new_contacts; + unique_new_contacts.reserve(contacts.size()); + std::unordered_map different_new_contacts; + std::unordered_set> different_new_phone_numbers; + size_t unique_size = 0; + for (size_t i = 0; i < contacts.size(); i++) { + auto it_success = different_new_contacts.emplace(std::move(contacts[i]), unique_size); + new_contacts_unique_id[i] = it_success.first->second; + if (it_success.second) { + unique_new_contacts.push_back(it_success.first->first); + different_new_phone_numbers.insert(unique_new_contacts.back().get_phone_number()); + unique_size++; + } + } + + vector to_delete; + vector to_delete_user_ids; + for (auto &old_contact : all_imported_contacts_) { + auto user_id = old_contact.get_user_id(); + auto it = different_new_contacts.find(old_contact); + if (it == different_new_contacts.end()) { + auto phone_number = old_contact.get_phone_number(); + if (different_new_phone_numbers.count(phone_number) == 0) { + to_delete.push_back(std::move(phone_number)); + if (user_id.is_valid()) { + to_delete_user_ids.push_back(user_id); + } + } + } else { + unique_new_contacts[it->second].set_user_id(user_id); + different_new_contacts.erase(it); + } + } + std::pair, vector> to_add; + for (auto &new_contact : different_new_contacts) { + to_add.first.push_back(new_contact.second); + to_add.second.push_back(new_contact.first); + } + + if (to_add.first.empty() && to_delete.empty()) { + for (size_t i = 0; i < contacts.size(); i++) { + auto unique_id = new_contacts_unique_id[i]; + contacts[i].set_user_id(unique_new_contacts[unique_id].get_user_id()); + } + + promise.set_value(Unit()); + return {transform(contacts, [&](const Contact &contact) { return contact.get_user_id(); }), + vector(contacts.size())}; + } + + are_imported_contacts_changing_ = true; + random_id = 1; + + remove_contacts_by_phone_number( + std::move(to_delete), std::move(to_delete_user_ids), + PromiseCreator::lambda([new_contacts = std::move(unique_new_contacts), + new_contacts_unique_id = std::move(new_contacts_unique_id), to_add = std::move(to_add), + promise = std::move(promise)](Result<> result) mutable { + if (result.is_ok()) { + send_closure_later(G()->user_manager(), &UserManager::on_clear_imported_contacts, std::move(new_contacts), + std::move(new_contacts_unique_id), std::move(to_add), std::move(promise)); + } else { + promise.set_error(result.move_as_error()); + } + })); + return {}; +} + +void UserManager::clear_imported_contacts(Promise &&promise) { + LOG(INFO) << "Delete imported contacts"; + + if (saved_contact_count_ == 0) { + promise.set_value(Unit()); + return; + } + + td_->create_handler(std::move(promise))->send(); +} + +void UserManager::on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, + std::pair, vector> &&to_add, + Promise &&promise) { + LOG(INFO) << "Add " << to_add.first.size() << " contacts"; + next_all_imported_contacts_ = std::move(contacts); + imported_contacts_unique_id_ = std::move(contacts_unique_id); + imported_contacts_pos_ = std::move(to_add.first); + + do_import_contacts(std::move(to_add.second), 1, std::move(promise)); +} + +void UserManager::on_update_contacts_reset() { + /* + UserId my_id = get_my_id(); + users_.foreach([&](const UserId &user_id, unique_ptr &user) { + User *u = user.get(); + if (u->is_contact) { + LOG(INFO) << "Drop contact with " << user_id; + if (user_id != my_id) { + CHECK(contacts_hints_.has_key(user_id.get())); + } + on_update_user_is_contact(u, user_id, false, false, false); + CHECK(u->is_is_contact_changed); + u->cache_version = 0; + u->is_repaired = false; + update_user(u, user_id); + CHECK(!u->is_contact); + if (user_id != my_id) { + CHECK(!contacts_hints_.has_key(user_id.get())); + } + } + }); + */ + + saved_contact_count_ = 0; + if (G()->use_chat_info_database()) { + G()->td_db()->get_binlog_pmc()->set("saved_contact_count", "0"); + G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto()); + } + if (!are_imported_contacts_loaded_) { + if (load_imported_contacts_queries_.empty()) { + CHECK(all_imported_contacts_.empty()); + LOG(INFO) << "Imported contacts were never loaded, just clear them"; + } else { + LOG(INFO) << "Imported contacts are being loaded, clear them after they will be loaded"; + need_clear_imported_contacts_ = true; + } + } else { + if (!are_imported_contacts_changing_) { + LOG(INFO) << "Imported contacts were loaded, but aren't changing now, just clear them"; + all_imported_contacts_.clear(); + } else { + LOG(INFO) << "Imported contacts are changing now, clear them after they will be changed"; + need_clear_imported_contacts_ = true; + } + } + reload_contacts(true); +} + +void UserManager::update_contacts_hints(const User *u, UserId user_id, bool from_database) { + bool is_contact = is_user_contact(u, user_id, false); + if (td_->auth_manager_->is_bot()) { + LOG_IF(ERROR, is_contact) << "Bot has " << user_id << " in the contacts list"; + return; + } + + int64 key = user_id.get(); + string old_value = contacts_hints_.key_to_string(key); + string new_value = is_contact ? get_user_search_text(u) : string(); + + if (new_value != old_value) { + if (is_contact) { + contacts_hints_.add(key, new_value); + } else { + contacts_hints_.remove(key); + } + } + + if (G()->use_chat_info_database()) { + // update contacts database + if (!are_contacts_loaded_) { + if (!from_database && load_contacts_queries_.empty() && is_contact && u->is_is_contact_changed) { + search_contacts("", std::numeric_limits::max(), Auto()); + } + } else { + if (old_value.empty() == is_contact) { + save_contacts_to_database(); + } + } + } +} + +std::pair> UserManager::search_contacts(const string &query, int32 limit, + Promise &&promise) { + LOG(INFO) << "Search contacts with query = \"" << query << "\" and limit = " << limit; + + if (limit < 0) { + promise.set_error(Status::Error(400, "Limit must be non-negative")); + return {}; + } + + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return {}; + } + reload_contacts(false); + + std::pair> result; + if (query.empty()) { + result = contacts_hints_.search_empty(limit); + } else { + result = contacts_hints_.search(query, limit); + } + + vector user_ids; + user_ids.reserve(result.second.size()); + for (auto key : result.second) { + user_ids.emplace_back(key); + } + + promise.set_value(Unit()); + return {narrow_cast(result.first), std::move(user_ids)}; +} + +void UserManager::reload_contact_birthdates(bool force) { + if (td_->option_manager_->get_option_boolean("dismiss_birthday_contact_today")) { + contact_birthdates_.need_drop_ = true; + if (!contact_birthdates_.is_being_synced_) { + contact_birthdates_.is_being_synced_ = true; + on_get_contact_birthdates(nullptr); + } + return; + } + if (!G()->close_flag() && !td_->auth_manager_->is_bot() && !contact_birthdates_.is_being_synced_ && + (contact_birthdates_.next_sync_time_ < Time::now() || force)) { + contact_birthdates_.is_being_synced_ = true; + td_->create_handler()->send(); + } +} + +void UserManager::on_get_contact_birthdates( + telegram_api::object_ptr &&birthdays) { + CHECK(contact_birthdates_.is_being_synced_); + contact_birthdates_.is_being_synced_ = false; + if (contact_birthdates_.need_drop_) { + birthdays = telegram_api::make_object(Auto(), Auto()); + contact_birthdates_.need_drop_ = false; + } + if (birthdays == nullptr) { + contact_birthdates_.next_sync_time_ = Time::now() + Random::fast(120, 180); + return; + } + contact_birthdates_.next_sync_time_ = Time::now() + Random::fast(86400 / 4, 86400 / 3); + + on_get_users(std::move(birthdays->users_), "on_get_contact_birthdates"); + vector> users; + for (auto &contact : birthdays->contacts_) { + UserId user_id(contact->contact_id_); + if (is_user_contact(user_id)) { + Birthdate birthdate(std::move(contact->birthday_)); + UserFull *user_full = get_user_full_force(user_id, "on_get_contact_birthdates"); + if (user_full != nullptr && user_full->birthdate != birthdate) { + user_full->birthdate = birthdate; + user_full->is_changed = true; + update_user_full(user_full, user_id, "on_get_contact_birthdates"); + } + if (!birthdate.is_empty()) { + users.emplace_back(user_id, birthdate); + } + } + } + if (contact_birthdates_.users_ != users) { + contact_birthdates_.users_ = std::move(users); + send_closure(G()->td(), &Td::send_update, get_update_contact_close_birthdays()); + } + // there is no need to save them between restarts +} + +void UserManager::hide_contact_birthdays(Promise &&promise) { + td_->create_handler(std::move(promise))->send(); +} + +vector UserManager::get_close_friends(Promise &&promise) { + if (!are_contacts_loaded_) { + load_contacts(std::move(promise)); + return {}; + } + reload_contacts(false); + + auto result = contacts_hints_.search_empty(10000); + + vector user_ids; + for (auto key : result.second) { + UserId user_id(key); + const User *u = get_user(user_id); + if (u != nullptr && u->is_close_friend) { + user_ids.push_back(user_id); + } + } + + promise.set_value(Unit()); + return user_ids; +} + +void UserManager::set_close_friends(vector user_ids, Promise &&promise) { + for (auto &user_id : user_ids) { + if (!have_user(user_id)) { + return promise.set_error(Status::Error(400, "User not found")); + } + } + + td_->create_handler(std::move(promise))->send(std::move(user_ids)); +} + +void UserManager::on_set_close_friends(const vector &user_ids, Promise &&promise) { + FlatHashSet close_friend_user_ids; + for (auto &user_id : user_ids) { + CHECK(user_id.is_valid()); + close_friend_user_ids.insert(user_id); + } + users_.foreach([&](const UserId &user_id, unique_ptr &user) { + User *u = user.get(); + if (u->is_contact && u->is_close_friend != (close_friend_user_ids.count(user_id) > 0)) { + on_update_user_is_contact(u, user_id, u->is_contact, u->is_mutual_contact, !u->is_close_friend); + update_user(u, user_id); + } + }); + promise.set_value(Unit()); +} + +UserId UserManager::search_user_by_phone_number(string phone_number, bool only_local, Promise &&promise) { + clean_phone_number(phone_number); + if (phone_number.empty()) { + promise.set_error(Status::Error(200, "Phone number is invalid")); + return UserId(); + } + + auto it = resolved_phone_numbers_.find(phone_number); + if (it != resolved_phone_numbers_.end()) { + promise.set_value(Unit()); + return it->second; + } + + if (only_local) { + promise.set_value(Unit()); + } else { + td_->create_handler(std::move(promise))->send(phone_number); + } + return UserId(); +} + +void UserManager::on_resolved_phone_number(const string &phone_number, UserId user_id) { + if (!user_id.is_valid()) { + resolved_phone_numbers_.emplace(phone_number, UserId()); // negative cache + return; + } + + auto it = resolved_phone_numbers_.find(phone_number); + if (it != resolved_phone_numbers_.end()) { + if (it->second != user_id) { + LOG(WARNING) << "Resolve phone number \"" << phone_number << "\" to " << user_id << ", but have it in " + << it->second; + it->second = user_id; + } + return; + } + + auto *u = get_user(user_id); + if (u == nullptr) { + LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to unknown " << user_id; + } else if (!u->phone_number.empty()) { + LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to " << user_id << " with phone number " + << u->phone_number; + } else { + // the user's phone number can be hidden by privacy settings, despite the user can be found by the phone number + } + resolved_phone_numbers_[phone_number] = user_id; // always update cached value +} + +const UserManager::UserFull *UserManager::get_user_full(UserId user_id) const { + return users_full_.get_pointer(user_id); +} + +UserManager::UserFull *UserManager::get_user_full(UserId user_id) { + return users_full_.get_pointer(user_id); +} + +UserManager::UserFull *UserManager::add_user_full(UserId user_id) { + CHECK(user_id.is_valid()); + auto &user_full_ptr = users_full_[user_id]; + if (user_full_ptr == nullptr) { + user_full_ptr = make_unique(); + user_full_contact_require_premium_.erase(user_id); + } + return user_full_ptr.get(); +} + +UserManager::UserFull *UserManager::get_user_full_force(UserId user_id, const char *source) { + if (!have_user_force(user_id, source)) { + return nullptr; + } + + UserFull *user_full = get_user_full(user_id); + if (user_full != nullptr) { + return user_full; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (!unavailable_user_fulls_.insert(user_id).second) { + return nullptr; + } + + LOG(INFO) << "Trying to load full " << user_id << " from database from " << source; + on_load_user_full_from_database(user_id, + G()->td_db()->get_sqlite_sync_pmc()->get(get_user_full_database_key(user_id))); + return get_user_full(user_id); +} + +void UserManager::load_user_full(UserId user_id, bool force, Promise &&promise, const char *source) { + auto u = get_user(user_id); + if (u == nullptr) { + return promise.set_error(Status::Error(400, "User not found")); + } + + auto user_full = get_user_full_force(user_id, source); + if (user_full == nullptr) { + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); + } + if (user_full->is_expired()) { + auto input_user = get_input_user_force(user_id); + if (td_->auth_manager_->is_bot() && !force) { + return send_get_user_full_query(user_id, std::move(input_user), std::move(promise), "load expired user_full"); + } + + send_get_user_full_query(user_id, std::move(input_user), Auto(), "load expired user_full"); + } + + td_->story_manager_->on_view_dialog_active_stories({DialogId(user_id)}); + promise.set_value(Unit()); +} + +void UserManager::reload_user_full(UserId user_id, Promise &&promise, const char *source) { + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + send_get_user_full_query(user_id, std::move(input_user), std::move(promise), source); +} + +void UserManager::send_get_user_full_query(UserId user_id, + telegram_api::object_ptr &&input_user, + Promise &&promise, const char *source) { + LOG(INFO) << "Get full " << user_id << " from " << source; + if (!user_id.is_valid()) { + return promise.set_error(Status::Error(500, "Invalid user_id")); + } + auto send_query = + PromiseCreator::lambda([td = td_, input_user = std::move(input_user)](Result> &&promise) mutable { + if (promise.is_ok() && !G()->close_flag()) { + td->create_handler(promise.move_as_ok())->send(std::move(input_user)); + } + }); + get_user_full_queries_.add_query(user_id.get(), std::move(send_query), std::move(promise)); +} + +void UserManager::on_get_user_full(telegram_api::object_ptr &&user) { + LOG(INFO) << "Receive " << to_string(user); + + UserId user_id(user->id_); + User *u = get_user(user_id); + if (u == nullptr) { + LOG(ERROR) << "Failed to find " << user_id; + return; + } + + apply_pending_user_photo(u, user_id); + + td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user->notify_settings_), + "on_get_user_full"); + + td_->messages_manager_->on_update_dialog_background(DialogId(user_id), std::move(user->wallpaper_)); + + td_->messages_manager_->on_update_dialog_theme_name(DialogId(user_id), std::move(user->theme_emoticon_)); + + td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(user_id), + MessageId(ServerMessageId(user->pinned_msg_id_))); + + td_->messages_manager_->on_update_dialog_folder_id(DialogId(user_id), FolderId(user->folder_id_)); + + td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(DialogId(user_id), user->has_scheduled_); + + td_->messages_manager_->on_update_dialog_message_ttl(DialogId(user_id), MessageTtl(user->ttl_period_)); + + td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), user->blocked_, + user->blocked_my_stories_from_); + + td_->messages_manager_->on_update_dialog_is_translatable(DialogId(user_id), !user->translations_disabled_); + + send_closure_later(td_->story_manager_actor_, &StoryManager::on_get_dialog_stories, DialogId(user_id), + std::move(user->stories_), Promise()); + + UserFull *user_full = add_user_full(user_id); + user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME; + + on_update_user_full_is_blocked(user_full, user_id, user->blocked_, user->blocked_my_stories_from_); + on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_); + on_update_user_full_location(user_full, user_id, DialogLocation(td_, std::move(user->business_location_))); + on_update_user_full_work_hours(user_full, user_id, BusinessWorkHours(std::move(user->business_work_hours_))); + on_update_user_full_away_message(user_full, user_id, BusinessAwayMessage(std::move(user->business_away_message_))); + on_update_user_full_greeting_message(user_full, user_id, + BusinessGreetingMessage(std::move(user->business_greeting_message_))); + on_update_user_full_intro(user_full, user_id, BusinessIntro(td_, std::move(user->business_intro_))); + on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, + user->settings_->need_contacts_exception_); + on_update_user_full_wallpaper_overridden(user_full, user_id, user->wallpaper_overridden_); + + bool can_pin_messages = user->can_pin_message_; + bool can_be_called = user->phone_calls_available_ && !user->phone_calls_private_; + bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_; + bool has_private_calls = user->phone_calls_private_; + bool voice_messages_forbidden = u->is_premium ? user->voice_messages_forbidden_ : false; + auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_)); + AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup); + AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast); + bool has_pinned_stories = user->stories_pinned_available_; + auto birthdate = Birthdate(std::move(user->birthday_)); + auto personal_channel_id = ChannelId(user->personal_channel_id_); + auto sponsored_enabled = user->sponsored_enabled_; + if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls || + user_full->has_private_calls != has_private_calls || + user_full->group_administrator_rights != group_administrator_rights || + user_full->broadcast_administrator_rights != broadcast_administrator_rights || + user_full->premium_gift_options != premium_gift_options || + user_full->voice_messages_forbidden != voice_messages_forbidden || + user_full->can_pin_messages != can_pin_messages || user_full->has_pinned_stories != has_pinned_stories || + user_full->sponsored_enabled != sponsored_enabled) { + user_full->can_be_called = can_be_called; + user_full->supports_video_calls = supports_video_calls; + user_full->has_private_calls = has_private_calls; + user_full->group_administrator_rights = group_administrator_rights; + user_full->broadcast_administrator_rights = broadcast_administrator_rights; + user_full->premium_gift_options = std::move(premium_gift_options); + user_full->voice_messages_forbidden = voice_messages_forbidden; + user_full->can_pin_messages = can_pin_messages; + user_full->has_pinned_stories = has_pinned_stories; + user_full->sponsored_enabled = sponsored_enabled; + + user_full->is_changed = true; + } + if (user_full->birthdate != birthdate) { + user_full->birthdate = birthdate; + user_full->is_changed = true; + + if (u->is_mutual_contact) { + reload_contact_birthdates(true); + } + } + + if (user_full->private_forward_name != user->private_forward_name_) { + if (user_full->private_forward_name.empty() != user->private_forward_name_.empty()) { + user_full->is_changed = true; + } + user_full->private_forward_name = std::move(user->private_forward_name_); + user_full->need_save_to_database = true; + } + if (user_full->read_dates_private != user->read_dates_private_ || + user_full->contact_require_premium != user->contact_require_premium_) { + user_full->read_dates_private = user->read_dates_private_; + user_full->contact_require_premium = user->contact_require_premium_; + user_full->need_save_to_database = true; + } + if (user_full->about != user->about_) { + user_full->about = std::move(user->about_); + user_full->is_changed = true; + td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); + } + string description; + Photo description_photo; + FileId description_animation_file_id; + if (user->bot_info_ != nullptr && !td_->auth_manager_->is_bot()) { + description = std::move(user->bot_info_->description_); + description_photo = get_photo(td_, std::move(user->bot_info_->description_photo_), DialogId(user_id)); + auto document = std::move(user->bot_info_->description_document_); + if (document != nullptr) { + int32 document_id = document->get_id(); + if (document_id == telegram_api::document::ID) { + auto parsed_document = td_->documents_manager_->on_get_document( + move_tl_object_as(document), DialogId(user_id)); + if (parsed_document.type == Document::Type::Animation) { + description_animation_file_id = parsed_document.file_id; + } else { + LOG(ERROR) << "Receive non-animation document in bot description"; + } + } + } + + on_update_user_full_commands(user_full, user_id, std::move(user->bot_info_->commands_)); + on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_)); + } + if (user_full->description != description) { + user_full->description = std::move(description); + user_full->is_changed = true; + } + if (user_full->description_photo != description_photo || + user_full->description_animation_file_id != description_animation_file_id) { + user_full->description_photo = std::move(description_photo); + user_full->description_animation_file_id = description_animation_file_id; + user_full->is_changed = true; + } + if (personal_channel_id != ChannelId() && + td_->chat_manager_->get_channel_type(personal_channel_id) != ChannelType::Broadcast) { + LOG(ERROR) << "Receive personal " << personal_channel_id << " of the type " + << static_cast(td_->chat_manager_->get_channel_type(personal_channel_id)); + personal_channel_id = ChannelId(); + } + if (user_full->personal_channel_id != personal_channel_id) { + user_full->personal_channel_id = personal_channel_id; + user_full->is_changed = true; + } + if (user_full->personal_channel_id != ChannelId()) { + auto personal_message_id = MessageId(ServerMessageId(user->personal_channel_message_)); + td_->messages_manager_->get_channel_difference_if_needed(DialogId(user_full->personal_channel_id), + personal_message_id, "on_get_user_full personal chat"); + } + + auto photo = get_photo(td_, std::move(user->profile_photo_), DialogId(user_id)); + auto personal_photo = get_photo(td_, std::move(user->personal_photo_), DialogId(user_id)); + auto fallback_photo = get_photo(td_, std::move(user->fallback_photo_), DialogId(user_id)); + // do_update_user_photo should be a no-op if server sent consistent data + const Photo *photo_ptr = nullptr; + bool is_personal = false; + if (!personal_photo.is_empty()) { + photo_ptr = &personal_photo; + is_personal = true; + } else if (!photo.is_empty()) { + photo_ptr = &photo; + } else { + photo_ptr = &fallback_photo; + } + bool is_photo_empty = photo_ptr->is_empty(); + do_update_user_photo(u, user_id, + as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, *photo_ptr, is_personal), + false, "on_get_user_full"); + if (photo != user_full->photo) { + user_full->photo = std::move(photo); + user_full->is_changed = true; + } + if (personal_photo != user_full->personal_photo) { + user_full->personal_photo = std::move(personal_photo); + user_full->is_changed = true; + } + if (fallback_photo != user_full->fallback_photo) { + user_full->fallback_photo = std::move(fallback_photo); + user_full->is_changed = true; + } + if (!user_full->photo.is_empty()) { + register_user_photo(u, user_id, user_full->photo); + } + if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { + register_suggested_profile_photo(user_full->fallback_photo); + } + if (is_photo_empty) { + drop_user_photos(user_id, true, "on_get_user_full"); + } + + // User must be updated before UserFull + if (u->is_changed) { + LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << user_id; + update_user(u, user_id); + } + + user_full->is_update_user_full_sent = true; + update_user_full(user_full, user_id, "on_get_user_full"); + + // update peer settings after UserFull is created and updated to not update twice need_phone_number_privacy_exception + td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user->settings_)); +} + +FileSourceId UserManager::get_user_full_file_source_id(UserId user_id) { + if (!user_id.is_valid()) { + return FileSourceId(); + } + + auto user_full = get_user_full(user_id); + if (user_full != nullptr) { + VLOG(file_references) << "Don't need to create file source for full " << user_id; + // user full was already added, source ID was registered and shouldn't be needed + return user_full->is_update_user_full_sent ? FileSourceId() : user_full->file_source_id; + } + + auto &source_id = user_full_file_source_ids_[user_id]; + if (!source_id.is_valid()) { + source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); + } + VLOG(file_references) << "Return " << source_id << " for full " << user_id; + return source_id; +} + +void UserManager::save_user_full(const UserFull *user_full, UserId user_id) { + if (!G()->use_chat_info_database()) { + return; + } + + LOG(INFO) << "Trying to save to database full " << user_id; + CHECK(user_full != nullptr); + G()->td_db()->get_sqlite_pmc()->set(get_user_full_database_key(user_id), get_user_full_database_value(user_full), + Auto()); +} + +string UserManager::get_user_full_database_key(UserId user_id) { + return PSTRING() << "usf" << user_id.get(); +} + +string UserManager::get_user_full_database_value(const UserFull *user_full) { + return log_event_store(*user_full).as_slice().str(); +} + +void UserManager::on_load_user_full_from_database(UserId user_id, string value) { + LOG(INFO) << "Successfully loaded full " << user_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); + // return; + + if (get_user_full(user_id) != nullptr || value.empty()) { + return; + } + + UserFull *user_full = add_user_full(user_id); + auto status = log_event_parse(*user_full, value); + if (status.is_error()) { + // can't happen unless database is broken + LOG(ERROR) << "Repair broken full " << user_id << ' ' << format::as_hex_dump<4>(Slice(value)); + + // just clean all known data about the user and pretend that there was nothing in the database + users_full_.erase(user_id); + G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); + return; + } + + Dependencies dependencies; + dependencies.add(user_id); + if (user_full->business_info != nullptr) { + user_full->business_info->add_dependencies(dependencies); + } + dependencies.add(user_full->personal_channel_id); + if (!dependencies.resolve_force(td_, "on_load_user_full_from_database")) { + users_full_.erase(user_id); + G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto()); + return; + } + + if (user_full->need_phone_number_privacy_exception && is_user_contact(user_id)) { + user_full->need_phone_number_privacy_exception = false; + } + + User *u = get_user(user_id); + CHECK(u != nullptr); + drop_user_full_photos(user_full, user_id, u->photo.id, "on_load_user_full_from_database"); + if (!user_full->photo.is_empty()) { + register_user_photo(u, user_id, user_full->photo); + } + if (user_id == get_my_id() && !user_full->fallback_photo.is_empty()) { + register_suggested_profile_photo(user_full->fallback_photo); + } + + td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, false); + + user_full->is_update_user_full_sent = true; + update_user_full(user_full, user_id, "on_load_user_full_from_database", true); + + if (is_user_deleted(u)) { + drop_user_full(user_id); + } else if (user_full->expires_at == 0.0) { + reload_user_full(user_id, Auto(), "on_load_user_full_from_database"); + } +} + +int64 UserManager::get_user_full_profile_photo_id(const UserFull *user_full) { + if (!user_full->personal_photo.is_empty()) { + return user_full->personal_photo.id.get(); + } + if (!user_full->photo.is_empty()) { + return user_full->photo.id.get(); + } + return user_full->fallback_photo.id.get(); +} + +void UserManager::drop_user_full_photos(UserFull *user_full, UserId user_id, int64 expected_photo_id, + const char *source) { + if (user_full == nullptr) { + return; + } + LOG(INFO) << "Expect full photo " << expected_photo_id << " from " << source; + for (auto photo_ptr : {&user_full->personal_photo, &user_full->photo, &user_full->fallback_photo}) { + if (photo_ptr->is_empty()) { + continue; + } + if (expected_photo_id == 0) { + // if profile photo is empty, we must drop the full photo + *photo_ptr = Photo(); + user_full->is_changed = true; + } else if (expected_photo_id != photo_ptr->id.get()) { + LOG(INFO) << "Drop full photo " << photo_ptr->id.get(); + // if full profile photo is unknown, we must drop the full photo + *photo_ptr = Photo(); + user_full->is_changed = true; + } else { + // nothing to drop + break; + } + } + if (expected_photo_id != get_user_full_profile_photo_id(user_full)) { + user_full->expires_at = 0.0; + } + if (user_full->is_update_user_full_sent) { + update_user_full(user_full, user_id, "drop_user_full_photos"); + } +} + +void UserManager::drop_user_photos(UserId user_id, bool is_empty, const char *source) { + LOG(INFO) << "Drop user photos to " << (is_empty ? "empty" : "unknown") << " from " << source; + auto user_photos = user_photos_.get_pointer(user_id); + if (user_photos != nullptr) { + int32 new_count = is_empty ? 0 : -1; + if (user_photos->count == new_count) { + CHECK(user_photos->photos.empty()); + CHECK(user_photos->offset == user_photos->count); + } else { + LOG(INFO) << "Drop photos of " << user_id << " to " << (is_empty ? "empty" : "unknown") << " from " << source; + user_photos->photos.clear(); + user_photos->count = new_count; + user_photos->offset = user_photos->count; + } + } +} + +void UserManager::drop_user_full(UserId user_id) { + auto user_full = get_user_full_force(user_id, "drop_user_full"); + + drop_user_photos(user_id, false, "drop_user_full"); + + if (user_full == nullptr) { + return; + } + + user_full->expires_at = 0.0; + + user_full->photo = Photo(); + user_full->personal_photo = Photo(); + user_full->fallback_photo = Photo(); + // user_full->is_blocked = false; + // user_full->is_blocked_for_stories = false; + user_full->can_be_called = false; + user_full->supports_video_calls = false; + user_full->has_private_calls = false; + user_full->need_phone_number_privacy_exception = false; + user_full->wallpaper_overridden = false; + user_full->about = string(); + user_full->description = string(); + user_full->description_photo = Photo(); + user_full->description_animation_file_id = FileId(); + user_full->menu_button = nullptr; + user_full->commands.clear(); + user_full->common_chat_count = 0; + user_full->personal_channel_id = ChannelId(); + user_full->business_info = nullptr; + user_full->private_forward_name.clear(); + user_full->group_administrator_rights = {}; + user_full->broadcast_administrator_rights = {}; + user_full->premium_gift_options.clear(); + user_full->voice_messages_forbidden = false; + user_full->has_pinned_stories = false; + user_full->read_dates_private = false; + user_full->contact_require_premium = false; + user_full->birthdate = {}; + user_full->sponsored_enabled = false; + user_full->is_changed = true; + + update_user_full(user_full, user_id, "drop_user_full"); + td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true); +} + +bool UserManager::have_secret_chat(SecretChatId secret_chat_id) const { + return secret_chats_.count(secret_chat_id) > 0; +} + +const UserManager::SecretChat *UserManager::get_secret_chat(SecretChatId secret_chat_id) const { + return secret_chats_.get_pointer(secret_chat_id); +} + +UserManager::SecretChat *UserManager::get_secret_chat(SecretChatId secret_chat_id) { + return secret_chats_.get_pointer(secret_chat_id); +} + +UserManager::SecretChat *UserManager::add_secret_chat(SecretChatId secret_chat_id) { + CHECK(secret_chat_id.is_valid()); + auto &secret_chat_ptr = secret_chats_[secret_chat_id]; + if (secret_chat_ptr == nullptr) { + secret_chat_ptr = make_unique(); + } + return secret_chat_ptr.get(); +} + +bool UserManager::have_secret_chat_force(SecretChatId secret_chat_id, const char *source) { + return get_secret_chat_force(secret_chat_id, source) != nullptr; +} + +UserManager::SecretChat *UserManager::get_secret_chat_force(SecretChatId secret_chat_id, const char *source) { + if (!secret_chat_id.is_valid()) { + return nullptr; + } + + SecretChat *c = get_secret_chat(secret_chat_id); + if (c != nullptr) { + if (!have_user_force(c->user_id, source)) { + LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id << " from " << source; + } + return c; + } + if (!G()->use_chat_info_database()) { + return nullptr; + } + if (loaded_from_database_secret_chats_.count(secret_chat_id)) { + return nullptr; + } + + LOG(INFO) << "Trying to load " << secret_chat_id << " from database from " << source; + on_load_secret_chat_from_database( + secret_chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_secret_chat_database_key(secret_chat_id)), true); + return get_secret_chat(secret_chat_id); +} + +bool UserManager::get_secret_chat(SecretChatId secret_chat_id, bool force, Promise &&promise) { + if (!secret_chat_id.is_valid()) { + promise.set_error(Status::Error(400, "Invalid secret chat identifier")); + return false; + } + + if (!have_secret_chat(secret_chat_id)) { + if (!force && G()->use_chat_info_database()) { + send_closure_later(actor_id(this), &UserManager::load_secret_chat_from_database, nullptr, secret_chat_id, + std::move(promise)); + return false; + } + + promise.set_error(Status::Error(400, "Secret chat not found")); + return false; + } + + promise.set_value(Unit()); + return true; +} + +void UserManager::save_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog) { + if (!G()->use_chat_info_database()) { + return; + } + CHECK(c != nullptr); + if (!c->is_saved) { + if (!from_binlog) { + auto log_event = SecretChatLogEvent(secret_chat_id, c); + auto storer = get_log_event_storer(log_event); + if (c->log_event_id == 0) { + c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SecretChatInfos, storer); + } else { + binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::SecretChatInfos, storer); + } + } + + save_secret_chat_to_database(c, secret_chat_id); + return; + } +} + +string UserManager::get_secret_chat_database_key(SecretChatId secret_chat_id) { + return PSTRING() << "sc" << secret_chat_id.get(); +} + +string UserManager::get_secret_chat_database_value(const SecretChat *c) { + return log_event_store(*c).as_slice().str(); +} + +void UserManager::save_secret_chat_to_database(SecretChat *c, SecretChatId secret_chat_id) { + CHECK(c != nullptr); + if (c->is_being_saved) { + return; + } + if (loaded_from_database_secret_chats_.count(secret_chat_id)) { + save_secret_chat_to_database_impl(c, secret_chat_id, get_secret_chat_database_value(c)); + return; + } + if (load_secret_chat_from_database_queries_.count(secret_chat_id) != 0) { + return; + } + + load_secret_chat_from_database_impl(secret_chat_id, Auto()); +} + +void UserManager::save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value) { + CHECK(c != nullptr); + CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); + CHECK(!c->is_being_saved); + c->is_being_saved = true; + c->is_saved = true; + LOG(INFO) << "Trying to save to database " << secret_chat_id; + G()->td_db()->get_sqlite_pmc()->set(get_secret_chat_database_key(secret_chat_id), std::move(value), + PromiseCreator::lambda([secret_chat_id](Result<> result) { + send_closure(G()->user_manager(), &UserManager::on_save_secret_chat_to_database, + secret_chat_id, result.is_ok()); + })); +} + +void UserManager::on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success) { + if (G()->close_flag()) { + return; + } + + SecretChat *c = get_secret_chat(secret_chat_id); + CHECK(c != nullptr); + CHECK(c->is_being_saved); + CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0); + c->is_being_saved = false; + + if (!success) { + LOG(ERROR) << "Failed to save " << secret_chat_id << " to database"; + c->is_saved = false; + } else { + LOG(INFO) << "Successfully saved " << secret_chat_id << " to database"; + } + if (c->is_saved) { + if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } else { + save_secret_chat(c, secret_chat_id, c->log_event_id != 0); + } +} + +void UserManager::load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, Promise promise) { + if (loaded_from_database_secret_chats_.count(secret_chat_id)) { + promise.set_value(Unit()); + return; + } + + CHECK(c == nullptr || !c->is_being_saved); + load_secret_chat_from_database_impl(secret_chat_id, std::move(promise)); +} + +void UserManager::load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise promise) { + LOG(INFO) << "Load " << secret_chat_id << " from database"; + auto &load_secret_chat_queries = load_secret_chat_from_database_queries_[secret_chat_id]; + load_secret_chat_queries.push_back(std::move(promise)); + if (load_secret_chat_queries.size() == 1u) { + G()->td_db()->get_sqlite_pmc()->get( + get_secret_chat_database_key(secret_chat_id), PromiseCreator::lambda([secret_chat_id](string value) { + send_closure(G()->user_manager(), &UserManager::on_load_secret_chat_from_database, secret_chat_id, + std::move(value), false); + })); + } +} + +void UserManager::on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force) { + if (G()->close_flag() && !force) { + // the secret chat is in Binlog and will be saved after restart + return; + } + + CHECK(secret_chat_id.is_valid()); + if (!loaded_from_database_secret_chats_.insert(secret_chat_id).second) { + return; + } + + auto it = load_secret_chat_from_database_queries_.find(secret_chat_id); + vector> promises; + if (it != load_secret_chat_from_database_queries_.end()) { + promises = std::move(it->second); + CHECK(!promises.empty()); + load_secret_chat_from_database_queries_.erase(it); + } + + LOG(INFO) << "Successfully loaded " << secret_chat_id << " of size " << value.size() << " from database"; + // G()->td_db()->get_sqlite_pmc()->erase(get_secret_chat_database_key(secret_chat_id), Auto()); + // return; + + SecretChat *c = get_secret_chat(secret_chat_id); + if (c == nullptr) { + if (!value.empty()) { + c = add_secret_chat(secret_chat_id); + + if (log_event_parse(*c, value).is_error()) { + LOG(ERROR) << "Failed to load " << secret_chat_id << " from database"; + secret_chats_.erase(secret_chat_id); + } else { + c->is_saved = true; + update_secret_chat(c, secret_chat_id, true, true); + } + } + } else { + CHECK(!c->is_saved); // secret chat can't be saved before load completes + CHECK(!c->is_being_saved); + auto new_value = get_secret_chat_database_value(c); + if (value != new_value) { + save_secret_chat_to_database_impl(c, secret_chat_id, std::move(new_value)); + } else if (c->log_event_id != 0) { + binlog_erase(G()->td_db()->get_binlog(), c->log_event_id); + c->log_event_id = 0; + } + } + + // TODO load users asynchronously + if (c != nullptr && !have_user_force(c->user_id, "on_load_secret_chat_from_database")) { + LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id; + } + + set_promises(promises); +} + +void UserManager::create_new_secret_chat(UserId user_id, Promise> &&promise) { + TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id)); + if (input_user->get_id() != telegram_api::inputUser::ID) { + return promise.set_error(Status::Error(400, "Can't create secret chat with the user")); + } + auto user = static_cast(input_user.get()); + + send_closure( + G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_), user->access_hash_, + PromiseCreator::lambda( + [actor_id = actor_id(this), promise = std::move(promise)](Result r_secret_chat_id) mutable { + if (r_secret_chat_id.is_error()) { + return promise.set_error(r_secret_chat_id.move_as_error()); + } + send_closure(actor_id, &UserManager::on_create_new_secret_chat, r_secret_chat_id.ok(), std::move(promise)); + })); +} + +void UserManager::on_create_new_secret_chat(SecretChatId secret_chat_id, + Promise> &&promise) { + TRY_STATUS_PROMISE(promise, G()->close_status()); + CHECK(secret_chat_id.is_valid()); + DialogId dialog_id(secret_chat_id); + td_->dialog_manager_->force_create_dialog(dialog_id, "on_create_new_secret_chat"); + promise.set_value(td_->messages_manager_->get_chat_object(dialog_id, "on_create_new_secret_chat")); +} + +void UserManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) { + CHECK(u != nullptr); + + if (u->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << user_id; + } + u->is_being_updated = true; + SCOPE_EXIT { + u->is_being_updated = false; + }; + + if (user_id == get_my_id()) { + if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) { + td_->option_manager_->set_option_boolean("is_premium", u->is_premium); + send_closure(td_->config_manager_, &ConfigManager::request_config, true); + if (!td_->auth_manager_->is_bot()) { + td_->reaction_manager_->reload_reaction_list(ReactionListType::Top, "update_user is_premium"); + td_->messages_manager_->update_is_translatable(u->is_premium); + } + } + } + if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) { + update_contacts_hints(u, user_id, from_database); + u->is_username_changed = false; + } + if (u->is_is_contact_changed) { + td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact); + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "is_contact"); + if (is_user_contact(u, user_id, false)) { + auto user_full = get_user_full(user_id); + if (user_full != nullptr && user_full->need_phone_number_privacy_exception) { + on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, false); + update_user_full(user_full, user_id, "update_user"); + } + } + u->is_is_contact_changed = false; + } + if (u->is_is_mutual_contact_changed) { + if (!from_database && u->is_update_user_sent) { + send_closure_later(td_->story_manager_actor_, &StoryManager::reload_dialog_expiring_stories, DialogId(user_id)); + } + u->is_is_mutual_contact_changed = false; + } + if (u->is_is_deleted_changed) { + td_->messages_manager_->on_dialog_user_is_deleted_updated(DialogId(user_id), u->is_deleted); + if (u->is_deleted) { + auto user_full = get_user_full(user_id); // must not load user_full from database before sending updateUser + if (user_full != nullptr) { + u->is_full_info_changed = false; + drop_user_full(user_id); + } + } + u->is_is_deleted_changed = false; + } + if (u->is_is_premium_changed) { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "is_premium"); + u->is_is_premium_changed = false; + } + if (u->is_name_changed) { + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_title_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_title_updated(DialogId(secret_chat_id)); + }); + u->is_name_changed = false; + } + if (u->is_photo_changed) { + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_photo_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_photo_updated(DialogId(secret_chat_id)); + }); + u->is_photo_changed = false; + } + if (u->is_accent_color_changed) { + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_accent_colors_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_accent_colors_updated(DialogId(secret_chat_id)); + }); + u->is_accent_color_changed = false; + } + if (u->is_phone_number_changed) { + if (!u->phone_number.empty() && !td_->auth_manager_->is_bot()) { + resolved_phone_numbers_[u->phone_number] = user_id; + } + u->is_phone_number_changed = false; + } + auto unix_time = G()->unix_time(); + if (u->is_status_changed && user_id != get_my_id()) { + auto left_time = get_user_was_online(u, user_id, unix_time) - G()->server_time(); + if (left_time >= 0 && left_time < 30 * 86400) { + left_time += 2.0; // to guarantee expiration + LOG(DEBUG) << "Set online timeout for " << user_id << " in " << left_time << " seconds"; + user_online_timeout_.set_timeout_in(user_id.get(), left_time); + } else { + LOG(DEBUG) << "Cancel online timeout for " << user_id; + user_online_timeout_.cancel_timeout(user_id.get()); + } + } + if (u->is_stories_hidden_changed) { + send_closure_later(td_->story_manager_actor_, &StoryManager::on_dialog_active_stories_order_updated, + DialogId(user_id), "stories_hidden"); + u->is_stories_hidden_changed = false; + } + if (!td_->auth_manager_->is_bot()) { + if (u->restriction_reasons.empty()) { + restricted_user_ids_.erase(user_id); + } else { + restricted_user_ids_.insert(user_id); + } + } + + auto effective_emoji_status = u->emoji_status.get_effective_emoji_status(u->is_premium, unix_time); + if (effective_emoji_status != u->last_sent_emoji_status) { + if (!u->last_sent_emoji_status.is_empty()) { + user_emoji_status_timeout_.cancel_timeout(user_id.get()); + } + u->last_sent_emoji_status = effective_emoji_status; + if (!u->last_sent_emoji_status.is_empty()) { + auto until_date = u->last_sent_emoji_status.get_until_date(); + auto left_time = until_date - unix_time; + if (left_time >= 0 && left_time < 30 * 86400) { + user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time); + } + } + u->is_changed = true; + + auto messages_manager = td_->messages_manager_.get(); + messages_manager->on_dialog_emoji_status_updated(DialogId(user_id)); + for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) { + messages_manager->on_dialog_emoji_status_updated(DialogId(secret_chat_id)); + }); + u->is_emoji_status_changed = false; + } else if (u->is_emoji_status_changed) { + LOG(DEBUG) << "Emoji status for " << user_id << " has changed"; + u->need_save_to_database = true; + u->is_emoji_status_changed = false; + } + + if (u->is_deleted) { + td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>()); + } + if (from_binlog || from_database) { + td_->dialog_manager_->on_dialog_usernames_received(DialogId(user_id), u->usernames, true); + } + + LOG(DEBUG) << "Update " << user_id << ": need_save_to_database = " << u->need_save_to_database + << ", is_changed = " << u->is_changed << ", is_status_changed = " << u->is_status_changed + << ", from_binlog = " << from_binlog << ", from_database = " << from_database; + u->need_save_to_database |= u->is_changed; + if (u->need_save_to_database) { + if (!from_database) { + u->is_saved = false; + } + u->need_save_to_database = false; + } + if (u->is_changed) { + send_closure(G()->td(), &Td::send_update, get_update_user_object(user_id, u)); + u->is_changed = false; + u->is_status_changed = false; + u->is_update_user_sent = true; + } + if (u->is_status_changed) { + if (!from_database) { + u->is_status_saved = false; + } + CHECK(u->is_update_user_sent); + send_closure( + G()->td(), &Td::send_update, + td_api::make_object(user_id.get(), get_user_status_object(user_id, u, unix_time))); + u->is_status_changed = false; + } + if (u->is_online_status_changed) { + td_->dialog_participant_manager_->update_user_online_member_count(user_id); + u->is_online_status_changed = false; + } + + if (!from_database) { + save_user(u, user_id, from_binlog); + } + + if (u->cache_version != User::CACHE_VERSION && !u->is_repaired && + have_input_peer_user(u, user_id, AccessRights::Read) && !G()->close_flag()) { + u->is_repaired = true; + + LOG(INFO) << "Repairing cache of " << user_id; + reload_user(user_id, Promise(), "update_user"); + } + + if (u->is_full_info_changed) { + u->is_full_info_changed = false; + auto user_full = get_user_full(user_id); + if (user_full != nullptr) { + user_full->need_send_update = true; + update_user_full(user_full, user_id, "update_user is_full_info_changed"); + reload_user_full(user_id, Promise(), "update_user"); + } + } +} + +void UserManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog, bool from_database) { + CHECK(c != nullptr); + + if (c->is_being_updated) { + LOG(ERROR) << "Detected recursive update of " << secret_chat_id; + } + c->is_being_updated = true; + SCOPE_EXIT { + c->is_being_updated = false; + }; + + LOG(DEBUG) << "Update " << secret_chat_id << ": need_save_to_database = " << c->need_save_to_database + << ", is_changed = " << c->is_changed; + c->need_save_to_database |= c->is_changed; + if (c->need_save_to_database) { + if (!from_database) { + c->is_saved = false; + } + c->need_save_to_database = false; + + DialogId dialog_id(secret_chat_id); + send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update secret chat", + true, true); + if (c->is_state_changed) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_secret_chat_state, secret_chat_id, + c->state); + c->is_state_changed = false; + } + if (c->is_ttl_changed) { + send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_message_ttl, + DialogId(secret_chat_id), MessageTtl(c->ttl)); + c->is_ttl_changed = false; + } + } + if (c->is_changed) { + send_closure(G()->td(), &Td::send_update, get_update_secret_chat_object(secret_chat_id, c)); + c->is_changed = false; + } + + if (!from_database) { + save_secret_chat(c, secret_chat_id, from_binlog); + } +} + +void UserManager::update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database) { + CHECK(user_full != nullptr); + + if (user_full->is_being_updated) { + LOG(ERROR) << "Detected recursive update of full " << user_id << " from " << source; + } + user_full->is_being_updated = true; + SCOPE_EXIT { + user_full->is_being_updated = false; + }; + + unavailable_user_fulls_.erase(user_id); // don't needed anymore + if (user_full->is_common_chat_count_changed) { + td_->common_dialog_manager_->drop_common_dialogs_cache(user_id); + user_full->is_common_chat_count_changed = false; + } + if (true) { + vector file_ids; + if (!user_full->personal_photo.is_empty()) { + append(file_ids, photo_get_file_ids(user_full->personal_photo)); + } + if (!user_full->fallback_photo.is_empty()) { + append(file_ids, photo_get_file_ids(user_full->fallback_photo)); + } + if (!user_full->description_photo.is_empty()) { + append(file_ids, photo_get_file_ids(user_full->description_photo)); + } + if (user_full->description_animation_file_id.is_valid()) { + file_ids.push_back(user_full->description_animation_file_id); + } + if (user_full->business_info != nullptr) { + append(file_ids, user_full->business_info->get_file_ids(td_)); + } + if (user_full->registered_file_ids != file_ids) { + auto &file_source_id = user_full->file_source_id; + if (!file_source_id.is_valid()) { + file_source_id = user_full_file_source_ids_.get(user_id); + if (file_source_id.is_valid()) { + VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id; + user_full_file_source_ids_.erase(user_id); + } else { + VLOG(file_references) << "Need to create new file source for full " << user_id; + file_source_id = td_->file_reference_manager_->create_user_full_file_source(user_id); + } + } + + td_->file_manager_->change_files_source(file_source_id, user_full->registered_file_ids, file_ids); + user_full->registered_file_ids = std::move(file_ids); + } + } + + user_full->need_send_update |= user_full->is_changed; + user_full->need_save_to_database |= user_full->is_changed; + user_full->is_changed = false; + if (user_full->need_send_update || user_full->need_save_to_database) { + LOG(INFO) << "Update full " << user_id << " from " << source; + } + if (user_full->need_send_update) { + { + auto u = get_user(user_id); + CHECK(u == nullptr || u->is_update_user_sent); + } + if (!user_full->is_update_user_full_sent) { + LOG(ERROR) << "Send partial updateUserFullInfo for " << user_id << " from " << source; + user_full->is_update_user_full_sent = true; + } + send_closure(G()->td(), &Td::send_update, + td_api::make_object(get_user_id_object(user_id, "updateUserFullInfo"), + get_user_full_info_object(user_id, user_full))); + user_full->need_send_update = false; + + if (user_id == get_my_id() && !user_full->birthdate.is_empty() && !td_->auth_manager_->is_bot()) { + dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::BirthdaySetup}, Promise()); + } + } + if (user_full->need_save_to_database) { + if (!from_database) { + save_user_full(user_full, user_id); + } + user_full->need_save_to_database = false; + } +} + +td_api::object_ptr UserManager::get_user_status_object(UserId user_id, const User *u, + int32 unix_time) const { + if (u->is_bot) { + return td_api::make_object(std::numeric_limits::max()); + } + + int32 was_online = get_user_was_online(u, user_id, unix_time); + switch (was_online) { + case -6: + case -3: + return td_api::make_object(was_online == -6); + case -5: + case -2: + return td_api::make_object(was_online == -5); + case -4: + case -1: + return td_api::make_object(was_online == -4); + case 0: + return td_api::make_object(); + default: { + int32 time = G()->unix_time(); + if (was_online > time) { + return td_api::make_object(was_online); + } else { + return td_api::make_object(was_online); + } + } + } +} + +bool UserManager::get_user_has_unread_stories(const User *u) { + CHECK(u != nullptr); + return u->max_active_story_id.get() > u->max_read_story_id.get(); +} + +td_api::object_ptr UserManager::get_update_user_object(UserId user_id, const User *u) const { + if (u == nullptr) { + return get_update_unknown_user_object(user_id); + } + return td_api::make_object(get_user_object(user_id, u)); +} + +td_api::object_ptr UserManager::get_update_unknown_user_object(UserId user_id) const { + auto have_access = user_id == get_my_id() || user_messages_.count(user_id) != 0; + return td_api::make_object(td_api::make_object( + user_id.get(), "", "", nullptr, "", td_api::make_object(), nullptr, + td_->theme_manager_->get_accent_color_id_object(AccentColorId(user_id)), 0, -1, 0, nullptr, false, false, false, + false, false, false, "", false, false, false, false, false, have_access, + td_api::make_object(), "", false)); +} + +int64 UserManager::get_user_id_object(UserId user_id, const char *source) const { + if (user_id.is_valid() && get_user(user_id) == nullptr && unknown_users_.count(user_id) == 0) { + LOG(ERROR) << "Have no information about " << user_id << " from " << source; + unknown_users_.insert(user_id); + send_closure(G()->td(), &Td::send_update, get_update_unknown_user_object(user_id)); + } + return user_id.get(); +} + +void UserManager::get_user_id_object_async(UserId user_id, Promise &&promise) { + promise.set_value(get_user_id_object(user_id, "get_user_id_object_async")); +} + +td_api::object_ptr UserManager::get_user_object(UserId user_id) const { + return get_user_object(user_id, get_user(user_id)); +} + +td_api::object_ptr UserManager::get_user_object(UserId user_id, const User *u) const { + if (u == nullptr) { + return nullptr; + } + td_api::object_ptr type; + if (u->is_deleted) { + type = td_api::make_object(); + } else if (u->is_bot) { + type = td_api::make_object( + u->can_be_edited_bot, u->can_join_groups, u->can_read_all_group_messages, u->is_inline_bot, + u->inline_query_placeholder, u->need_location_bot, u->is_business_bot, u->can_be_added_to_attach_menu); + } else { + type = td_api::make_object(); + } + + auto emoji_status = u->last_sent_emoji_status.get_emoji_status_object(); + auto have_access = user_id == get_my_id() || have_input_peer_user(u, user_id, AccessRights::Know); + auto restricts_new_chats = u->contact_require_premium && !u->is_mutual_contact; + return td_api::make_object( + user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number, + get_user_status_object(user_id, u, G()->unix_time()), + get_profile_photo_object(td_->file_manager_.get(), u->photo), + td_->theme_manager_->get_accent_color_id_object(u->accent_color_id, AccentColorId(user_id)), + u->background_custom_emoji_id.get(), + td_->theme_manager_->get_profile_accent_color_id_object(u->profile_accent_color_id), + u->profile_background_custom_emoji_id.get(), std::move(emoji_status), u->is_contact, u->is_mutual_contact, + u->is_close_friend, u->is_verified, u->is_premium, u->is_support, + get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, + u->max_active_story_id.is_valid(), get_user_has_unread_stories(u), restricts_new_chats, have_access, + std::move(type), u->language_code, u->attach_menu_enabled); +} + +vector UserManager::get_user_ids_object(const vector &user_ids, const char *source) const { + return transform(user_ids, [this, source](UserId user_id) { return get_user_id_object(user_id, source); }); +} + +td_api::object_ptr UserManager::get_users_object(int32 total_count, + const vector &user_ids) const { + if (total_count == -1) { + total_count = narrow_cast(user_ids.size()); + } + return td_api::make_object(total_count, get_user_ids_object(user_ids, "get_users_object")); +} + +td_api::object_ptr UserManager::get_user_full_info_object(UserId user_id) const { + return get_user_full_info_object(user_id, get_user_full(user_id)); +} + +td_api::object_ptr UserManager::get_user_full_info_object(UserId user_id, + const UserFull *user_full) const { + CHECK(user_full != nullptr); + td_api::object_ptr bot_info; + const User *u = get_user(user_id); + bool is_bot = is_user_bot(u); + bool is_premium = is_user_premium(u); + td_api::object_ptr bio_object; + if (is_bot) { + auto menu_button = get_bot_menu_button_object(td_, user_full->menu_button.get()); + auto commands = + transform(user_full->commands, [](const auto &command) { return command.get_bot_command_object(); }); + bot_info = td_api::make_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), + user_full->group_administrator_rights == AdministratorRights() + ? nullptr + : user_full->group_administrator_rights.get_chat_administrator_rights_object(), + user_full->broadcast_administrator_rights == AdministratorRights() + ? nullptr + : user_full->broadcast_administrator_rights.get_chat_administrator_rights_object(), + nullptr, nullptr, nullptr, nullptr); + if (u != nullptr && u->can_be_edited_bot && u->usernames.has_editable_username()) { + auto bot_username = u->usernames.get_editable_username(); + bot_info->edit_commands_link_ = td_api::make_object( + "botfather", PSTRING() << bot_username << "-commands", true); + bot_info->edit_description_link_ = td_api::make_object( + "botfather", PSTRING() << bot_username << "-intro", true); + bot_info->edit_description_media_link_ = td_api::make_object( + "botfather", PSTRING() << bot_username << "-intropic", true); + bot_info->edit_settings_link_ = + td_api::make_object("botfather", bot_username, true); + } + } else { + FormattedText bio; + bio.text = user_full->about; + bio.entities = find_entities(bio.text, true, true); + if (!is_premium) { + td::remove_if(bio.entities, [&](const MessageEntity &entity) { + if (entity.type == MessageEntity::Type::EmailAddress) { + return true; + } + if (entity.type == MessageEntity::Type::Url && + !LinkManager::is_internal_link(utf8_utf16_substr(bio.text, entity.offset, entity.length))) { + return true; + } + return false; + }); + } + bio_object = get_formatted_text_object(bio, true, 0); + } + auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false; + auto block_list_id = BlockListId(user_full->is_blocked, user_full->is_blocked_for_stories); + auto business_info = is_premium && user_full->business_info != nullptr + ? user_full->business_info->get_business_info_object(td_) + : nullptr; + int64 personal_chat_id = 0; + if (user_full->personal_channel_id.is_valid()) { + DialogId dialog_id(user_full->personal_channel_id); + td_->dialog_manager_->force_create_dialog(dialog_id, "get_user_full_info_object", true); + personal_chat_id = td_->dialog_manager_->get_chat_id_object(dialog_id, "get_user_full_info_object"); + } + return td_api::make_object( + get_chat_photo_object(td_->file_manager_.get(), user_full->personal_photo), + get_chat_photo_object(td_->file_manager_.get(), user_full->photo), + get_chat_photo_object(td_->file_manager_.get(), user_full->fallback_photo), block_list_id.get_block_list_object(), + user_full->can_be_called, user_full->supports_video_calls, user_full->has_private_calls, + !user_full->private_forward_name.empty(), voice_messages_forbidden, user_full->has_pinned_stories, + user_full->sponsored_enabled, user_full->need_phone_number_privacy_exception, user_full->wallpaper_overridden, + std::move(bio_object), user_full->birthdate.get_birthdate_object(), personal_chat_id, + get_premium_payment_options_object(user_full->premium_gift_options), user_full->common_chat_count, + std::move(business_info), std::move(bot_info)); +} + +td_api::object_ptr UserManager::get_update_contact_close_birthdays() const { + return td_api::make_object( + transform(contact_birthdates_.users_, [this](const std::pair &user) { + return td_api::make_object(get_user_id_object(user.first, "closeBirthdayUser"), + user.second.get_birthdate_object()); + })); +} + +td_api::object_ptr UserManager::get_secret_chat_state_object(SecretChatState state) { + switch (state) { + case SecretChatState::Waiting: + return td_api::make_object(); + case SecretChatState::Active: + return td_api::make_object(); + case SecretChatState::Closed: + case SecretChatState::Unknown: + return td_api::make_object(); + default: + UNREACHABLE(); + return nullptr; + } +} + +td_api::object_ptr UserManager::get_update_secret_chat_object(SecretChatId secret_chat_id, + const SecretChat *secret_chat) { + if (secret_chat == nullptr) { + return get_update_unknown_secret_chat_object(secret_chat_id); + } + return td_api::make_object(get_secret_chat_object(secret_chat_id, secret_chat)); +} + +td_api::object_ptr UserManager::get_update_unknown_secret_chat_object( + SecretChatId secret_chat_id) { + return td_api::make_object(td_api::make_object( + secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, string(), 0)); +} + +int32 UserManager::get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const { + if (secret_chat_id.is_valid() && get_secret_chat(secret_chat_id) == nullptr && + unknown_secret_chats_.count(secret_chat_id) == 0) { + LOG(ERROR) << "Have no information about " << secret_chat_id << " from " << source; + unknown_secret_chats_.insert(secret_chat_id); + send_closure(G()->td(), &Td::send_update, get_update_unknown_secret_chat_object(secret_chat_id)); + } + return secret_chat_id.get(); +} + +td_api::object_ptr UserManager::get_secret_chat_object(SecretChatId secret_chat_id) { + return get_secret_chat_object(secret_chat_id, get_secret_chat(secret_chat_id)); +} + +td_api::object_ptr UserManager::get_secret_chat_object(SecretChatId secret_chat_id, + const SecretChat *secret_chat) { + if (secret_chat == nullptr) { + return nullptr; + } + get_user_force(secret_chat->user_id, "get_secret_chat_object"); + return get_secret_chat_object_const(secret_chat_id, secret_chat); +} + +td_api::object_ptr UserManager::get_secret_chat_object_const(SecretChatId secret_chat_id, + const SecretChat *secret_chat) const { + return td_api::make_object(secret_chat_id.get(), + get_user_id_object(secret_chat->user_id, "secretChat"), + get_secret_chat_state_object(secret_chat->state), + secret_chat->is_outbound, secret_chat->key_hash, secret_chat->layer); +} + +void UserManager::get_current_state(vector> &updates) const { + for (auto user_id : unknown_users_) { + if (!have_min_user(user_id)) { + updates.push_back(get_update_unknown_user_object(user_id)); + } + } + for (auto secret_chat_id : unknown_secret_chats_) { + if (!have_secret_chat(secret_chat_id)) { + updates.push_back(get_update_unknown_secret_chat_object(secret_chat_id)); + } + } + + users_.foreach([&](const UserId &user_id, const unique_ptr &user) { + updates.push_back(get_update_user_object(user_id, user.get())); + }); + // secret chat objects contain user_id, so they must be sent after users + secret_chats_.foreach([&](const SecretChatId &secret_chat_id, const unique_ptr &secret_chat) { + updates.push_back( + td_api::make_object(get_secret_chat_object_const(secret_chat_id, secret_chat.get()))); + }); + + users_full_.foreach([&](const UserId &user_id, const unique_ptr &user_full) { + updates.push_back(td_api::make_object( + user_id.get(), get_user_full_info_object(user_id, user_full.get()))); + }); + + if (!contact_birthdates_.users_.empty()) { + updates.push_back(get_update_contact_close_birthdays()); + } +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/UserManager.h b/lib/tgchat/ext/td/td/telegram/UserManager.h new file mode 100644 index 00000000..b8c7b91b --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/UserManager.h @@ -0,0 +1,1121 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/AccentColorId.h" +#include "td/telegram/AccessRights.h" +#include "td/telegram/Birthdate.h" +#include "td/telegram/BotCommand.h" +#include "td/telegram/BotMenuButton.h" +#include "td/telegram/ChannelId.h" +#include "td/telegram/Contact.h" +#include "td/telegram/CustomEmojiId.h" +#include "td/telegram/DialogId.h" +#include "td/telegram/DialogLocation.h" +#include "td/telegram/DialogParticipant.h" +#include "td/telegram/EmojiStatus.h" +#include "td/telegram/files/FileId.h" +#include "td/telegram/files/FileSourceId.h" +#include "td/telegram/FolderId.h" +#include "td/telegram/MessageFullId.h" +#include "td/telegram/Photo.h" +#include "td/telegram/PremiumGiftOption.h" +#include "td/telegram/QueryCombiner.h" +#include "td/telegram/QueryMerger.h" +#include "td/telegram/RestrictionReason.h" +#include "td/telegram/SecretChatId.h" +#include "td/telegram/StoryId.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" +#include "td/telegram/UserId.h" +#include "td/telegram/Usernames.h" + +#include "td/actor/actor.h" +#include "td/actor/MultiPromise.h" +#include "td/actor/MultiTimeout.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/Hints.h" +#include "td/utils/Promise.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/WaitFreeHashMap.h" +#include "td/utils/WaitFreeHashSet.h" + +#include +#include +#include + +namespace td { + +struct BinlogEvent; +class BusinessAwayMessage; +class BusinessGreetingMessage; +class BusinessInfo; +class BusinessIntro; +class BusinessWorkHours; +class Td; + +class UserManager final : public Actor { + public: + UserManager(Td *td, ActorShared<> parent); + UserManager(const UserManager &) = delete; + UserManager &operator=(const UserManager &) = delete; + UserManager(UserManager &&) = delete; + UserManager &operator=(UserManager &&) = delete; + ~UserManager() final; + + static UserId get_user_id(const telegram_api::object_ptr &user); + + static UserId load_my_id(); + + UserId get_my_id() const; + + static UserId get_service_notifications_user_id(); + + UserId add_service_notifications_user(); + + static UserId get_replies_bot_user_id(); + + static UserId get_anonymous_bot_user_id(); + + static UserId get_channel_bot_user_id(); + + static UserId get_anti_spam_bot_user_id(); + + UserId add_anonymous_bot_user(); + + UserId add_channel_bot_user(); + + struct MyOnlineStatusInfo { + bool is_online_local = false; + bool is_online_remote = false; + int32 was_online_local = 0; + int32 was_online_remote = 0; + }; + MyOnlineStatusInfo get_my_online_status() const; + + void set_my_online_status(bool is_online, bool send_update, bool is_local); + + void on_get_user(telegram_api::object_ptr &&user, const char *source); + + void on_get_users(vector> &&users, const char *source); + + void on_binlog_user_event(BinlogEvent &&event); + + void on_binlog_secret_chat_event(BinlogEvent &&event); + + void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames); + + void on_update_user_phone_number(UserId user_id, string &&phone_number); + + void register_suggested_profile_photo(const Photo &photo); + + void on_update_user_emoji_status(UserId user_id, telegram_api::object_ptr &&emoji_status); + + void on_update_user_story_ids(UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); + + void on_update_user_max_read_story_id(UserId user_id, StoryId max_read_story_id); + + void on_update_user_stories_hidden(UserId user_id, bool stories_hidden); + + void on_update_user_online(UserId user_id, telegram_api::object_ptr &&status); + + void on_update_user_local_was_online(UserId user_id, int32 local_was_online); + + // use on_update_dialog_is_blocked instead + void on_update_user_is_blocked(UserId user_id, bool is_blocked, bool is_blocked_for_stories); + + void on_update_user_has_pinned_stories(UserId user_id, bool has_pinned_stories); + + void on_update_user_common_chat_count(UserId user_id, int32 common_chat_count); + + void on_update_user_location(UserId user_id, DialogLocation &&location); + + void on_update_user_work_hours(UserId user_id, BusinessWorkHours &&work_hours); + + void on_update_user_away_message(UserId user_id, BusinessAwayMessage &&away_message); + + void on_update_user_greeting_message(UserId user_id, BusinessGreetingMessage &&greeting_message); + + void on_update_user_intro(UserId user_id, BusinessIntro &&intro); + + void on_update_user_commands(UserId user_id, + vector> &&bot_commands); + + void on_update_user_need_phone_number_privacy_exception(UserId user_id, bool need_phone_number_privacy_exception); + + void on_update_user_wallpaper_overridden(UserId user_id, bool wallpaper_overridden); + + void on_update_bot_menu_button(UserId bot_user_id, + telegram_api::object_ptr &&bot_menu_button); + + void on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, SecretChatState state, + bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer, + FolderId initial_folder_id); + + void on_update_online_status_privacy(); + + void on_update_phone_number_privacy(); + + void on_ignored_restriction_reasons_changed(); + + void invalidate_user_full(UserId user_id); + + bool have_user(UserId user_id) const; + + bool have_min_user(UserId user_id) const; + + bool have_user_force(UserId user_id, const char *source); + + static void send_get_me_query(Td *td, Promise &&promise); + + UserId get_me(Promise &&promise); + + bool get_user(UserId user_id, int left_tries, Promise &&promise); + + void reload_user(UserId user_id, Promise &&promise, const char *source); + + Result> get_input_user(UserId user_id) const; + + telegram_api::object_ptr get_input_user_force(UserId user_id) const; + + bool have_input_peer_user(UserId user_id, AccessRights access_rights) const; + + telegram_api::object_ptr get_input_peer_user(UserId user_id, + AccessRights access_rights) const; + + bool have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const; + + telegram_api::object_ptr get_input_encrypted_chat(SecretChatId secret_chat_id, + AccessRights access_rights) const; + + bool is_user_contact(UserId user_id, bool is_mutual = false) const; + + bool is_user_premium(UserId user_id) const; + + bool is_user_deleted(UserId user_id) const; + + bool is_user_support(UserId user_id) const; + + bool is_user_bot(UserId user_id) const; + + struct BotData { + string username; + bool can_be_edited; + bool can_join_groups; + bool can_read_all_group_messages; + bool is_inline; + bool is_business; + bool need_location; + bool can_be_added_to_attach_menu; + }; + Result get_bot_data(UserId user_id) const TD_WARN_UNUSED_RESULT; + + bool is_user_online(UserId user_id, int32 tolerance = 0, int32 unix_time = 0) const; + + int32 get_user_was_online(UserId user_id, int32 unix_time = 0) const; + + bool is_user_status_exact(UserId user_id) const; + + bool is_user_received_from_server(UserId user_id) const; + + bool can_report_user(UserId user_id) const; + + const DialogPhoto *get_user_dialog_photo(UserId user_id); + + const DialogPhoto *get_secret_chat_dialog_photo(SecretChatId secret_chat_id); + + int32 get_user_accent_color_id_object(UserId user_id) const; + + int32 get_secret_chat_accent_color_id_object(SecretChatId secret_chat_id) const; + + CustomEmojiId get_user_background_custom_emoji_id(UserId user_id) const; + + CustomEmojiId get_secret_chat_background_custom_emoji_id(SecretChatId secret_chat_id) const; + + int32 get_user_profile_accent_color_id_object(UserId user_id) const; + + int32 get_secret_chat_profile_accent_color_id_object(SecretChatId secret_chat_id) const; + + CustomEmojiId get_user_profile_background_custom_emoji_id(UserId user_id) const; + + CustomEmojiId get_secret_chat_profile_background_custom_emoji_id(SecretChatId secret_chat_id) const; + + string get_user_title(UserId user_id) const; + + string get_secret_chat_title(SecretChatId secret_chat_id) const; + + RestrictedRights get_user_default_permissions(UserId user_id) const; + + RestrictedRights get_secret_chat_default_permissions(SecretChatId secret_chat_id) const; + + td_api::object_ptr get_user_emoji_status_object(UserId user_id) const; + + td_api::object_ptr get_secret_chat_emoji_status_object(SecretChatId secret_chat_id) const; + + bool get_user_stories_hidden(UserId user_id) const; + + bool can_poll_user_active_stories(UserId user_id) const; + + string get_user_about(UserId user_id); + + string get_secret_chat_about(SecretChatId secret_chat_id); + + string get_user_private_forward_name(UserId user_id); + + bool get_user_voice_messages_forbidden(UserId user_id) const; + + bool get_user_read_dates_private(UserId user_id); + + string get_user_search_text(UserId user_id) const; + + void for_each_secret_chat_with_user(UserId user_id, const std::function &f); + + string get_user_first_username(UserId user_id) const; + + int32 get_secret_chat_date(SecretChatId secret_chat_id) const; + + int32 get_secret_chat_ttl(SecretChatId secret_chat_id) const; + + UserId get_secret_chat_user_id(SecretChatId secret_chat_id) const; + + bool get_secret_chat_is_outbound(SecretChatId secret_chat_id) const; + + SecretChatState get_secret_chat_state(SecretChatId secret_chat_id) const; + + int32 get_secret_chat_layer(SecretChatId secret_chat_id) const; + + FolderId get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const; + + vector get_bot_commands(vector> &&bot_infos, + const vector *participants); + + void set_name(const string &first_name, const string &last_name, Promise &&promise); + + void set_bio(const string &bio, Promise &&promise); + + void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about); + + FileId get_profile_photo_file_id(int64 photo_id) const; + + void set_bot_profile_photo(UserId bot_user_id, const td_api::object_ptr &input_photo, + Promise &&promise); + + void set_profile_photo(const td_api::object_ptr &input_photo, bool is_fallback, + Promise &&promise); + + void set_user_profile_photo(UserId user_id, const td_api::object_ptr &input_photo, + bool only_suggest, Promise &&promise); + + void send_update_profile_photo_query(UserId user_id, FileId file_id, int64 old_photo_id, bool is_fallback, + Promise &&promise); + + void on_set_profile_photo(UserId user_id, telegram_api::object_ptr &&photo, + bool is_fallback, int64 old_photo_id, Promise &&promise); + + void delete_profile_photo(int64 profile_photo_id, bool is_recursive, Promise &&promise); + + void on_delete_profile_photo(int64 profile_photo_id, Promise promise); + + void set_username(const string &username, Promise &&promise); + + void toggle_username_is_active(string &&username, bool is_active, Promise &&promise); + + void reorder_usernames(vector &&usernames, Promise &&promise); + + void toggle_bot_username_is_active(UserId bot_user_id, string &&username, bool is_active, Promise &&promise); + + void reorder_bot_usernames(UserId bot_user_id, vector &&usernames, Promise &&promise); + + void on_update_username_is_active(UserId user_id, string &&username, bool is_active, Promise &&promise); + + void on_update_active_usernames_order(UserId user_id, vector &&usernames, Promise &&promise); + + void set_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise); + + void set_profile_accent_color(AccentColorId accent_color_id, CustomEmojiId background_custom_emoji_id, + Promise &&promise); + + void on_update_accent_color_success(bool for_profile, AccentColorId accent_color_id, + CustomEmojiId background_custom_emoji_id); + + void set_birthdate(Birthdate &&birthdate, Promise &&promise); + + void set_personal_channel(DialogId dialog_id, Promise &&promise); + + void set_emoji_status(const EmojiStatus &emoji_status, Promise &&promise); + + void toggle_sponsored_messages(bool sponsored_enabled, Promise &&promise); + + void get_support_user(Promise> &&promise); + + void get_user_profile_photos(UserId user_id, int32 offset, int32 limit, + Promise> &&promise); + + void reload_user_profile_photo(UserId user_id, int64 photo_id, Promise &&promise); + + FileSourceId get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id); + + void on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count, + vector> photos); + + void register_message_users(MessageFullId message_full_id, vector user_ids); + + void unregister_message_users(MessageFullId message_full_id, vector user_ids); + + void can_send_message_to_user(UserId user_id, bool force, + Promise> &&promise); + + void on_get_is_premium_required_to_contact_users(vector &&user_ids, vector &&is_premium_required, + Promise &&promise); + + void allow_send_message_to_user(UserId user_id); + + void share_phone_number(UserId user_id, Promise &&promise); + + void reload_contacts(bool force); + + void on_get_contacts(telegram_api::object_ptr &&new_contacts); + + void on_get_contacts_failed(Status error); + + void on_get_contacts_statuses(vector> &&statuses); + + void add_contact(Contact contact, bool share_phone_number, Promise &&promise); + + std::pair, vector> import_contacts(const vector &contacts, int64 &random_id, + Promise &&promise); + + void on_imported_contacts(int64 random_id, + Result> result); + + void remove_contacts(const vector &user_ids, Promise &&promise); + + void remove_contacts_by_phone_number(vector user_phone_numbers, vector user_ids, + Promise &&promise); + + void on_deleted_contacts(const vector &deleted_contact_user_ids); + + int32 get_imported_contact_count(Promise &&promise); + + std::pair, vector> change_imported_contacts(vector &contacts, int64 &random_id, + Promise &&promise); + + void clear_imported_contacts(Promise &&promise); + + void on_update_contacts_reset(); + + std::pair> search_contacts(const string &query, int32 limit, Promise &&promise); + + void reload_contact_birthdates(bool force); + + void on_get_contact_birthdates(telegram_api::object_ptr &&birthdays); + + void hide_contact_birthdays(Promise &&promise); + + vector get_close_friends(Promise &&promise); + + void set_close_friends(vector user_ids, Promise &&promise); + + void on_set_close_friends(const vector &user_ids, Promise &&promise); + + UserId search_user_by_phone_number(string phone_number, bool only_local, Promise &&promise); + + void on_resolved_phone_number(const string &phone_number, UserId user_id); + + void load_user_full(UserId user_id, bool force, Promise &&promise, const char *source); + + void reload_user_full(UserId user_id, Promise &&promise, const char *source); + + void on_get_user_full(telegram_api::object_ptr &&user); + + FileSourceId get_user_full_file_source_id(UserId user_id); + + bool have_secret_chat(SecretChatId secret_chat_id) const; + + bool have_secret_chat_force(SecretChatId secret_chat_id, const char *source); + + bool get_secret_chat(SecretChatId secret_chat_id, bool force, Promise &&promise); + + void create_new_secret_chat(UserId user_id, Promise> &&promise); + + int64 get_user_id_object(UserId user_id, const char *source) const; + + void get_user_id_object_async(UserId user_id, Promise &&promise); + + td_api::object_ptr get_user_object(UserId user_id) const; + + vector get_user_ids_object(const vector &user_ids, const char *source) const; + + td_api::object_ptr get_users_object(int32 total_count, const vector &user_ids) const; + + td_api::object_ptr get_user_full_info_object(UserId user_id) const; + + int32 get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const; + + td_api::object_ptr get_secret_chat_object(SecretChatId secret_chat_id); + + void get_current_state(vector> &updates) const; + + private: + struct User { + string first_name; + string last_name; + Usernames usernames; + string phone_number; + int64 access_hash = -1; + EmojiStatus emoji_status; + EmojiStatus last_sent_emoji_status; + + ProfilePhoto photo; + + vector restriction_reasons; + string inline_query_placeholder; + int32 bot_info_version = -1; + + AccentColorId accent_color_id; + CustomEmojiId background_custom_emoji_id; + AccentColorId profile_accent_color_id; + CustomEmojiId profile_background_custom_emoji_id; + + int32 was_online = 0; + int32 local_was_online = 0; + + double max_active_story_id_next_reload_time = 0.0; + StoryId max_active_story_id; + StoryId max_read_story_id; + + string language_code; + + FlatHashSet photo_ids; + + static constexpr uint32 CACHE_VERSION = 4; + uint32 cache_version = 0; + + bool is_min_access_hash = true; + bool is_received = false; + bool is_verified = false; + bool is_premium = false; + bool is_support = false; + bool is_deleted = true; + bool is_bot = true; + bool can_join_groups = true; + bool can_read_all_group_messages = true; + bool can_be_edited_bot = false; + bool is_inline_bot = false; + bool is_business_bot = false; + bool need_location_bot = false; + bool is_scam = false; + bool is_fake = false; + bool is_contact = false; + bool is_mutual_contact = false; + bool is_close_friend = false; + bool need_apply_min_photo = false; + bool can_be_added_to_attach_menu = false; + bool attach_menu_enabled = false; + bool stories_hidden = false; + bool contact_require_premium = false; + + bool is_photo_inited = false; + + bool is_repaired = false; // whether cached value is rechecked + + bool is_name_changed = true; + bool is_username_changed = true; + bool is_photo_changed = true; + bool is_accent_color_changed = true; + bool is_phone_number_changed = true; + bool is_emoji_status_changed = true; + bool is_is_contact_changed = true; + bool is_is_mutual_contact_changed = true; + bool is_is_deleted_changed = true; + bool is_is_premium_changed = true; + bool is_stories_hidden_changed = true; + bool is_full_info_changed = false; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_status_changed = true; + bool is_online_status_changed = true; // whether online/offline has changed + bool is_update_user_sent = false; + + bool is_saved = false; // is current user version being saved/is saved to the database + bool is_being_saved = false; // is current user being saved to the database + bool is_status_saved = false; // is current user status being saved/is saved to the database + + bool is_received_from_server = false; // true, if the user was received from the server and not the database + + uint64 log_event_id = 0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + // do not forget to update drop_user_full and on_get_user_full + struct UserFull { + Photo photo; + Photo fallback_photo; + Photo personal_photo; + + string about; + string private_forward_name; + string description; + Photo description_photo; + FileId description_animation_file_id; + vector registered_file_ids; + FileSourceId file_source_id; + + vector premium_gift_options; + + unique_ptr menu_button; + vector commands; + AdministratorRights group_administrator_rights; + AdministratorRights broadcast_administrator_rights; + + int32 common_chat_count = 0; + Birthdate birthdate; + + ChannelId personal_channel_id; + + unique_ptr business_info; + + bool is_blocked = false; + bool is_blocked_for_stories = false; + bool can_be_called = false; + bool supports_video_calls = false; + bool has_private_calls = false; + bool can_pin_messages = true; + bool need_phone_number_privacy_exception = false; + bool wallpaper_overridden = false; + bool voice_messages_forbidden = false; + bool has_pinned_stories = false; + bool read_dates_private = false; + bool contact_require_premium = false; + bool sponsored_enabled = false; + + bool is_common_chat_count_changed = true; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_send_update = true; // have new changes that need only to be sent to the client + bool need_save_to_database = true; // have new changes that need only to be saved to the database + bool is_update_user_full_sent = false; + + double expires_at = 0.0; + + bool is_expired() const { + return expires_at < Time::now(); + } + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct SecretChat { + int64 access_hash = 0; + UserId user_id; + SecretChatState state = SecretChatState::Unknown; + string key_hash; + int32 ttl = 0; + int32 date = 0; + int32 layer = 0; + FolderId initial_folder_id; + + bool is_outbound = false; + + bool is_ttl_changed = true; + bool is_state_changed = true; + bool is_being_updated = false; + bool is_changed = true; // have new changes that need to be sent to the client and database + bool need_save_to_database = true; // have new changes that need only to be saved to the database + + bool is_saved = false; // is current secret chat version being saved/is saved to the database + bool is_being_saved = false; // is current secret chat being saved to the database + + uint64 log_event_id = 0; + + template + void store(StorerT &storer) const; + + template + void parse(ParserT &parser); + }; + + struct PendingGetPhotoRequest { + int32 offset = 0; + int32 limit = 0; + int32 retry_count = 0; + Promise> promise; + }; + + struct UserPhotos { + vector photos; + int32 count = -1; + int32 offset = -1; + + vector pending_requests; + }; + + class UserLogEvent; + class SecretChatLogEvent; + + static constexpr int32 MAX_GET_PROFILE_PHOTOS = 100; // server side limit + static constexpr size_t MAX_NAME_LENGTH = 64; // server side limit for first/last name + + static constexpr int32 MAX_ACTIVE_STORY_ID_RELOAD_TIME = 3600; // some reasonable limit + + // the True fields aren't set for manually created telegram_api::user objects, therefore the flags must be used + static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0; + static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1; + static constexpr int32 USER_FLAG_HAS_LAST_NAME = 1 << 2; + static constexpr int32 USER_FLAG_HAS_USERNAME = 1 << 3; + static constexpr int32 USER_FLAG_HAS_PHONE_NUMBER = 1 << 4; + static constexpr int32 USER_FLAG_HAS_PHOTO = 1 << 5; + static constexpr int32 USER_FLAG_HAS_STATUS = 1 << 6; + static constexpr int32 USER_FLAG_HAS_BOT_INFO_VERSION = 1 << 14; + static constexpr int32 USER_FLAG_IS_ME = 1 << 10; + static constexpr int32 USER_FLAG_IS_CONTACT = 1 << 11; + static constexpr int32 USER_FLAG_IS_MUTUAL_CONTACT = 1 << 12; + static constexpr int32 USER_FLAG_IS_DELETED = 1 << 13; + static constexpr int32 USER_FLAG_IS_BOT = 1 << 14; + static constexpr int32 USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED = 1 << 15; + static constexpr int32 USER_FLAG_IS_PRIVATE_BOT = 1 << 16; + static constexpr int32 USER_FLAG_IS_VERIFIED = 1 << 17; + static constexpr int32 USER_FLAG_IS_RESTRICTED = 1 << 18; + static constexpr int32 USER_FLAG_IS_INLINE_BOT = 1 << 19; + static constexpr int32 USER_FLAG_IS_INACCESSIBLE = 1 << 20; + static constexpr int32 USER_FLAG_NEED_LOCATION_BOT = 1 << 21; + static constexpr int32 USER_FLAG_HAS_LANGUAGE_CODE = 1 << 22; + static constexpr int32 USER_FLAG_IS_SUPPORT = 1 << 23; + static constexpr int32 USER_FLAG_IS_SCAM = 1 << 24; + static constexpr int32 USER_FLAG_NEED_APPLY_MIN_PHOTO = 1 << 25; + static constexpr int32 USER_FLAG_IS_FAKE = 1 << 26; + static constexpr int32 USER_FLAG_IS_ATTACH_MENU_BOT = 1 << 27; + static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28; + static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29; + static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30; + static constexpr int32 USER_FLAG_HAS_USERNAMES = 1 << 0; + static constexpr int32 USER_FLAG_CAN_BE_EDITED_BOT = 1 << 1; + static constexpr int32 USER_FLAG_IS_CLOSE_FRIEND = 1 << 2; + + static constexpr int32 USER_FULL_EXPIRE_TIME = 60; + + static constexpr int32 ACCOUNT_UPDATE_FIRST_NAME = 1 << 0; + static constexpr int32 ACCOUNT_UPDATE_LAST_NAME = 1 << 1; + static constexpr int32 ACCOUNT_UPDATE_ABOUT = 1 << 2; + + void tear_down() final; + + static void on_user_online_timeout_callback(void *user_manager_ptr, int64 user_id_long); + + void on_user_online_timeout(UserId user_id); + + static void on_user_emoji_status_timeout_callback(void *user_manager_ptr, int64 user_id_long); + + void on_user_emoji_status_timeout(UserId user_id); + + void set_my_id(UserId my_id); + + const User *get_user(UserId user_id) const; + + User *get_user(UserId user_id); + + User *add_user(UserId user_id); + + void save_user(User *u, UserId user_id, bool from_binlog); + + static string get_user_database_key(UserId user_id); + + static string get_user_database_value(const User *u); + + void save_user_to_database(User *u, UserId user_id); + + void save_user_to_database_impl(User *u, UserId user_id, string value); + + void on_save_user_to_database(UserId user_id, bool success); + + void load_user_from_database(User *u, UserId user_id, Promise promise); + + void load_user_from_database_impl(UserId user_id, Promise promise); + + void on_load_user_from_database(UserId user_id, string value, bool force); + + User *get_user_force(UserId user_id, const char *source); + + User *get_user_force_impl(UserId user_id, const char *source); + + bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const; + + static bool is_user_premium(const User *u); + + static bool is_user_deleted(const User *u); + + static bool is_user_support(const User *u); + + static bool is_user_bot(const User *u); + + int32 get_user_was_online(const User *u, UserId user_id, int32 unix_time) const; + + void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name); + + void on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames); + + void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number); + + void on_update_user_photo(User *u, UserId user_id, telegram_api::object_ptr &&photo, + const char *source); + + void do_update_user_photo(User *u, UserId user_id, telegram_api::object_ptr &&photo, + const char *source); + + void do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, bool invalidate_photo_cache, + const char *source); + + void register_user_photo(User *u, UserId user_id, const Photo &photo); + + void on_update_user_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id); + + void on_update_user_background_custom_emoji_id(User *u, UserId user_id, CustomEmojiId background_custom_emoji_id); + + void on_update_user_profile_accent_color_id(User *u, UserId user_id, AccentColorId accent_color_id); + + void on_update_user_profile_background_custom_emoji_id(User *u, UserId user_id, + CustomEmojiId background_custom_emoji_id); + + void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status); + + void on_update_user_story_ids_impl(User *u, UserId user_id, StoryId max_active_story_id, StoryId max_read_story_id); + + void on_update_user_max_read_story_id(User *u, UserId user_id, StoryId max_read_story_id); + + void on_update_user_stories_hidden(User *u, UserId user_id, bool stories_hidden); + + void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact, + bool is_close_friend); + + void on_update_user_online(User *u, UserId user_id, telegram_api::object_ptr &&status); + + void on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online); + + static void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked, + bool is_blocked_for_stories); + + static void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count); + + static void on_update_user_full_location(UserFull *user_full, UserId user_id, DialogLocation &&location); + + static void on_update_user_full_work_hours(UserFull *user_full, UserId user_id, BusinessWorkHours &&work_hours); + + void on_update_user_full_away_message(UserFull *user_full, UserId user_id, BusinessAwayMessage &&away_message) const; + + void on_update_user_full_greeting_message(UserFull *user_full, UserId user_id, + BusinessGreetingMessage &&greeting_message) const; + + static void on_update_user_full_intro(UserFull *user_full, UserId user_id, BusinessIntro &&intro); + + static void on_update_user_full_commands(UserFull *user_full, UserId user_id, + vector> &&bot_commands); + + void on_update_user_full_need_phone_number_privacy_exception(UserFull *user_full, UserId user_id, + bool need_phone_number_privacy_exception) const; + + void on_update_user_full_wallpaper_overridden(UserFull *user_full, UserId user_id, bool wallpaper_overridden) const; + + static void on_update_user_full_menu_button(UserFull *user_full, UserId user_id, + telegram_api::object_ptr &&bot_menu_button); + + bool have_input_peer_user(const User *u, UserId user_id, AccessRights access_rights) const; + + static bool have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights); + + bool need_poll_user_active_stories(const User *u, UserId user_id) const; + + static string get_user_search_text(const User *u); + + void set_profile_photo_impl(UserId user_id, const td_api::object_ptr &input_photo, + bool is_fallback, bool only_suggest, Promise &&promise); + + void upload_profile_photo(UserId user_id, FileId file_id, bool is_fallback, bool only_suggest, bool is_animation, + double main_frame_timestamp, Promise &&promise, int reupload_count = 0, + vector bad_parts = {}); + + void on_upload_profile_photo(FileId file_id, telegram_api::object_ptr input_file); + + void on_upload_profile_photo_error(FileId file_id, Status status); + + void add_set_profile_photo_to_cache(UserId user_id, Photo &&photo, bool is_fallback); + + bool delete_my_profile_photo_from_cache(int64 profile_photo_id); + + void toggle_username_is_active_impl(string &&username, bool is_active, Promise &&promise); + + void reorder_usernames_impl(vector &&usernames, Promise &&promise); + + void on_set_birthdate(Birthdate birthdate, Promise &&promise); + + void on_set_personal_channel(ChannelId channel_id, Promise &&promise); + + void on_set_emoji_status(EmojiStatus emoji_status, Promise &&promise); + + void on_toggle_sponsored_messages(bool sponsored_enabled, Promise &&promise); + + void on_get_support_user(UserId user_id, Promise> &&promise); + + void send_get_user_photos_query(UserId user_id, const UserPhotos *user_photos); + + void on_get_user_profile_photos(UserId user_id, Result &&result); + + UserPhotos *add_user_photos(UserId user_id); + + void apply_pending_user_photo(User *u, UserId user_id); + + void load_contacts(Promise &&promise); + + int64 get_contacts_hash(); + + void save_next_contacts_sync_date(); + + void save_contacts_to_database(); + + void on_load_contacts_from_database(string value); + + void on_get_contacts_finished(size_t expected_contact_count); + + void do_import_contacts(vector contacts, int64 random_id, Promise &&promise); + + void on_import_contacts_finished(int64 random_id, vector imported_contact_user_ids, + vector unimported_contact_invites); + + void load_imported_contacts(Promise &&promise); + + void on_load_imported_contacts_from_database(string value); + + void on_load_imported_contacts_finished(); + + void on_clear_imported_contacts(vector &&contacts, vector contacts_unique_id, + std::pair, vector> &&to_add, Promise &&promise); + + void update_contacts_hints(const User *u, UserId user_id, bool from_database); + + const UserFull *get_user_full(UserId user_id) const; + + UserFull *get_user_full(UserId user_id); + + UserFull *add_user_full(UserId user_id); + + UserFull *get_user_full_force(UserId user_id, const char *source); + + void send_get_user_full_query(UserId user_id, telegram_api::object_ptr &&input_user, + Promise &&promise, const char *source); + + static void save_user_full(const UserFull *user_full, UserId user_id); + + static string get_user_full_database_key(UserId user_id); + + static string get_user_full_database_value(const UserFull *user_full); + + void on_load_user_full_from_database(UserId user_id, string value); + + int64 get_user_full_profile_photo_id(const UserFull *user_full); + + void drop_user_full_photos(UserFull *user_full, UserId user_id, int64 expected_photo_id, const char *source); + + void drop_user_photos(UserId user_id, bool is_empty, const char *source); + + void drop_user_full(UserId user_id); + + const SecretChat *get_secret_chat(SecretChatId secret_chat_id) const; + + SecretChat *get_secret_chat(SecretChatId secret_chat_id); + + SecretChat *add_secret_chat(SecretChatId secret_chat_id); + + SecretChat *get_secret_chat_force(SecretChatId secret_chat_id, const char *source); + + void save_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog); + + static string get_secret_chat_database_key(SecretChatId secret_chat_id); + + static string get_secret_chat_database_value(const SecretChat *c); + + void save_secret_chat_to_database(SecretChat *c, SecretChatId secret_chat_id); + + void save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value); + + void on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success); + + void load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, Promise promise); + + void load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise promise); + + void on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force); + + void on_create_new_secret_chat(SecretChatId secret_chat_id, Promise> &&promise); + + void update_user(User *u, UserId user_id, bool from_binlog = false, bool from_database = false); + + void update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog = false, + bool from_database = false); + + void update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database = false); + + td_api::object_ptr get_user_status_object(UserId user_id, const User *u, int32 unix_time) const; + + static bool get_user_has_unread_stories(const User *u); + + td_api::object_ptr get_update_user_object(UserId user_id, const User *u) const; + + td_api::object_ptr get_update_unknown_user_object(UserId user_id) const; + + td_api::object_ptr get_user_object(UserId user_id, const User *u) const; + + td_api::object_ptr get_user_full_info_object(UserId user_id, const UserFull *user_full) const; + + td_api::object_ptr get_update_contact_close_birthdays() const; + + static td_api::object_ptr get_secret_chat_state_object(SecretChatState state); + + td_api::object_ptr get_update_secret_chat_object(SecretChatId secret_chat_id, + const SecretChat *secret_chat); + + static td_api::object_ptr get_update_unknown_secret_chat_object( + SecretChatId secret_chat_id); + + td_api::object_ptr get_secret_chat_object(SecretChatId secret_chat_id, + const SecretChat *secret_chat); + + td_api::object_ptr get_secret_chat_object_const(SecretChatId secret_chat_id, + const SecretChat *secret_chat) const; + + Td *td_; + ActorShared<> parent_; + UserId my_id_; + UserId support_user_id_; + int32 my_was_online_local_ = 0; + + WaitFreeHashMap, UserIdHash> users_; + WaitFreeHashMap, UserIdHash> users_full_; + WaitFreeHashMap, UserIdHash> user_photos_; + mutable FlatHashSet unknown_users_; + WaitFreeHashMap, UserIdHash> pending_user_photos_; + struct UserIdPhotoIdHash { + uint32 operator()(const std::pair &pair) const { + return combine_hashes(UserIdHash()(pair.first), Hash()(pair.second)); + } + }; + WaitFreeHashMap, FileSourceId, UserIdPhotoIdHash> user_profile_photo_file_source_ids_; + FlatHashMap my_photo_file_id_; + WaitFreeHashMap user_full_file_source_ids_; + + WaitFreeHashMap, SecretChatIdHash> secret_chats_; + mutable FlatHashSet unknown_secret_chats_; + + FlatHashMap, UserIdHash> secret_chats_with_user_; + + FlatHashMap>, UserIdHash> load_user_from_database_queries_; + FlatHashSet loaded_from_database_users_; + FlatHashSet unavailable_user_fulls_; + + FlatHashMap>, SecretChatIdHash> load_secret_chat_from_database_queries_; + FlatHashSet loaded_from_database_secret_chats_; + + QueryMerger get_user_queries_{"GetUserMerger", 3, 50}; + + QueryMerger get_is_premium_required_to_contact_queries_{"GetIsPremiumRequiredToContactMerger", 3, 100}; + + QueryCombiner get_user_full_queries_{"GetUserFullCombiner", 2.0}; + class UploadProfilePhotoCallback; + std::shared_ptr upload_profile_photo_callback_; + + struct UploadedProfilePhoto { + UserId user_id; + bool is_fallback; + bool only_suggest; + double main_frame_timestamp; + bool is_animation; + int reupload_count; + Promise promise; + + UploadedProfilePhoto(UserId user_id, bool is_fallback, bool only_suggest, double main_frame_timestamp, + bool is_animation, int32 reupload_count, Promise promise) + : user_id(user_id) + , is_fallback(is_fallback) + , only_suggest(only_suggest) + , main_frame_timestamp(main_frame_timestamp) + , is_animation(is_animation) + , reupload_count(reupload_count) + , promise(std::move(promise)) { + } + }; + FlatHashMap uploaded_profile_photos_; + + struct ImportContactsTask { + Promise promise_; + vector input_contacts_; + vector imported_user_ids_; + vector unimported_contact_invites_; + }; + FlatHashMap> import_contact_tasks_; + + FlatHashMap, vector>> imported_contacts_; + + FlatHashMap resolved_phone_numbers_; + + FlatHashMap, UserIdHash> user_messages_; + + bool are_contacts_loaded_ = false; + int32 next_contacts_sync_date_ = 0; + Hints contacts_hints_; // search contacts by first name, last name and usernames + vector> load_contacts_queries_; + MultiPromiseActor load_contact_users_multipromise_{"LoadContactUsersMultiPromiseActor"}; + int32 saved_contact_count_ = -1; + + int32 was_online_local_ = 0; + int32 was_online_remote_ = 0; + + bool are_imported_contacts_loaded_ = false; + vector> load_imported_contacts_queries_; + MultiPromiseActor load_imported_contact_users_multipromise_{"LoadImportedContactUsersMultiPromiseActor"}; + vector all_imported_contacts_; + bool are_imported_contacts_changing_ = false; + bool need_clear_imported_contacts_ = false; + + FlatHashMap user_full_contact_require_premium_; + + WaitFreeHashSet restricted_user_ids_; + + struct ContactBirthdates { + vector> users_; + double next_sync_time_ = 0.0; + bool is_being_synced_ = false; + bool need_drop_ = false; + }; + ContactBirthdates contact_birthdates_; + + vector next_all_imported_contacts_; + vector imported_contacts_unique_id_; + vector imported_contacts_pos_; + + vector imported_contact_user_ids_; // result of change_imported_contacts + vector unimported_contact_invites_; // result of change_imported_contacts + + MultiTimeout user_online_timeout_{"UserOnlineTimeout"}; + MultiTimeout user_emoji_status_timeout_{"UserEmojiStatusTimeout"}; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.cpp b/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.cpp index ff0214a0..c6e2e4a6 100644 --- a/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.cpp +++ b/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.cpp @@ -48,6 +48,9 @@ UserPrivacySetting::UserPrivacySetting(const telegram_api::PrivacyKey &key) { case telegram_api::privacyKeyAbout::ID: type_ = Type::UserBio; break; + case telegram_api::privacyKeyBirthday::ID: + type_ = Type::UserBirthdate; + break; default: UNREACHABLE(); type_ = Type::UserStatus; @@ -76,6 +79,8 @@ td_api::object_ptr UserPrivacySetting::get_user_priv return make_tl_object(); case Type::UserBio: return make_tl_object(); + case Type::UserBirthdate: + return make_tl_object(); default: UNREACHABLE(); return nullptr; @@ -103,6 +108,8 @@ telegram_api::object_ptr UserPrivacySetting::get_ return make_tl_object(); case Type::UserBio: return make_tl_object(); + case Type::UserBirthdate: + return make_tl_object(); default: UNREACHABLE(); return nullptr; @@ -141,6 +148,9 @@ UserPrivacySetting::UserPrivacySetting(const td_api::UserPrivacySetting &key) { case td_api::userPrivacySettingShowBio::ID: type_ = Type::UserBio; break; + case td_api::userPrivacySettingShowBirthdate::ID: + type_ = Type::UserBirthdate; + break; default: UNREACHABLE(); type_ = Type::UserStatus; diff --git a/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.h b/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.h index 26a50465..502ff674 100644 --- a/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.h +++ b/lib/tgchat/ext/td/td/telegram/UserPrivacySetting.h @@ -27,6 +27,7 @@ class UserPrivacySetting { FindByPhoneNumber, VoiceMessages, UserBio, + UserBirthdate, Size }; diff --git a/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.cpp b/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.cpp index 65e516cb..1618f502 100644 --- a/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.cpp +++ b/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.cpp @@ -8,10 +8,11 @@ #include "td/telegram/ChannelId.h" #include "td/telegram/ChatId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Td.h" +#include "td/telegram/UserManager.h" #include "td/utils/algorithm.h" #include "td/utils/logging.h" @@ -35,7 +36,7 @@ void UserPrivacySettingRule::set_dialog_ids(Td *td, const vector &chat_id break; case DialogType::Channel: { auto channel_id = dialog_id.get_channel_id(); - if (!td->contacts_manager_->is_megagroup_channel(channel_id)) { + if (!td->chat_manager_->is_megagroup_channel(channel_id)) { LOG(INFO) << "Ignore broadcast " << channel_id; break; } @@ -53,6 +54,9 @@ UserPrivacySettingRule::UserPrivacySettingRule(Td *td, const td_api::UserPrivacy case td_api::userPrivacySettingRuleAllowContacts::ID: type_ = Type::AllowContacts; break; + case td_api::userPrivacySettingRuleAllowPremiumUsers::ID: + type_ = Type::AllowPremium; + break; case td_api::userPrivacySettingRuleAllowAll::ID: type_ = Type::AllowAll; break; @@ -91,6 +95,9 @@ UserPrivacySettingRule::UserPrivacySettingRule(Td *td, case telegram_api::privacyValueAllowContacts::ID: type_ = Type::AllowContacts; break; + case telegram_api::privacyValueAllowPremium::ID: + type_ = Type::AllowPremium; + break; case telegram_api::privacyValueAllowCloseFriends::ID: type_ = Type::AllowCloseFriends; break; @@ -125,7 +132,7 @@ UserPrivacySettingRule::UserPrivacySettingRule(Td *td, UNREACHABLE(); } td::remove_if(user_ids_, [td](UserId user_id) { - if (!td->contacts_manager_->have_user(user_id)) { + if (!td->user_manager_->have_user(user_id)) { LOG(ERROR) << "Receive unknown " << user_id; return true; } @@ -138,10 +145,10 @@ void UserPrivacySettingRule::set_dialog_ids_from_server(Td *td, const vectorcontacts_manager_->have_chat(chat_id)) { + if (!td->chat_manager_->have_chat(chat_id)) { ChannelId channel_id(server_chat_id); dialog_id = DialogId(channel_id); - if (!td->contacts_manager_->have_channel(channel_id)) { + if (!td->chat_manager_->have_channel(channel_id)) { LOG(ERROR) << "Receive unknown group " << server_chat_id << " from the server"; continue; } @@ -156,6 +163,8 @@ td_api::object_ptr UserPrivacySettingRule::get_u switch (type_) { case Type::AllowContacts: return make_tl_object(); + case Type::AllowPremium: + return make_tl_object(); case Type::AllowCloseFriends: LOG(ERROR) << "Have AllowCloseFriends rule"; return make_tl_object(); @@ -163,7 +172,7 @@ td_api::object_ptr UserPrivacySettingRule::get_u return make_tl_object(); case Type::AllowUsers: return make_tl_object( - td->contacts_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleAllowUsers")); + td->user_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleAllowUsers")); case Type::AllowChatParticipants: return make_tl_object( td->dialog_manager_->get_chat_ids_object(dialog_ids_, "UserPrivacySettingRule")); @@ -173,7 +182,7 @@ td_api::object_ptr UserPrivacySettingRule::get_u return make_tl_object(); case Type::RestrictUsers: return make_tl_object( - td->contacts_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleRestrictUsers")); + td->user_manager_->get_user_ids_object(user_ids_, "userPrivacySettingRuleRestrictUsers")); case Type::RestrictChatParticipants: return make_tl_object( td->dialog_manager_->get_chat_ids_object(dialog_ids_, "UserPrivacySettingRule")); @@ -187,6 +196,8 @@ telegram_api::object_ptr UserPrivacySettingRule: switch (type_) { case Type::AllowContacts: return make_tl_object(); + case Type::AllowPremium: + return make_tl_object(); case Type::AllowCloseFriends: return make_tl_object(); case Type::AllowAll: @@ -211,7 +222,7 @@ telegram_api::object_ptr UserPrivacySettingRule: vector> UserPrivacySettingRule::get_input_users(Td *td) const { vector> result; for (auto user_id : user_ids_) { - auto r_input_user = td->contacts_manager_->get_input_user(user_id); + auto r_input_user = td->user_manager_->get_input_user(user_id); if (r_input_user.is_ok()) { result.push_back(r_input_user.move_as_ok()); } else { @@ -256,8 +267,8 @@ void UserPrivacySettingRule::add_dependencies(Dependencies &dependencies) const UserPrivacySettingRules UserPrivacySettingRules::get_user_privacy_setting_rules( Td *td, telegram_api::object_ptr rules) { - td->contacts_manager_->on_get_users(std::move(rules->users_), "on get privacy rules"); - td->contacts_manager_->on_get_chats(std::move(rules->chats_), "on get privacy rules"); + td->user_manager_->on_get_users(std::move(rules->users_), "on get privacy rules"); + td->chat_manager_->on_get_chats(std::move(rules->chats_), "on get privacy rules"); return get_user_privacy_setting_rules(td, std::move(rules->rules_)); } @@ -345,7 +356,7 @@ td_api::object_ptr UserPrivacySettingRules::get_st if (rules_.size() == 2u && rules_[0].type_ == UserPrivacySettingRule::Type::RestrictUsers && rules_[1].type_ == UserPrivacySettingRule::Type::AllowAll) { return td_api::make_object( - td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsEveryone")); + td->user_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsEveryone")); } if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowContacts) { return td_api::make_object(); @@ -353,14 +364,14 @@ td_api::object_ptr UserPrivacySettingRules::get_st if (rules_.size() == 2u && rules_[0].type_ == UserPrivacySettingRule::Type::RestrictUsers && rules_[1].type_ == UserPrivacySettingRule::Type::AllowContacts) { return td_api::make_object( - td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsContacts")); + td->user_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsContacts")); } if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowCloseFriends) { return td_api::make_object(); } if (rules_.size() == 1u && rules_[0].type_ == UserPrivacySettingRule::Type::AllowUsers) { return td_api::make_object( - td->contacts_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsSelectedUsers")); + td->user_manager_->get_user_ids_object(rules_[0].user_ids_, "storyPrivacySettingsSelectedUsers")); } return td_api::make_object(); } diff --git a/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.h b/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.h index 483a263b..3e16dcb3 100644 --- a/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.h +++ b/lib/tgchat/ext/td/td/telegram/UserPrivacySettingRule.h @@ -69,8 +69,8 @@ class UserPrivacySettingRule { parser.set_error("Failed to parse chat identifiers"); } } - } else if (type_ != Type::AllowContacts && type_ != Type::AllowCloseFriends && type_ != Type::AllowAll && - type_ != Type::RestrictContacts && type_ != Type::RestrictAll) { + } else if (type_ != Type::AllowContacts && type_ != Type::AllowPremium && type_ != Type::AllowCloseFriends && + type_ != Type::AllowAll && type_ != Type::RestrictContacts && type_ != Type::RestrictAll) { parser.set_error("Invalid privacy rule type"); } } @@ -85,7 +85,8 @@ class UserPrivacySettingRule { RestrictContacts, RestrictAll, RestrictUsers, - RestrictChatParticipants + RestrictChatParticipants, + AllowPremium } type_ = Type::RestrictAll; friend class UserPrivacySettingRules; diff --git a/lib/tgchat/ext/td/td/telegram/Version.h b/lib/tgchat/ext/td/td/telegram/Version.h index 35d70825..062d9302 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 = 176; +constexpr int32 MTPROTO_LAYER = 181; enum class Version : int32 { Initial, // 0 @@ -66,6 +66,7 @@ enum class Version : int32 { AddPageBlockChatLinkFlags, // 50 SupportRepliesInOtherChats, SupportMultipleSharedUsers, + SupportMoreEmojiGroups, Next }; diff --git a/lib/tgchat/ext/td/td/telegram/WebApp.cpp b/lib/tgchat/ext/td/td/telegram/WebApp.cpp index 7e8e61a7..f4c0bfad 100644 --- a/lib/tgchat/ext/td/td/telegram/WebApp.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebApp.cpp @@ -61,12 +61,6 @@ td_api::object_ptr WebApp::get_web_app_object(Td *td) const { td->animations_manager_->get_animation_object(animation_file_id_)); } -td_api::object_ptr WebApp::get_message_sponsor_type_web_app( - const string &bot_username, const string &start_parameter) const { - return td_api::make_object( - title_, td_api::make_object(bot_username, short_name_, start_parameter)); -} - bool operator==(const WebApp &lhs, const WebApp &rhs) { return lhs.id_ == rhs.id_ && lhs.access_hash_ == rhs.access_hash_ && lhs.short_name_ == rhs.short_name_ && lhs.title_ == rhs.title_ && lhs.description_ == rhs.description_ && lhs.photo_ == rhs.photo_ && diff --git a/lib/tgchat/ext/td/td/telegram/WebApp.h b/lib/tgchat/ext/td/td/telegram/WebApp.h index 5c01072b..a9d9fd25 100644 --- a/lib/tgchat/ext/td/td/telegram/WebApp.h +++ b/lib/tgchat/ext/td/td/telegram/WebApp.h @@ -45,9 +45,6 @@ class WebApp { td_api::object_ptr get_web_app_object(Td *td) const; - td_api::object_ptr get_message_sponsor_type_web_app( - const string &bot_username, const string &start_parameter) const; - template void store(StorerT &storer) const; diff --git a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp index 73201dbb..50cb3fb2 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPageBlock.cpp @@ -12,7 +12,7 @@ #include "td/telegram/AudiosManager.h" #include "td/telegram/AudiosManager.hpp" #include "td/telegram/ChannelId.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/DialogId.h" #include "td/telegram/Dimensions.h" #include "td/telegram/Document.h" @@ -2204,13 +2204,13 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptrcontacts_manager_->have_channel_force(channel_id, "pageBlockChannel")) { - td->contacts_manager_->on_get_chat(std::move(page_block->channel_), "pageBlockChannel"); + if (td->chat_manager_->have_channel_force(channel_id, "pageBlockChannel")) { + td->chat_manager_->on_get_chat(std::move(page_block->channel_), "pageBlockChannel"); LOG(INFO) << "Receive known min " << channel_id; - return td::make_unique(td->contacts_manager_->get_channel_title(channel_id), - *td->contacts_manager_->get_channel_dialog_photo(channel_id), - td->contacts_manager_->get_channel_first_username(channel_id), - td->contacts_manager_->get_channel_accent_color_id(channel_id), + return td::make_unique(td->chat_manager_->get_channel_title(channel_id), + *td->chat_manager_->get_channel_dialog_photo(channel_id), + td->chat_manager_->get_channel_first_username(channel_id), + td->chat_manager_->get_channel_accent_color_id(channel_id), channel_id); } else { bool has_access_hash = (channel->flags_ & telegram_api::channel::ACCESS_HASH_MASK) != 0; @@ -2219,9 +2219,7 @@ unique_ptr get_web_page_block(Td *td, tl_object_ptrtitle_), get_dialog_photo(td->file_manager_.get(), DialogId(channel_id), has_access_hash ? channel->access_hash_ : 0, std::move(channel->photo_)), - std::move(channel->username_), - peer_color.accent_color_id_.is_valid() ? peer_color.accent_color_id_ : AccentColorId(channel_id), - channel_id); + std::move(channel->username_), peer_color.accent_color_id_, channel_id); } } else { LOG(ERROR) << "Receive wrong channel " << to_string(page_block->channel_); diff --git a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp index 7e0d24a2..dd030061 100644 --- a/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/WebPagesManager.cpp @@ -9,7 +9,7 @@ #include "td/telegram/AnimationsManager.h" #include "td/telegram/AudiosManager.h" #include "td/telegram/AuthManager.h" -#include "td/telegram/ContactsManager.h" +#include "td/telegram/ChatManager.h" #include "td/telegram/Dependencies.h" #include "td/telegram/DialogManager.h" #include "td/telegram/Dimensions.h" @@ -26,13 +26,16 @@ #include "td/telegram/MessagesManager.h" #include "td/telegram/Photo.h" #include "td/telegram/PhotoFormat.h" +#include "td/telegram/StickerFormat.h" #include "td/telegram/StickersManager.h" +#include "td/telegram/StickersManager.hpp" #include "td/telegram/StoryFullId.h" #include "td/telegram/StoryId.h" #include "td/telegram/StoryManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/UserManager.h" #include "td/telegram/VideoNotesManager.h" #include "td/telegram/VideosManager.h" #include "td/telegram/VoiceNotesManager.h" @@ -125,8 +128,8 @@ class GetWebPageQuery final : public Td::ResultHandler { auto ptr = result_ptr.move_as_ok(); LOG(INFO) << "Receive result for GetWebPageQuery: " << to_string(ptr); - td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetWebPageQuery"); - td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetWebPageQuery"); + td_->user_manager_->on_get_users(std::move(ptr->users_), "GetWebPageQuery"); + td_->chat_manager_->on_get_chats(std::move(ptr->chats_), "GetWebPageQuery"); auto page = std::move(ptr->webpage_); if (page->get_id() == telegram_api::webPageNotModified::ID) { if (web_page_id_.is_valid()) { @@ -242,6 +245,7 @@ class WebPagesManager::WebPage { Document document_; vector documents_; vector story_full_ids_; + vector sticker_ids_; WebPageInstantView instant_view_; FileSourceId file_source_id_; @@ -266,6 +270,7 @@ class WebPagesManager::WebPage { bool has_no_hash = true; bool has_documents = !documents_.empty(); bool has_story_full_ids = !story_full_ids_.empty(); + bool has_sticker_ids = !sticker_ids_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_type); STORE_FLAG(has_site_name); @@ -283,6 +288,7 @@ class WebPagesManager::WebPage { STORE_FLAG(has_documents); STORE_FLAG(has_story_full_ids); STORE_FLAG(has_large_media_); + STORE_FLAG(has_sticker_ids); END_STORE_FLAGS(); store(url_, storer); @@ -324,6 +330,13 @@ class WebPagesManager::WebPage { if (has_story_full_ids) { store(story_full_ids_, storer); } + if (has_sticker_ids) { + Td *td = storer.context()->td().get_actor_unsafe(); + store(static_cast(sticker_ids_.size()), storer); + for (auto &sticker_id : sticker_ids_) { + td->stickers_manager_->store_sticker(sticker_id, false, storer, "WebPage"); + } + } } template @@ -344,6 +357,7 @@ class WebPagesManager::WebPage { bool has_no_hash; bool has_documents; bool has_story_full_ids; + bool has_sticker_ids; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_type); PARSE_FLAG(has_site_name); @@ -361,6 +375,7 @@ class WebPagesManager::WebPage { PARSE_FLAG(has_documents); PARSE_FLAG(has_story_full_ids); PARSE_FLAG(has_large_media_); + PARSE_FLAG(has_sticker_ids); END_PARSE_FLAGS(); parse(url_, parser); @@ -407,6 +422,17 @@ class WebPagesManager::WebPage { parse(story_full_ids_, parser); td::remove_if(story_full_ids_, [](StoryFullId story_full_id) { return !story_full_id.is_server(); }); } + if (has_sticker_ids) { + Td *td = parser.context()->td().get_actor_unsafe(); + uint32 sticker_count; + parse(sticker_count, parser); + for (size_t i = 0; i < sticker_count; i++) { + auto sticker_id = td->stickers_manager_->parse_sticker(false, parser); + if (sticker_id.is_valid()) { + sticker_ids_.push_back(sticker_id); + } + } + } if (has_instant_view) { instant_view_.is_empty_ = false; @@ -424,7 +450,7 @@ class WebPagesManager::WebPage { lhs.duration_ == rhs.duration_ && lhs.author_ == rhs.author_ && lhs.has_large_media_ == rhs.has_large_media_ && lhs.document_ == rhs.document_ && lhs.documents_ == rhs.documents_ && lhs.story_full_ids_ == rhs.story_full_ids_ && - lhs.instant_view_.is_empty_ == rhs.instant_view_.is_empty_ && + lhs.sticker_ids_ == rhs.sticker_ids_ && lhs.instant_view_.is_empty_ == rhs.instant_view_.is_empty_ && lhs.instant_view_.is_v2_ == rhs.instant_view_.is_v2_; } }; @@ -591,6 +617,22 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr page->story_full_ids_.push_back(story_full_id); break; } + case telegram_api::webPageAttributeStickerSet::ID: { + auto attribute = telegram_api::move_object_as(attribute_ptr); + if (!page->sticker_ids_.empty()) { + LOG(ERROR) << "Receive duplicate webPageAttributeStickerSet"; + } + for (auto &sticker : attribute->stickers_) { + auto sticker_id = + td_->stickers_manager_->on_get_sticker_document(std::move(sticker), StickerFormat::Unknown).second; + if (sticker_id.is_valid() && page->sticker_ids_.size() < 4) { + page->sticker_ids_.push_back(sticker_id); + } + } + break; + } + default: + UNREACHABLE(); } } if (web_page->cached_page_ != nullptr) { @@ -837,6 +879,8 @@ void WebPagesManager::unregister_web_page(WebPageId web_page_id, MessageFullId m web_page_messages_.erase(web_page_id); if (pending_get_web_pages_.count(web_page_id) == 0) { pending_web_pages_timeout_.cancel_timeout(web_page_id.get()); + } else { + LOG(INFO) << "Still waiting for " << web_page_id; } } } @@ -920,7 +964,7 @@ void WebPagesManager::get_web_page_preview(td_api::object_ptrlink_preview_options_ = std::move(link_preview_options); td_->create_handler(std::move(promise)) ->send(formatted_text.text, - get_input_message_entities(td_->contacts_manager_.get(), formatted_text.entities, "get_web_page_preview"), + get_input_message_entities(td_->user_manager_.get(), formatted_text.entities, "get_web_page_preview"), std::move(options)); } @@ -1392,7 +1436,9 @@ tl_object_ptr WebPagesManager::get_web_page_object(WebPageId we } return false; }(); - return make_tl_object( + auto stickers = transform(web_page->sticker_ids_, + [&](FileId sticker_id) { return td_->stickers_manager_->get_sticker_object(sticker_id); }); + return td_api::make_object( web_page->url_, web_page->display_url_, web_page->type_, web_page->site_name_, web_page->title_, get_formatted_text_object(description, true, duration == 0 ? std::numeric_limits::max() : duration), get_photo_object(td_->file_manager_.get(), web_page->photo_), web_page->embed_url_, web_page->embed_type_, @@ -1419,7 +1465,7 @@ tl_object_ptr WebPagesManager::get_web_page_object(WebPageId we web_page->document_.type == Document::Type::VoiceNote ? td_->voice_notes_manager_->get_voice_note_object(web_page->document_.file_id) : nullptr, - td_->dialog_manager_->get_chat_id_object(story_sender_dialog_id, "webPage"), story_id.get(), + td_->dialog_manager_->get_chat_id_object(story_sender_dialog_id, "webPage"), story_id.get(), std::move(stickers), instant_view_version); } @@ -1462,7 +1508,7 @@ void WebPagesManager::on_web_page_changed(WebPageId web_page_id, bool have_web_p if (!have_web_page) { td_->messages_manager_->delete_pending_message_web_page(message_full_id); } else { - td_->messages_manager_->on_external_update_message_content(message_full_id); + td_->messages_manager_->on_external_update_message_content(message_full_id, "on_web_page_changed"); } } @@ -1529,6 +1575,7 @@ void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) { return; } + LOG(INFO) << "Process timeout for " << web_page_id; int32 count = 0; auto it = web_page_messages_.find(web_page_id); if (it != web_page_messages_.end()) { @@ -1554,7 +1601,7 @@ void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) { } } if (count == 0) { - LOG(WARNING) << "Have no messages and requests waiting for " << web_page_id; + LOG(INFO) << "Have no messages and requests waiting for " << web_page_id; } } @@ -1620,7 +1667,7 @@ void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_ if (document_id != 0) { get_map(document.type)->emplace(document_id, document.file_id); } else { - LOG(ERROR) << document.type << " has zero ID"; + LOG(ERROR) << document.type << " has zero identifier"; } } else { LOG(ERROR) << document.type << " has no remote location"; @@ -1629,9 +1676,12 @@ void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_ if (!web_page->document_.empty()) { add_document(web_page->document_); } - for (auto &document : web_page->documents_) { + for (const auto &document : web_page->documents_) { add_document(document); } + for (auto sticker_id : web_page->sticker_ids_) { + add_document({Document::Type::Sticker, sticker_id}); + } LOG(INFO) << "Receive a web page instant view with " << page->blocks_.size() << " blocks, " << animations.size() << " animations, " << audios.size() << " audios, " << documents.size() << " documents, " << photos.size() @@ -1951,6 +2001,7 @@ vector WebPagesManager::get_web_page_file_ids(const WebPage *web_page) c for (auto &document : web_page->documents_) { document.append_file_ids(td_, result); } + append(result, web_page->sticker_ids_); if (!web_page->instant_view_.is_empty_) { for (auto &page_block : web_page->instant_view_.page_blocks_) { page_block->append_file_ids(td_, result); diff --git a/lib/tgchat/ext/td/td/telegram/cli.cpp b/lib/tgchat/ext/td/td/telegram/cli.cpp index 17061ace..265aeeef 100644 --- a/lib/tgchat/ext/td/td/telegram/cli.cpp +++ b/lib/tgchat/ext/td/td/telegram/cli.cpp @@ -585,8 +585,8 @@ class CliClient final : public Actor { } td_api::object_ptr as_business_recipients(string chat_ids) const { - return td_api::make_object(as_chat_ids(chat_ids), rand_bool(), rand_bool(), rand_bool(), - rand_bool(), rand_bool()); + return td_api::make_object(as_chat_ids(chat_ids), Auto(), rand_bool(), rand_bool(), + rand_bool(), rand_bool(), rand_bool()); } static td_api::object_ptr as_sticker_format(string sticker_format) { @@ -1019,8 +1019,8 @@ class CliClient final : public Actor { }; void get_args(string &args, InputInvoice &arg) const { - if (args.size() > 1 && args[0] == '#') { - arg.invoice_name = args; + if (args.size() > 1 && (args[0] == '#' || args[0] == '$')) { + arg.invoice_name = args.substr(1); } else { string chat_id; string message_id; @@ -1187,7 +1187,15 @@ class CliClient final : public Actor { } struct BackgroundType { - enum class Type : int32 { Null, Wallpaper, SolidPattern, GradientPattern, Fill, ChatTheme }; + enum class Type : int32 { + Null, + Wallpaper, + SolidPattern, + GradientPattern, + FreeformGradientPattern, + Fill, + ChatTheme + }; Type type = Type::Null; vector colors; string theme_name; @@ -1202,6 +1210,8 @@ class CliClient final : public Actor { return as_solid_pattern_background(0xABCDef, 49, true); case Type::GradientPattern: return as_gradient_pattern_background(0xABCDEF, 0xFE, 51, rand_bool(), false); + case Type::FreeformGradientPattern: + return as_freeform_gradient_pattern_background({0xABCDEF, 0xFE, 0xFF0000}, 52, rand_bool(), rand_bool()); case Type::Fill: if (colors.size() == 1) { return as_solid_background(colors[0]); @@ -1230,6 +1240,8 @@ class CliClient final : public Actor { arg.type = BackgroundType::Type::SolidPattern; } else if (args == "gp") { arg.type = BackgroundType::Type::GradientPattern; + } else if (args == "fgp") { + arg.type = BackgroundType::Type::FreeformGradientPattern; } else if (args[0] == 't') { arg.type = BackgroundType::Type::ChatTheme; arg.theme_name = args.substr(1); @@ -1239,6 +1251,27 @@ class CliClient final : public Actor { } } + struct ReactionNotificationSource { + string source; + + operator td_api::object_ptr() const { + if (source == "none" || source == "n") { + return td_api::make_object(); + } + if (source == "contacts" || source == "c") { + return td_api::make_object(); + } + if (source == "all" || source == "a") { + return td_api::make_object(); + } + return nullptr; + } + }; + + void get_args(string &args, ReactionNotificationSource &arg) const { + arg.source = trim(args); + } + struct PrivacyRules { string rules_str; @@ -1505,6 +1538,21 @@ class CliClient final : public Actor { } break; } + case td_api::updateNewBusinessMessage::ID: { + const auto *update = static_cast(result.get()); + const auto *message = update->message_->message_.get(); + if (!message->is_outgoing_ && use_test_dc_) { + auto old_business_connection_id = std::move(business_connection_id_); + business_connection_id_ = update->connection_id_; + on_cmd("gbc"); + send_message(message->chat_id_, + td_api::make_object(as_formatted_text("Welcome!"), + get_link_preview_options(), true), + false, false); + business_connection_id_ = std::move(old_business_connection_id); + } + break; + } case td_api::file::ID: on_get_file(*static_cast(result.get())); break; @@ -1713,7 +1761,7 @@ class CliClient final : public Actor { static td_api::object_ptr as_formatted_text( const string &text, vector> entities = {}) { if (entities.empty() && !text.empty()) { - Slice unused_reserved_characters("#+-={}.!"); + Slice unused_reserved_characters("#+-={}."); string new_text; for (size_t i = 0; i < text.size(); i++) { auto c = text[i]; @@ -2089,6 +2137,12 @@ class CliClient final : public Actor { if (action == "checks") { return td_api::make_object(); } + if (action == "extend") { + return td_api::make_object(""); + } + if (action == "annual") { + return td_api::make_object(); + } if (begins_with(action, "giga")) { return td_api::make_object(as_supergroup_id(action.substr(4))); } @@ -2257,7 +2311,7 @@ class CliClient final : public Actor { } static td_api::object_ptr as_background_fill(int32 top_color, int32 bottom_color) { - return td_api::make_object(top_color, bottom_color, Random::fast(0, 7) * 45); + return td_api::make_object(top_color, bottom_color, Random::fast(1, 7) * 45); } static td_api::object_ptr as_background_fill(vector colors) { @@ -2305,7 +2359,7 @@ class CliClient final : public Actor { } td_api::object_ptr as_phone_number_authentication_settings() const { - return td_api::make_object(false, true, false, false, nullptr, + return td_api::make_object(false, true, false, false, false, nullptr, vector(authentication_tokens_)); } @@ -2343,10 +2397,21 @@ class CliClient final : public Actor { void send_message(int64 chat_id, td_api::object_ptr &&input_message_content, bool disable_notification = false, bool from_background = false) { + if (!business_connection_id_.empty()) { + send_request(td_api::make_object( + business_connection_id_, chat_id, get_input_message_reply_to(), disable_notification, rand_bool(), + message_effect_id_, nullptr, std::move(input_message_content))); + return; + } + if (!quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + quick_reply_shortcut_name_, reply_message_id_, std::move(input_message_content))); + return; + } auto id = send_request(td_api::make_object( chat_id, message_thread_id_, get_input_message_reply_to(), - td_api::make_object(disable_notification, from_background, true, true, - as_message_scheduling_state(schedule_date_), + td_api::make_object(disable_notification, from_background, false, false, + as_message_scheduling_state(schedule_date_), message_effect_id_, Random::fast(1, 1000), only_preview_), nullptr, std::move(input_message_content))); if (id != 0) { @@ -2355,8 +2420,9 @@ class CliClient final : public Actor { } td_api::object_ptr default_message_send_options() const { - return td_api::make_object( - false, false, false, true, as_message_scheduling_state(schedule_date_), Random::fast(1, 1000), only_preview_); + return td_api::make_object(false, false, false, true, + as_message_scheduling_state(schedule_date_), + message_effect_id_, Random::fast(1, 1000), only_preview_); } void send_get_background_url(td_api::object_ptr &&background_type) { @@ -2400,7 +2466,7 @@ class CliClient final : public Actor { string args; std::tie(op, args) = split(cmd); - const int32 OP_BLOCK_COUNT = 10; + const int32 OP_BLOCK_COUNT = 19; int32 op_not_found_count = 0; if (op == "gas") { @@ -2411,13 +2477,15 @@ class CliClient final : public Actor { } else if (op == "sae" || op == "saea") { send_request(td_api::make_object(args)); } else if (op == "rac") { - send_request(td_api::make_object()); + send_request(td_api::make_object(nullptr)); } else if (op == "sdek") { send_request(td_api::make_object(args)); } else if (op == "caec") { send_request(td_api::make_object(as_email_address_authentication(args))); } else if (op == "cac") { send_request(td_api::make_object(args)); + } else if (op == "racmg") { + send_request(td_api::make_object(args)); } else if (op == "ru" || op == "rus") { string first_name; string last_name; @@ -2537,12 +2605,6 @@ class CliClient final : public Actor { td_api::make_object(form_id, as_passport_element_types(types))); } else if (op == "gpcl") { send_request(td_api::make_object(args)); - } else if (op == "spnvc" || op == "SendPhoneNumberVerificationCode") { - send_request(td_api::make_object(args, nullptr)); - } else if (op == "cpnvc" || op == "CheckPhoneNumberVerificationCode") { - send_request(td_api::make_object(args)); - } else if (op == "rpnvc" || op == "ResendPhoneNumberVerificationCode") { - send_request(td_api::make_object()); } else if (op == "seavc" || op == "SendEmailAddressVerificationCode") { send_request(td_api::make_object(args)); } else if (op == "ceavc" || op == "CheckEmailAddressVerificationCode") { @@ -2568,16 +2630,11 @@ class CliClient final : public Actor { send_request(td_api::make_object()); } else if (op == "creav") { send_request(td_api::make_object()); - } else if (op == "spncc") { - string hash; - string phone_number; - get_args(args, hash, phone_number); - send_request(td_api::make_object(hash, phone_number, nullptr)); - } else if (op == "cpncc") { - send_request(td_api::make_object(args)); - } else if (op == "rpncc") { - send_request(td_api::make_object()); - } else if (op == "rpr") { + } else { + op_not_found_count++; + } + + if (op == "rpr") { send_request(td_api::make_object()); } else if (op == "cprc") { string recovery_code = args; @@ -2682,6 +2739,12 @@ class CliClient final : public Actor { send_request(td_api::make_object( input_invoice, payment_form_id, order_info_id, shipping_option_id, td_api::make_object(data, true), tip_amount)); + } else if (op == "spfstar") { + InputInvoice input_invoice; + int64 payment_form_id; + get_args(args, input_invoice, payment_form_id); + send_request( + td_api::make_object(input_invoice, payment_form_id, string(), string(), nullptr, 0)); } else if (op == "gpre") { ChatId chat_id; MessageId message_id; @@ -2697,6 +2760,11 @@ class CliClient final : public Actor { // 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; + get_args(args, user_id, telegram_payment_charge_id); + send_request(td_api::make_object(user_id, telegram_payment_charge_id)); } else if (op == "gpr") { send_request(td_api::make_object(as_user_privacy_setting(args))); } else if (op == "spr") { @@ -2704,12 +2772,26 @@ class CliClient final : public Actor { PrivacyRules rules; get_args(args, setting, rules); send_request(td_api::make_object(as_user_privacy_setting(setting), rules)); - } else if (op == "cp" || op == "ChangePhone") { - send_request(td_api::make_object(args, nullptr)); - } else if (op == "ccpc" || op == "CheckChangePhoneCode") { - send_request(td_api::make_object(args)); - } else if (op == "rcpc" || op == "ResendChangePhoneCode") { - send_request(td_api::make_object()); + } else if (op == "spncc") { + send_request(td_api::make_object( + args, nullptr, td_api::make_object())); + } else if (op == "spncv") { + send_request(td_api::make_object( + args, nullptr, td_api::make_object())); + } else if (op == "spncco") { + string hash; + string phone_number; + get_args(args, hash, phone_number); + send_request(td_api::make_object( + phone_number, nullptr, td_api::make_object(hash))); + } else if (op == "spnfs") { + send_request(td_api::make_object(args)); + } else if (op == "rpncm") { + send_request(td_api::make_object(args)); + } else if (op == "rpnc") { + send_request(td_api::make_object(nullptr)); + } else if (op == "cpnc") { + send_request(td_api::make_object(args)); } else if (op == "gco") { if (args.empty()) { send_request(td_api::make_object()); @@ -2731,10 +2813,10 @@ class CliClient final : public Actor { get_args(args, user_id, first_name, last_name); send_request(td_api::make_object( td_api::make_object(string(), first_name, last_name, string(), user_id), false)); - } else if (op == "subpn") { + } else if (op == "subpn" || op == "subpnl") { string phone_number; get_args(args, phone_number); - send_request(td_api::make_object(phone_number)); + send_request(td_api::make_object(phone_number, op == "subpnl")); } else if (op == "spn") { UserId user_id; get_args(args, user_id); @@ -2887,6 +2969,8 @@ class CliClient final : public Actor { string label; get_args(args, reaction, label); send_request(td_api::make_object(as_reaction_type(reaction), label)); + } else if (op == "gme") { + send_request(td_api::make_object(to_integer(args))); } else if (op == "gmpf") { ChatId chat_id; MessageId message_id; @@ -2916,7 +3000,7 @@ class CliClient final : public Actor { search_chat_id_ = as_chat_id(args); send_request(td_api::make_object( search_chat_id_, "", nullptr, 0, 0, 100, as_search_messages_filter("pvi"), 0, get_saved_messages_topic_id())); - } else if (op == "Search" || op == "SearchA" || op == "SearchM") { + } else if (op == "Search" || op == "SearchA" || op == "SearchM" || op == "SearchC") { string query; string limit; string filter; @@ -2929,8 +3013,9 @@ class CliClient final : public Actor { if (op == "SearchM") { chat_list = td_api::make_object(); } - send_request(td_api::make_object(std::move(chat_list), query, offset, as_limit(limit), - as_search_messages_filter(filter), 1, 2147483647)); + send_request(td_api::make_object(std::move(chat_list), op == "SearchC", query, offset, + as_limit(limit), as_search_messages_filter(filter), 1, + 2147483647)); } else if (op == "SCM") { ChatId chat_id; SearchQuery query; @@ -2973,6 +3058,23 @@ class CliClient final : public Actor { SearchQuery query; get_args(args, query); send_request(td_api::make_object(query.query, query.limit)); + } else if (op == "sphm") { + string hashtag; + string limit; + string offset; + get_args(args, hashtag, limit, offset); + send_request(td_api::make_object(hashtag, offset, as_limit(limit))); + } else if (op == "gsfh") { + string hashtag; + string limit; + get_args(args, hashtag, limit); + send_request(td_api::make_object(hashtag, as_limit(limit))); + } else if (op == "rsfh") { + string hashtag; + get_args(args, hashtag); + send_request(td_api::make_object(hashtag)); + } else if (op == "csfh") { + send_request(td_api::make_object()); } else if (op == "DeleteAllCallMessages") { bool revoke = as_bool(args); send_request(td_api::make_object(revoke)); @@ -3106,7 +3208,11 @@ class CliClient final : public Actor { send_request(td_api::make_object(language_code, std::move(str))); } else if (op == "dlp") { send_request(td_api::make_object(args)); - } else if (op == "on" || op == "off") { + } else { + op_not_found_count++; + } + + if (op == "on" || op == "off") { send_request(td_api::make_object("online", td_api::make_object(op == "on"))); } else if (op == "go") { @@ -3254,6 +3360,12 @@ class CliClient final : public Actor { send_request(td_api::make_object(args)); } else if (op == "gpnis") { execute(td_api::make_object(rand_bool() ? "en" : "", args)); + } else if (op == "gciu") { + send_request(td_api::make_object( + td_api::make_object(args))); + } else if (op == "gcipn") { + send_request(td_api::make_object( + td_api::make_object(args))); } else if (op == "gadl") { send_request(td_api::make_object()); } else if (op == "gprl") { @@ -3290,30 +3402,49 @@ class CliClient final : public Actor { MessageId message_id; get_args(args, chat_id, message_id); send_request(td_api::make_object(chat_id, message_id)); - } else if (op == "cppr" || op == "cpprb") { + } else if (op == "gspo") { + send_request(td_api::make_object()); + } else if (op == "gsta" || op == "gsti" || op == "gsto") { + 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(args, std::move(direction))); + } else if (op == "cpfs" || op == "cpfsb") { UserId user_id; string currency; int64 amount; ChatId boosted_chat_id; get_args(args, user_id, currency, amount, boosted_chat_id); if (currency.empty()) { - send_request(td_api::make_object( + send_request(td_api::make_object( td_api::make_object(false, false))); - } else if (op == "cppr") { - send_request(td_api::make_object( + } else if (op == "cpfs") { + send_request(td_api::make_object( td_api::make_object(user_id, currency, amount))); } else { - send_request(td_api::make_object( + send_request(td_api::make_object( td_api::make_object(boosted_chat_id, currency, amount, vector{user_id}))); } - } else if (op == "cpprg") { + } else if (op == "cpfsg") { PremiumGiveawayParameters parameters; string currency; int64 amount; get_args(args, parameters, currency, amount); - send_request(td_api::make_object( + send_request(td_api::make_object( td_api::make_object(parameters, currency, amount))); + } else if (op == "cpfss") { + string currency; + int64 amount; + int64 star_count; + get_args(args, currency, amount, star_count); + send_request(td_api::make_object( + td_api::make_object(currency, amount, star_count))); + } else if (op == "gbf") { + send_request(td_api::make_object(nullptr)); } else if (op == "atos") { send_request(td_api::make_object(args)); } else if (op == "gdli") { @@ -3348,6 +3479,8 @@ class CliClient final : public Actor { SearchQuery query; get_args(args, query); send_request(td_api::make_object(as_sticker_type(op), query.query, query.limit)); + } else if (op == "ggs") { + send_request(td_api::make_object()); } else if (op == "gprst") { string limit; get_args(args, limit); @@ -3374,7 +3507,11 @@ class CliClient final : public Actor { FileId file_id; get_args(args, file_id); send_request(td_api::make_object(file_id)); - } else if (op == "storage") { + } else { + op_not_found_count++; + } + + if (op == "storage") { int32 chat_limit; get_args(args, chat_limit); send_request(td_api::make_object(chat_limit)); @@ -3496,11 +3633,16 @@ class CliClient final : public Actor { get_args(args, title, name, stickers); auto input_stickers = transform(autosplit(stickers), [op](Slice sticker) -> td_api::object_ptr { - return td_api::make_object(as_input_file(sticker), "😀", as_mask_position(op), - vector{"keyword"}); + return td_api::make_object(as_input_file(sticker), as_sticker_format(op), "😀", + as_mask_position(op), vector{"keyword"}); }); - send_request(td_api::make_object( - my_id_, title, name, as_sticker_format(op), as_sticker_type(op), false, std::move(input_stickers), "tg_cli")); + send_request(td_api::make_object(my_id_, title, name, as_sticker_type(op), false, + std::move(input_stickers), "tg_cli")); + } else if (op == "goss") { + int64 sticker_set_id; + string limit; + get_args(args, sticker_set_id, limit); + send_request(td_api::make_object(sticker_set_id, as_limit(limit))); } else if (op == "sss") { send_request(td_api::make_object(args)); } else if (op == "siss") { @@ -3550,7 +3692,7 @@ class CliClient final : public Actor { send_request(td_api::make_object(args, vector())); } else if (op == "gkeru") { send_request(td_api::make_object(args, vector{"ru_RU"})); - } else if (op == "gec" || op == "geces" || op == "geccp") { + } else if (op == "gec" || op == "geces" || op == "geccp" || op == "gecrs") { auto type = [&]() -> td_api::object_ptr { if (op == "geces") { return td_api::make_object(); @@ -3558,6 +3700,9 @@ class CliClient final : public Actor { if (op == "geccp") { return td_api::make_object(); } + if (op == "gecrs") { + return td_api::make_object(); + } return td_api::make_object(); }(); send_request(td_api::make_object(std::move(type))); @@ -3696,6 +3841,12 @@ class CliClient final : public Actor { MessageId message_id; get_args(args, chat_id, message_id); send_request(td_api::make_object(chat_id, message_id)); + } else if (op == "rcspm") { + ChatId chat_id; + MessageId message_id; + string option_id; + get_args(args, chat_id, message_id, option_id); + send_request(td_api::make_object(chat_id, message_id, option_id)); } else if (op == "gmlink") { ChatId chat_id; MessageId message_id; @@ -3873,9 +4024,14 @@ class CliClient final : public Actor { string quote; int32 quote_position; get_args(args, chat_id, message_ids, quote, quote_position); - send_request(td_api::make_object( - chat_id, as_message_ids(message_ids), - td_api::make_object(as_formatted_text(quote), quote_position))); + if (quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + chat_id, as_message_ids(message_ids), + td_api::make_object(as_formatted_text(quote), quote_position))); + } else { + send_request(td_api::make_object(quick_reply_shortcut_name_, + as_message_ids(message_ids))); + } } else if (op == "csc" || op == "CreateSecretChat") { send_request(td_api::make_object(as_secret_chat_id(args))); } else if (op == "cnsc" || op == "CreateNewSecretChat") { @@ -4109,7 +4265,11 @@ class CliClient final : public Actor { GroupCallId group_call_id; get_args(args, group_call_id); send_request(td_api::make_object(group_call_id)); - } else if (op == "rpcil") { + } else { + op_not_found_count++; + } + + if (op == "rpcil") { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); @@ -4476,12 +4636,13 @@ class CliClient final : public Actor { StoryPrivacySettings rules; get_args(args, story_id, rules); send_request(td_api::make_object(story_id, rules)); - } else if (op == "tsip") { + } else if (op == "tsiptcp") { ChatId story_sender_chat_id; StoryId story_id; - bool is_pinned; - get_args(args, story_sender_chat_id, story_id, is_pinned); - send_request(td_api::make_object(story_sender_chat_id, story_id, is_pinned)); + bool is_posted_to_chat_page; + get_args(args, story_sender_chat_id, story_id, is_posted_to_chat_page); + send_request(td_api::make_object(story_sender_chat_id, story_id, + is_posted_to_chat_page)); } else if (op == "ds") { ChatId story_sender_chat_id; StoryId story_id; @@ -4493,18 +4654,32 @@ class CliClient final : public Actor { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id, as_story_list(op))); - } else if (op == "gcps") { + } else if (op == "gcptcps") { ChatId chat_id; StoryId from_story_id; string limit; get_args(args, chat_id, from_story_id, limit); - send_request(td_api::make_object(chat_id, from_story_id, as_limit(limit))); + send_request( + td_api::make_object(chat_id, from_story_id, as_limit(limit))); } else if (op == "gcast") { ChatId chat_id; StoryId from_story_id; string limit; get_args(args, chat_id, from_story_id, limit); send_request(td_api::make_object(chat_id, from_story_id, as_limit(limit))); + } else if (op == "scps") { + ChatId chat_id; + get_args(args, chat_id, args); + vector story_ids; + while (true) { + StoryId story_id; + get_args(args, story_id, args); + if (story_id <= 0) { + break; + } + story_ids.push_back(story_id); + } + send_request(td_api::make_object(chat_id, std::move(story_ids))); } else if (op == "gsnse") { send_request(td_api::make_object()); } else if (op == "gcas") { @@ -4601,7 +4776,11 @@ class CliClient final : public Actor { UserId user_id; get_args(args, chat_id, user_id); send_request(td_api::make_object(chat_id, user_id)); - } else if (op == "gamb") { + } else { + op_not_found_count++; + } + + if (op == "gamb") { UserId user_id; get_args(args, user_id); send_request(td_api::make_object(user_id)); @@ -4651,7 +4830,8 @@ class CliClient final : public Actor { ChatId chat_id; string action; get_args(args, chat_id, action); - send_request(td_api::make_object(chat_id, message_thread_id_, as_chat_action(action))); + send_request(td_api::make_object(chat_id, message_thread_id_, business_connection_id_, + as_chat_action(action))); } else if (op == "smt" || op == "smtp" || op == "smtf" || op == "smtpf") { ChatId chat_id; get_args(args, chat_id); @@ -4663,7 +4843,7 @@ class CliClient final : public Actor { if (op[3] == 'p') { send_message(chat_id, td_api::make_object( as_local_file("rgb.jpg"), nullptr, Auto(), 0, 0, as_caption(message), - get_message_self_destruct_type(), has_spoiler_)); + show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else { send_message(chat_id, td_api::make_object(as_formatted_text(message), get_link_preview_options(), true)); @@ -4679,10 +4859,14 @@ class CliClient final : public Actor { as_search_messages_filter(filter))); } else if (op == "ssd") { schedule_date_ = std::move(args); + } else if (op == "smei") { + message_effect_id_ = to_integer(args); } else if (op == "sop") { only_preview_ = as_bool(args); } else if (op == "smti") { get_args(args, message_thread_id_); + } else if (op == "sbci") { + business_connection_id_ = args; } else if (op == "shs") { has_spoiler_ = as_bool(args); } else if (op == "smsdt") { @@ -4707,8 +4891,12 @@ class CliClient final : public Actor { } else if (op == "slpo") { get_args(args, link_preview_is_disabled_, link_preview_url_, link_preview_force_small_media_, link_preview_force_large_media_, link_preview_show_above_text_); + } else if (op == "sscam") { + get_args(args, show_caption_above_media_); } else if (op == "ssmt") { saved_messages_topic_id_ = as_chat_id(args); + } else if (op == "sqrs") { + quick_reply_shortcut_name_ = args; } else if (op == "sm" || op == "sms" || op == "smf") { ChatId chat_id; string message; @@ -4746,13 +4934,22 @@ class CliClient final : public Actor { get_args(args, chat_id, args); auto input_message_contents = transform(full_split(args), [this](const string &photo) { td_api::object_ptr content = td_api::make_object( - as_input_file(photo), nullptr, Auto(), 0, 0, as_caption(""), + as_input_file(photo), nullptr, Auto(), 0, 0, as_caption(""), show_caption_above_media_, rand_bool() ? get_message_self_destruct_type() : nullptr, has_spoiler_ && rand_bool()); return content; }); - send_request(td_api::make_object( - chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(), - std::move(input_message_contents))); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object( + business_connection_id_, chat_id, get_input_message_reply_to(), rand_bool(), rand_bool(), + message_effect_id_, std::move(input_message_contents))); + } else if (!quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + quick_reply_shortcut_name_, reply_message_id_, std::move(input_message_contents))); + } else { + send_request(td_api::make_object( + chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(), + std::move(input_message_contents))); + } } else if (op == "smad") { ChatId chat_id; get_args(args, chat_id, args); @@ -4761,8 +4958,23 @@ class CliClient final : public Actor { td_api::make_object(as_input_file(document), nullptr, true, as_caption("")); return content; }); - send_request(td_api::make_object( - chat_id, message_thread_id_, nullptr, default_message_send_options(), std::move(input_message_contents))); + if (!business_connection_id_.empty()) { + send_request(td_api::make_object( + business_connection_id_, chat_id, get_input_message_reply_to(), rand_bool(), rand_bool(), + message_effect_id_, std::move(input_message_contents))); + } else if (!quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + quick_reply_shortcut_name_, reply_message_id_, std::move(input_message_contents))); + } else { + send_request(td_api::make_object( + chat_id, message_thread_id_, get_input_message_reply_to(), default_message_send_options(), + std::move(input_message_contents))); + } + } else if (op == "savt") { + int64 verification_id; + string token; + get_args(args, verification_id, token); + send_request(td_api::make_object(verification_id, token)); } else if (op == "gmft") { auto r_message_file_head = read_file_str(args, 2 << 10); if (r_message_file_head.is_error()) { @@ -4794,6 +5006,14 @@ class CliClient final : public Actor { send_request(td_api::make_object( chat_id, message_id, nullptr, td_api::make_object(as_formatted_text(message), get_link_preview_options(), true))); + } else if (op == "eqrm") { + ShortcutId shortcut_id; + MessageId message_id; + string message; + get_args(args, shortcut_id, message_id, message); + send_request(td_api::make_object( + shortcut_id, message_id, + td_api::make_object(as_formatted_text(message), get_link_preview_options(), true))); } else if (op == "eman") { ChatId chat_id; MessageId message_id; @@ -4802,13 +5022,15 @@ class CliClient final : public Actor { send_request(td_api::make_object( chat_id, message_id, nullptr, td_api::make_object(as_input_file(animation), nullptr, vector(), 0, 0, - 0, as_caption("animation"), has_spoiler_))); + 0, as_caption("animation"), show_caption_above_media_, + has_spoiler_))); } else if (op == "emc") { ChatId chat_id; MessageId message_id; string caption; get_args(args, chat_id, message_id, caption); - send_request(td_api::make_object(chat_id, message_id, nullptr, as_caption(caption))); + send_request(td_api::make_object(chat_id, message_id, nullptr, as_caption(caption), + show_caption_above_media_)); } else if (op == "emd") { ChatId chat_id; MessageId message_id; @@ -4817,6 +5039,14 @@ class CliClient final : public Actor { send_request(td_api::make_object( chat_id, message_id, nullptr, td_api::make_object(as_input_file(document), nullptr, false, as_caption("")))); + } else if (op == "eqrmd") { + ShortcutId shortcut_id; + MessageId message_id; + string document; + get_args(args, shortcut_id, message_id, document); + send_request(td_api::make_object( + shortcut_id, message_id, + td_api::make_object(as_input_file(document), nullptr, false, as_caption("")))); } else if (op == "emp") { ChatId chat_id; MessageId message_id; @@ -4825,7 +5055,17 @@ class CliClient final : public Actor { send_request(td_api::make_object( chat_id, message_id, nullptr, td_api::make_object(as_input_file(photo), as_input_thumbnail(photo), Auto(), 0, 0, - as_caption(""), get_message_self_destruct_type(), + as_caption(""), show_caption_above_media_, + get_message_self_destruct_type(), has_spoiler_))); + } else if (op == "eqrmp") { + ShortcutId shortcut_id; + MessageId message_id; + string photo; + get_args(args, shortcut_id, message_id, photo); + send_request(td_api::make_object( + shortcut_id, message_id, + td_api::make_object(as_input_file(photo), as_input_thumbnail(photo), Auto(), 0, 0, + as_caption(""), show_caption_above_media_, nullptr, has_spoiler_))); } else if (op == "emvt") { ChatId chat_id; @@ -4836,19 +5076,21 @@ class CliClient final : public Actor { send_request(td_api::make_object( chat_id, message_id, nullptr, td_api::make_object(as_input_file(video), as_input_thumbnail(thumbnail), Auto(), 1, - 2, 3, true, as_caption(""), get_message_self_destruct_type(), - has_spoiler_))); + 2, 3, true, as_caption(""), show_caption_above_media_, + get_message_self_destruct_type(), has_spoiler_))); } else if (op == "emll") { ChatId chat_id; MessageId message_id; string latitude; string longitude; + int32 live_period; string accuracy; int32 heading; int32 proximity_alert_radius; - get_args(args, chat_id, message_id, latitude, longitude, accuracy, heading, proximity_alert_radius); - send_request(td_api::make_object( - chat_id, message_id, nullptr, as_location(latitude, longitude, accuracy), heading, proximity_alert_radius)); + get_args(args, chat_id, message_id, latitude, longitude, live_period, accuracy, heading, proximity_alert_radius); + send_request(td_api::make_object(chat_id, message_id, nullptr, + as_location(latitude, longitude, accuracy), + live_period, heading, proximity_alert_radius)); } else if (op == "emss") { ChatId chat_id; MessageId message_id; @@ -4856,7 +5098,17 @@ class CliClient final : public Actor { get_args(args, chat_id, message_id, date); send_request(td_api::make_object(chat_id, message_id, as_message_scheduling_state(date))); - } else if (op == "cqrsn") { + } else if (op == "smfc") { + ChatId chat_id; + MessageId message_id; + string message; + get_args(args, chat_id, message_id, message); + send_request(td_api::make_object(chat_id, message_id, as_formatted_text(message))); + } else { + op_not_found_count++; + } + + if (op == "cqrsn") { execute(td_api::make_object(args)); } else if (op == "lqrs") { send_request(td_api::make_object()); @@ -4980,8 +5232,13 @@ class CliClient final : public Actor { int64 query_id; string result_id; get_args(args, chat_id, query_id, result_id); - send_request(td_api::make_object( - chat_id, message_thread_id_, nullptr, default_message_send_options(), query_id, result_id, op == "siqrh")); + if (quick_reply_shortcut_name_.empty()) { + send_request(td_api::make_object( + chat_id, message_thread_id_, nullptr, default_message_send_options(), query_id, result_id, op == "siqrh")); + } else { + send_request(td_api::make_object( + quick_reply_shortcut_name_, reply_message_id_, query_id, result_id, op == "siqrh")); + } } else if (op == "gcqa") { ChatId chat_id; MessageId message_id; @@ -5014,9 +5271,9 @@ class CliClient final : public Actor { int32 height; string caption; get_args(args, chat_id, animation_path, width, height, caption); - send_message(chat_id, td_api::make_object(as_input_file(animation_path), nullptr, - vector(), 60, width, height, - as_caption(caption), has_spoiler_)); + send_message(chat_id, td_api::make_object( + as_input_file(animation_path), nullptr, vector(), 60, width, height, + as_caption(caption), show_caption_above_media_, has_spoiler_)); } else if (op == "sang") { ChatId chat_id; string animation_path; @@ -5024,27 +5281,28 @@ class CliClient final : public Actor { get_args(args, chat_id, animation_path, animation_conversion); send_message(chat_id, td_api::make_object( as_generated_file(animation_path, animation_conversion), nullptr, vector(), 60, - 0, 0, as_caption(""), has_spoiler_)); + 0, 0, as_caption(""), show_caption_above_media_, has_spoiler_)); } else if (op == "sanid") { ChatId chat_id; string file_id; get_args(args, chat_id, file_id); - send_message(chat_id, - td_api::make_object( - as_input_file_id(file_id), nullptr, vector(), 0, 0, 0, as_caption(""), has_spoiler_)); + send_message(chat_id, td_api::make_object( + as_input_file_id(file_id), nullptr, vector(), 0, 0, 0, as_caption(""), + show_caption_above_media_, has_spoiler_)); } else if (op == "sanurl") { ChatId chat_id; string url; get_args(args, chat_id, url); - send_message(chat_id, td_api::make_object(as_generated_file(url, "#url#"), nullptr, - vector(), 0, 0, 0, as_caption(""), - has_spoiler_)); + send_message(chat_id, td_api::make_object( + as_generated_file(url, "#url#"), nullptr, vector(), 0, 0, 0, as_caption(""), + show_caption_above_media_, has_spoiler_)); } else if (op == "sanurl2") { ChatId chat_id; string url; get_args(args, chat_id, url); send_message(chat_id, td_api::make_object( - as_remote_file(url), nullptr, vector(), 0, 0, 0, as_caption(""), has_spoiler_)); + as_remote_file(url), nullptr, vector(), 0, 0, 0, as_caption(""), + show_caption_above_media_, has_spoiler_)); } else if (op == "sau") { ChatId chat_id; string audio_path; @@ -5078,7 +5336,8 @@ class CliClient final : public Actor { get_args(args, chat_id, from_chat_id, from_message_id); td_api::object_ptr copy_options; if (op == "scopy") { - copy_options = td_api::make_object(true, rand_bool(), as_caption("_as_d")); + copy_options = td_api::make_object(true, rand_bool(), as_caption("_as_d"), + show_caption_above_media_); } send_message(chat_id, td_api::make_object(from_chat_id, from_message_id, true, std::move(copy_options))); @@ -5175,7 +5434,7 @@ class CliClient final : public Actor { ChatId chat_id; string question; get_args(args, chat_id, question, args); - auto options = autosplit_str(args); + auto options = transform(autosplit_str(args), [](const string &option) { return as_formatted_text(option); }); td_api::object_ptr poll_type; if (op == "squiz") { poll_type = td_api::make_object(narrow_cast(options.size() - 1), @@ -5183,34 +5442,38 @@ class CliClient final : public Actor { } else { poll_type = td_api::make_object(op == "spollm"); } - send_message(chat_id, td_api::make_object(question, std::move(options), op != "spollp", - std::move(poll_type), 0, 0, false)); + send_message(chat_id, + td_api::make_object(as_formatted_text(question), std::move(options), + op != "spollp", std::move(poll_type), 0, 0, false)); } else if (op == "sp") { ChatId chat_id; string photo; 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), get_message_self_destruct_type(), has_spoiler_)); + 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_)); } else if (op == "spg") { ChatId chat_id; string photo_path; string conversion; int64 expected_size; get_args(args, chat_id, photo_path, conversion, expected_size); - send_message(chat_id, td_api::make_object( - as_generated_file(photo_path, conversion, expected_size), nullptr, vector(), 0, - 0, as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, + td_api::make_object( + as_generated_file(photo_path, conversion, expected_size), nullptr, vector(), 0, 0, + as_caption(""), show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "spt") { ChatId chat_id; string photo_path; string thumbnail_path; get_args(args, chat_id, photo_path, thumbnail_path); - send_message(chat_id, td_api::make_object( - as_input_file(photo_path), as_input_thumbnail(thumbnail_path, 90, 89), vector(), - 0, 0, as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, + td_api::make_object( + as_input_file(photo_path), as_input_thumbnail(thumbnail_path, 90, 89), vector(), 0, 0, + as_caption(""), show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "sptg") { ChatId chat_id; string photo_path; @@ -5220,7 +5483,8 @@ class CliClient final : public Actor { send_message(chat_id, td_api::make_object( as_input_file(photo_path), as_input_thumbnail(thumbnail_path, thumbnail_conversion, 90, 89), - vector(), 0, 0, as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + vector(), 0, 0, as_caption(""), show_caption_above_media_, + get_message_self_destruct_type(), has_spoiler_)); } else if (op == "spgtg") { ChatId chat_id; string photo_path; @@ -5228,17 +5492,18 @@ class CliClient final : public Actor { string thumbnail_path; string thumbnail_conversion; get_args(args, chat_id, photo_path, conversion, thumbnail_path, thumbnail_conversion); - send_message(chat_id, td_api::make_object( - as_generated_file(photo_path, conversion), - as_input_thumbnail(thumbnail_path, thumbnail_conversion, 90, 89), vector(), 0, 0, - as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, + td_api::make_object( + as_generated_file(photo_path, conversion), + as_input_thumbnail(thumbnail_path, thumbnail_conversion, 90, 89), vector(), 0, 0, + as_caption(""), show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "spid") { ChatId chat_id; string file_id; get_args(args, chat_id, file_id); send_message(chat_id, td_api::make_object( as_input_file_id(file_id), nullptr, vector(), 0, 0, as_caption(""), - get_message_self_destruct_type(), has_spoiler_)); + show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "ss") { ChatId chat_id; string sticker_path; @@ -5276,17 +5541,19 @@ class CliClient final : public Actor { } else { sticker_file_ids = to_integers(sticker_file_ids_str); } - send_message(chat_id, td_api::make_object( - as_input_file(video_path), nullptr, std::move(sticker_file_ids), 1, 2, 3, true, - as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, + td_api::make_object( + as_input_file(video_path), nullptr, std::move(sticker_file_ids), 1, 2, 3, true, as_caption(""), + show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "svt") { ChatId chat_id; string video; string thumbnail; get_args(args, chat_id, video, thumbnail); - send_message(chat_id, td_api::make_object( - as_input_file(video), as_input_thumbnail(thumbnail), vector(), 0, 0, 0, true, - as_caption(""), get_message_self_destruct_type(), has_spoiler_)); + send_message(chat_id, + td_api::make_object( + as_input_file(video), as_input_thumbnail(thumbnail), vector(), 0, 0, 0, true, + as_caption(""), show_caption_above_media_, get_message_self_destruct_type(), has_spoiler_)); } else if (op == "svn") { ChatId chat_id; string video_path; @@ -5364,6 +5631,8 @@ class CliClient final : public Actor { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); + } else if (op == "grc") { + send_request(td_api::make_object()); } else if (op == "gcsc") { ChatId chat_id; get_args(args, chat_id); @@ -5392,6 +5661,8 @@ class CliClient final : public Actor { send_request(td_api::make_object()); } else if (op == "gisc") { send_request(td_api::make_object()); + } else if (op == "gspc") { + send_request(td_api::make_object()); } else if (op == "cpc") { UserId user_id; bool force; @@ -5407,7 +5678,11 @@ class CliClient final : public Actor { bool force; get_args(args, supergroup_id, force); send_request(td_api::make_object(as_supergroup_id(supergroup_id), force)); - } else if (op == "gcltac") { + } else { + op_not_found_count++; + } + + if (op == "gcltac") { ChatId chat_id; get_args(args, chat_id); send_request(td_api::make_object(chat_id)); @@ -5774,6 +6049,21 @@ class CliClient final : public Actor { send_request(td_api::make_object(username, is_active)); } else if (op == "raun") { send_request(td_api::make_object(autosplit_str(args))); + } else if (op == "sbd") { + int32 day; + int32 month; + int32 year; + get_args(args, day, month, year); + if (day == 0) { + send_request(td_api::make_object(nullptr)); + } else { + send_request( + td_api::make_object(td_api::make_object(day, month, year))); + } + } else if (op == "spec") { + ChatId chat_id; + get_args(args, chat_id); + send_request(td_api::make_object(chat_id)); } else if (op == "sese") { send_request(td_api::make_object(nullptr)); } else if (op == "ses") { @@ -5782,6 +6072,8 @@ class CliClient final : public Actor { get_args(args, custom_emoji_id, expiration_date); send_request(td_api::make_object( td_api::make_object(custom_emoji_id, expiration_date))); + } else if (op == "thsme") { + send_request(td_api::make_object(as_bool(args))); } else if (op == "gtes") { send_request(td_api::make_object()); } else if (op == "gdes") { @@ -5846,6 +6138,12 @@ class CliClient final : public Actor { get_args(args, supergroup_id, is_all_history_available); send_request(td_api::make_object(as_supergroup_id(supergroup_id), is_all_history_available)); + } else if (op == "tsgchsm") { + string supergroup_id; + bool can_have_sponsored_messages; + get_args(args, supergroup_id, can_have_sponsored_messages); + send_request(td_api::make_object( + as_supergroup_id(supergroup_id), can_have_sponsored_messages)); } else if (op == "tsghhm") { string supergroup_id; bool has_hidden_members; @@ -5885,16 +6183,22 @@ class CliClient final : public Actor { get_args(args, supergroup_id, join_by_request); send_request( td_api::make_object(as_supergroup_id(supergroup_id), join_by_request)); - } else if (op == "scar") { + } else { + op_not_found_count++; + } + + if (op == "scar") { ChatId chat_id; + int32 max_reaction_count; string available_reactions; - get_args(args, chat_id, available_reactions); + get_args(args, chat_id, max_reaction_count, available_reactions); td_api::object_ptr chat_available_reactions; if (available_reactions == "all") { - chat_available_reactions = td_api::make_object(); + chat_available_reactions = td_api::make_object(max_reaction_count); } else if (!available_reactions.empty()) { auto reactions = transform(autosplit_str(available_reactions), as_reaction_type); - chat_available_reactions = td_api::make_object(std::move(reactions)); + chat_available_reactions = + td_api::make_object(std::move(reactions), max_reaction_count); } send_request( td_api::make_object(chat_id, std::move(chat_available_reactions))); @@ -6025,19 +6329,60 @@ class CliClient final : public Actor { td_api::make_object(shortcut_id, as_business_recipients(chat_ids), std::move(schedule_object), op == "sbamso"))); } + } else if (op == "sbsp") { + string title; + string message; + string sticker; + get_args(args, title, message, sticker); + if (title.empty()) { + send_request(td_api::make_object(nullptr)); + } else { + send_request(td_api::make_object( + td_api::make_object(title, message, as_input_file(sticker)))); + } } else if (op == "gbcb") { send_request(td_api::make_object()); } else if (op == "sbcb") { UserId bot_user_id; string chat_ids; - bool can_reply = false; + bool can_reply; get_args(args, bot_user_id, chat_ids, can_reply); send_request(td_api::make_object( td_api::make_object(bot_user_id, as_business_recipients(chat_ids), can_reply))); + } else if (op == "tbcbcip") { + ChatId chat_id; + bool is_paused; + get_args(args, chat_id, is_paused); + send_request(td_api::make_object(chat_id, is_paused)); + } else if (op == "rbcbfc") { + ChatId chat_id; + get_args(args, chat_id); + send_request(td_api::make_object(chat_id)); } else if (op == "dbcb") { UserId bot_user_id; get_args(args, bot_user_id); send_request(td_api::make_object(bot_user_id)); + } else if (op == "gbcl") { + send_request(td_api::make_object()); + } else if (op == "cbcl") { + string text; + string title; + get_args(args, text, title); + send_request(td_api::make_object( + td_api::make_object(as_formatted_text(text), title))); + } else if (op == "ebcl") { + string link; + string text; + string title; + get_args(args, link, text, title); + send_request(td_api::make_object( + link, td_api::make_object(as_formatted_text(text), title))); + } else if (op == "dbcl") { + send_request(td_api::make_object(args)); + } else if (op == "gbcli") { + send_request(td_api::make_object(args)); + } else if (op == "gbc") { + send_request(td_api::make_object(args.empty() ? business_connection_id_ : args)); } else if (op == "sco") { SearchQuery query; get_args(args, query); @@ -6087,7 +6432,11 @@ class CliClient final : public Actor { UserId bot_user_id; get_args(args, bot_user_id); send_request(td_api::make_object(bot_user_id)); - } else if (op == "swacr") { + } else { + op_not_found_count++; + } + + if (op == "swacr") { UserId bot_user_id; string method; string parameters; @@ -6100,7 +6449,7 @@ class CliClient final : public Actor { send_request(td_api::make_object(bot_user_id, language_code)); send_request(td_api::make_object(bot_user_id, language_code)); send_request(td_api::make_object(bot_user_id, language_code)); - } else if (op == "sbi") { + } else if (op == "sbit") { UserId bot_user_id; string language_code; string name; @@ -6292,6 +6641,15 @@ class CliClient final : public Actor { as_chat_id(chat_id), as_message_id(message_id), std::move(settings))); } } + } else if (op == "srns") { + ReactionNotificationSource message_reactions; + ReactionNotificationSource story_reactions; + int64 sound_id; + bool show_preview; + get_args(args, message_reactions, story_reactions, sound_id, show_preview); + send_request(td_api::make_object( + td_api::make_object(message_reactions, story_reactions, sound_id, + show_preview))); } else if (op == "rans") { send_request(td_api::make_object()); } else if (op == "rn") { @@ -6336,6 +6694,22 @@ class CliClient final : public Actor { bool is_dark; get_args(args, chat_id, is_dark); send_request(td_api::make_object(chat_id, is_dark)); + } else if (op == "gcrst") { + ChatId chat_id; + bool is_dark; + get_args(args, chat_id, is_dark); + send_request(td_api::make_object(chat_id, is_dark)); + } else if (op == "gcrwu") { + ChatId chat_id; + string password; + get_args(args, chat_id, password); + send_request(td_api::make_object(chat_id, password)); + } else if (op == "gcrt") { + ChatId chat_id; + int32 offset; + string limit; + get_args(args, chat_id, offset, limit); + send_request(td_api::make_object(chat_id, offset, as_limit(limit))); } else { op_not_found_count++; } @@ -6373,6 +6747,8 @@ class CliClient final : public Actor { send_request(td_api::make_object(chat_id, token, x)); } else if (op == "hsa") { send_request(td_api::make_object(as_suggested_action(args))); + } else if (op == "hccb") { + send_request(td_api::make_object()); } else if (op == "glui" || op == "glu" || op == "glua") { ChatId chat_id; MessageId message_id; @@ -6675,8 +7051,10 @@ class CliClient final : public Actor { int64 my_id_ = 0; td_api::object_ptr authorization_state_; string schedule_date_; + int64 message_effect_id_ = 0; bool only_preview_ = false; MessageThreadId message_thread_id_; + string business_connection_id_; bool has_spoiler_ = false; int32 message_self_destruct_time_ = 0; int64 opened_chat_id_ = 0; @@ -6694,7 +7072,9 @@ class CliClient final : public Actor { bool link_preview_force_small_media_ = false; bool link_preview_force_large_media_ = false; bool link_preview_show_above_text_ = false; + bool show_caption_above_media_ = false; int64 saved_messages_topic_id_ = 0; + string quick_reply_shortcut_name_; ConcurrentScheduler *scheduler_{nullptr}; diff --git a/lib/tgchat/ext/td/td/telegram/files/FileDownloader.cpp b/lib/tgchat/ext/td/td/telegram/files/FileDownloader.cpp index 62f0c691..1ddd27fc 100644 --- a/lib/tgchat/ext/td/td/telegram/files/FileDownloader.cpp +++ b/lib/tgchat/ext/td/td/telegram/files/FileDownloader.cpp @@ -253,12 +253,12 @@ Result> FileDownloader::start_part(Part part, int32 net_query = remote_.is_web() ? G()->net_query_creator().create( - unique_id, + unique_id, nullptr, telegram_api::upload_getWebFile(remote_.as_input_web_file_location(), narrow_cast(part.offset), narrow_cast(size)), {}, dc_id, net_query_type, NetQuery::AuthFlag::On) : G()->net_query_creator().create( - unique_id, + unique_id, nullptr, telegram_api::upload_getFile(flags, false /*ignored*/, false /*ignored*/, remote_.as_input_file_location(), part.offset, narrow_cast(size)), {}, dc_id, net_query_type, NetQuery::AuthFlag::On); @@ -272,11 +272,11 @@ Result> FileDownloader::start_part(Part part, int32 cdn_part_file_token_generation_[part.id] = cdn_file_token_generation_; net_query = G()->net_query_creator().create(UniqueId::next(UniqueId::Type::Default, static_cast(QueryType::CDN)), - query, {}, cdn_dc_id_, net_query_type, NetQuery::AuthFlag::Off); + nullptr, query, {}, cdn_dc_id_, net_query_type, NetQuery::AuthFlag::Off); } else { auto query = telegram_api::upload_reuploadCdnFile(BufferSlice(cdn_file_token_), BufferSlice(it->second)); net_query = G()->net_query_creator().create( - UniqueId::next(UniqueId::Type::Default, static_cast(QueryType::ReuploadCDN)), query, {}, + UniqueId::next(UniqueId::Type::Default, static_cast(QueryType::ReuploadCDN)), nullptr, query, {}, remote_.get_dc_id(), net_query_type, NetQuery::AuthFlag::On); cdn_part_reupload_token_.erase(it); } diff --git a/lib/tgchat/ext/td/td/telegram/files/FileManager.cpp b/lib/tgchat/ext/td/td/telegram/files/FileManager.cpp index 719d1254..557dd4c5 100644 --- a/lib/tgchat/ext/td/td/telegram/files/FileManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/files/FileManager.cpp @@ -1903,6 +1903,31 @@ Status FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sync) { return Status::OK(); } +void FileManager::try_merge_documents(FileId old_file_id, FileId new_file_id) { + if (!old_file_id.is_valid() || !new_file_id.is_valid()) { + return; + } + FileView old_file_view = get_file_view(old_file_id); + FileView new_file_view = get_file_view(new_file_id); + // if file type has changed, but file size remains the same, we are trying to update local location of the new + // file with the old local location + if (old_file_view.has_local_location() && !new_file_view.has_local_location() && old_file_view.size() != 0 && + old_file_view.size() == new_file_view.size()) { + auto old_file_type = old_file_view.get_type(); + auto new_file_type = new_file_view.get_type(); + + if (is_document_file_type(old_file_type) && is_document_file_type(new_file_type)) { + auto &old_location = old_file_view.local_location(); + auto r_file_id = + register_local(FullLocalFileLocation(new_file_type, old_location.path_, old_location.mtime_nsec_), DialogId(), + old_file_view.size()); + if (r_file_id.is_ok()) { + LOG_STATUS(merge(new_file_id, r_file_id.ok())); + } + } + } +} + void FileManager::add_file_source(FileId file_id, FileSourceId file_source_id) { auto node = get_sync_file_node(file_id); // synchronously load the file to preload known file sources if (!node) { @@ -2750,7 +2775,11 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar .release(); return; } - LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority << " and force = " << force; + if (new_priority == 0) { + LOG(INFO) << "Cancel upload of file " << file_id; + } else { + LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority << " and force = " << force; + } if (force) { node->remote_.is_full_alive = false; @@ -2766,7 +2795,7 @@ void FileManager::resume_upload(FileId file_id, vector bad_parts, std::shar }; FileView file_view(node); if (file_view.has_active_upload_remote_location() && can_reuse_remote_file(file_view.get_type())) { - LOG(INFO) << "File " << file_id << " is already uploaded"; + LOG(INFO) << "Upload of file " << file_id << " has already been completed"; if (callback) { callback->on_upload_ok(file_id, nullptr); } @@ -2857,6 +2886,14 @@ bool FileManager::delete_partial_remote_location(FileId file_id) { return true; } +void FileManager::delete_partial_remote_location_if_needed(FileId file_id, const Status &error) { + if (error.code() != 429 && error.code() < 500 && !G()->close_flag()) { + delete_partial_remote_location(file_id); + } else { + cancel_upload(file_id); + } +} + void FileManager::delete_file_reference(FileId file_id, Slice file_reference) { VLOG(file_references) << "Delete file reference of file " << file_id << " " << tag("reference_base64", base64_encode(file_reference)); @@ -3399,7 +3436,8 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr auto file_id = file_hash_to_file_id_.get(hash); LOG(INFO) << "Found file " << file_id << " by hash " << hex_encode(hash); if (file_id.is_valid()) { - auto file_view = get_file_view(file_id); + auto file_node = get_file_node(file_id); + auto file_view = FileView(file_node); if (!file_view.empty()) { if (force_reuse) { return file_id; @@ -3408,8 +3446,13 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr return file_id; } if (file_view.is_uploading()) { + CHECK(file_node); + LOG(DEBUG) << "File " << file_id << " is still uploading: " << file_node->upload_priority_ << ' ' + << file_node->generate_upload_priority_ << ' ' << file_node->upload_pause_; hash.clear(); } + } else { + LOG(DEBUG) << "File " << file_id << " isn't found"; } } } diff --git a/lib/tgchat/ext/td/td/telegram/files/FileManager.h b/lib/tgchat/ext/td/td/telegram/files/FileManager.h index 2d9744c2..4b69cee6 100644 --- a/lib/tgchat/ext/td/td/telegram/files/FileManager.h +++ b/lib/tgchat/ext/td/td/telegram/files/FileManager.h @@ -472,6 +472,8 @@ class FileManager final : public FileLoadManager::Callback { Status merge(FileId x_file_id, FileId y_file_id, bool no_sync = false); + void try_merge_documents(FileId old_file_id, FileId new_file_id); + void add_file_source(FileId file_id, FileSourceId file_source_id); void remove_file_source(FileId file_id, FileSourceId file_source_id); @@ -495,6 +497,7 @@ class FileManager final : public FileLoadManager::Callback { int32 new_priority, uint64 upload_order, bool force = false, bool prefer_small = false); void cancel_upload(FileId file_id); bool delete_partial_remote_location(FileId file_id); + void delete_partial_remote_location_if_needed(FileId file_id, const Status &error); void delete_file_reference(FileId file_id, Slice file_reference); void get_content(FileId file_id, Promise promise); diff --git a/lib/tgchat/ext/td/td/telegram/logevent/LogEvent.h b/lib/tgchat/ext/td/td/telegram/logevent/LogEvent.h index 7a53ad6e..f9d5331b 100644 --- a/lib/tgchat/ext/td/td/telegram/logevent/LogEvent.h +++ b/lib/tgchat/ext/td/td/telegram/logevent/LogEvent.h @@ -104,6 +104,8 @@ class LogEvent { DeleteTopicHistoryOnServer = 0x125, ToggleDialogIsTranslatableOnServer = 0x126, ToggleDialogViewAsMessagesOnServer = 0x127, + SendQuickReplyShortcutMessages = 0x128, + UpdateReactionNotificationSettingsOnServer = 0x129, GetChannelDifference = 0x140, AddMessagePushNotification = 0x200, EditMessagePushNotification = 0x201, diff --git a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h index e210feeb..d43bd92f 100644 --- a/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h +++ b/lib/tgchat/ext/td/td/telegram/net/ConnectionCreator.h @@ -119,7 +119,7 @@ class ConnectionCreator final : public NetQueryCallback { struct ClientInfo { class Backoff { -#if TD_ANDROID || TD_DARWIN_IOS || TD_DARWIN_WATCH_OS || TD_TIZEN +#if TD_ANDROID || TD_DARWIN_IOS || TD_DARWIN_VISION_OS || TD_DARWIN_WATCH_OS || TD_TIZEN static constexpr int32 MAX_BACKOFF = 300; #else static constexpr int32 MAX_BACKOFF = 16; diff --git a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp index a24a1c81..86f6409d 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/DcAuthManager.cpp @@ -156,8 +156,9 @@ void DcAuthManager::dc_loop(DcInfo &dc) { // send auth.exportAuthorization to auth_dc VLOG(dc) << "Send exportAuthorization to " << dc.dc_id; auto id = UniqueId::next(); - auto query = G()->net_query_creator().create(id, telegram_api::auth_exportAuthorization(dc.dc_id.get_raw_id()), - {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); + auto query = + G()->net_query_creator().create(id, nullptr, telegram_api::auth_exportAuthorization(dc.dc_id.get_raw_id()), + {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); query->total_timeout_limit_ = 60 * 60 * 24; G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, dc.dc_id.get_raw_id())); dc.wait_id = id; @@ -173,7 +174,7 @@ void DcAuthManager::dc_loop(DcInfo &dc) { uint64 id = UniqueId::next(); VLOG(dc) << "Send importAuthorization to " << dc.dc_id; auto query = G()->net_query_creator().create( - id, telegram_api::auth_importAuthorization(dc.export_id, std::move(dc.export_bytes)), {}, dc.dc_id, + id, nullptr, telegram_api::auth_importAuthorization(dc.export_id, std::move(dc.export_bytes)), {}, dc.dc_id, NetQuery::Type::Common, NetQuery::AuthFlag::Off); query->total_timeout_limit_ = 60 * 60 * 24; G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, dc.dc_id.get_raw_id())); diff --git a/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.h b/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.h index e29bf84e..65c4a8d5 100644 --- a/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.h +++ b/lib/tgchat/ext/td/td/telegram/net/DcOptionsSet.h @@ -88,7 +88,11 @@ class DcOptionsSet { struct DcOptionId { size_t pos = 0; - auto as_tie() const { + + explicit constexpr DcOptionId(size_t pos) : pos(pos) { + } + + size_t as_tie() const { return pos; } bool operator==(const DcOptionId &other) const { diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQuery.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQuery.cpp index 83bc08c1..c35ac6de 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQuery.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/NetQuery.cpp @@ -59,6 +59,45 @@ NetQuery::NetQuery(uint64 id, BufferSlice &&query, DcId dc_id, Type type, AuthFl } } +void NetQuery::clear() { + if (!is_ready()) { + auto guard = lock(); + LOG(ERROR) << "Destroy not ready query " << *this << " " << tag("state", get_data_unsafe().state_); + } + // TODO: CHECK if net_query is lost here + cancel_slot_.close(); + *this = NetQuery(); +} + +void NetQuery::resend(DcId new_dc_id) { + VLOG(net_query) << "Resend " << *this; + { + auto guard = lock(); + get_data_unsafe().resend_count_++; + } + dc_id_ = new_dc_id; + status_ = Status::OK(); + state_ = State::Query; +} + +bool NetQuery::update_is_ready() { + if (state_ == State::Query) { + if (cancellation_token_.load(std::memory_order_relaxed) == 0 || cancel_slot_.was_signal()) { + set_error_canceled(); + return true; + } + return false; + } + return true; +} + +void NetQuery::set_ok(BufferSlice slice) { + VLOG(net_query) << "Receive answer " << *this; + CHECK(state_ == State::Query); + answer_ = std::move(slice); + state_ = State::OK; +} + void NetQuery::on_net_write(size_t size) { const auto &callbacks = G()->get_net_stats_file_callbacks(); if (static_cast(file_type_) < callbacks.size()) { @@ -101,4 +140,46 @@ void NetQuery::set_error(Status status, string source) { set_error_impl(std::move(status), std::move(source)); } +void NetQuery::set_error_impl(Status status, string source) { + VLOG(net_query) << "Receive error " << *this << " " << status; + status_ = std::move(status); + state_ = State::Error; + source_ = std::move(source); +} + +StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_query) { + stream << "[Query:"; + stream << tag("id", net_query.id()); + stream << tag("tl", format::as_hex(net_query.tl_constructor())); + auto message_id = net_query.message_id(); + if (message_id != 0) { + stream << tag("msg_id", format::as_hex(message_id)); + } + if (net_query.is_error()) { + stream << net_query.error(); + } else if (net_query.is_ok()) { + stream << tag("result_tl", format::as_hex(net_query.ok_tl_constructor())); + } + stream << ']'; + return stream; +} + +StringBuilder &operator<<(StringBuilder &stream, const NetQueryPtr &net_query_ptr) { + if (net_query_ptr.empty()) { + return stream << "[Query: null]"; + } + return stream << *net_query_ptr; +} + +void NetQuery::add_verification_prefix(const string &prefix) { + CHECK(is_ready()); + CHECK(is_error()); + CHECK(!query_.empty()); + BufferSlice query(prefix.size() + query_.size() - verification_prefix_length_); + query.as_mutable_slice().copy_from(prefix); + query.as_mutable_slice().substr(prefix.size()).copy_from(query_.as_slice().substr(verification_prefix_length_)); + verification_prefix_length_ = narrow_cast(prefix.size()); + query_ = std::move(query); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQuery.h b/lib/tgchat/ext/td/td/telegram/net/NetQuery.h index 7724eee7..22bf27d0 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQuery.h +++ b/lib/tgchat/ext/td/td/telegram/net/NetQuery.h @@ -80,16 +80,7 @@ class NetQuery final : public TsListNode { return tl_constructor_; } - void resend(DcId new_dc_id) { - VLOG(net_query) << "Resend " << *this; - { - auto guard = lock(); - get_data_unsafe().resend_count_++; - } - dc_id_ = new_dc_id; - status_ = Status::OK(); - state_ = State::Query; - } + void resend(DcId new_dc_id); void resend() { resend(dc_id_); @@ -121,12 +112,7 @@ class NetQuery final : public TsListNode { return status; } - void set_ok(BufferSlice slice) { - VLOG(net_query) << "Receive answer " << *this; - CHECK(state_ == State::Query); - answer_ = std::move(slice); - state_ = State::OK; - } + void set_ok(BufferSlice slice); void on_net_write(size_t size); void on_net_read(size_t size); @@ -145,16 +131,7 @@ class NetQuery final : public TsListNode { set_error_impl(Status::Error()); } - bool update_is_ready() { - if (state_ == State::Query) { - if (cancellation_token_.load(std::memory_order_relaxed) == 0 || cancel_slot_.was_signal()) { - set_error_canceled(); - return true; - } - return false; - } - return true; - } + bool update_is_ready(); bool is_ready() const { return state_ != State::Query; @@ -208,15 +185,8 @@ class NetQuery final : public TsListNode { cancellation_token_.store(cancellation_token, std::memory_order_relaxed); } - void clear() { - if (!is_ready()) { - auto guard = lock(); - LOG(ERROR) << "Destroy not ready query " << *this << " " << tag("state", get_data_unsafe().state_); - } - // TODO: CHECK if net_query is lost here - cancel_slot_.close(); - *this = NetQuery(); - } + void clear(); + bool empty() const { return state_ == State::Empty || !nq_counter_ || may_be_lost_; } @@ -268,6 +238,12 @@ class NetQuery final : public TsListNode { return in_sequence_dispacher_; } + void add_verification_prefix(const string &prefix); + + bool has_verification_prefix() const { + return verification_prefix_length_ != 0; + } + private: State state_ = State::Empty; Type type_ = Type::Common; @@ -281,6 +257,7 @@ class NetQuery final : public TsListNode { BufferSlice query_; BufferSlice answer_; int32 tl_constructor_ = 0; + int32 verification_prefix_length_ = 0; vector invoke_after_; vector chain_ids_; @@ -312,12 +289,7 @@ class NetQuery final : public TsListNode { movable_atomic cancellation_token_{-1}; // == 0 if query is canceled ActorShared callback_; - void set_error_impl(Status status, string source = string()) { - VLOG(net_query) << "Receive error " << *this << " " << status; - status_ = std::move(status); - state_ = State::Error; - source_ = std::move(source); - } + void set_error_impl(Status status, string source = string()); static int32 tl_magic(const BufferSlice &buffer_slice); @@ -337,29 +309,9 @@ class NetQuery final : public TsListNode { int32 tl_constructor, int32 total_timeout_limit, NetQueryStats *stats, vector chain_ids); }; -inline StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_query) { - stream << "[Query:"; - stream << tag("id", net_query.id()); - stream << tag("tl", format::as_hex(net_query.tl_constructor())); - auto message_id = net_query.message_id(); - if (message_id != 0) { - stream << tag("msg_id", format::as_hex(message_id)); - } - if (net_query.is_error()) { - stream << net_query.error(); - } else if (net_query.is_ok()) { - stream << tag("result_tl", format::as_hex(net_query.ok_tl_constructor())); - } - stream << ']'; - return stream; -} +StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_query); -inline StringBuilder &operator<<(StringBuilder &stream, const NetQueryPtr &net_query_ptr) { - if (net_query_ptr.empty()) { - return stream << "[Query: null]"; - } - return stream << *net_query_ptr; -} +StringBuilder &operator<<(StringBuilder &stream, const NetQueryPtr &net_query_ptr); inline void cancel_query(NetQueryRef &ref) { if (ref.empty()) { diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.cpp index 05715c3d..7638019e 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.cpp @@ -17,6 +17,7 @@ #include "td/utils/format.h" #include "td/utils/Gzip.h" #include "td/utils/logging.h" +#include "td/utils/Slice.h" #include "td/utils/Storer.h" namespace td { @@ -29,17 +30,36 @@ NetQueryCreator::NetQueryCreator(std::shared_ptr net_query_stats) NetQueryPtr NetQueryCreator::create(const telegram_api::Function &function, vector chain_ids, DcId dc_id, NetQuery::Type type) { - return create(UniqueId::next(), function, std::move(chain_ids), dc_id, type, NetQuery::AuthFlag::On); + return create(UniqueId::next(), nullptr, function, std::move(chain_ids), dc_id, type, NetQuery::AuthFlag::On); } -NetQueryPtr NetQueryCreator::create(uint64 id, const telegram_api::Function &function, vector &&chain_ids, - DcId dc_id, NetQuery::Type type, NetQuery::AuthFlag auth_flag) { +NetQueryPtr NetQueryCreator::create_with_prefix(const telegram_api::object_ptr &prefix, + const telegram_api::Function &function, DcId dc_id, + vector chain_ids, NetQuery::Type type) { + return create(UniqueId::next(), prefix, function, std::move(chain_ids), dc_id, type, NetQuery::AuthFlag::On); +} + +NetQueryPtr NetQueryCreator::create(uint64 id, const telegram_api::object_ptr &prefix, + const telegram_api::Function &function, vector &&chain_ids, DcId dc_id, + NetQuery::Type type, NetQuery::AuthFlag auth_flag) { LOG(INFO) << "Create query " << to_string(function); + string prefix_str; + if (prefix != nullptr) { + auto storer = DefaultStorer(*prefix); + prefix_str.resize(storer.size()); + auto real_size = storer.store(MutableSlice(prefix_str).ubegin()); + CHECK(real_size == prefix_str.size()); + } + auto storer = DefaultStorer(function); - BufferSlice slice(storer.size()); - auto real_size = storer.store(slice.as_mutable_slice().ubegin()); - LOG_CHECK(real_size == slice.size()) << real_size << " " << slice.size() << " " - << format::as_hex_dump<4>(slice.as_slice()); + BufferSlice slice(prefix_str.size() + storer.size()); + auto real_size = storer.store(slice.as_mutable_slice().ubegin() + prefix_str.size()); + LOG_CHECK(prefix_str.size() + real_size == slice.size()) + << prefix_str.size() << ' ' << real_size << ' ' << slice.size() << ' ' + << format::as_hex_dump<4>(slice.as_slice()); + if (prefix != nullptr) { + slice.as_mutable_slice().copy_from(prefix_str); + } size_t min_gzipped_size = 128; int32 tl_constructor = function.get_id(); diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.h b/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.h index 04c851e0..d2e774be 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.h +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryCreator.h @@ -35,10 +35,15 @@ class NetQueryCreator { NetQuery::Type type = NetQuery::Type::Common); NetQueryPtr create_unauth(const telegram_api::Function &function, DcId dc_id = DcId::main()) { - return create(UniqueId::next(), function, {}, dc_id, NetQuery::Type::Common, NetQuery::AuthFlag::Off); + return create(UniqueId::next(), nullptr, function, {}, dc_id, NetQuery::Type::Common, NetQuery::AuthFlag::Off); } - NetQueryPtr create(uint64 id, const telegram_api::Function &function, vector &&chain_ids, DcId dc_id, + NetQueryPtr create_with_prefix(const telegram_api::object_ptr &prefix, + const telegram_api::Function &function, DcId dc_id, vector chain_ids = {}, + NetQuery::Type type = NetQuery::Type::Common); + + NetQueryPtr create(uint64 id, const telegram_api::object_ptr &prefix, + const telegram_api::Function &function, vector &&chain_ids, DcId dc_id, NetQuery::Type type, NetQuery::AuthFlag auth_flag); private: diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryDelayer.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQueryDelayer.cpp index 94145396..7462c8c4 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryDelayer.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryDelayer.cpp @@ -21,7 +21,7 @@ namespace td { void NetQueryDelayer::delay(NetQueryPtr query) { query->debug("trying to delay"); - query->is_ready(); + CHECK(query->is_ready()); CHECK(query->is_error()); auto code = query->error().code(); int32 timeout = 0; @@ -34,10 +34,26 @@ void NetQueryDelayer::delay(NetQueryPtr query) { } } else if (code == 420) { auto error_message = query->error().message(); - for (auto prefix : - {Slice("FLOOD_WAIT_"), Slice("SLOWMODE_WAIT_"), Slice("2FA_CONFIRM_WAIT_"), Slice("TAKEOUT_INIT_DELAY_")}) { + for (auto prefix : {Slice("FLOOD_WAIT_"), Slice("SLOWMODE_WAIT_"), Slice("2FA_CONFIRM_WAIT_"), + Slice("TAKEOUT_INIT_DELAY_"), Slice("FLOOD_PREMIUM_WAIT_")}) { if (begins_with(error_message, prefix)) { timeout = clamp(to_integer(error_message.substr(prefix.size())), 1, 14 * 24 * 60 * 60); + if (prefix == "FLOOD_PREMIUM_WAIT_") { + switch (query->type()) { + case NetQuery::Type::Common: + LOG(ERROR) << "Receive " << error_message << " for " << query; + break; + case NetQuery::Type::Upload: + G()->notify_speed_limited(true); + break; + case NetQuery::Type::Download: + case NetQuery::Type::DownloadSmall: + G()->notify_speed_limited(false); + break; + default: + UNREACHABLE(); + } + } break; } } @@ -119,6 +135,7 @@ void NetQueryDelayer::tear_down() { query_slot.query_->set_error(Global::request_aborted_error()); G()->net_query_dispatcher().dispatch(std::move(query_slot.query_)); }); + parent_.reset(); } } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp index e4d5d9c5..20f2e82c 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.cpp @@ -11,6 +11,7 @@ #include "td/telegram/net/DcAuthManager.h" #include "td/telegram/net/NetQuery.h" #include "td/telegram/net/NetQueryDelayer.h" +#include "td/telegram/net/NetQueryVerifier.h" #include "td/telegram/net/PublicRsaKeySharedCdn.h" #include "td/telegram/net/PublicRsaKeySharedMain.h" #include "td/telegram/net/PublicRsaKeyWatchdog.h" @@ -31,6 +32,8 @@ namespace td { +#define TD_TEST_VERIFICATION 0 + void NetQueryDispatcher::complete_net_query(NetQueryPtr net_query) { auto callback = net_query->move_callback(); if (callback.empty()) { @@ -42,11 +45,18 @@ void NetQueryDispatcher::complete_net_query(NetQueryPtr net_query) { } } -void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { - // net_query->debug("dispatch"); +bool NetQueryDispatcher::check_stop_flag(NetQueryPtr &net_query) const { if (stop_flag_.load(std::memory_order_relaxed)) { net_query->set_error(Global::request_aborted_error()); - return complete_net_query(std::move(net_query)); + complete_net_query(std::move(net_query)); + return true; + } + return false; +} + +void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { + if (check_stop_flag(net_query)) { + return; } if (G()->get_option_boolean("test_flood_wait")) { net_query->set_error(Status::Error(429, "Too Many Requests: retry after 10")); @@ -55,13 +65,23 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { // net_query->set_error(Status::Error(420, "FLOOD_WAIT_10")); // } } - if (net_query->tl_constructor() == telegram_api::account_getPassword::ID && false) { + if (false && net_query->tl_constructor() == telegram_api::account_getPassword::ID) { net_query->set_error(Status::Error(429, "Too Many Requests: retry after 10")); return complete_net_query(std::move(net_query)); } +#if TD_TEST_VERIFICATION + if (net_query->tl_constructor() == telegram_api::account_getAuthorizations::ID && + !net_query->has_verification_prefix() && !net_query->is_ready()) { + net_query->set_error(Status::Error(403, "APNS_VERIFY_CHECK_ABCD")); + } +#endif if (!net_query->in_sequence_dispatcher() && !net_query->get_chain_ids().empty()) { net_query->debug("sent to main sequence dispatcher"); + std::lock_guard guard(mutex_); + if (check_stop_flag(net_query)) { + return; + } send_closure_later(sequence_dispatcher_, &MultiSequenceDispatcher::send, std::move(net_query)); return; } @@ -76,7 +96,28 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { (code == 420 && !begins_with(net_query->error().message(), "STORY_SEND_FLOOD_") && !begins_with(net_query->error().message(), "PREMIUM_SUB_ACTIVE_UNTIL_"))) { net_query->debug("sent to NetQueryDelayer"); + std::lock_guard guard(mutex_); + if (check_stop_flag(net_query)) { + return; + } return send_closure_later(delayer_, &NetQueryDelayer::delay, std::move(net_query)); +#if TD_ANDROID || TD_DARWIN_IOS || TD_DARWIN_VISION_OS || TD_DARWIN_WATCH_OS || TD_TEST_VERIFICATION + } else if (code == 403) { +#if TD_ANDROID + Slice prefix("INTEGRITY_CHECK_CLASSIC_"); +#else + Slice prefix("APNS_VERIFY_CHECK_"); +#endif + if (begins_with(net_query->error().message(), prefix)) { + net_query->debug("sent to NetQueryVerifier"); + std::lock_guard guard(mutex_); + if (check_stop_flag(net_query)) { + return; + } + string nonce = net_query->error().message().substr(prefix.size()).str(); + return send_closure_later(verifier_, &NetQueryVerifier::verify, std::move(net_query), std::move(nonce)); + } +#endif } } @@ -104,6 +145,10 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { auto dc_pos = static_cast(dest_dc_id.get_raw_id() - 1); CHECK(dc_pos < dcs_.size()); + std::lock_guard guard(mutex_); + if (check_stop_flag(net_query)) { + return; + } switch (net_query->type()) { case NetQuery::Type::Common: net_query->debug(PSTRING() << "sent to main session multi proxy " << dest_dc_id); @@ -121,6 +166,8 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) { net_query->debug(PSTRING() << "sent to download small session multi proxy " << dest_dc_id); send_closure_later(dcs_[dc_pos].download_small_session_, &SessionMultiProxy::send, std::move(net_query)); break; + default: + UNREACHABLE(); } } @@ -146,7 +193,7 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) { } if (should_init) { - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); if (stop_flag_.load(std::memory_order_relaxed) || need_destroy_auth_key_) { return Status::Error("Closing"); } @@ -208,10 +255,10 @@ void NetQueryDispatcher::dispatch_with_callback(NetQueryPtr net_query, ActorShar } void NetQueryDispatcher::stop() { - std::lock_guard guard(main_dc_id_mutex_); - td_guard_.reset(); + std::lock_guard guard(mutex_); stop_flag_ = true; delayer_.reset(); + verifier_.reset(); for (auto &dc : dcs_) { dc.main_session_.reset(); dc.upload_session_.reset(); @@ -221,10 +268,11 @@ void NetQueryDispatcher::stop() { public_rsa_key_watchdog_.reset(); dc_auth_manager_.reset(); sequence_dispatcher_.reset(); + td_guard_.reset(); } void NetQueryDispatcher::update_session_count() { - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); int32 session_count = get_session_count(); bool use_pfs = get_use_pfs(); for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { @@ -245,7 +293,7 @@ void NetQueryDispatcher::destroy_auth_keys(Promise<> promise) { } } - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); LOG(INFO) << "Destroy auth keys"; need_destroy_auth_key_ = true; for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { @@ -257,7 +305,7 @@ void NetQueryDispatcher::destroy_auth_keys(Promise<> promise) { } void NetQueryDispatcher::update_use_pfs() { - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); bool use_pfs = get_use_pfs(); for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { if (is_dc_inited(i)) { @@ -270,7 +318,7 @@ void NetQueryDispatcher::update_use_pfs() { } void NetQueryDispatcher::update_mtproto_header() { - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); for (int32 i = 1; i < DcId::MAX_RAW_DC_ID; i++) { if (is_dc_inited(i)) { send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_mtproto_header); @@ -303,6 +351,9 @@ NetQueryDispatcher::NetQueryDispatcher(const std::function()> &cre main_dc_id_ = to_integer(s_main_dc_id); } delayer_ = create_actor("NetQueryDelayer", create_reference()); +#if TD_ANDROID || TD_DARWIN_IOS || TD_DARWIN_VISION_OS || TD_DARWIN_WATCH_OS || TD_TEST_VERIFICATION + verifier_ = create_actor("NetQueryVerifier", create_reference()); +#endif dc_auth_manager_ = create_actor_on_scheduler("DcAuthManager", get_main_session_scheduler_id(), create_reference()); public_rsa_key_watchdog_ = create_actor("PublicRsaKeyWatchdog", create_reference()); @@ -341,8 +392,7 @@ void NetQueryDispatcher::set_main_dc_id(int32 new_main_dc_id) { return; } - // Very rare event; mutex is ok. - std::lock_guard guard(main_dc_id_mutex_); + std::lock_guard guard(mutex_); if (new_main_dc_id == main_dc_id_) { return; } @@ -361,7 +411,19 @@ void NetQueryDispatcher::set_main_dc_id(int32 new_main_dc_id) { } void NetQueryDispatcher::check_authorization_is_ok() { + std::lock_guard guard(mutex_); + if (stop_flag_.load(std::memory_order_relaxed)) { + return; + } send_closure(dc_auth_manager_, &DcAuthManager::check_authorization_is_ok); } +void NetQueryDispatcher::set_verification_token(int64 verification_id, string &&token, Promise &&promise) { + if (verifier_.empty()) { + return promise.set_error(Status::Error(400, "Application verification not allowed")); + } + send_closure_later(verifier_, &NetQueryVerifier::set_verification_token, verification_id, std::move(token), + std::move(promise)); +} + } // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h index b2a30b85..6023530c 100644 --- a/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryDispatcher.h @@ -27,6 +27,7 @@ namespace td { class DcAuthManager; class MultiSequenceDispatcher; class NetQueryDelayer; +class NetQueryVerifier; class PublicRsaKeyWatchdog; class SessionMultiProxy; @@ -56,10 +57,13 @@ class NetQueryDispatcher { void set_main_dc_id(int32 new_main_dc_id); void check_authorization_is_ok(); + void set_verification_token(int64 verification_id, string &&token, Promise &&promise); + private: std::atomic stop_flag_{false}; bool need_destroy_auth_key_{false}; ActorOwn delayer_; + ActorOwn verifier_; ActorOwn dc_auth_manager_; ActorOwn sequence_dispatcher_; struct Dc { @@ -79,7 +83,7 @@ class NetQueryDispatcher { std::atomic main_dc_id_{1}; #endif ActorOwn public_rsa_key_watchdog_; - std::mutex main_dc_id_mutex_; + std::mutex mutex_; std::shared_ptr td_guard_; Status wait_dc_init(DcId dc_id, bool force); @@ -90,6 +94,7 @@ class NetQueryDispatcher { static bool get_use_pfs(); static void complete_net_query(NetQueryPtr net_query); + bool check_stop_flag(NetQueryPtr &net_query) const; void try_fix_migrate(NetQueryPtr &net_query); }; diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.cpp b/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.cpp new file mode 100644 index 00000000..12dae68a --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.cpp @@ -0,0 +1,77 @@ +// +// 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/net/NetQueryVerifier.h" + +#include "td/telegram/Global.h" +#include "td/telegram/net/NetQueryDispatcher.h" +#include "td/telegram/Td.h" +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/Storer.h" +#include "td/utils/utf8.h" + +namespace td { + +void NetQueryVerifier::verify(NetQueryPtr query, string nonce) { + CHECK(query->is_ready()); + CHECK(query->is_error()); + + if (!check_utf8(nonce)) { + query->set_error(Status::Error(400, "Invalid verification nonce")); + G()->net_query_dispatcher().dispatch(std::move(query)); + return; + } + + auto query_id = next_query_id_++; + queries_.emplace(query_id, std::make_pair(std::move(query), nonce)); + + send_closure(G()->td(), &Td::send_update, + td_api::make_object(query_id, nonce)); +} + +void NetQueryVerifier::set_verification_token(int64 query_id, string &&token, Promise &&promise) { + auto it = queries_.find(query_id); + if (it == queries_.end()) { + return promise.set_error(Status::Error(400, "Verification not found")); + } + auto query = std::move(it->second.first); + auto nonce = std::move(it->second.second); + queries_.erase(it); + promise.set_value(Unit()); + + if (token.empty()) { + query->set_error(Status::Error(400, "VERIFICATION_FAILED")); + } else { +#if TD_ANDROID + telegram_api::invokeWithGooglePlayIntegrityPrefix prefix(nonce, token); +#else + telegram_api::invokeWithApnsSecretPrefix prefix(nonce, token); +#endif + auto storer = DefaultStorer(prefix); + string prefix_str(storer.size(), '\0'); + auto real_size = storer.store(MutableSlice(prefix_str).ubegin()); + CHECK(real_size == prefix_str.size()); + query->add_verification_prefix(prefix_str); + query->resend(); + } + G()->net_query_dispatcher().dispatch(std::move(query)); +} + +void NetQueryVerifier::tear_down() { + for (auto &it : queries_) { + it.second.first->set_error(Global::request_aborted_error()); + G()->net_query_dispatcher().dispatch(std::move(it.second.first)); + } + queries_.clear(); + parent_.reset(); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.h b/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.h new file mode 100644 index 00000000..038abf81 --- /dev/null +++ b/lib/tgchat/ext/td/td/telegram/net/NetQueryVerifier.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/net/NetQuery.h" + +#include "td/actor/actor.h" + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/Promise.h" + +#include + +namespace td { + +class NetQueryVerifier final : public Actor { + public: + explicit NetQueryVerifier(ActorShared<> parent) : parent_(std::move(parent)) { + } + + void verify(NetQueryPtr query, string nonce); + + void set_verification_token(int64 query_id, string &&token, Promise &&promise); + + private: + void tear_down() final; + + ActorShared<> parent_; + + FlatHashMap> queries_; + int64 next_query_id_ = 1; +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/td/telegram/net/Session.cpp b/lib/tgchat/ext/td/td/telegram/net/Session.cpp index 6bb84a8d..8e3d5ec8 100644 --- a/lib/tgchat/ext/td/td/telegram/net/Session.cpp +++ b/lib/tgchat/ext/td/td/telegram/net/Session.cpp @@ -1345,8 +1345,8 @@ bool Session::connection_send_check_main_key(ConnectionInfo *info) { LOG(INFO) << "Check main key"; being_checked_main_auth_key_id_ = key_id; last_check_query_id_ = UniqueId::next(UniqueId::BindKey); - NetQueryPtr query = G()->net_query_creator().create(last_check_query_id_, telegram_api::help_getNearestDc(), {}, - DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); + NetQueryPtr query = G()->net_query_creator().create(last_check_query_id_, nullptr, telegram_api::help_getNearestDc(), + {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); query->dispatch_ttl_ = 0; query->set_callback(actor_shared(this)); connection_send_query(info, std::move(query)); @@ -1382,7 +1382,7 @@ bool Session::connection_send_bind_key(ConnectionInfo *info) { LOG(INFO) << "Bind key: " << tag("tmp", key_id) << tag("perm", static_cast(perm_auth_key_id)); NetQueryPtr query = G()->net_query_creator().create( - last_bind_query_id_, + last_bind_query_id_, nullptr, telegram_api::auth_bindTempAuthKey(perm_auth_key_id, nonce, expires_at, std::move(encrypted)), {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On); query->dispatch_ttl_ = 0; diff --git a/lib/tgchat/ext/td/td/tl/TlObject.h b/lib/tgchat/ext/td/td/tl/TlObject.h index f3a0d3eb..ae4d3806 100644 --- a/lib/tgchat/ext/td/td/tl/TlObject.h +++ b/lib/tgchat/ext/td/td/tl/TlObject.h @@ -243,8 +243,8 @@ tl_object_ptr make_tl_object(Args &&...args) { * } * \endcode * - * \tparam ToT Type of a TL-object to move to. - * \tparam FromT Type of a TL-object to move from, this is auto-deduced. + * \tparam ToT Type of TL-object to move to. + * \tparam FromT Type of TL-object to move from, this is auto-deduced. * \param[in] from Wrapped pointer to a TL-object. */ template diff --git a/lib/tgchat/ext/td/tdactor/td/actor/PromiseFuture.h b/lib/tgchat/ext/td/tdactor/td/actor/PromiseFuture.h index 779421da..ce3b7bb4 100644 --- a/lib/tgchat/ext/td/tdactor/td/actor/PromiseFuture.h +++ b/lib/tgchat/ext/td/tdactor/td/actor/PromiseFuture.h @@ -291,12 +291,20 @@ class PromiseFuture { FutureActor future_; }; -template -FutureActor send_promise(ActorId actor_id, ResultT (ActorBT::*func)(PromiseActor &&, DestArgsT...), - ArgsT &&...args) { +template +FutureActor send_promise_immediately(ActorId actor_id, + ResultT (ActorBT::*func)(PromiseActor &&, DestArgsT...), ArgsT &&...args) { PromiseFuture pf; - Scheduler::instance()->send_closure( + Scheduler::instance()->send_closure_immediately( + std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward(args)...)); + return pf.move_future(); +} + +template +FutureActor send_promise_later(ActorId actor_id, ResultT (ActorBT::*func)(PromiseActor &&, DestArgsT...), + ArgsT &&...args) { + PromiseFuture pf; + Scheduler::instance()->send_closure_later( std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward(args)...)); return pf.move_future(); } diff --git a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler-decl.h b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler-decl.h index 581b3979..3aa9f8c3 100644 --- a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler-decl.h +++ b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler-decl.h @@ -39,8 +39,6 @@ extern int VERBOSITY_NAME(actor); class ActorInfo; -enum class ActorSendType { Immediate, Later }; - class Scheduler; class SchedulerGuard { public: @@ -103,17 +101,27 @@ class Scheduler { template void destroy_on_scheduler(int32 sched_id, T &value); + template + void destroy_on_scheduler_unique_ptr(int32 sched_id, T &value); + template void destroy_on_scheduler(int32 sched_id, ArgsT &...values); - template - void send_lambda(ActorRef actor_ref, EventT &&func); + template + void send_lambda_immediately(ActorRef actor_ref, EventT &&func); + + template + void send_lambda_later(ActorRef actor_ref, EventT &&func); - template - void send_closure(ActorRef actor_ref, EventT &&closure); + template + void send_closure_immediately(ActorRef actor_ref, EventT &&closure); - template - void send(ActorRef actor_ref, Event &&event); + template + void send_closure_later(ActorRef actor_ref, EventT &&closure); + + void send_immediately(ActorRef actor_ref, Event &&event); + + void send_later(ActorRef actor_ref, Event &&event); void before_tail_send(const ActorId<> &actor_id); @@ -196,8 +204,13 @@ class Scheduler { void flush_mailbox(ActorInfo *actor_info); - template - void send_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func); + void get_actor_sched_id_to_send_immediately(const ActorInfo *actor_info, int32 &actor_sched_id, + bool &on_current_sched, bool &can_send_immediately); + + template + void send_immediately_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func); + + void send_later_impl(const ActorId<> &actor_id, Event &&event); Timestamp run_timeout(); void run_mailbox(); @@ -269,8 +282,8 @@ void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) { using FunctionClassT = member_function_class_t; static_assert(std::is_base_of::value, "unsafe send_closure"); - Scheduler::instance()->send_closure( - std::forward(actor_id), create_immediate_closure(function, std::forward(args)...)); + Scheduler::instance()->send_closure_immediately(std::forward(actor_id), + create_immediate_closure(function, std::forward(args)...)); } template @@ -279,23 +292,23 @@ void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args using FunctionClassT = member_function_class_t; static_assert(std::is_base_of::value, "unsafe send_closure"); - Scheduler::instance()->send(std::forward(actor_id), - Event::delayed_closure(function, std::forward(args)...)); + Scheduler::instance()->send_later(std::forward(actor_id), + Event::delayed_closure(function, std::forward(args)...)); } template void send_lambda(ActorRef actor_ref, ArgsT &&...args) { - Scheduler::instance()->send_lambda(actor_ref, std::forward(args)...); + Scheduler::instance()->send_lambda_immediately(actor_ref, std::forward(args)...); } template void send_event(ActorRef actor_ref, ArgsT &&...args) { - Scheduler::instance()->send(actor_ref, std::forward(args)...); + Scheduler::instance()->send_immediately(actor_ref, std::forward(args)...); } template void send_event_later(ActorRef actor_ref, ArgsT &&...args) { - Scheduler::instance()->send(actor_ref, std::forward(args)...); + Scheduler::instance()->send_later(actor_ref, std::forward(args)...); } } // namespace td diff --git a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.cpp b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.cpp index 7a4f944c..f107602f 100644 --- a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.cpp +++ b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.cpp @@ -301,6 +301,34 @@ void Scheduler::do_event(ActorInfo *actor_info, Event &&event) { // can't clear event here. It may be already destroyed during destroy_actor } +void Scheduler::get_actor_sched_id_to_send_immediately(const ActorInfo *actor_info, int32 &actor_sched_id, + bool &on_current_sched, bool &can_send_immediately) { + bool is_migrating; + std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic(); + on_current_sched = !is_migrating && sched_id_ == actor_sched_id; + CHECK(has_guard_ || !on_current_sched); + can_send_immediately = on_current_sched && !actor_info->is_running() && actor_info->mailbox_.empty(); +} + +void Scheduler::send_later_impl(const ActorId<> &actor_id, Event &&event) { + ActorInfo *actor_info = actor_id.get_actor_info(); + if (unlikely(actor_info == nullptr || close_flag_)) { + return; + } + + int32 actor_sched_id; + bool is_migrating; + std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic(); + bool on_current_sched = !is_migrating && sched_id_ == actor_sched_id; + CHECK(has_guard_ || !on_current_sched); + + if (on_current_sched) { + add_to_mailbox(actor_info, std::move(event)); + } else { + send_to_scheduler(actor_sched_id, actor_id, std::move(event)); + } +} + void Scheduler::register_migrated_actor(ActorInfo *actor_info) { VLOG(actor) << "Register migrated actor " << *actor_info << ", " << tag("actor_count", actor_count_); actor_count_++; @@ -540,7 +568,7 @@ Timestamp Scheduler::run_timeout() { while (!timeout_queue_.empty() && timeout_queue_.top_key() < now) { HeapNode *node = timeout_queue_.pop(); ActorInfo *actor_info = ActorInfo::from_heap_node(node); - send(actor_info->actor_id(), Event::timeout()); + send_immediately(actor_info->actor_id(), Event::timeout()); } return get_timeout(); } diff --git a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.h b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.h index 2158a240..075adbaa 100644 --- a/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.h +++ b/lib/tgchat/ext/td/tdactor/td/actor/impl/Scheduler.h @@ -108,12 +108,12 @@ ActorOwn Scheduler::register_actor_impl(Slice name, ActorT *actor_ptr, A ActorId actor_id = weak_info->actor_id(actor_ptr); if (sched_id != sched_id_) { - send(actor_id, Event::start()); + send_later(actor_id, Event::start()); do_migrate_actor(actor_info, sched_id); } else { pending_actors_list_.put(weak_info->get_list_node()); if (ActorTraits::need_start_up) { - send(actor_id, Event::start()); + send_later(actor_id, Event::start()); } } @@ -158,6 +158,15 @@ void Scheduler::destroy_on_scheduler(int32 sched_id, T &value) { } } +template +void Scheduler::destroy_on_scheduler_unique_ptr(int32 sched_id, T &value) { + if (value != nullptr) { + destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([value = std::move(value)](Unit) { + // destroy value + })); + } +} + template void Scheduler::destroy_on_scheduler(int32 sched_id, ArgsT &...values) { destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([values = std::make_tuple(std::move(values)...)](Unit) { @@ -169,22 +178,20 @@ inline void Scheduler::before_tail_send(const ActorId<> &actor_id) { // TODO } -template -void Scheduler::send_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func) { +template +void Scheduler::send_immediately_impl(const ActorId<> &actor_id, const RunFuncT &run_func, + const EventFuncT &event_func) { ActorInfo *actor_info = actor_id.get_actor_info(); if (unlikely(actor_info == nullptr || close_flag_)) { return; } - CHECK(actor_info != nullptr); int32 actor_sched_id; - bool is_migrating; - std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic(); - bool on_current_sched = !is_migrating && sched_id_ == actor_sched_id; - CHECK(has_guard_ || !on_current_sched); + bool on_current_sched; + bool can_send_immediately; + get_actor_sched_id_to_send_immediately(actor_info, actor_sched_id, on_current_sched, can_send_immediately); - if (likely(send_type == ActorSendType::Immediate && on_current_sched && !actor_info->is_running() && - actor_info->mailbox_.empty())) { // run immediately + if (likely(can_send_immediately)) { // run immediately EventGuard guard(this, actor_info); run_func(actor_info); } else { @@ -196,9 +203,9 @@ void Scheduler::send_impl(const ActorId<> &actor_id, const RunFuncT &run_func, c } } -template -void Scheduler::send_lambda(ActorRef actor_ref, EventT &&func) { - return send_impl( +template +void Scheduler::send_lambda_immediately(ActorRef actor_ref, EventT &&func) { + return send_immediately_impl( actor_ref.get(), [&](ActorInfo *actor_info) { event_context_ptr_->link_token = actor_ref.token(); @@ -211,9 +218,16 @@ void Scheduler::send_lambda(ActorRef actor_ref, EventT &&func) { }); } -template -void Scheduler::send_closure(ActorRef actor_ref, EventT &&closure) { - return send_impl( +template +void Scheduler::send_lambda_later(ActorRef actor_ref, EventT &&func) { + auto event = Event::from_lambda(std::forward(func)); + event.set_link_token(actor_ref.token()); + return send_later_impl(actor_ref.get(), std::move(event)); +} + +template +void Scheduler::send_closure_immediately(ActorRef actor_ref, EventT &&closure) { + return send_immediately_impl( actor_ref.get(), [&](ActorInfo *actor_info) { event_context_ptr_->link_token = actor_ref.token(); @@ -226,14 +240,25 @@ void Scheduler::send_closure(ActorRef actor_ref, EventT &&closure) { }); } -template -void Scheduler::send(ActorRef actor_ref, Event &&event) { +template +void Scheduler::send_closure_later(ActorRef actor_ref, EventT &&closure) { + auto event = Event::immediate_closure(std::forward(closure)); + event.set_link_token(actor_ref.token()); + return send_later_impl(actor_ref.get(), std::move(event)); +} + +inline void Scheduler::send_immediately(ActorRef actor_ref, Event &&event) { event.set_link_token(actor_ref.token()); - return send_impl( + return send_immediately_impl( actor_ref.get(), [&](ActorInfo *actor_info) { do_event(actor_info, std::move(event)); }, [&] { return std::move(event); }); } +inline void Scheduler::send_later(ActorRef actor_ref, Event &&event) { + event.set_link_token(actor_ref.token()); + return send_later_impl(actor_ref.get(), std::move(event)); +} + inline void Scheduler::subscribe(PollableFd fd, PollFlags flags) { instance()->poll_.subscribe(std::move(fd), flags); } @@ -250,7 +275,7 @@ inline void Scheduler::yield_actor(Actor *actor) { yield_actor(actor->get_info()); } inline void Scheduler::yield_actor(ActorInfo *actor_info) { - send(actor_info->actor_id(), Event::yield()); + send_later(actor_info->actor_id(), Event::yield()); } inline void Scheduler::stop_actor(Actor *actor) { diff --git a/lib/tgchat/ext/td/tdactor/test/actors_main.cpp b/lib/tgchat/ext/td/tdactor/test/actors_main.cpp index 412888f5..09b9db6e 100644 --- a/lib/tgchat/ext/td/tdactor/test/actors_main.cpp +++ b/lib/tgchat/ext/td/tdactor/test/actors_main.cpp @@ -126,8 +126,8 @@ class QueryActor final : public td::Actor { callback_->on_result(std::move(query)); } else { auto future = td::Random::fast(0, 3) == 0 - ? td::send_promise(rand_elem(workers_), &Worker::query, x, p) - : td::send_promise(rand_elem(workers_), &Worker::query, x, p); + ? td::send_promise_immediately(rand_elem(workers_), &Worker::query, x, p) + : td::send_promise_later(rand_elem(workers_), &Worker::query, x, p); if (future.is_ready()) { query.result = future.move_as_ok(); callback_->on_result(std::move(query)); @@ -301,9 +301,8 @@ class SimpleActor final : public td::Actor { } q_++; p_ = td::Random::fast_bool() ? 1 : 10000; - auto future = td::Random::fast(0, 3) == 0 - ? td::send_promise(worker_, &Worker::query, q_, p_) - : td::send_promise(worker_, &Worker::query, q_, p_); + auto future = td::Random::fast(0, 3) == 0 ? td::send_promise_immediately(worker_, &Worker::query, q_, p_) + : td::send_promise_later(worker_, &Worker::query, q_, p_); if (future.is_ready()) { auto result = future.move_as_ok(); CHECK(result == fast_pow_mod_uint32(q_, p_)); diff --git a/lib/tgchat/ext/td/tddb/td/db/binlog/BinlogEvent.cpp b/lib/tgchat/ext/td/tddb/td/db/binlog/BinlogEvent.cpp index dea997d4..9ad4c5d6 100644 --- a/lib/tgchat/ext/td/tddb/td/db/binlog/BinlogEvent.cpp +++ b/lib/tgchat/ext/td/tddb/td/db/binlog/BinlogEvent.cpp @@ -23,7 +23,7 @@ void BinlogEvent::init(string raw_event) { flags_ = parser.fetch_int(); extra_ = static_cast(parser.fetch_long()); CHECK(size_ >= MIN_SIZE); - parser.fetch_string_raw(size_ - MIN_SIZE); // skip data + parser.template fetch_string_raw(size_ - MIN_SIZE); // skip data crc32_ = static_cast(parser.fetch_int()); raw_event_ = std::move(raw_event); } @@ -43,7 +43,7 @@ Status BinlogEvent::validate() const { return Status::Error(PSLICE() << "Size of event changed: " << tag("was", size_) << tag("now", size) << tag("real size", raw_event_.size())); } - parser.fetch_string_raw(size_ - TAIL_SIZE - sizeof(int)); // skip + parser.template fetch_string_raw(size_ - TAIL_SIZE - sizeof(int)); // skip auto stored_crc32 = static_cast(parser.fetch_int()); auto calculated_crc = crc32(Slice(as_slice(raw_event_).data(), size_ - TAIL_SIZE)); if (calculated_crc != crc32_ || calculated_crc != stored_crc32) { diff --git a/lib/tgchat/ext/td/tddb/td/db/binlog/binlog_dump.cpp b/lib/tgchat/ext/td/tddb/td/db/binlog/binlog_dump.cpp index 194bf846..050b7b99 100644 --- a/lib/tgchat/ext/td/tddb/td/db/binlog/binlog_dump.cpp +++ b/lib/tgchat/ext/td/tddb/td/db/binlog/binlog_dump.cpp @@ -125,7 +125,7 @@ int main(int argc, char *argv[]) { info[0].compressed_size += event.raw_event_.size(); info[event.type_].compressed_size += event.raw_event_.size(); if (event.type_ == ConfigPmcMagic || event.type_ == BinlogPmcMagic) { - auto key = td::TlParser(event.get_data()).fetch_string(); + auto key = td::TlParser(event.get_data()).template fetch_string(); info[event.type_].compressed_trie.add(key); } }, @@ -134,7 +134,7 @@ int main(int argc, char *argv[]) { info[0].full_size += event.raw_event_.size(); info[event.type_].full_size += event.raw_event_.size(); if (event.type_ == ConfigPmcMagic || event.type_ == BinlogPmcMagic) { - auto key = td::TlParser(event.get_data()).fetch_string(); + auto key = td::TlParser(event.get_data()).template fetch_string(); info[event.type_].trie.add(key); } LOG(PLAIN) << "LogEvent[" << td::tag("event_id", td::format::as_hex(event.id_)) diff --git a/lib/tgchat/ext/td/tdtl/td/tl/tl_writer.cpp b/lib/tgchat/ext/td/tdtl/td/tl/tl_writer.cpp index 11b84b42..c423d7c4 100644 --- a/lib/tgchat/ext/td/tdtl/td/tl/tl_writer.cpp +++ b/lib/tgchat/ext/td/tdtl/td/tl/tl_writer.cpp @@ -70,7 +70,7 @@ bool TL_writer::is_combinator_supported(const tl_combinator *constructor) const if (a.flags & FLAG_EXCL) { assert(t->var_num >= 0); if (is_function_result[t->var_num]) { - return false; // lazy to check that result of two function calls is the same + return false; // lazy to check that results of two function calls are the same } is_function_result[t->var_num] = true; } else { diff --git a/lib/tgchat/ext/td/tdutils/CMakeLists.txt b/lib/tgchat/ext/td/tdutils/CMakeLists.txt index 1240682f..f63bd90e 100644 --- a/lib/tgchat/ext/td/tdutils/CMakeLists.txt +++ b/lib/tgchat/ext/td/tdutils/CMakeLists.txt @@ -107,6 +107,7 @@ set(TDUTILS_SOURCE td/utils/Gzip.cpp td/utils/GzipByteFlow.cpp td/utils/Hints.cpp + td/utils/HttpDate.cpp td/utils/HttpUrl.cpp td/utils/JsonBuilder.cpp td/utils/logging.cpp @@ -231,6 +232,7 @@ set(TDUTILS_SOURCE td/utils/HazardPointers.h td/utils/Heap.h td/utils/Hints.h + td/utils/HttpDate.h td/utils/HttpUrl.h td/utils/int_types.h td/utils/invoke.h diff --git a/lib/tgchat/ext/td/tdutils/td/utils/ConcurrentHashTable.h b/lib/tgchat/ext/td/tdutils/td/utils/ConcurrentHashTable.h index 5bc94c4d..0dfbbe34 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/ConcurrentHashTable.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/ConcurrentHashTable.h @@ -113,7 +113,7 @@ class ConcurrentHashMap { } static std::string get_name() { - return "ConcurrrentHashMap"; + return "ConcurrentHashMap"; } static KeyT empty_key() { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMap.h b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMap.h index 3e8f7688..6ea919a4 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMap.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMap.h @@ -17,7 +17,7 @@ namespace td { template , class EqT = std::equal_to> -using FlatHashMap = FlatHashTable, HashT, EqT>; +using FlatHashMap = FlatHashTable, HashT, EqT>; //using FlatHashMap = FlatHashMapChunks; //using FlatHashMap = std::unordered_map; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMapChunks.h b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMapChunks.h index cd8e73f2..c5fcf4a3 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMapChunks.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashMapChunks.h @@ -257,7 +257,7 @@ class FlatHashTableChunks { } Iterator find(const KeyT &key) { - if (empty() || is_hash_table_key_empty(key)) { + if (empty() || is_hash_table_key_empty(key)) { return end(); } const auto hash = calc_hash(key); @@ -326,7 +326,7 @@ class FlatHashTableChunks { template std::pair emplace(KeyT key, ArgsT &&...args) { - CHECK(!is_hash_table_key_empty(key)); + CHECK(!is_hash_table_key_empty(key)); auto it = find(key); if (it != end()) { return {it, false}; @@ -562,10 +562,10 @@ class FlatHashTableChunks { }; template , class EqT = std::equal_to> -using FlatHashMapChunks = FlatHashTableChunks, HashT, EqT>; +using FlatHashMapChunks = FlatHashTableChunks, HashT, EqT>; template , class EqT = std::equal_to> -using FlatHashSetChunks = FlatHashTableChunks, HashT, EqT>; +using FlatHashSetChunks = FlatHashTableChunks, HashT, EqT>; template void table_remove_if(FlatHashTableChunks &table, FuncT &&func) { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashSet.h b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashSet.h index 96bb1928..ca4398fd 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashSet.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashSet.h @@ -17,7 +17,7 @@ namespace td { template , class EqT = std::equal_to> -using FlatHashSet = FlatHashTable, HashT, EqT>; +using FlatHashSet = FlatHashTable, HashT, EqT>; //using FlatHashSet = FlatHashSetChunks; //using FlatHashSet = std::unordered_set; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashTable.h b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashTable.h index f818cbea..f2af44b8 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/FlatHashTable.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/FlatHashTable.h @@ -308,7 +308,7 @@ class FlatHashTable { template std::pair emplace(KeyT key, ArgsT &&...args) { - CHECK(!is_hash_table_key_empty(key)); + CHECK(!is_hash_table_key_empty(key)); if (unlikely(bucket_count_mask_ == 0)) { CHECK(used_node_count_ == 0); resize(8); @@ -447,7 +447,7 @@ class FlatHashTable { } NodeT *find_impl(const KeyT &key) { - if (unlikely(nodes_ == nullptr) || is_hash_table_key_empty(key)) { + if (unlikely(nodes_ == nullptr) || is_hash_table_key_empty(key)) { return nullptr; } auto bucket = calc_bucket(key); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/HashTableUtils.h b/lib/tgchat/ext/td/tdutils/td/utils/HashTableUtils.h index 620796d4..21d4104d 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/HashTableUtils.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/HashTableUtils.h @@ -13,9 +13,9 @@ namespace td { -template +template bool is_hash_table_key_empty(const KeyT &key) { - return key == KeyT(); + return EqT()(key, KeyT()); } inline uint32 randomize_hash(uint32 h) { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.cpp b/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.cpp new file mode 100644 index 00000000..da290ae3 --- /dev/null +++ b/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.cpp @@ -0,0 +1,92 @@ +// +// 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/utils/HttpDate.h" + +#include "td/utils/misc.h" +#include "td/utils/Parser.h" +#include "td/utils/Slice.h" + +namespace td { + +Result HttpDate::to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second) { + if (year < 1970 || year > 2037) { + return Status::Error("Invalid year"); + } + if (month < 1 || month > 12) { + return Status::Error("Invalid month"); + } + if (day < 1 || day > days_in_month(year, month)) { + return Status::Error("Invalid day"); + } + if (hour < 0 || hour >= 24) { + return Status::Error("Invalid hour"); + } + if (minute < 0 || minute >= 60) { + return Status::Error("Invalid minute"); + } + if (second < 0 || second > 60) { + return Status::Error("Invalid second"); + } + + int32 res = 0; + for (int32 y = 1970; y < year; y++) { + res += (is_leap(y) + 365) * seconds_in_day(); + } + for (int32 m = 1; m < month; m++) { + res += days_in_month(year, m) * seconds_in_day(); + } + res += (day - 1) * seconds_in_day(); + res += hour * 60 * 60; + res += minute * 60; + res += second; + return res; +} + +Result HttpDate::parse_http_date(string slice) { + Parser p(slice); + p.read_till(','); // ignore week day + p.skip(','); + p.skip_whitespaces(); + p.skip_nofail('0'); + TRY_RESULT(day, to_integer_safe(p.read_word())); + auto month_name = p.read_word(); + to_lower_inplace(month_name); + TRY_RESULT(year, to_integer_safe(p.read_word())); + p.skip_whitespaces(); + p.skip_nofail('0'); + TRY_RESULT(hour, to_integer_safe(p.read_till(':'))); + p.skip(':'); + p.skip_nofail('0'); + TRY_RESULT(minute, to_integer_safe(p.read_till(':'))); + p.skip(':'); + p.skip_nofail('0'); + TRY_RESULT(second, to_integer_safe(p.read_word())); + auto gmt = p.read_word(); + TRY_STATUS(std::move(p.status())); + if (gmt != "GMT") { + return Status::Error("Timezone must be GMT"); + } + + static Slice month_names[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; + + int month = 0; + + for (int m = 1; m <= 12; m++) { + if (month_names[m - 1] == month_name) { + month = m; + break; + } + } + + if (month == 0) { + return Status::Error("Unknown month name"); + } + + return to_unix_time(year, month, day, hour, minute, second); +} + +} // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.h b/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.h new file mode 100644 index 00000000..a2daf078 --- /dev/null +++ b/lib/tgchat/ext/td/tdutils/td/utils/HttpDate.h @@ -0,0 +1,34 @@ +// +// 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/utils/common.h" +#include "td/utils/Status.h" + +namespace td { + +class HttpDate { + static bool is_leap(int32 year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + static int32 seconds_in_day() { + return 24 * 60 * 60; + } + + public: + static int32 days_in_month(int32 year, int32 month) { + static int cnt[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + return cnt[month - 1] + (month == 2 && is_leap(year)); + } + + static Result to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second); + + static Result parse_http_date(string slice); +}; + +} // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.cpp b/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.cpp index b4071ecd..a0aebb61 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.cpp @@ -542,6 +542,13 @@ Slice JsonValue::get_type_name(Type type) { } } +JsonObject::JsonObject(vector> &&field_values) : field_values_(std::move(field_values)) { +} + +size_t JsonObject::field_count() const { + return field_values_.size(); +} + JsonValue JsonObject::extract_field(Slice name) { for (auto &field_value : field_values_) { if (field_value.first == name) { diff --git a/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.h b/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.h index 1b3e9ed7..3989cf76 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/JsonBuilder.h @@ -464,8 +464,7 @@ class JsonObject { JsonObject() = default; - explicit JsonObject(vector> &&field_values) : field_values_(std::move(field_values)) { - } + explicit JsonObject(vector> &&field_values); JsonObject(const JsonObject &) = delete; JsonObject &operator=(const JsonObject &) = delete; @@ -473,9 +472,7 @@ class JsonObject { JsonObject &operator=(JsonObject &&) = default; ~JsonObject() = default; - size_t field_count() const { - return field_values_.size(); - } + size_t field_count() const; JsonValue extract_field(Slice name); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/MapNode.h b/lib/tgchat/ext/td/tdutils/td/utils/MapNode.h index d75c8e86..9d87aad8 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/MapNode.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/MapNode.h @@ -15,7 +15,7 @@ namespace td { -template +template struct MapNode { using first_type = KeyT; using second_type = ValueT; @@ -72,7 +72,7 @@ struct MapNode { } bool empty() const { - return is_hash_table_key_empty(first); + return is_hash_table_key_empty(first); } void clear() { @@ -91,8 +91,8 @@ struct MapNode { } }; -template -struct MapNode 28 * sizeof(void *))>> { +template +struct MapNode 28 * sizeof(void *))>> { struct Impl { using first_type = KeyT; using second_type = ValueT; @@ -105,7 +105,7 @@ struct MapNode Impl(InputKeyT &&key, ArgsT &&...args) : first(std::forward(key)) { new (&second) ValueT(std::forward(args)...); - DCHECK(!is_hash_table_key_empty(first)); + DCHECK(!is_hash_table_key_empty(first)); } Impl(const Impl &) = delete; Impl &operator=(const Impl &) = delete; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/MpmcQueue.h b/lib/tgchat/ext/td/tdutils/td/utils/MpmcQueue.h index 9b7299dc..1a08768d 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/MpmcQueue.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/MpmcQueue.h @@ -324,7 +324,7 @@ class MpmcQueueOld { char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic)]; size_t block_size_; HazardPointers hazard_pointers_; - // HazardPointers is already padded + // HazardPointers class is already padded }; template @@ -454,7 +454,7 @@ class MpmcQueue { std::atomic read_pos_{nullptr}; char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic)]; HazardPointers hazard_pointers_; - // HazardPointers is already padded + // HazardPointers class is already padded }; } // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/SetNode.h b/lib/tgchat/ext/td/tdutils/td/utils/SetNode.h index 35e525cf..a637a836 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/SetNode.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/SetNode.h @@ -14,7 +14,7 @@ namespace td { -template +template struct SetNode { using public_key_type = KeyT; using public_type = const KeyT; @@ -54,7 +54,7 @@ struct SetNode { } bool empty() const { - return is_hash_table_key_empty(first); + return is_hash_table_key_empty(first); } void clear() { @@ -67,8 +67,8 @@ struct SetNode { } }; -template -struct SetNode 28 * sizeof(void *))>> { +template +struct SetNode 28 * sizeof(void *))>> { struct Impl { using second_type = KeyT; @@ -76,7 +76,7 @@ struct SetNode 28 * sizeof(void template explicit Impl(InputKeyT &&key) : first(std::forward(key)) { - DCHECK(!is_hash_table_key_empty(first)); + DCHECK(!is_hash_table_key_empty(first)); } Impl(const Impl &) = delete; Impl &operator=(const Impl &) = delete; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.cpp index d3c5195c..72578a9b 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.cpp @@ -369,4 +369,12 @@ Result ServerSocketFd::open(int32 port, CSlice addr) { return ServerSocketFd(std::move(impl)); } +Result ServerSocketFd::maximize_snd_buffer(uint32 max_size) { + return get_native_fd().maximize_snd_buffer(max_size); +} + +Result ServerSocketFd::maximize_rcv_buffer(uint32 max_size) { + return get_native_fd().maximize_rcv_buffer(max_size); +} + } // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.h b/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.h index 52557cda..dcee8991 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/ServerSocketFd.h @@ -33,6 +33,9 @@ class ServerSocketFd { ServerSocketFd &operator=(ServerSocketFd &&) noexcept; ~ServerSocketFd(); + Result maximize_snd_buffer(uint32 max_size = 0); + Result maximize_rcv_buffer(uint32 max_size = 0); + static Result open(int32 port, CSlice addr = CSlice("0.0.0.0")) TD_WARN_UNUSED_RESULT; PollableFdInfo &get_poll_info(); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp index e5253505..ac4038c3 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.cpp @@ -717,4 +717,12 @@ Result SocketFd::read(MutableSlice slice) { return impl_->read(slice); } +Result SocketFd::maximize_snd_buffer(uint32 max_size) { + return get_native_fd().maximize_snd_buffer(max_size); +} + +Result SocketFd::maximize_rcv_buffer(uint32 max_size) { + return get_native_fd().maximize_rcv_buffer(max_size); +} + } // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.h b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.h index 5427cc99..40f3a932 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/SocketFd.h @@ -37,6 +37,9 @@ class SocketFd { SocketFd &operator=(SocketFd &&) noexcept; ~SocketFd(); + Result maximize_snd_buffer(uint32 max_size = 0); + Result maximize_rcv_buffer(uint32 max_size = 0); + static Result open(const IPAddress &address) TD_WARN_UNUSED_RESULT; PollableFdInfo &get_poll_info(); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.cpp index 858aa969..cd4641b1 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.cpp @@ -789,54 +789,6 @@ const NativeFd &UdpSocketFd::get_native_fd() const { return get_poll_info().native_fd(); } -#if TD_PORT_POSIX -static Result maximize_buffer(int socket_fd, int optname, uint32 max_size) { - if (setsockopt(socket_fd, SOL_SOCKET, optname, &max_size, sizeof(max_size)) == 0) { - // fast path - return max_size; - } - - /* Start with the default size. */ - uint32 old_size = 0; - socklen_t intsize = sizeof(old_size); - if (getsockopt(socket_fd, SOL_SOCKET, optname, &old_size, &intsize)) { - return OS_ERROR("getsockopt() failed"); - } -#if TD_LINUX - old_size /= 2; -#endif - - /* Binary-search for the real maximum. */ - uint32 last_good_size = old_size; - uint32 min_size = old_size; - while (min_size <= max_size) { - uint32 avg_size = min_size + (max_size - min_size) / 2; - if (setsockopt(socket_fd, SOL_SOCKET, optname, &avg_size, sizeof(avg_size)) == 0) { - last_good_size = avg_size; - min_size = avg_size + 1; - } else { - max_size = avg_size - 1; - } - } - return last_good_size; -} - -Result UdpSocketFd::maximize_snd_buffer(uint32 max_size) { - return maximize_buffer(get_native_fd().fd(), SO_SNDBUF, max_size == 0 ? DEFAULT_UDP_MAX_SND_BUFFER_SIZE : max_size); -} - -Result UdpSocketFd::maximize_rcv_buffer(uint32 max_size) { - return maximize_buffer(get_native_fd().fd(), SO_RCVBUF, max_size == 0 ? DEFAULT_UDP_MAX_RCV_BUFFER_SIZE : max_size); -} -#else -Result UdpSocketFd::maximize_snd_buffer(uint32 max_size) { - return 0; -} -Result UdpSocketFd::maximize_rcv_buffer(uint32 max_size) { - return 0; -} -#endif - #if TD_PORT_POSIX Status UdpSocketFd::send_message(const OutboundMessage &message, bool &is_sent) { return impl_->send_message(message, is_sent); @@ -870,4 +822,12 @@ bool UdpSocketFd::is_critical_read_error(const Status &status) { return status.code() == ENOMEM || status.code() == ENOBUFS; } +Result UdpSocketFd::maximize_snd_buffer(uint32 max_size) { + return get_native_fd().maximize_snd_buffer(max_size); +} + +Result UdpSocketFd::maximize_rcv_buffer(uint32 max_size) { + return get_native_fd().maximize_rcv_buffer(max_size); +} + } // namespace td diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.h b/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.h index dfbfbb67..53d7755b 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/UdpSocketFd.h @@ -83,8 +83,6 @@ class UdpSocketFd { #endif private: - static constexpr uint32 DEFAULT_UDP_MAX_SND_BUFFER_SIZE = (1 << 24); - static constexpr uint32 DEFAULT_UDP_MAX_RCV_BUFFER_SIZE = (1 << 24); std::unique_ptr impl_; explicit UdpSocketFd(unique_ptr impl); }; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.cpp index 2a9f656b..479498ee 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.cpp @@ -12,6 +12,8 @@ #if TD_PORT_POSIX #include +#include +#include #include #endif @@ -223,6 +225,45 @@ Status NativeFd::duplicate(const NativeFd &to) const { #endif } +static Result maximize_buffer(const NativeFd::Socket &socket, int optname, uint32 max_size) { + if (setsockopt(socket, SOL_SOCKET, optname, reinterpret_cast(&max_size), sizeof(max_size)) == 0) { + // fast path + return max_size; + } + + /* Start with the default size. */ + uint32 old_size = 0; + socklen_t intsize = sizeof(old_size); + if (getsockopt(socket, SOL_SOCKET, optname, reinterpret_cast(&old_size), &intsize)) { + return OS_SOCKET_ERROR("getsockopt() failed"); + } +#if TD_LINUX + old_size /= 2; +#endif + + /* Binary-search for the real maximum. */ + uint32 last_good_size = old_size; + uint32 min_size = old_size; + while (min_size <= max_size) { + uint32 avg_size = min_size + (max_size - min_size) / 2; + if (setsockopt(socket, SOL_SOCKET, optname, reinterpret_cast(&avg_size), sizeof(avg_size)) == 0) { + last_good_size = avg_size; + min_size = avg_size + 1; + } else { + max_size = avg_size - 1; + } + } + return last_good_size; +} + +Result NativeFd::maximize_snd_buffer(uint32 max_size) const { + return maximize_buffer(socket(), SO_SNDBUF, max_size == 0 ? DEFAULT_MAX_SND_BUFFER_SIZE : max_size); +} + +Result NativeFd::maximize_rcv_buffer(uint32 max_size) const { + return maximize_buffer(socket(), SO_RCVBUF, max_size == 0 ? DEFAULT_MAX_RCV_BUFFER_SIZE : max_size); +} + void NativeFd::close() { if (!*this) { return; diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.h b/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.h index db59fd35..a2af773b 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/detail/NativeFd.h @@ -49,6 +49,9 @@ class NativeFd { Status duplicate(const NativeFd &to) const; + Result maximize_snd_buffer(uint32 max_size = 0) const; + Result maximize_rcv_buffer(uint32 max_size = 0) const; + void close(); Fd release(); @@ -61,6 +64,8 @@ class NativeFd { #if TD_PORT_WINDOWS bool is_socket_{false}; #endif + static constexpr uint32 DEFAULT_MAX_SND_BUFFER_SIZE = (1 << 24); + static constexpr uint32 DEFAULT_MAX_RCV_BUFFER_SIZE = (1 << 24); }; StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd); diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/platform.h b/lib/tgchat/ext/td/tdutils/td/utils/port/platform.h index 09f111b1..7ca7ee20 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/platform.h +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/platform.h @@ -25,6 +25,8 @@ #define TD_DARWIN_IOS 1 #elif TARGET_OS_TV #define TD_DARWIN_TV_OS 1 + #elif TARGET_OS_VISION + #define TD_DARWIN_VISION_OS 1 #elif TARGET_OS_WATCH #define TD_DARWIN_WATCH_OS 1 #else diff --git a/lib/tgchat/ext/td/tdutils/td/utils/port/uname.cpp b/lib/tgchat/ext/td/tdutils/td/utils/port/uname.cpp index a6a3299c..5b9dc855 100644 --- a/lib/tgchat/ext/td/tdutils/td/utils/port/uname.cpp +++ b/lib/tgchat/ext/td/tdutils/td/utils/port/uname.cpp @@ -81,6 +81,8 @@ Slice get_operating_system_version() { return "iOS" + os_version; #elif TD_DARWIN_TV_OS return "tvOS" + os_version; +#elif TD_DARWIN_VISION_OS + return "visionOS" + os_version; #elif TD_DARWIN_WATCH_OS return "watchOS" + os_version; #elif TD_DARWIN_MAC diff --git a/lib/tgchat/ext/td/tdutils/test/hashset_benchmark.cpp b/lib/tgchat/ext/td/tdutils/test/hashset_benchmark.cpp index f066f94c..35e1ae5a 100644 --- a/lib/tgchat/ext/td/tdutils/test/hashset_benchmark.cpp +++ b/lib/tgchat/ext/td/tdutils/test/hashset_benchmark.cpp @@ -590,7 +590,7 @@ BENCHMARK_TEMPLATE(BM_mask, td::MaskSse2); #endif template , class EqT = std::equal_to> -using FlatHashMapImpl = td::FlatHashTable, HashT, EqT>; +using FlatHashMapImpl = td::FlatHashTable, HashT, EqT>; #define FOR_EACH_TABLE(F) \ F(FlatHashMapImpl) \ diff --git a/lib/tgchat/ext/td/tdutils/test/misc.cpp b/lib/tgchat/ext/td/tdutils/test/misc.cpp index 119b89ac..cabd4e6e 100644 --- a/lib/tgchat/ext/td/tdutils/test/misc.cpp +++ b/lib/tgchat/ext/td/tdutils/test/misc.cpp @@ -83,14 +83,11 @@ TEST(Misc, update_atime_saves_mtime) { r_file.move_as_ok().close(); auto info = td::stat(name).ok(); - td::int32 tests_ok = 0; td::int32 tests_wa = 0; for (int i = 0; i < 10000; i++) { td::update_atime(name).ensure(); auto new_info = td::stat(name).ok(); - if (info.mtime_nsec_ == new_info.mtime_nsec_) { - tests_ok++; - } else { + if (info.mtime_nsec_ != new_info.mtime_nsec_) { tests_wa++; info.mtime_nsec_ = new_info.mtime_nsec_; } diff --git a/lib/tgchat/ext/td/test/CMakeLists.txt b/lib/tgchat/ext/td/test/CMakeLists.txt index 2a140a3a..a45b04ad 100644 --- a/lib/tgchat/ext/td/test/CMakeLists.txt +++ b/lib/tgchat/ext/td/test/CMakeLists.txt @@ -40,7 +40,7 @@ if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN) if (OPENSSL_FOUND) add_executable(test-crypto EXCLUDE_FROM_ALL crypto.cpp) target_include_directories(test-crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(test-crypto PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES} tdutils tdcore) + target_link_libraries(test-crypto PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES} tdutils tdmtproto) if (WIN32) if (MINGW) diff --git a/lib/tgchat/ext/td/test/http.cpp b/lib/tgchat/ext/td/test/http.cpp index 6500c8f7..5deca6d0 100644 --- a/lib/tgchat/ext/td/test/http.cpp +++ b/lib/tgchat/ext/td/test/http.cpp @@ -144,9 +144,17 @@ TEST(Http, reader) { #pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wunknown-warning-option" #pragma clang diagnostic ignored "-Wself-move" +#endif +#if TD_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Wself-move" #endif a = std::move(a); b = std::move(b); +#if TD_GCC +#pragma GCC diagnostic pop +#endif #if TD_CLANG #pragma clang diagnostic pop #endif diff --git a/lib/tgchat/ext/td/test/link.cpp b/lib/tgchat/ext/td/test/link.cpp index 8e9262a8..fe975a42 100644 --- a/lib/tgchat/ext/td/test/link.cpp +++ b/lib/tgchat/ext/td/test/link.cpp @@ -219,6 +219,10 @@ static auto bot_start_in_group(const td::string &bot_username, const td::string std::move(administrator_rights)); } +static auto business_chat(const td::string &link_name) { + return td::td_api::make_object(link_name); +} + static auto change_phone_number() { return td::td_api::make_object(); } @@ -314,8 +318,8 @@ static auto proxy_socks(const td::string &server, td::int32 port, const td::stri server, port, td::td_api::make_object(username, password)); } -static auto public_chat(const td::string &chat_username) { - return td::td_api::make_object(chat_username); +static auto public_chat(const td::string &chat_username, const td::string &draft_text = td::string()) { + return td::td_api::make_object(chat_username, draft_text); } static auto qr_code_authentication() { @@ -359,8 +363,8 @@ static auto unsupported_proxy() { return td::td_api::make_object(); } -static auto user_phone_number(const td::string &phone_number) { - return td::td_api::make_object(phone_number); +static auto user_phone_number(const td::string &phone_number, const td::string &draft_text = td::string()) { + return td::td_api::make_object('+' + phone_number, draft_text); } static auto user_token(const td::string &token) { @@ -463,6 +467,7 @@ TEST(Link, parse_internal_link_part1) { attachment_menu_bot(nullptr, public_chat("telegram"), "test", "1")); parse_internal_link("tg:resolve?phone=1", user_phone_number("1")); + parse_internal_link("tg:resolve?phone=+1", user_phone_number("1")); parse_internal_link("tg:resolve?phone=123456", user_phone_number("123456")); parse_internal_link("tg:resolve?phone=123456&startattach", user_phone_number("123456")); parse_internal_link("tg:resolve?phone=123456&startattach=123", user_phone_number("123456")); @@ -471,6 +476,10 @@ TEST(Link, parse_internal_link_part1) { parse_internal_link("tg:resolve?phone=123456&attach=&startattach=123", user_phone_number("123456")); parse_internal_link("tg:resolve?phone=123456&attach=test", attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "")); + parse_internal_link("tg:resolve?phone=+123456&attach=test", + attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "")); + parse_internal_link("tg:resolve?phone=++123456&attach=test", + unknown_deep_link("tg://resolve?phone=++123456&attach=test")); parse_internal_link("tg:resolve?phone=123456&attach=test&startattach&choose=users", attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "")); parse_internal_link("tg:resolve?phone=123456&attach=test&startattach=123", @@ -480,8 +489,14 @@ TEST(Link, parse_internal_link_part1) { parse_internal_link("tg:resolve?phone=012345678901234567890123456789123", unknown_deep_link("tg://resolve?phone=012345678901234567890123456789123")); parse_internal_link("tg:resolve?phone=", unknown_deep_link("tg://resolve?phone=")); - parse_internal_link("tg:resolve?phone=+123", unknown_deep_link("tg://resolve?phone=+123")); + parse_internal_link("tg:resolve?phone=+123", user_phone_number("123")); parse_internal_link("tg:resolve?phone=123456 ", unknown_deep_link("tg://resolve?phone=123456 ")); + parse_internal_link("tg:resolve?domain=telegram&text=asd", public_chat("telegram", "asd")); + parse_internal_link("tg:resolve?phone=12345678901&text=asd", user_phone_number("12345678901", "asd")); + parse_internal_link("tg:resolve?domain=telegram&text=@asd", public_chat("telegram", " @asd")); + parse_internal_link("tg:resolve?phone=12345678901&text=@asd", user_phone_number("12345678901", " @asd")); + parse_internal_link("tg:resolve?domain=telegram&text=1%A02", public_chat("telegram")); + parse_internal_link("tg:resolve?phone=12345678901&text=1%A02", user_phone_number("12345678901")); parse_internal_link("tg:contact?token=1", user_token("1")); parse_internal_link("tg:contact?token=123456", user_token("123456")); @@ -666,6 +681,24 @@ TEST(Link, parse_internal_link_part2) { parse_internal_link("tg:giftcode?slug=abc%30ef", premium_gift_code("abc0ef")); parse_internal_link("tg://giftcode?slug=", unknown_deep_link("tg://giftcode?slug=")); + parse_internal_link("t.me/m?slug=abcdef", nullptr); + parse_internal_link("t.me/m", nullptr); + parse_internal_link("t.me/m/", nullptr); + parse_internal_link("t.me/m//abcdef", nullptr); + parse_internal_link("t.me/m?/abcdef", nullptr); + parse_internal_link("t.me/m/?abcdef", nullptr); + parse_internal_link("t.me/m/#abcdef", nullptr); + parse_internal_link("t.me/m/abacaba", business_chat("abacaba")); + parse_internal_link("t.me/m/aba%20aba", business_chat("aba aba")); + parse_internal_link("t.me/m/123456a", business_chat("123456a")); + parse_internal_link("t.me/m/12345678901", business_chat("12345678901")); + parse_internal_link("t.me/m/123456", business_chat("123456")); + parse_internal_link("t.me/m/123456/123123/12/31/a/s//21w/?asdas#test", business_chat("123456")); + + parse_internal_link("tg:message?slug=abcdef", business_chat("abcdef")); + parse_internal_link("tg:message?slug=abc%30ef", business_chat("abc0ef")); + parse_internal_link("tg://message?slug=", unknown_deep_link("tg://message?slug=")); + parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true)); parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false)); parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false)); @@ -774,6 +807,13 @@ TEST(Link, parse_internal_link_part2) { parse_internal_link("t.me/+123456?attach=bot&startattach=1", attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "1")); + parse_internal_link("telegram.t.me?text=asd", public_chat("telegram", "asd")); + parse_internal_link("t.me/%2012345678901?text=asd", user_phone_number("12345678901", "asd")); + parse_internal_link("t.me/telegram?text=@asd", public_chat("telegram", " @asd")); + parse_internal_link("t.me/%2012345678901?text=@asd", user_phone_number("12345678901", " @asd")); + parse_internal_link("t.me/telegram?text=1%A02", public_chat("telegram")); + parse_internal_link("t.me/%2012345678901?text=1%A02", user_phone_number("12345678901")); + parse_internal_link("t.me/addlist?invite=abcdef", nullptr); parse_internal_link("t.me/addlist", nullptr); parse_internal_link("t.me/addlist/", nullptr); @@ -1295,6 +1335,7 @@ TEST(Link, parse_internal_link_part4) { parse_internal_link("invoice.t.me", nullptr); parse_internal_link("joinchat.t.me", nullptr); parse_internal_link("login.t.me", nullptr); + parse_internal_link("m.t.me", nullptr); parse_internal_link("proxy.t.me", nullptr); parse_internal_link("setlanguage.t.me", nullptr); parse_internal_link("share.t.me", nullptr); diff --git a/lib/tgchat/ext/td/test/message_entities.cpp b/lib/tgchat/ext/td/test/message_entities.cpp index d0ee0a79..05d0d38f 100644 --- a/lib/tgchat/ext/td/test/message_entities.cpp +++ b/lib/tgchat/ext/td/test/message_entities.cpp @@ -1160,7 +1160,9 @@ TEST(MessageEntities, fix_formatted_text) { if (keep_url && ((1 << static_cast(entity.type)) & splittable_mask) == 0 && !(end <= url_offset || url_end <= offset)) { - keep_url = (entity.type == td::MessageEntity::Type::BlockQuote && offset <= url_offset && url_end <= end); + keep_url = ((entity.type == td::MessageEntity::Type::BlockQuote || + entity.type == td::MessageEntity::Type::ExpandableBlockQuote) && + offset <= url_offset && url_end <= end); } } ASSERT_EQ(keep_url, std::count(entities.begin(), entities.end(), url_entity) == 1); @@ -1183,7 +1185,8 @@ TEST(MessageEntities, fix_formatted_text) { // pre can't contain other entities ASSERT_TRUE((type_mask & pre_mask) == 0); - if ((type_mask & splittable_mask) == 0 && entities[i].type != td::MessageEntity::Type::BlockQuote) { + if ((type_mask & splittable_mask) == 0 && entities[i].type != td::MessageEntity::Type::BlockQuote && + entities[i].type != td::MessageEntity::Type::ExpandableBlockQuote) { // continuous entities can contain only splittable entities ASSERT_TRUE(((1 << static_cast(entities[j].type)) & splittable_mask) != 0); } @@ -1243,8 +1246,7 @@ TEST(MessageEntities, parse_html) { check_parse_html("🏟 🏟<", "Unsupported start tag \"abac\" at byte offset 13"); check_parse_html("🏟 🏟<", "Unsupported start tag \"abac\" at byte offset 13"); check_parse_html("🏟 🏟<", "Empty attribute name in the tag \"i\" at byte offset 13"); - check_parse_html("🏟 🏟<", - "Expected equal sign in declaration of an attribute of the tag \"i\" at byte offset 13"); + check_parse_html("🏟 🏟<", "Can't find end tag corresponding to start tag \"i\""); check_parse_html("🏟 🏟<", "Unclosed start tag at byte offset 13"); @@ -1352,8 +1354,14 @@ TEST(MessageEntities, parse_html) { check_parse_html("🏟 🏟🏟1", "🏟 🏟🏟1", {{td::MessageEntity::Type::Bold, 5, 3}, {td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast(1))}}); - check_parse_html("
a<
b;", "aa<
b;", "aa<
b;", "aa<
b;", "aa<
b;", "a*b\n>ld \n>bo\nld*\nasd\ndef", "Can't find end of Bold entity at byte offset 1");
   check_parse_markdown(">\n*a*>2", "Character '>' is reserved and must be escaped with the preceding '\\'");
+  check_parse_markdown(">asd\n>q||e||w||\n||asdad", "Can't find end of Spoiler entity at byte offset 16");
+  check_parse_markdown(">asd\n>q||ew\n||asdad", "Can't find end of Spoiler entity at byte offset 7");
+  check_parse_markdown(">asd\n>q||e||w__\n||asdad", "Can't find end of Underline entity at byte offset 13");
+  check_parse_markdown(">asd\n>q||e||w||a\n||asdad", "Can't find end of Spoiler entity at byte offset 13");
 
   check_parse_markdown("", "", {});
   check_parse_markdown("\\\\", "\\", {});
@@ -1520,6 +1532,7 @@ TEST(MessageEntities, parse_markdown) {
                        {{td::MessageEntity::Type::BlockQuote, 0, 1}, {td::MessageEntity::Type::BlockQuote, 2, 1}});
   check_parse_markdown(">\n**>2", "\n2",
                        {{td::MessageEntity::Type::BlockQuote, 0, 1}, {td::MessageEntity::Type::BlockQuote, 1, 1}});
+  check_parse_markdown(">**\n>2", "\n2", {{td::MessageEntity::Type::BlockQuote, 0, 2}});
   // check_parse_markdown("*>abcd*", "abcd",
   //                      {{td::MessageEntity::Type::BlockQuote, 0, 4}, {td::MessageEntity::Type::Bold, 0, 4}});
   check_parse_markdown(">*abcd*", "abcd",
@@ -1532,6 +1545,27 @@ TEST(MessageEntities, parse_markdown) {
                        {{td::MessageEntity::Type::BlockQuote, 0, 5}, {td::MessageEntity::Type::Bold, 0, 5}});
   check_parse_markdown("abc\n>def\n>def\n\r>ghi2\njkl", "abc\ndef\ndef\n\rghi2\njkl",
                        {{td::MessageEntity::Type::BlockQuote, 4, 8}, {td::MessageEntity::Type::BlockQuote, 13, 5}});
+  check_parse_markdown(
+      ">asd\n>q||e||w||\nasdad", "asd\nqew\nasdad",
+      {{td::MessageEntity::Type::ExpandableBlockQuote, 0, 8}, {td::MessageEntity::Type::Spoiler, 5, 1}});
+  check_parse_markdown(">asd\n>q||ew||\nasdad", "asd\nqew\nasdad",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 8}, {td::MessageEntity::Type::Spoiler, 5, 2}});
+  check_parse_markdown(
+      ">asd\r\n>q||e||w||\r\nasdad", "asd\r\nqew\r\nasdad",
+      {{td::MessageEntity::Type::ExpandableBlockQuote, 0, 10}, {td::MessageEntity::Type::Spoiler, 6, 1}});
+  check_parse_markdown(">asd\r\n>q||ew||\r\nasdad", "asd\r\nqew\r\nasdad",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 10}, {td::MessageEntity::Type::Spoiler, 6, 2}});
+  check_parse_markdown(
+      ">asd\r\n>q||e||w||\r\n", "asd\r\nqew\r\n",
+      {{td::MessageEntity::Type::ExpandableBlockQuote, 0, 10}, {td::MessageEntity::Type::Spoiler, 6, 1}});
+  check_parse_markdown(">asd\r\n>q||ew||\r\n", "asd\r\nqew\r\n",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 10}, {td::MessageEntity::Type::Spoiler, 6, 2}});
+  check_parse_markdown(
+      ">asd\r\n>q||e||w||", "asd\r\nqew",
+      {{td::MessageEntity::Type::ExpandableBlockQuote, 0, 8}, {td::MessageEntity::Type::Spoiler, 6, 1}});
+  check_parse_markdown(">asd\r\n>q||ew||", "asd\r\nqew",
+                       {{td::MessageEntity::Type::BlockQuote, 0, 8}, {td::MessageEntity::Type::Spoiler, 6, 2}});
+  check_parse_markdown(">||", "", {});
 }
 
 static void check_parse_markdown_v3(td::string text, td::vector entities,
diff --git a/lib/tgchat/ext/td/test/mtproto.cpp b/lib/tgchat/ext/td/test/mtproto.cpp
index 29305018..39087599 100644
--- a/lib/tgchat/ext/td/test/mtproto.cpp
+++ b/lib/tgchat/ext/td/test/mtproto.cpp
@@ -34,6 +34,7 @@
 #include "td/utils/BufferedFd.h"
 #include "td/utils/common.h"
 #include "td/utils/crypto.h"
+#include "td/utils/HttpDate.h"
 #include "td/utils/logging.h"
 #include "td/utils/port/Clocks.h"
 #include "td/utils/port/IPAddress.h"
diff --git a/lib/tgchat/ext/td/test/online.cpp b/lib/tgchat/ext/td/test/online.cpp
index 65114c6d..db5faa87 100644
--- a/lib/tgchat/ext/td/test/online.cpp
+++ b/lib/tgchat/ext/td/test/online.cpp
@@ -224,7 +224,9 @@ class InitTask : public Task {
 
   void start_up() override {
     send_query(td::make_tl_object("version"),
-               [](auto res) { LOG(INFO) << td::td_api::to_string(res.ok()); });
+               [](td::Result> res) {
+                 LOG(INFO) << td::td_api::to_string(res.ok());
+               });
   }
   void process_authorization_state(td::tl_object_ptr authorization_state) {
     td::tl_object_ptr function;
@@ -257,7 +259,7 @@ class InitTask : public Task {
   }
   template 
   void send(T &&query) {
-    send_query(std::move(query), [this](auto res) {
+    send_query(std::move(query), [this](td::Result res) {
       if (is_alive()) {
         res.ensure();
       }
@@ -283,7 +285,9 @@ class GetMe : public Task {
   explicit GetMe(Promise promise) : promise_(std::move(promise)) {
   }
   void start_up() override {
-    send_query(td::make_tl_object(), [this](auto res) { with_user_id(res.move_as_ok()->id_); });
+    send_query(
+        td::make_tl_object(),
+        [this](td::Result> res) { with_user_id(res.move_as_ok()->id_); });
   }
 
  private:
@@ -292,8 +296,9 @@ class GetMe : public Task {
 
   void with_user_id(int64 user_id) {
     result_.user_id = user_id;
-    send_query(td::make_tl_object(user_id, false),
-               [this](auto res) { with_chat_id(res.move_as_ok()->id_); });
+    send_query(
+        td::make_tl_object(user_id, false),
+        [this](td::Result> res) { with_chat_id(res.move_as_ok()->id_); });
   }
 
   void with_chat_id(int64 chat_id) {
@@ -336,7 +341,7 @@ class UploadFile : public Task {
                    td::make_tl_object(
                        td::make_tl_object(content_path_), nullptr, true,
                        td::make_tl_object("tag", td::Auto()))),
-               [this](auto res) { with_message(res.move_as_ok()); });
+               [this](td::Result> res) { with_message(res.move_as_ok()); });
   }
 
  private:
@@ -392,7 +397,7 @@ class TestDownloadFile : public Task {
   }
   void start_up() override {
     send_query(td::make_tl_object(remote_id_, nullptr),
-               [this](auto res) { start_file(*res.ok()); });
+               [this](td::Result> res) { start_file(*res.ok()); });
   }
 
  private:
@@ -454,7 +459,7 @@ class TestDownloadFile : public Task {
     send_query(td::make_tl_object(
                    file_id_, 1, static_cast(ranges_.back().begin),
                    static_cast(ranges_.back().end - ranges_.back().begin), true),
-               [this](auto res) { on_get_chunk(*res.ok()); });
+               [this](td::Result> res) { on_get_chunk(*res.ok()); });
   }
 };
 
diff --git a/lib/tgchat/ext/td/test/secret.cpp b/lib/tgchat/ext/td/test/secret.cpp
index be3274d0..15802756 100644
--- a/lib/tgchat/ext/td/test/secret.cpp
+++ b/lib/tgchat/ext/td/test/secret.cpp
@@ -530,7 +530,7 @@ class FakeSecretChatContext final : public SecretChatActor::Context {
     return false;
   }
 
-  // We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager.
+  // We don't want to expose the whole NetQueryDispatcher, MessagesManager and UserManager.
   // So it is more clear which parts of MessagesManager is really used. And it is much easier to create tests.
   void send_net_query(NetQueryPtr query, ActorShared callback, bool ordered) final;
 
diff --git a/lib/tgchat/src/tgchat.cpp b/lib/tgchat/src/tgchat.cpp
index 66020478..8417bb89 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 = 20240308;
+static const int s_TdlibDate = 20240528;
 
 namespace detail
 {
@@ -173,6 +173,7 @@ class TgChat::Impl
                        Reactions& p_Reactions);
   void GetReactionsEmojis(td::td_api::object_ptr& p_AvailableReactions,
                           std::set& p_Emojis);
+  int64_t GetDummyUserId(const std::string& p_Name);
 
 private:
   std::thread m_ServiceThread;
@@ -2831,76 +2832,35 @@ void TgChat::Impl::GetSponsoredMessages(const std::string& p_ChatId)
       chatMessage.timeSent = std::numeric_limits::max();
       chatMessage.isOutgoing = false;
 
-      td::td_api::object_ptr link;
-      std::string url;
-
-      if (sponsoredMessage->sponsor_->type_->get_id() == td::td_api::messageSponsorTypeBot::ID)
-      {
-        auto& messageSponsorTypeBot = static_cast(*sponsoredMessage->sponsor_->type_);
-        chatMessage.senderId = StrUtil::NumToHex(messageSponsorTypeBot.bot_user_id_);
-        link = td::move_tl_object_as(messageSponsorTypeBot.link_);
-      }
-      else if (sponsoredMessage->sponsor_->type_->get_id() == td::td_api::messageSponsorTypePublicChannel::ID)
-      {
-        auto& messageSponsorTypePublicChannel =
-          static_cast(*sponsoredMessage->sponsor_->type_);
-        chatMessage.senderId = StrUtil::NumToHex(messageSponsorTypePublicChannel.chat_id_);
-        link = td::move_tl_object_as(messageSponsorTypePublicChannel.link_);
-      }
-      else if (sponsoredMessage->sponsor_->type_->get_id() == td::td_api::messageSponsorTypePrivateChannel::ID)
-      {
-        auto& messageSponsorTypePrivateChannel =
-          static_cast(*sponsoredMessage->sponsor_->type_);
-        // @todo: chatMessage.senderId = StrUtil::NumToHex(messageSponsorTypePrivateChannel.bot_user_id_);
-        url = messageSponsorTypePrivateChannel.invite_link_;
-      }
-      else if (sponsoredMessage->sponsor_->type_->get_id() == td::td_api::messageSponsorTypeWebsite::ID)
-      {
-        auto& messageSponsorTypeWebsite =
-          static_cast(*sponsoredMessage->sponsor_->type_);
-        // @todo: chatMessage.senderId = StrUtil::NumToHex(messageSponsorTypeWebsite.bot_user_id_);
-        url = messageSponsorTypeWebsite.url_;
-      }
-
-      if (link)
-      {
-        if (link->get_id() == td::td_api::internalLinkTypeMessage::ID)
-        {
-          auto internalLink = td::move_tl_object_as(link);
-          url = internalLink->url_;
-        }
-        else if (link->get_id() == td::td_api::internalLinkTypeBotStart::ID)
-        {
-          auto internalLink = td::move_tl_object_as(link);
-          url = "https://t.me/" + internalLink->bot_username_ + "?start=" + internalLink->start_parameter_;
-        }
-        else
-        {
-          LOG_WARNING("unknown internal link type: %lld", link->get_id());
-        }
-      }
-      else
-      {
-        // @todo review if still needed: url = "https://t.me/c/" + std::to_string(sponsoredMessage->sponsor_chat_id_).substr(4);
-      }
+      const std::string contactName = sponsoredMessage->title_;
+      const int64_t contactId = GetDummyUserId(contactName);
+      chatMessage.senderId = StrUtil::NumToHex(contactId);
 
+      td::td_api::object_ptr link;
+      std::string url = sponsoredMessage->sponsor_->url_;
       if (!url.empty())
       {
-        chatMessage.text += "\n[" + url + "]";
+        chatMessage.text += "\n" + url + "";
       }
 
-      chatMessage.link = chatMessage.senderId;
       chatMessages.push_back(chatMessage);
       m_SponsoredMessageIds[p_ChatId].insert(chatMessage.id);
       LOG_DEBUG("new sponsored message %s (%lld)", chatMessage.id.c_str(), sponsoredMessageId);
 
-      // request chat type for senders
-      const std::vector chatIds = { chatMessage.senderId };
-      std::shared_ptr deferGetChatDetailsRequest =
-        std::make_shared();
-      deferGetChatDetailsRequest->isGetTypeOnly = true;
-      deferGetChatDetailsRequest->chatIds = chatIds;
-      SendRequest(deferGetChatDetailsRequest);
+      // provide info for sender
+      ContactInfo contactInfo;
+      contactInfo.id = StrUtil::NumToHex(contactId);
+      contactInfo.name = contactName;
+      contactInfo.isSelf = IsSelf(contactId);
+      m_ContactInfos[contactId] = contactInfo;
+
+      std::vector contactInfos;
+      contactInfos.push_back(contactInfo);
+
+      std::shared_ptr newContactsNotify =
+        std::make_shared(m_ProfileId);
+      newContactsNotify->contactInfos = contactInfos;
+      CallMessageHandler(newContactsNotify);
     }
 
     std::shared_ptr newMessagesNotify = std::make_shared(m_ProfileId);
@@ -3176,3 +3136,10 @@ void TgChat::Impl::GetReactionsEmojis(td::td_api::object_ptrrecent_reactions_, p_Emojis);
   ReactionsArrayToEmojiSet(p_AvailableReactions->popular_reactions_, p_Emojis);
 }
+
+int64_t TgChat::Impl::GetDummyUserId(const std::string& p_Name)
+{
+  uint64_t id = static_cast(std::hash{ }(p_Name)) & 0x00ffffffffffffff;
+  id |= (1ll << 60); // tdlib userid use int53, set bit to ensure no clash
+  return id;
+}
diff --git a/src/nchat.1 b/src/nchat.1
index 1cac12ae..37338c02 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" "May 2024" "nchat v4.85" "User Commands"
+.TH NCHAT "1" "June 2024" "nchat v4.86" "User Commands"
 .SH NAME
 nchat \- ncurses chat
 .SH SYNOPSIS