Support bot emoji status access.

This commit is contained in:
John Preston 2024-11-08 21:56:54 +04:00
parent 4198203a7f
commit 3d77bff0c9
15 changed files with 179 additions and 5 deletions

Binary file not shown.

View file

@ -1461,6 +1461,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_open_app" = "Open App";
"lng_profile_open_app_about" = "By launching this mini app, you agree to the {terms}.";
"lng_profile_open_app_terms" = "Terms of Service for Mini Apps";
"lng_profile_bot_permissions_title" = "Allow access to";
"lng_profile_bot_emoji_status_access" = "Emoji Status";
"lng_info_add_as_contact" = "Add to contacts";
"lng_profile_shared_media" = "Shared media";
"lng_profile_suggest_photo" = "Suggest Profile Photo";
@ -3421,6 +3423,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bot_emoji_status_confirm" = "Confirm";
"lng_bot_emoji_status_title" = "Set Emoji Status";
"lng_bot_emoji_status_text" = "Do you want to set this emoji status suggested by {bot}?";
"lng_bot_emoji_status_access_text" = "{bot} requests access to set your **emoji status**. You will be able to revoke this access in the profile page of {name}.";
"lng_bot_emoji_status_access_allow" = "Allow";
"lng_bot_status_users#one" = "{count} monthly user";
"lng_bot_status_users#other" = "{count} monthly users";

View file

@ -28,6 +28,7 @@
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
<file alias="search.tgs">../../animations/search.tgs</file>
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
<file alias="hello_status.tgs">../../animations/hello_status.tgs</file>
<file alias="dice_idle.tgs">../../animations/dice/dice_idle.tgs</file>
<file alias="dart_idle.tgs">../../animations/dice/dart_idle.tgs</file>

View file

@ -745,6 +745,14 @@ bool DocumentData::emojiUsesTextColor() const {
return (_flags & Flag::UseTextColor);
}
void DocumentData::overrideEmojiUsesTextColor(bool value) {
if (value) {
_flags |= Flag::UseTextColor;
} else {
_flags &= ~Flag::UseTextColor;
}
}
bool DocumentData::hasThumbnail() const {
return _thumbnail.location.valid()
&& !thumbnailFailed()

View file

@ -206,6 +206,7 @@ public:
[[nodiscard]] bool isPremiumSticker() const;
[[nodiscard]] bool isPremiumEmoji() const;
[[nodiscard]] bool emojiUsesTextColor() const;
void overrideEmojiUsesTextColor(bool value);
[[nodiscard]] bool hasThumbnail() const;
[[nodiscard]] bool thumbnailLoading() const;

View file

@ -580,6 +580,9 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
} else {
user->setBotInfoVersion(-1);
}
if (const auto info = user->botInfo.get()) {
info->canManageEmojiStatus = update.is_bot_can_manage_emoji_status();
}
if (const auto pinned = update.vpinned_msg_id()) {
SetTopPinnedMessageId(user, pinned->v);
}

View file

@ -2435,6 +2435,14 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (const auto sticker = emojiStickers->stickerForEmoji(
isolated)) {
addDocumentActions(sticker.document, item);
} else if (v::is<QString>(isolated.items.front())
&& v::is_null(isolated.items[1])) {
const auto id = v::get<QString>(isolated.items.front());
const auto docId = id.toULongLong();
const auto document = session->data().document(docId);
if (document->sticker()) {
addDocumentActions(document, item);
}
}
}
}

View file

@ -470,6 +470,7 @@ infoIconMediaSaved: icon {{ "info/info_media_saved", infoIconFg }};
infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }};
infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }};
infoIconMediaGifts: icon {{ "menu/gift_premium", infoIconFg, point(4px, 4px) }};
infoIconEmojiStatusAccess: icon {{ "menu/read_reactions", infoIconFg, point(4px, 4px) }};
infoIconShare: icon {{ "info/info_share", infoIconFg }};
infoIconEdit: icon {{ "info/info_edit", infoIconFg }};

View file

