Implement new business bots permissions.

This commit is contained in:
John Preston 2025-03-18 16:46:24 +04:00
parent ab2a947992
commit a2a16f3f23
10 changed files with 197 additions and 27 deletions

View file

@ -2942,8 +2942,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_chatbots_include_button" = "Select Chats";
"lng_chatbots_exclude_about" = "Select chats or entire chat categories which the bot will not have access to.";
"lng_chatbots_permissions_title" = "Bot permissions";
"lng_chatbots_manage_messages" = "Manage Messages";
"lng_chatbots_read" = "Read Messages";
"lng_chatbots_reply" = "Reply to Messages";
"lng_chatbots_reply_about" = "The bot can only reply on your behalf in chats that were active during the last 24h.";
"lng_chatbots_mark_as_read" = "Mark Messages as Read";
"lng_chatbots_delete_sent" = "Delete Sent Messages";
"lng_chatbots_delete_received" = "Delete Received Messages";
"lng_chatbots_manage_profile" = "Manage Profile";
"lng_chatbots_edit_name" = "Edit Name";
"lng_chatbots_edit_bio" = "Edit Bio";
"lng_chatbots_edit_userpic" = "Edit Profile Picture";
"lng_chatbots_edit_username" = "Edit Username";
"lng_chatbots_manage_gifts" = "Manage Gifts and Stars";
"lng_chatbots_view_gifts" = "View Gifts";
"lng_chatbots_sell_gifts" = "Sell Gifts";
"lng_chatbots_gift_settings" = "Change Gift Settings";
"lng_chatbots_transfer_gifts" = "Transfer and Upgrade Gifts";
"lng_chatbots_transfer_stars" = "Transfer Stars";
"lng_chatbots_manage_stories" = "Manage Stories";
"lng_chatbots_remove" = "Remove Bot";
"lng_chatbots_not_found" = "Chatbot not found.";
"lng_chatbots_not_supported" = "This bot doesn't support Telegram Business yet.";

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/admin_log/history_admin_log_filter.h"
#include "core/ui_integration.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/business/data_business_chatbots.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_session.h"
@ -63,6 +64,11 @@ constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
return std::vector<std::pair<Flag, Flag>>{};
}
[[nodiscard]] auto Dependencies(Data::ChatbotsPermissions) {
using Flag = Data::ChatbotsPermission;
return std::vector<std::pair<Flag, Flag>>{};
}
[[nodiscard]] auto NestedRestrictionLabelsList(
Data::RestrictionsSetOptions options)
-> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
@ -678,7 +684,7 @@ template <typename Flags>
checkView->setChecked(false, anim::type::instant);
} else if (locked.has_value()) {
if (checked != toggled) {
if (!state->toast) {
if (!state->toast && !locked->isEmpty()) {
state->toast = Ui::Toast::Show(container, {
.text = { *locked },
.duration = kForceDisableTooltipDuration,
@ -1488,3 +1494,20 @@ EditFlagsControl<AdminLog::FilterValue::Flags> CreateEditAdminLogFilter(
return result;
}
EditFlagsControl<Data::ChatbotsPermissions> CreateEditChatbotPermissions(
QWidget *parent,
Data::ChatbotsPermissions flags) {
auto widget = object_ptr<Ui::VerticalLayout>(parent);
auto descriptor = Data::ChatbotsPermissionsLabels();
descriptor.disabledMessages.emplace(
Data::ChatbotsPermission::ViewMessages,
QString());
auto result = CreateEditFlags(
widget.data(),
flags | Data::ChatbotsPermission::ViewMessages,
std::move(descriptor));
result.widget = std::move(widget);
return result;
}

View file

@ -27,6 +27,11 @@ enum Flag : uint32;
using Flags = base::flags<Flag>;
} // namespace PowerSaving
namespace Data {
enum class ChatbotsPermission;
using ChatbotsPermissions = base::flags<ChatbotsPermission>;
} // namespace Data
template <typename Object>
class object_ptr;
@ -120,3 +125,8 @@ using AdminRightLabel = EditFlagsLabel<ChatAdminRights>;
AdminLog::FilterValue::Flags flags,
bool isChannel
) -> EditFlagsControl<AdminLog::FilterValue::Flags>;
[[nodiscard]] auto CreateEditChatbotPermissions(
QWidget *parent,
Data::ChatbotsPermissions flags
) -> EditFlagsControl<Data::ChatbotsPermissions>;

View file

@ -8,10 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/business/data_business_chatbots.h"
#include "apiwrap.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "data/business/data_business_common.h"
#include "data/business/data_business_info.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
namespace Data {
@ -41,7 +43,7 @@ void Chatbots::preload() {
_settings = ChatbotsSettings{
.bot = _owner->session().data().user(botId),
.recipients = FromMTP(_owner, bot.vrecipients()),
.repliesAllowed = bot.vrights().data().is_reply(),
.permissions = FromMTP(bot.vrights()),
};
} else {
_settings.force_assign(ChatbotsSettings());
@ -83,9 +85,7 @@ void Chatbots::save(
const auto api = &_owner->session().api();
api->request(MTPaccount_UpdateConnectedBot(
MTP_flags(!settings.bot ? Flag::f_deleted : Flag::f_rights),
MTP_businessBotRights(MTP_flags(settings.repliesAllowed
? RightFlag::f_reply
: RightFlag())),
ToMTP(settings.permissions),
(settings.bot ? settings.bot : was.bot)->inputUser,
ForBotsToMTP(settings.recipients)
)).done([=](const MTPUpdates &result) {
@ -180,4 +180,39 @@ void Chatbots::reload() {
preload();
}
EditFlagsDescriptor<ChatbotsPermissions> ChatbotsPermissionsLabels() {
using Flag = ChatbotsPermission;
using PermissionLabel = EditFlagsLabel<ChatbotsPermissions>;
auto messages = std::vector<PermissionLabel>{
{ Flag::ViewMessages, tr::lng_chatbots_read(tr::now) },
{ Flag::ReplyToMessages, tr::lng_chatbots_reply(tr::now) },
{ Flag::MarkAsRead, tr::lng_chatbots_mark_as_read(tr::now) },
{ Flag::DeleteSent, tr::lng_chatbots_delete_sent(tr::now) },
{ Flag::DeleteReceived, tr::lng_chatbots_delete_received(tr::now) },
};
auto manage = std::vector<PermissionLabel>{
{ Flag::EditName, tr::lng_chatbots_edit_name(tr::now) },
{ Flag::EditBio, tr::lng_chatbots_edit_bio(tr::now) },
{ Flag::EditUserpic, tr::lng_chatbots_edit_userpic(tr::now) },
{ Flag::EditUsername, tr::lng_chatbots_edit_username(tr::now) },
};
auto gifts = std::vector<PermissionLabel>{
{ Flag::ViewGifts, tr::lng_chatbots_view_gifts(tr::now) },
{ Flag::SellGifts, tr::lng_chatbots_sell_gifts(tr::now) },
{ Flag::GiftSettings, tr::lng_chatbots_gift_settings(tr::now) },
{ Flag::TransferGifts, tr::lng_chatbots_transfer_gifts(tr::now) },
{ Flag::TransferStars, tr::lng_chatbots_transfer_stars(tr::now) },
};
auto stories = std::vector<PermissionLabel>{
{ Flag::ManageStories, tr::lng_chatbots_manage_stories(tr::now) },
};
return { .labels = {
{ tr::lng_chatbots_manage_messages(), std::move(messages) },
{ tr::lng_chatbots_manage_profile(), std::move(manage) },
{ tr::lng_chatbots_manage_gifts(), std::move(gifts) },
{ std::nullopt, std::move(stories) },
}, .st = nullptr };
}
} // namespace Data

View file

@ -11,6 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData;
template <typename Flags>
struct EditFlagsDescriptor;
namespace Data {
class Session;
@ -18,7 +21,7 @@ class Session;
struct ChatbotsSettings {
UserData *bot = nullptr;
BusinessRecipients recipients;
bool repliesAllowed = false;
ChatbotsPermissions permissions;
friend inline bool operator==(
const ChatbotsSettings &,
@ -67,4 +70,7 @@ private:
};
[[nodiscard]] auto ChatbotsPermissionsLabels()
-> EditFlagsDescriptor<ChatbotsPermissions>;
} // namespace Data

View file

@ -159,6 +159,47 @@ BusinessRecipients FromMTP(
return result;
}
ChatbotsPermissions FromMTP(const MTPBusinessBotRights &rights) {
using Flag = ChatbotsPermission;
const auto &data = rights.data();
return Flag::ViewMessages
| (data.is_reply() ? Flag::ReplyToMessages : Flag())
| (data.is_read_messages() ? Flag::MarkAsRead : Flag())
| (data.is_delete_sent_messages() ? Flag::DeleteSent : Flag())
| (data.is_delete_received_messages() ? Flag::DeleteReceived : Flag())
| (data.is_edit_name() ? Flag::EditName : Flag())
| (data.is_edit_bio() ? Flag::EditBio : Flag())
| (data.is_edit_profile_photo() ? Flag::EditUserpic : Flag())
| (data.is_edit_username() ? Flag::EditUsername : Flag())
| (data.is_view_gifts() ? Flag::ViewGifts : Flag())
| (data.is_sell_gifts() ? Flag::SellGifts : Flag())
| (data.is_change_gift_settings() ? Flag::GiftSettings : Flag())
| (data.is_transfer_and_upgrade_gifts() ? Flag::TransferGifts : Flag())
| (data.is_transfer_stars() ? Flag::TransferStars : Flag())
| (data.is_manage_stories() ? Flag::ManageStories : Flag());
}
MTPBusinessBotRights ToMTP(ChatbotsPermissions rights) {
using Flag = MTPDbusinessBotRights::Flag;
using Right = ChatbotsPermission;
return MTP_businessBotRights(MTP_flags(Flag()
| ((rights & Right::ReplyToMessages) ? Flag::f_reply : Flag())
| ((rights & Right::MarkAsRead) ? Flag::f_read_messages : Flag())
| ((rights & Right::DeleteSent) ? Flag::f_delete_sent_messages : Flag())
| ((rights & Right::DeleteReceived) ? Flag::f_delete_received_messages : Flag())
| ((rights & Right::EditName) ? Flag::f_edit_name : Flag())
| ((rights & Right::EditBio) ? Flag::f_edit_bio : Flag())
| ((rights & Right::EditUserpic) ? Flag::f_edit_profile_photo : Flag())
| ((rights & Right::EditUsername) ? Flag::f_edit_username : Flag())
| ((rights & Right::ViewGifts) ? Flag::f_view_gifts : Flag())
| ((rights & Right::SellGifts) ? Flag::f_sell_gifts : Flag())
| ((rights & Right::GiftSettings) ? Flag::f_change_gift_settings : Flag())
| ((rights & Right::TransferGifts) ? Flag::f_transfer_and_upgrade_gifts : Flag())
| ((rights & Right::TransferStars) ? Flag::f_transfer_stars : Flag())
| ((rights & Right::ManageStories) ? Flag::f_manage_stories : Flag())));
}
BusinessDetails FromMTP(
not_null<Session*> owner,
const tl::conditional<MTPBusinessWorkHours> &hours,

View file

@ -57,6 +57,26 @@ enum class BusinessRecipientsType : uchar {
Bots,
};
enum class ChatbotsPermission {
ViewMessages = 0x0001,
ReplyToMessages = 0x0002,
MarkAsRead = 0x0004,
DeleteSent = 0x0008,
DeleteReceived = 0x0010,
EditName = 0x0020,
EditBio = 0x0040,
EditUserpic = 0x0080,
EditUsername = 0x0100,
ViewGifts = 0x0200,
SellGifts = 0x0400,
GiftSettings = 0x0800,
TransferGifts = 0x1000,
TransferStars = 0x2000,
ManageStories = 0x4000,
};
inline constexpr bool is_flag_type(ChatbotsPermission) { return true; }
using ChatbotsPermissions = base::flags<ChatbotsPermission>;
[[nodiscard]] MTPInputBusinessRecipients ForMessagesToMTP(
const BusinessRecipients &data);
[[nodiscard]] MTPInputBusinessBotRecipients ForBotsToMTP(
@ -67,6 +87,9 @@ enum class BusinessRecipientsType : uchar {
[[nodiscard]] BusinessRecipients FromMTP(
not_null<Session*> owner,
const MTPBusinessBotRecipients &recipients);
[[nodiscard]] ChatbotsPermissions FromMTP(
const MTPBusinessBotRights &rights);
[[nodiscard]] MTPBusinessBotRights ToMTP(ChatbotsPermissions rights);
struct Timezone {
QString id;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/business/settings_chatbots.h"
#include "apiwrap.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/prepare_short_info_box.h"
#include "boxes/peer_list_box.h"
#include "core/application.h"
@ -47,6 +48,25 @@ struct BotState {
LookupState state = LookupState::Empty;
};
[[nodiscard]] constexpr Data::ChatbotsPermissions Defaults() {
using Flag = Data::ChatbotsPermission;
return Flag::ViewMessages
| Flag::ReplyToMessages
| Flag::MarkAsRead
| Flag::DeleteSent
| Flag::DeleteReceived
| Flag::EditName
| Flag::EditBio
| Flag::EditUserpic
| Flag::EditUsername
| Flag::ViewGifts
| Flag::SellGifts
| Flag::GiftSettings
| Flag::TransferGifts
| Flag::TransferStars
| Flag::ManageStories;
}
class Chatbots final : public BusinessSection<Chatbots> {
public:
Chatbots(
@ -70,7 +90,8 @@ private:
rpl::variable<Data::BusinessRecipients> _recipients;
rpl::variable<QString> _usernameValue;
rpl::variable<BotState> _botValue;
rpl::variable<bool> _repliesAllowed = true;
rpl::variable<Data::ChatbotsPermissions> _permissions = Defaults();
Fn<Data::ChatbotsPermissions()> _resolvePermissions;
};
@ -410,7 +431,7 @@ void Chatbots::setupContent(
const auto current = controller->session().data().chatbots().current();
_recipients = Data::BusinessRecipients::MakeValid(current.recipients);
_repliesAllowed = current.repliesAllowed;
_permissions = current.permissions;
AddDividerTextWithLottie(content, {
.lottie = u"robot"_q,
@ -472,21 +493,14 @@ void Chatbots::setupContent(
Ui::AddSkip(content);
Ui::AddSubsectionTitle(content, tr::lng_chatbots_permissions_title());
content->add(object_ptr<Ui::SettingsButton>(
content,
tr::lng_chatbots_reply(),
st::settingsButtonNoIcon
))->toggleOn(_repliesAllowed.value())->toggledChanges(
) | rpl::start_with_next([=](bool value) {
_repliesAllowed = value;
}, content->lifetime());
Ui::AddSkip(content);
Ui::AddDividerText(
auto permissions = CreateEditChatbotPermissions(
content,
tr::lng_chatbots_reply_about(),
st::settingsChatbotsBottomTextMargin,
RectPart::Top);
_permissions.current());
content->add(std::move(permissions.widget));
_resolvePermissions = permissions.value;
Ui::AddSkip(content);
Ui::ResizeFitChild(this, content);
}
@ -503,7 +517,7 @@ void Chatbots::save() {
controller()->session().data().chatbots().save({
.bot = _botValue.current().bot,
.recipients = _recipients.current(),
.repliesAllowed = _repliesAllowed.current(),
.permissions = _resolvePermissions(),
}, [=] {
}, fail);
}

View file

@ -148,7 +148,6 @@ using Order = std::vector<QString>;
tr::lng_business_subtitle_chat_intro(),
tr::lng_business_about_chat_intro(),
PremiumFeature::ChatIntro,
true,
},
},
{
@ -158,7 +157,6 @@ using Order = std::vector<QString>;
tr::lng_business_subtitle_chat_links(),
tr::lng_business_about_chat_links(),
PremiumFeature::ChatLinks,
true,
},
},
{
@ -168,7 +166,6 @@ using Order = std::vector<QString>;
tr::lng_premium_summary_subtitle_filter_tags(),
tr::lng_premium_summary_about_filter_tags(),
PremiumFeature::FilterTags,
true,
},
},
};

View file

@ -1734,10 +1734,10 @@ object_ptr<Ui::RpWidget> GiftsAutoSavePrivacyController::setupBelowWidget(
content,
tr::lng_edit_privacy_gifts_types());
const auto types = base::flat_map<Type, rpl::producer<QString>>{
{ Type::Premium, tr::lng_edit_privacy_gifts_premium() },
{ Type::Unlimited, tr::lng_edit_privacy_gifts_unlimited() },
{ Type::Limited, tr::lng_edit_privacy_gifts_limited() },
{ Type::Unique, tr::lng_edit_privacy_gifts_unique() },
{ Type::Premium, tr::lng_edit_privacy_gifts_premium() },
};
for (const auto &[type, title] : types) {
const auto button = content->add(object_ptr<Ui::SettingsButton>(