diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fadd8700..2fdd1a519 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,6 +286,8 @@ set(TDLIB_SOURCE td/telegram/BackgroundType.cpp td/telegram/BotCommand.cpp td/telegram/BotCommandScope.cpp + td/telegram/BotMenuButton.cpp + td/telegram/BotMenuButton.h td/telegram/CallActor.cpp td/telegram/CallDiscardReason.cpp td/telegram/CallManager.cpp diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 49696deaf..6e9889ad2 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -385,6 +385,9 @@ botCommand command:string description:string = BotCommand; //@description Contains a list of bot commands @bot_user_id Bot's user identifier @commands List of bot commands botCommands bot_user_id:int53 commands:vector = BotCommands; +//@description Describes a button to be shown instead of bot commands menu button @text Text of the button @url URL to be passed to openWebApp +botMenuButton text:string url:string = BotMenuButton; + //@description Represents a location to which a chat is connected @location The location @address Location address; 1-64 characters, as defined by the chat owner chatLocation location:location address:string = ChatLocation; @@ -472,10 +475,11 @@ user id:int53 first_name:string last_name:string username:string phone_number:st //@description Contains information about a bot //@share_text The text that is shown on the bot's profile page and is sent together with the link when users share the bot //@param_description The text shown in the chat with the bot if the chat is empty +//@menu_button Information about a button to show instead of the bot commands menu button; may be null if ordinary bot commands menu must be shown //@commands List of the bot commands //@default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null //@default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null -botInfo share_text:string description:string commands:vector default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo; +botInfo share_text:string description:string menu_button:botMenuButton commands:vector default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo; //@description Contains full information about a user //@photo User profile photo; may be null @@ -4885,11 +4889,11 @@ getWebAppUrl bot_user_id:int53 url:string theme:themeParameters = HttpUrl; //@bot_user_id Identifier of the target bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the web app @data Received data sendWebAppData bot_user_id:int53 button_text:string data:string = Ok; -//@description Informs TDLib that a web app is being opened from attach menu, bot menu, an internalLinkTypeAttachMenuBot link, or an inlineKeyboardButtonTypeWebApp button. +//@description Informs TDLib that a web app is being opened from attach menu, a botMenuButton button, an internalLinkTypeAttachMenuBot link, or an inlineKeyboardButtonTypeWebApp button. //-For each bot, a confirmation alert about data sent to the bot must be shown once //@chat_id Identifier of the chat in which the web app is opened. Web apps can be opened only in private chats for now //@bot_user_id Identifier of the bot, providing the web app -//@url The URL from the inlineKeyboardButtonTypeWebApp button or the internalLinkTypeAttachMenuBot link, or an empty string otherwise +//@url The URL from an inlineKeyboardButtonTypeWebApp button, a botMenuButton button, or an internalLinkTypeAttachMenuBot link, or an empty string otherwise //@from_bot_menu Pass true if the web app is opened from bot menu //@theme Preferred web app theme; pass null to use the default theme //@reply_to_message_id Identifier of the replied message for the message sent by the web app; 0 if none diff --git a/td/telegram/BotMenuButton.cpp b/td/telegram/BotMenuButton.cpp new file mode 100644 index 000000000..ccc3413cb --- /dev/null +++ b/td/telegram/BotMenuButton.cpp @@ -0,0 +1,48 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/telegram/BotMenuButton.h" + +namespace td { + +unique_ptr BotMenuButton::get_bot_menu_button( + telegram_api::object_ptr &&bot_menu_button) { + CHECK(bot_menu_button != nullptr); + switch (bot_menu_button->get_id()) { + case telegram_api::botMenuButtonCommands::ID: + return nullptr; + case telegram_api::botMenuButtonDefault::ID: + return td::make_unique(string(), "default"); + case telegram_api::botMenuButton::ID: { + auto button = telegram_api::move_object_as(bot_menu_button); + if (button->text_.empty()) { + LOG(ERROR) << "Receive bot menu button with empty text: " << to_string(button); + return nullptr; + } + return td::make_unique(std::move(button->text_), std::move(button->url_)); + } + default: + UNREACHABLE(); + return nullptr; + } +} + +td_api::object_ptr BotMenuButton::get_bot_menu_button_object() const { + return td_api::make_object(text_, url_); +} + +bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs) { + return lhs.text_ == rhs.text_ && lhs.url_ == rhs.url_; +} + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs) { + if (lhs == nullptr) { + return rhs == nullptr; + } + return rhs != nullptr && *lhs == *rhs; +} + +} // namespace td diff --git a/td/telegram/BotMenuButton.h b/td/telegram/BotMenuButton.h new file mode 100644 index 000000000..cd1f19e9f --- /dev/null +++ b/td/telegram/BotMenuButton.h @@ -0,0 +1,77 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/telegram/td_api.h" +#include "td/telegram/telegram_api.h" + +#include "td/utils/common.h" +#include "td/utils/tl_helpers.h" + +namespace td { + +class Td; + +class BotMenuButton { + string text_; + string url_; + + friend bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs); + + public: + BotMenuButton() = default; + + BotMenuButton(string &&text, string &&url) : text_(std::move(text)), url_(std::move(url)) { + } + + static unique_ptr get_bot_menu_button( + telegram_api::object_ptr &&bot_menu_button); + + td_api::object_ptr get_bot_menu_button_object() const; + + template + void store(StorerT &storer) const { + bool has_text = !text_.empty(); + bool has_url = !url_.empty(); + BEGIN_STORE_FLAGS(); + STORE_FLAG(has_text); + STORE_FLAG(has_url); + END_STORE_FLAGS(); + if (has_text) { + td::store(text_, storer); + } + if (has_url) { + td::store(url_, storer); + } + } + + template + void parse(ParserT &parser) { + bool has_text; + bool has_url; + BEGIN_PARSE_FLAGS(); + PARSE_FLAG(has_text); + PARSE_FLAG(has_url); + END_PARSE_FLAGS(); + if (has_text) { + td::parse(text_, parser); + } + if (has_url) { + td::parse(url_, parser); + } + } +}; + +bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs); + +bool operator==(const unique_ptr &lhs, const unique_ptr &rhs); + +inline bool operator!=(const unique_ptr &lhs, const unique_ptr &rhs) { + return !(lhs == rhs); +} + +} // namespace td diff --git a/td/telegram/ContactsManager.cpp b/td/telegram/ContactsManager.cpp index 9e4edba41..4488964f3 100644 --- a/td/telegram/ContactsManager.cpp +++ b/td/telegram/ContactsManager.cpp @@ -7,6 +7,7 @@ #include "td/telegram/ContactsManager.h" #include "td/telegram/AuthManager.h" +#include "td/telegram/BotMenuButton.h" #include "td/telegram/ChannelParticipantFilter.h" #include "td/telegram/ConfigShared.h" #include "td/telegram/Dependencies.h" @@ -3700,6 +3701,7 @@ void ContactsManager::UserFull::store(StorerT &storer) const { 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; BEGIN_STORE_FLAGS(); STORE_FLAG(has_about); STORE_FLAG(is_blocked); @@ -3714,6 +3716,7 @@ void ContactsManager::UserFull::store(StorerT &storer) const { STORE_FLAG(has_private_forward_name); STORE_FLAG(has_group_administrator_rights); STORE_FLAG(has_broadcast_administrator_rights); + STORE_FLAG(has_menu_button); END_STORE_FLAGS(); if (has_about) { store(about, storer); @@ -3738,6 +3741,9 @@ void ContactsManager::UserFull::store(StorerT &storer) const { if (has_broadcast_administrator_rights) { store(broadcast_administrator_rights, storer); } + if (has_menu_button) { + store(menu_button, storer); + } } template @@ -3750,6 +3756,7 @@ void ContactsManager::UserFull::parse(ParserT &parser) { bool has_private_forward_name; bool has_group_administrator_rights; bool has_broadcast_administrator_rights; + bool has_menu_button; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_about); PARSE_FLAG(is_blocked); @@ -3764,6 +3771,7 @@ void ContactsManager::UserFull::parse(ParserT &parser) { PARSE_FLAG(has_private_forward_name); PARSE_FLAG(has_group_administrator_rights); PARSE_FLAG(has_broadcast_administrator_rights); + PARSE_FLAG(has_menu_button); END_PARSE_FLAGS(); if (has_about) { parse(about, parser); @@ -3788,6 +3796,9 @@ void ContactsManager::UserFull::parse(ParserT &parser) { if (has_broadcast_administrator_rights) { parse(broadcast_administrator_rights, parser); } + if (has_menu_button) { + parse(menu_button, parser); + } } template @@ -6180,6 +6191,26 @@ void ContactsManager::on_update_bot_commands(DialogId dialog_id, UserId bot_user } } +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(bot_user_id) || !is_user_bot(bot_user_id)) { + return; + } + if (td_->auth_manager_->is_bot()) { + return; + } + + auto user_full = get_user_full(bot_user_id); + 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()) { @@ -10549,6 +10580,7 @@ void ContactsManager::on_get_user_full(tl_object_ptr &&u description = std::move(user->bot_info_->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); @@ -11531,6 +11563,17 @@ void ContactsManager::on_update_user_full_commands(UserFull *user_full, UserId u } } +void ContactsManager::on_update_user_full_menu_button(UserFull *user_full, UserId user_id, + tl_object_ptr &&bot_menu_button) { + CHECK(user_full != nullptr); + CHECK(bot_menu_button != nullptr); + auto new_button = BotMenuButton::get_bot_menu_button(std::move(bot_menu_button)); + if (user_full->menu_button != new_button) { + 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 " @@ -11781,6 +11824,7 @@ void ContactsManager::drop_user_full(UserId user_id) { user_full->need_phone_number_privacy_exception = false; user_full->about = string(); user_full->description = string(); + user_full->menu_button = nullptr; user_full->commands.clear(); user_full->common_chat_count = 0; user_full->private_forward_name.clear(); @@ -16311,10 +16355,14 @@ tl_object_ptr ContactsManager::get_user_full_info_object(U td_api::object_ptr bot_info; bool is_bot = is_user_bot(user_id); if (is_bot) { + td_api::object_ptr menu_button; + if (user_full->menu_button != nullptr) { + menu_button = user_full->menu_button->get_bot_menu_button_object(); + } 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, std::move(commands), + user_full->about, user_full->description, std::move(menu_button), std::move(commands), user_full->group_administrator_rights == AdministratorRights() ? nullptr : user_full->group_administrator_rights.get_chat_administrator_rights_object(), diff --git a/td/telegram/ContactsManager.h b/td/telegram/ContactsManager.h index f18a75b31..8aef32e28 100644 --- a/td/telegram/ContactsManager.h +++ b/td/telegram/ContactsManager.h @@ -57,6 +57,8 @@ namespace td { struct BinlogEvent; +class BotMenuButton; + class ChannelParticipantFilter; struct MinChannel; @@ -219,6 +221,8 @@ class ContactsManager final : public Actor { 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 on_update_dialog_administrators(DialogId dialog_id, vector &&administrators, bool have_access, bool from_database); @@ -701,6 +705,7 @@ class ContactsManager final : public Actor { string description; string private_forward_name; + unique_ptr menu_button; vector commands; AdministratorRights group_administrator_rights; AdministratorRights broadcast_administrator_rights; @@ -1235,6 +1240,8 @@ class ContactsManager final : public Actor { static void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count); static void on_update_user_full_commands(UserFull *user_full, UserId user_id, vector> &&bot_commands); + 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; diff --git a/td/telegram/ReplyMarkup.hpp b/td/telegram/ReplyMarkup.hpp index be30608c1..657505217 100644 --- a/td/telegram/ReplyMarkup.hpp +++ b/td/telegram/ReplyMarkup.hpp @@ -16,7 +16,6 @@ namespace td { template void store(KeyboardButton button, StorerT &storer) { bool has_url = !button.url.empty(); - ; BEGIN_STORE_FLAGS(); STORE_FLAG(has_url); END_STORE_FLAGS(); diff --git a/td/telegram/UpdatesManager.cpp b/td/telegram/UpdatesManager.cpp index f2a7d8cdb..8022577a7 100644 --- a/td/telegram/UpdatesManager.cpp +++ b/td/telegram/UpdatesManager.cpp @@ -2955,6 +2955,7 @@ void UpdatesManager::on_update(tl_object_ptr up } 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_)); promise.set_value(Unit()); }