@ -958,6 +958,7 @@ private:
object_ptr<Ui::RpWidget> setupInfo();
object_ptr<Ui::RpWidget> setupMuteToggle();
void setupMainApp();
void setupBotPermissions();
void setupMainButtons();
Ui::MultiSlideTracker fillTopicButtons();
Ui::MultiSlideTracker fillUserButtons(
@ -1865,6 +1866,37 @@ void DetailsFiller::setupMainApp() {
Ui::AddSkip(_wrap);
}
void DetailsFiller::setupBotPermissions() {
AddSkip(_wrap);
AddSubsectionTitle(_wrap, tr::lng_profile_bot_permissions_title());
const auto emoji = _wrap->add(
object_ptr<Ui::SettingsButton>(
_wrap,
tr::lng_profile_bot_emoji_status_access(),
st::infoSharedMediaButton));
object_ptr<Profile::FloatingIcon>(
emoji,
st::infoIconEmojiStatusAccess,
st::infoSharedMediaButtonIconPosition);
const auto user = _peer->asUser();
emoji->toggleOn(
rpl::single(bool(user->botInfo->canManageEmojiStatus))
)->toggledValue() | rpl::filter([=](bool allowed) {
return allowed != user->botInfo->canManageEmojiStatus;
}) | rpl::start_with_next([=](bool allowed) {
user->botInfo->canManageEmojiStatus = allowed;
const auto session = &user->session();
session->api().request(MTPbots_ToggleUserEmojiStatusPermission(
user->inputUser,
MTP_bool(allowed)
)).send();
}, emoji->lifetime());
AddSkip(_wrap);
AddDivider(_wrap);
AddSkip(_wrap);
}
void DetailsFiller::setupMainButtons() {
auto wrapButtons = [=](auto &&callback) {
auto topSkip = _wrap->add(CreateSlideSkipWidget(_wrap));
@ -2059,6 +2091,9 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
if (info->hasMainApp) {
setupMainApp();
}
if (info->canManageEmojiStatus) {
setupBotPermissions();
}
}
}
if (!_peer->isSelf()) {

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer_rpl.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/share_box.h"
#include "chat_helpers/stickers_lottie.h"
#include "chat_helpers/tabbed_panel.h"
#include "core/application.h"
#include "core/click_handler_types.h"
@ -447,11 +448,21 @@ std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
raw,
rpl::single(peer->name()),
st::botEmojiStatusName);
auto emojiText = TextWithEntities();
const auto makeContext = [=](Fn<void()> update) {
return Core::MarkedTextContext{
.session = &peer->session(),
.customEmojiRepaint = update,
};
};
const auto emoji = raw->lifetime().make_state<Ui::FlatLabel>(
raw,
rpl::single(emojiText),
st::botEmojiStatusName);
rpl::single(
Ui::Text::SingleCustomEmoji(
Data::SerializeCustomEmojiId(document->id),
document->sticker() ? document->sticker()->alt : QString())),
st::botEmojiStatusEmoji,
st::defaultPopupMenu,
makeContext);
const auto userpic = raw->lifetime().make_state<Ui::UserpicButton>(
raw,
peer,
@ -496,6 +507,59 @@ std::unique_ptr<Ui::RpWidget> MakeEmojiSetStatusPreview(
return result;
}
void ConfirmEmojiStatusAccessBox(
not_null<Ui::GenericBox*> box,
not_null<UserData*> bot,
Fn<void(bool)> done) {
box->setNoContentMargin(true);
const auto set = box->lifetime().make_state<bool>();
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
AddSkip(box->verticalLayout(), 4 * st::defaultVerticalListSkip);
const auto statusIcon = ChatHelpers::GenerateLocalTgsSticker(
&bot->session(),
u"hello_status"_q);
statusIcon->overrideEmojiUsesTextColor(true);
auto ownedSet = MakeEmojiSetStatusPreview(
box,
bot->session().user(),
statusIcon);
box->addRow(
object_ptr<Ui::RpWidget>::fromRaw(ownedSet.release()));
AddSkip(box->verticalLayout(), 2 * st::defaultVerticalListSkip);
auto name = Ui::Text::Bold(bot->name());
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_bot_emoji_status_access_text(
lt_bot,
rpl::single(name),
lt_name,
rpl::single(name),
Ui::Text::RichLangValue),
st::botEmojiStatusText));
box->addButton(tr::lng_bot_emoji_status_access_allow(), [=] {
*set = true;
box->closeBox();
done(true);
});
box->addButton(tr::lng_cancel(), [=] {
const auto was = *set;
box->closeBox();
if (!was) {
done(false);
}
});
}
void ConfirmEmojiStatusBox(
not_null<Ui::GenericBox*> box,
not_null<UserData*> bot,
@ -511,6 +575,10 @@ void ConfirmEmojiStatusBox(
const auto set = box->lifetime().make_state<bool>();
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
box->addRow(object_ptr<Ui::FlatLabel>(
box,
tr::lng_bot_emoji_status_title(),
@ -525,7 +593,7 @@ void ConfirmEmojiStatusBox(
Ui::Text::RichLangValue),
st::botEmojiStatusText));
AddSkip(box->verticalLayout());
AddSkip(box->verticalLayout(), 2 * st::defaultVerticalListSkip);
auto ownedSet = MakeEmojiSetStatusPreview(
box,
@ -1600,6 +1668,33 @@ void WebViewInstance::botAllowWriteAccess(Fn<void(bool allowed)> callback) {
}).send();
}
void WebViewInstance::botRequestEmojiStatusAccess(
Fn<void(bool allowed)> callback) {
if (_bot->botInfo->canManageEmojiStatus) {
callback(true);
} else if (const auto panel = _panel.get()) {
const auto bot = _bot;
panel->showBox(Box(ConfirmEmojiStatusAccessBox, bot, [=](bool ok) {
if (!ok) {
callback(false);
return;
}
const auto session = &bot->session();
bot->botInfo->canManageEmojiStatus = true;
session->api().request(MTPbots_ToggleUserEmojiStatusPermission(
bot->inputUser,
MTP_bool(true)
)).done([=] {
callback(true);
}).fail([=] {
callback(false);
}).send();
}));
} else {
callback(false);
}
}
void WebViewInstance::botSharePhone(Fn<void(bool shared)> callback) {
const auto history = _bot->owner().history(_bot);
if (_bot->isBlocked()) {

View file

@ -260,6 +260,8 @@ private:
QString query) override;
void botCheckWriteAccess(Fn<void(bool allowed)> callback) override;
void botAllowWriteAccess(Fn<void(bool allowed)> callback) override;
void botRequestEmojiStatusAccess(
Fn<void(bool allowed)> callback) override;
void botSharePhone(Fn<void(bool shared)> callback) override;
void botInvokeCustomMethod(
Ui::BotWebView::CustomMethodRequest request) override;

View file

@ -774,7 +774,7 @@ void TopBarUser::updateTitle(
{ EntityType::CustomEmoji, 0, 1, entityEmojiData },
Ui::Text::Link(text, linkIndex).entities.front(),
};
auto title = (setId == coloredId)
auto title = (setId != coloredId)
? tr::lng_premium_emoji_status_title_colored(
tr::now,
lt_user,

View file

@ -826,6 +826,8 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
processBottomBarColor(arguments);
} else if (command == "web_app_set_emoji_status") {
processEmojiStatusRequest(arguments);
} else if (command == "web_app_request_emoji_status_access") {
processEmojiStatusAccessRequest();
} else if (command == "share_score") {
_delegate->botHandleMenuButton(MenuButton::ShareGame);
}
@ -1000,6 +1002,15 @@ void Panel::processEmojiStatusRequest(const QJsonObject &args) {
});
}
void Panel::processEmojiStatusAccessRequest() {
auto callback = crl::guard(this, [=](bool allowed) {
postEvent("emoji_status_access_requested", allowed
? "{ status: \"allowed\" }"
: "{ status: \"cancelled\" }");
});
_delegate->botRequestEmojiStatusAccess(std::move(callback));
}
void Panel::openTgLink(const QJsonObject &args) {
if (args.isEmpty()) {
LOG(("BotWebView Error: Bad arguments in 'web_app_open_tg_link'."));

View file

@ -71,6 +71,8 @@ public:
QString query) = 0;
virtual void botCheckWriteAccess(Fn<void(bool allowed)> callback) = 0;
virtual void botAllowWriteAccess(Fn<void(bool allowed)> callback) = 0;
virtual void botRequestEmojiStatusAccess(
Fn<void(bool allowed)> callback) = 0;
virtual void botSharePhone(Fn<void(bool shared)> callback) = 0;
virtual void botInvokeCustomMethod(CustomMethodRequest request) = 0;
virtual void botSetEmojiStatus(SetEmojiStatusRequest request) = 0;

View file

@ -1178,3 +1178,6 @@ botEmojiStatusUserpic: UserpicButton(defaultUserpicButton) {
}
botEmojiStatusName: FlatLabel(defaultFlatLabel) {
}
botEmojiStatusEmoji: FlatLabel(botEmojiStatusName) {
textFg: profileVerifiedCheckBg;
}