Initial support for managed bot creation.

This commit is contained in:
John Preston
2026-03-17 19:01:19 +04:00
parent 8224e6ece1
commit 358a9f87a1
27 changed files with 983 additions and 54 deletions
+2
View File
@@ -225,6 +225,8 @@ PRIVATE
boxes/peers/channel_ownership_transfer.h
boxes/peers/choose_peer_box.cpp
boxes/peers/choose_peer_box.h
boxes/peers/create_managed_bot_box.cpp
boxes/peers/create_managed_bot_box.h
boxes/peers/edit_contact_box.cpp
boxes/peers/edit_contact_box.h
boxes/peers/edit_forum_topic_box.cpp
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" fill="none" fill-rule="evenodd">
<path d="M5.9010,12.0 L8.9994,8.4976 A0.6,0.6 0 0,0 8.1006,7.7024 L4.5919,11.6690 A0.5,0.5 0 0,0 4.5919,12.3310 L8.1006,16.2976 A0.6,0.6 0 0,0 8.9994,15.5024 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M18.0990,12.0 L15.0006,8.4976 A0.6,0.6 0 0,1 15.8994,7.7024 L19.4081,11.6690 A0.5,0.5 0 0,1 19.4081,12.3310 L15.8994,16.2976 A0.6,0.6 0 0,1 15.0006,15.5024 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
<path d="M13.7387,6.2657 L11.4387,17.9657 A0.6,0.6 0 0,1 10.2613,17.7343 L12.5613,6.0343 A0.6,0.6 0 0,1 13.7387,6.2657 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 800 B

+12
View File
@@ -2546,6 +2546,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_new_creator_pending" = "{user} will become the new main admin in 7 days if {from} does not return.";
"lng_action_managed_bot_created" = "{from} created a bot {bot}.";
"lng_create_bot_title" = "Create Bot";
"lng_create_bot_subtitle" = "{bot} would like to create and manage a chatbot on your behalf.";
"lng_create_bot_name_placeholder" = "Bot Name";
"lng_create_bot_username_placeholder" = "Bot Username";
"lng_create_bot_username_available" = "{username} is available.";
"lng_create_bot_username_taken" = "This username is already taken.";
"lng_create_bot_username_bad_symbols" = "Username can only contain a-z, 0-9, and underscores.";
"lng_create_bot_username_too_short" = "Username must be at least 5 characters.";
"lng_create_bot_button" = "Create";
"lng_managed_bot_label" = "{icon} Created and managed by {bot}.";
"lng_managed_bot_ready" = "**{name}** is ready!\n\nClick **Start** below to test your new chatbot. Its behavior is defined by **{parent}**.";
"lng_stake_game_title" = "Emoji Stake";
"lng_stake_game_beta" = "Beta";
"lng_stake_game_about" = "A limited play-test of the upcoming emoji mini-game platform for a small group of users.";
+45 -3
View File
@@ -11,10 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_cloud_password.h"
#include "api/api_send_progress.h"
#include "api/api_suggest_post.h"
#include "boxes/share_box.h"
#include "boxes/passcode_box.h"
#include "boxes/url_auth_box.h"
#include "boxes/peers/choose_peer_box.h"
#include "boxes/peers/create_managed_bot_box.h"
#include "boxes/passcode_box.h"
#include "boxes/share_box.h"
#include "boxes/url_auth_box.h"
#include "lang/lang_keys.h"
#include "chat_helpers/bot_command.h"
#include "core/core_cloud_password.h"
@@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h"
#include <QtCore/QDataStream>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
@@ -546,6 +548,46 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) {
QVariant::fromValue(context),
});
} break;
case ButtonType::CreateBot: {
HideSingleUseKeyboard(controller, item);
auto suggestedName = QString();
auto suggestedUsername = QString();
{
auto stream = QDataStream(button->data);
stream >> suggestedName >> suggestedUsername;
}
const auto peer = item->history()->peer;
const auto itemId = item->id;
const auto id = int32(button->buttonId);
const auto bot = item->getMessageBot();
if (!bot) {
break;
}
ShowCreateManagedBotBox({
.show = controller->uiShow(),
.manager = bot,
.suggestedName = suggestedName,
.suggestedUsername = suggestedUsername,
.done = [=](not_null<UserData*> createdBot) {
using Flag = MTPmessages_SendBotRequestedPeer::Flag;
peer->session().api().request(
MTPmessages_SendBotRequestedPeer(
MTP_flags(Flag::f_msg_id),
peer->input(),
MTP_int(itemId),
MTPstring(),
MTP_int(id),
MTP_vector<MTPInputPeer>(
1,
createdBot->input()))
).done([=](const MTPUpdates &result) {
peer->session().api().applyUpdates(result);
}).send();
},
});
} break;
}
}
+12
View File
@@ -2111,6 +2111,18 @@ void Updates::feedUpdate(const MTPUpdate &update) {
}
} break;
case mtpc_updateManagedBot: {
const auto &d = update.c_updateManagedBot();
if (const auto user = session().data().userLoaded(
UserId(d.vuser_id().v))) {
session().api().requestFullPeer(user);
}
if (const auto bot = session().data().userLoaded(
UserId(d.vbot_id().v))) {
session().api().requestFullPeer(bot);
}
} break;
case mtpc_updatePendingJoinRequests: {
const auto &d = update.c_updatePendingJoinRequests();
if (const auto peer = session().data().peerLoaded(peerFromMTP(d.vpeer()))) {
+26
View File
@@ -1235,3 +1235,29 @@ disableSharingButtonLock: IconEmoji {
icon: icon {{ "emoji/premium_lock", activeButtonFg }};
padding: margins(-2px, 1px, 0px, 0px);
}
createBotBox: Box(defaultBox) {
shadowIgnoreTopSkip: true;
}
createBotUserpicPadding: margins(0px, 16px, 0px, 10px);
createBotTitlePadding: margins(0px, 0px, 0px, 8px);
createBotSubtitlePadding: margins(0px, 0px, 0px, 4px);
createBotCenteredText: FlatLabel(defaultFlatLabel) {
align: align(top);
minWidth: 40px;
}
createBotFieldSpacing: 8px;
createBotUsernameField: InputField(defaultInputField) {
textMargins: margins(0px, 28px, 0px, 4px);
}
createBotUsernamePrefix: FlatLabel(defaultFlatLabel) {
style: boxTextStyle;
}
createBotUsernameSuffix: FlatLabel(createBotUsernamePrefix) {
textFg: windowSubTextFg;
}
managedBotIconEmoji: IconEmoji {
icon: icon {{ "chat/code_tags", windowSubTextFg }};
padding: margins(-2px, -2px, -2px, 0px);
}
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/add_contact_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/premium_limits_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
@@ -37,7 +38,7 @@ class ChoosePeerBoxController final
, public base::has_weak_ptr {
public:
ChoosePeerBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> callback);
@@ -59,7 +60,7 @@ private:
void prepareRestrictions();
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
not_null<UserData*> _bot;
RequestPeerQuery _query;
base::flat_set<not_null<PeerData*>> _commonGroups;
@@ -324,12 +325,12 @@ object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
}
ChoosePeerBoxController::ChoosePeerBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> callback)
: ChatsListBoxController(&navigation->session())
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _bot(bot)
, _query(query)
, _callback(std::move(callback)) {
@@ -339,7 +340,7 @@ ChoosePeerBoxController::ChoosePeerBoxController(
}
Main::Session &ChoosePeerBoxController::session() const {
return _navigation->session();
return *_session;
}
void ChoosePeerBoxController::prepareRestrictions() {
@@ -380,8 +381,16 @@ void ChoosePeerBoxController::prepareRestrictions() {
}, icon->lifetime());
button->setClickedCallback([=] {
_navigation->parentController()->show(
CreatePeerByQueryBox(_navigation, _bot, _query, _callback));
const auto controller = ChatHelpers::ResolveWindowDefault()(
_session);
if (controller) {
delegate()->peerListUiShow()->showBox(
CreatePeerByQueryBox(
controller,
_bot,
_query,
_callback));
}
});
button->events(
@@ -495,13 +504,26 @@ void ShowChoosePeerBox(
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
ShowChoosePeerBox(
navigation->uiShow(),
bot,
query,
std::move(chosen));
}
void ShowChoosePeerBox(
std::shared_ptr<Main::SessionShow> show,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
const auto session = &show->session();
const auto needCommonGroups = query.isBotParticipant
&& (query.type == RequestPeerQuery::Type::Group)
&& !query.myRights;
if (needCommonGroups && !bot->session().api().botCommonGroups(bot)) {
const auto weak = base::make_weak(navigation);
bot->session().api().requestBotCommonGroups(bot, [=] {
if (const auto strong = weak.get()) {
if (needCommonGroups && !session->api().botCommonGroups(bot)) {
const auto weak = std::weak_ptr(show);
session->api().requestBotCommonGroups(bot, [=] {
if (const auto strong = weak.lock()) {
ShowChoosePeerBox(strong, bot, query, chosen);
}
});
@@ -517,7 +539,7 @@ void ShowChoosePeerBox(
};
const auto limit = query.maxQuantity;
auto controller = std::make_unique<ChoosePeerBoxController>(
navigation,
session,
bot,
query,
std::move(callback));
@@ -540,7 +562,7 @@ void ShowChoosePeerBox(
});
}, box->lifetime());
};
*weak = navigation->parentController()->show(Box<PeerListBox>(
*weak = show->show(Box<PeerListBox>(
std::move(controller),
std::move(initBox)));
}
@@ -9,6 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
struct RequestPeerQuery;
namespace Main {
class Session;
class SessionShow;
} // namespace Main
namespace Ui {
class BoxContent;
} // namespace Ui
@@ -22,3 +27,9 @@ void ShowChoosePeerBox(
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> chosen);
void ShowChoosePeerBox(
std::shared_ptr<Main::SessionShow> show,
not_null<UserData*> bot,
RequestPeerQuery query,
Fn<void(std::vector<not_null<PeerData*>>)> chosen);
@@ -0,0 +1,347 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peers/create_managed_bot_box.h"
#include "base/timer.h"
#include "boxes/peers/edit_peer_common.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "mtproto/sender.h"
#include "ui/controls/userpic_button.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/fields/special_fields.h"
#include "ui/widgets/labels.h"
#include "ui/vertical_list.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
void CreateManagedBotBox(
not_null<Ui::GenericBox*> box,
CreateManagedBotDescriptor &&descriptor) {
struct State {
base::Timer checkTimer;
mtpRequestId checkRequestId = 0;
mtpRequestId createRequestId = 0;
QString checkUsername;
QString errorText;
QString goodText;
};
const auto show = descriptor.show;
const auto session = &show->session();
const auto viaDeeplink = descriptor.viaDeeplink;
const auto done = std::move(descriptor.done);
const auto manager = descriptor.manager;
const auto api = box->lifetime().make_state<MTP::Sender>(
&session->mtp());
const auto state = box->lifetime().make_state<State>();
box->setStyle(st::createBotBox);
box->setWidth(st::boxWidth);
box->setNoContentMargin(true);
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
if (manager) {
auto userpic = object_ptr<Ui::UserpicButton>(
box,
manager,
st::defaultUserpicButton);
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
box->addRow(
std::move(userpic),
st::boxRowPadding + st::createBotUserpicPadding,
style::al_top);
}
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_create_bot_title(),
st::boxTitle),
st::boxRowPadding + st::createBotTitlePadding,
style::al_top);
if (manager) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_create_bot_subtitle(
lt_bot,
rpl::single(tr::bold(manager->name())),
tr::rich),
st::createBotCenteredText),
st::boxRowPadding + st::createBotSubtitlePadding,
style::al_top
)->setTryMakeSimilarLines(true);
}
const auto name = box->addRow(object_ptr<Ui::InputField>(
box,
st::defaultInputField,
tr::lng_create_bot_name_placeholder(),
descriptor.suggestedName));
name->setMaxLength(Ui::EditPeer::kMaxGroupChannelTitle);
Ui::AddSkip(box->verticalLayout(), st::createBotFieldSpacing);
const auto botPrefixText = u"@"_q;
const auto botSuffixText = u"bot"_q;
auto initialUsername = descriptor.suggestedUsername;
if (initialUsername.startsWith(botPrefixText)) {
initialUsername.remove(0, botPrefixText.size());
}
if (initialUsername.endsWith(botSuffixText, Qt::CaseInsensitive)) {
initialUsername.chop(3);
}
const auto fieldSt = box->lifetime().make_state<style::InputField>(
st::createBotUsernameField);
fieldSt->textMargins.setLeft(
st::defaultFlatLabel.style.font->width(botPrefixText));
fieldSt->textMargins.setRight(
st::createBotUsernameSuffix.style.font->width(botSuffixText));
fieldSt->placeholderMargins.setLeft(-fieldSt->textMargins.left());
fieldSt->placeholderMargins.setRight(-fieldSt->textMargins.right());
const auto usernameWrap = box->addRow(object_ptr<Ui::RpWidget>(box));
const auto username = Ui::CreateChild<Ui::UsernameInput>(
usernameWrap,
*fieldSt,
tr::lng_create_bot_username_placeholder(),
initialUsername,
QString()); // linkPlaceholder
usernameWrap->widthValue() | rpl::on_next([=](int width) {
username->resizeToWidth(width);
}, username->lifetime());
username->heightValue() | rpl::on_next([=](int height) {
usernameWrap->resize(usernameWrap->width(), height);
}, username->lifetime());
const auto botPrefix = Ui::CreateChild<Ui::FlatLabel>(
username,
botPrefixText,
st::createBotUsernamePrefix);
const auto botSuffix = Ui::CreateChild<Ui::FlatLabel>(
username,
botSuffixText,
st::createBotUsernameSuffix);
botPrefix->setAttribute(Qt::WA_TransparentForMouseEvents);
botPrefix->show();
botSuffix->setAttribute(Qt::WA_TransparentForMouseEvents);
botSuffix->show();
const auto updatePositions = [=] {
const auto &margin = fieldSt->textMargins;
const auto &font = fieldSt->style.font;
const auto text = username->getLastText();
const auto textWidth = font->width(text);
const auto maxX = username->width() - margin.right();
const auto x = std::min(margin.left() + textWidth, maxX);
botPrefix->move(0, margin.top());
botSuffix->move(x, margin.top());
};
username->geometryValue(
) | rpl::on_next(updatePositions, username->lifetime());
const auto statusWrapper = box->addRow(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding + QMargins(0, st::defaultVerticalListSkip, 0, 0));
const auto statusLabel = Ui::CreateChild<Ui::FlatLabel>(
statusWrapper,
st::aboutRevokePublicLabel);
statusLabel->move(0, 0);
const auto maxHeight = box->lifetime().make_state<int>(0);
statusLabel->heightValue(
) | rpl::on_next([=](int height) {
const auto newMax = std::max({
*maxHeight,
height,
st::aboutRevokePublicLabel.style.font->height,
});
if (*maxHeight != newMax) {
*maxHeight = newMax;
statusWrapper->resize(statusWrapper->width(), newMax);
}
}, statusWrapper->lifetime());
statusWrapper->widthValue(
) | rpl::on_next([=](int width) {
statusLabel->resizeToWidth(width);
}, statusWrapper->lifetime());
box->setFocusCallback([=] { name->setFocusFast(); });
const auto setError = [=](const QString &text) {
state->errorText = text;
state->goodText = QString();
statusLabel->setText(text);
statusLabel->setTextColorOverride(
text.isEmpty()
? std::optional<QColor>()
: st::boxTextFgError->c);
state->checkTimer.cancel();
};
const auto checkUsername = [=] {
api->request(base::take(state->checkRequestId)).cancel();
auto raw = username->getLastText().trimmed();
if (raw.startsWith(QChar('@'))) {
raw = raw.mid(1);
}
const auto value = raw + u"bot"_q;
if (value.size() < Ui::EditPeer::kMinUsernameLength) {
return;
}
state->checkUsername = value;
state->checkRequestId = api->request(MTPbots_CheckUsername(
MTP_string(value)
)).done([=](const MTPBool &result) {
state->checkRequestId = 0;
if (mtpIsTrue(result)) {
state->errorText = QString();
state->goodText = tr::lng_create_bot_username_available(
tr::now,
lt_username,
state->checkUsername);
statusLabel->setText(state->goodText);
statusLabel->setTextColorOverride(st::boxTextFgGood->c);
} else {
setError(tr::lng_create_bot_username_taken(tr::now));
}
}).fail([=](const MTP::Error &error) {
state->checkRequestId = 0;
setError(tr::lng_create_bot_username_taken(tr::now));
}).send();
};
state->checkTimer.setCallback(checkUsername);
const auto usernameChanged = [=] {
const auto value = username->getLastText().trimmed();
if (value.isEmpty()) {
setError(QString());
return;
}
const auto len = int(value.size());
for (auto i = 0; i < len; ++i) {
const auto ch = value.at(i);
if ((ch < 'A' || ch > 'Z')
&& (ch < 'a' || ch > 'z')
&& (ch < '0' || ch > '9')
&& ch != '_') {
setError(
tr::lng_create_bot_username_bad_symbols(tr::now));
return;
}
}
if ((value + u"bot"_q).size() < Ui::EditPeer::kMinUsernameLength) {
setError(tr::lng_create_bot_username_too_short(tr::now));
return;
}
setError(QString());
state->checkTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
};
const auto submit = [=] {
if (state->createRequestId) {
return;
}
const auto nameValue = name->getLastText().trimmed();
if (nameValue.isEmpty()) {
name->showError();
return;
}
auto rawUsername = username->getLastText().trimmed();
if (rawUsername.startsWith(QChar('@'))) {
rawUsername = rawUsername.mid(1);
}
const auto usernameValue = rawUsername + u"bot"_q;
if (rawUsername.isEmpty()) {
username->showError();
return;
}
if (!state->errorText.isEmpty() || state->goodText.isEmpty()) {
username->showError();
return;
}
using Flag = MTPbots_CreateBot::Flag;
const auto flags = viaDeeplink ? Flag::f_via_deeplink : Flag(0);
const auto managerInput = manager
? manager->inputUser()
: MTP_inputUserEmpty();
const auto weak = base::make_weak(box);
state->createRequestId = api->request(MTPbots_CreateBot(
MTP_flags(flags),
MTP_string(nameValue),
MTP_string(usernameValue),
managerInput
)).done([=](const MTPUser &result) {
state->createRequestId = 0;
const auto user = session->data().processUser(result);
if (done) {
done(user);
}
if (const auto strong = weak.get()) {
strong->closeBox();
}
}).fail([=](const MTP::Error &error) {
state->createRequestId = 0;
const auto type = error.type();
if (type == u"USERNAME_OCCUPIED"_q
|| type == u"USERNAME_INVALID"_q) {
username->showError();
setError(tr::lng_create_bot_username_taken(tr::now));
} else {
show->showToast(type);
}
}).send();
};
QObject::connect(username, &Ui::UsernameInput::changed, [=] {
usernameChanged();
updatePositions();
});
name->submits(
) | rpl::on_next([=](auto) {
username->setFocus();
}, name->lifetime());
QObject::connect(username, &Ui::UsernameInput::submitted, submit);
box->addButton(tr::lng_create_bot_button(), submit);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
if (!descriptor.suggestedUsername.isEmpty()) {
usernameChanged();
}
}
void ShowCreateManagedBotBox(CreateManagedBotDescriptor &&descriptor) {
const auto show = descriptor.show;
show->showBox(Box(CreateManagedBotBox, std::move(descriptor)));
}
@@ -0,0 +1,33 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
class UserData;
namespace Main {
class SessionShow;
} // namespace Main
namespace Ui {
class GenericBox;
} // namespace Ui
struct CreateManagedBotDescriptor {
std::shared_ptr<Main::SessionShow> show;
UserData *manager = nullptr;
QString suggestedName;
QString suggestedUsername;
bool viaDeeplink = false;
Fn<void(not_null<UserData*>)> done;
};
void CreateManagedBotBox(
not_null<Ui::GenericBox*> box,
CreateManagedBotDescriptor &&descriptor);
void ShowCreateManagedBotBox(CreateManagedBotDescriptor &&descriptor);
@@ -7,6 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "core/deep_links/deep_links_router.h"
#include "apiwrap.h"
#include "boxes/peers/create_managed_bot_box.h"
#include "data/data_peer_id.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
namespace Core::DeepLinks {
@@ -36,6 +42,54 @@ Result ShowAddContact(const Context &ctx) {
return Result::Handled;
}
Result ShowNewBot(const Context &ctx) {
if (!ctx.controller) {
return Result::NeedsAuth;
}
const auto manager = ctx.params.value(u"manager"_q);
const auto username = ctx.params.value(u"username"_q);
const auto title = ctx.params.value(u"name"_q);
if (manager.isEmpty()) {
return Result::Handled;
}
const auto session = &ctx.controller->session();
const auto weak = base::make_weak(ctx.controller);
session->api().request(MTPcontacts_ResolveUsername(
MTP_flags(0),
MTP_string(manager),
MTP_string()
)).done([=](const MTPcontacts_ResolvedPeer &result) {
const auto strong = weak.get();
if (!strong) {
return;
}
result.match([&](const MTPDcontacts_resolvedPeer &data) {
strong->session().data().processUsers(data.vusers());
strong->session().data().processChats(data.vchats());
const auto peerId = peerFromMTP(data.vpeer());
if (const auto managerBot = strong->session().data().userLoaded(
peerToUser(peerId))) {
if (!managerBot->isBot()) {
return;
}
ShowCreateManagedBotBox({
.show = strong->uiShow(),
.manager = managerBot,
.suggestedName = title,
.suggestedUsername = username,
.viaDeeplink = true,
.done = [weak](not_null<UserData*> createdBot) {
if (const auto strong = weak.get()) {
strong->showPeerHistory(createdBot);
}
},
});
}
});
}).send();
return Result::Handled;
}
} // namespace
void RegisterNewHandlers(Router &router) {
@@ -53,6 +107,11 @@ void RegisterNewHandlers(Router &router) {
.path = u"contact"_q,
.action = CodeBlock{ ShowAddContact },
});
router.add(u"newbot"_q, {
.path = QString(),
.action = CodeBlock{ ShowNewBot },
});
}
} // namespace Core::DeepLinks
@@ -1887,6 +1887,18 @@ QString TryConvertUrlToLocal(QString url) {
} else if (const auto callMatch = regex_match(u"^call/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"_q, query, matchOptions)) {
const auto slug = callMatch->captured(1);
return u"tg://call?slug="_q + slug;
} else if (const auto newbotMatch = regex_match(u"^newbot/([a-zA-Z0-9\\.\\_]+)(/([a-zA-Z0-9\\.\\_]+))?(\\?(.+))?$"_q, query, matchOptions)) {
const auto manager = newbotMatch->captured(1);
const auto username = newbotMatch->captured(3);
const auto params = newbotMatch->captured(5);
auto result = u"tg://newbot?manager="_q + url_encode(manager);
if (!username.isEmpty()) {
result += u"&username="_q + url_encode(username);
}
if (!params.isEmpty()) {
result += '&' + params;
}
return result;
} else if (const auto privateMatch = regex_match(u"^"
"c/(\\-?\\d+)"
"("
+9
View File
@@ -318,6 +318,14 @@ void UserData::setPersonalChannel(ChannelId channelId, MsgId messageId) {
}
}
UserId UserData::botManagerId() const {
return _botManagerId;
}
void UserData::setBotManagerId(UserId managerId) {
_botManagerId = managerId;
}
MTPInputUser UserData::inputUser() const {
const auto item = isLoaded() ? nullptr : owner().messageWithPeer(id);
if (item) {
@@ -993,6 +1001,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setPersonalChannel(
update.vpersonal_channel_id().value_or_empty(),
update.vpersonal_channel_message().value_or_empty());
user->setBotManagerId(update.vbot_manager_id().value_or_empty());
if (user->isSelf()) {
user->owner().businessInfo().applyAwaySettings(
FromMTP(&user->owner(), update.vbusiness_away_message()));
+4
View File
@@ -291,6 +291,9 @@ public:
[[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId);
[[nodiscard]] UserId botManagerId() const;
void setBotManagerId(UserId managerId);
[[nodiscard]] MTPInputUser inputUser() const;
QString firstName;
@@ -334,6 +337,7 @@ private:
ChannelId _personalChannelId = 0;
MsgId _personalChannelMessageId = 0;
UserId _botManagerId = 0;
uint64 _accessHash = 0;
static constexpr auto kInaccessibleAccessHashOld
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h"
#include "inline_bots/bot_attach_web_view.h"
#include <QtCore/QDataStream>
namespace {
[[nodiscard]] HistoryMessageMarkupButton::Visual ParseVisual(
@@ -36,7 +38,9 @@ namespace {
};
}
[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL(
} // namespace
RequestPeerQuery RequestPeerQueryFromTL(
const MTPDkeyboardButtonRequestPeer &query) {
using Type = RequestPeerQuery::Type;
using Restriction = RequestPeerQuery::Restriction;
@@ -75,8 +79,6 @@ namespace {
return result;
}
} // namespace
InlineBots::PeerTypes PeerTypesFromMTP(
const MTPvector<MTPInlineQueryPeerType> &types) {
using namespace InlineBots;
@@ -170,16 +172,38 @@ void HistoryMessageMarkupData::fillRows(
qs(data.vtext()),
ParseVisual(data.vstyle()));
}, [&](const MTPDkeyboardButtonRequestPeer &data) {
const auto query = RequestPeerQueryFromTL(data);
row.emplace_back(
Type::RequestPeer,
qs(data.vtext()),
ParseVisual(data.vstyle()),
QByteArray(
reinterpret_cast<const char*>(&query),
sizeof(query)),
QString(),
int64(data.vbutton_id().v));
data.vpeer_type().match([&](
const MTPDrequestPeerTypeCreateBot &createData) {
auto serialized = QByteArray();
{
auto stream = QDataStream(
&serialized,
QIODevice::WriteOnly);
stream
<< qs(createData.vsuggested_name()
.value_or_empty())
<< qs(createData.vsuggested_username()
.value_or_empty());
}
row.emplace_back(
Type::CreateBot,
qs(data.vtext()),
ParseVisual(data.vstyle()),
serialized,
QString(),
int64(data.vbutton_id().v));
}, [&](const auto &) {
const auto query = RequestPeerQueryFromTL(data);
row.emplace_back(
Type::RequestPeer,
qs(data.vtext()),
ParseVisual(data.vstyle()),
QByteArray(
reinterpret_cast<const char*>(&query),
sizeof(query)),
QString(),
int64(data.vbutton_id().v));
});
}, [&](const MTPDkeyboardButtonUrl &data) {
row.emplace_back(
Type::Url,
@@ -69,6 +69,11 @@ struct RequestPeerQuery {
};
static_assert(std::is_trivially_copy_assignable_v<RequestPeerQuery>);
class MTPDkeyboardButtonRequestPeer;
[[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL(
const MTPDkeyboardButtonRequestPeer &query);
struct HistoryMessageMarkupButton {
enum class Type : uchar {
Default,
@@ -92,6 +97,7 @@ struct HistoryMessageMarkupButton {
SuggestDecline,
SuggestAccept,
SuggestChange,
CreateBot,
};
enum class Color : uchar {
@@ -7,10 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/history_view_about_view.h"
#include "api/api_peer_colors.h"
#include "api/api_premium.h"
#include "api/api_sending.h"
#include "apiwrap.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "ui/effects/premium_stars.h"
#include "boxes/premium_preview_box.h"
#include "chat_helpers/stickers_lottie.h"
@@ -21,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
#include "data/data_document.h"
#include "data/data_emoji_statuses.h"
#include "data/data_photo.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "history/view/history_view_group_call_bar.h"
@@ -40,15 +44,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/sections/settings_credits.h" // BuyStarsHandler
#include "settings/sections/settings_premium.h"
#include "ui/chat/chat_style.h"
#include "ui/image/image_location_factory.h"
#include "ui/text/custom_emoji_instance.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_options.h"
#include "ui/dynamic_image.h"
#include "ui/empty_userpic.h"
#include "ui/painter.h"
#include "ui/top_background_gradient.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" // GroupCallUserpics
#include "styles/style_credits.h"
#include "styles/style_menu_icons.h"
namespace HistoryView {
namespace {
@@ -603,6 +611,83 @@ bool EmptyChatLockedBox::hasHeavyPart() {
void EmptyChatLockedBox::unloadHeavyPart() {
}
QImage GenerateManagedBotImage(not_null<UserData*> user) {
auto centerColor = QColor();
auto edgeColor = QColor();
if (const auto collectible = user->emojiStatusId().collectible) {
centerColor = collectible->centerColor;
edgeColor = collectible->edgeColor;
} else if (const auto color
= user->session().api().peerColors().colorProfileFor(user)) {
if (color->bg.size() > 1) {
centerColor = color->bg[1];
edgeColor = color->bg[0];
}
}
if (!centerColor.isValid()) {
const auto colorIndex = Ui::EmptyUserpic::ColorIndex(
user->id.value);
const auto colors = Ui::EmptyUserpic::UserpicColor(colorIndex);
centerColor = colors.color1->c;
edgeColor = colors.color2->c;
}
const auto size = QSize(
st::managedBotImageWidth,
st::managedBotImageHeight);
auto image = Ui::CreateTopBgGradient(
size,
centerColor,
edgeColor,
false);
if (image.isNull()) {
return image;
}
auto p = QPainter(&image);
auto hq = PainterHighQualityEnabler(p);
auto iconColor = edgeColor.toHsv();
iconColor.setHsv(
iconColor.hsvHue(),
iconColor.hsvSaturation(),
std::max(iconColor.value() - 64, 0));
iconColor = iconColor.toRgb();
const auto width = size.width();
const auto height = size.height();
const auto &icon = st::menuIconBot;
const auto &points = Ui::PatternBgPoints();
for (const auto &point : points) {
const auto cx = point.position.x() * width;
const auto cy = point.position.y() * height;
p.save();
p.setOpacity(point.opacity);
if (point.scale < 1.) {
p.translate(cx, cy);
p.scale(point.scale, point.scale);
p.translate(-cx, -cy);
}
const auto x = int(cx) - icon.width() / 2;
const auto y = int(cy) - icon.height() / 2;
icon.paint(p, x, y, width, iconColor);
p.restore();
}
const auto ratio = style::DevicePixelRatio();
const auto iheight = st::managedBotCodeIcon.height();
const auto scale = (size.height() * ratio * 100) / iheight;
auto iconImage = st::managedBotCodeIcon.instance(Qt::white, scale, true);
iconImage.setDevicePixelRatio(ratio);
const auto iw = iconImage.width() / ratio;
const auto ih = iconImage.height() / ratio;
p.drawImage(
QRect((width - iw) / 2, (height - ih) / 2, iw, ih),
iconImage);
p.end();
return image;
}
} // namespace
AboutView::AboutView(
@@ -704,6 +789,12 @@ bool AboutView::refresh() {
}
setItem(makeNewBotThread(), nullptr);
return true;
} else if (user->botManagerId() && info->description.isEmpty()) {
if (_item) {
return false;
}
setItem(makeManagedBotInfo(user), nullptr);
return true;
}
const auto version = info->descriptionVersion;
if (_version == version) {
@@ -1062,4 +1153,41 @@ AdminLog::OwnedItem AboutView::makeNewBotThread() {
return result;
}
AdminLog::OwnedItem AboutView::makeManagedBotInfo(
not_null<UserData*> user) {
const auto image = GenerateManagedBotImage(user);
const auto photoImage = image.isNull()
? ImageWithLocation()
: Images::FromImageInMemory(image, "PNG");
const auto photo = _history->session().data().photo(
base::RandomValue<PhotoId>(),
uint64(0),
QByteArray(),
base::unixtime::now(),
0,
false,
QByteArray(),
ImageWithLocation{},
photoImage,
photoImage,
ImageWithLocation{},
ImageWithLocation{},
crl::time(0));
const auto managerId = user->botManagerId();
const auto managerUser = user->owner().userLoaded(managerId);
const auto parentName = managerUser
? managerUser->name()
: QString();
const auto text = tr::lng_managed_bot_ready(
tr::now,
lt_name,
tr::bold(user->name()),
lt_parent,
tr::bold(parentName),
tr::rich);
return makeAboutSimple(text, nullptr, photo);
}
} // namespace HistoryView
@@ -53,6 +53,8 @@ private:
not_null<UserData*> user);
[[nodiscard]] AdminLog::OwnedItem makeBlocked();
[[nodiscard]] AdminLog::OwnedItem makeNewBotThread();
[[nodiscard]] AdminLog::OwnedItem makeManagedBotInfo(
not_null<UserData*> user);
void makeIntro(not_null<UserData*> user);
void setItem(AdminLog::OwnedItem item, DocumentData *sticker);
void setHelloChosen(not_null<DocumentData*> sticker);
@@ -1165,7 +1165,8 @@ Element::Element(
if (user
&& user->isBot()
&& !user->isRepliesChat()
&& !user->isVerifyCodes()) {
&& !user->isVerifyCodes()
&& !user->botManagerId()) {
AddComponents(FakeBotAboutTop::Bit());
}
}
@@ -208,12 +208,15 @@ QSize Photo::countOptimalSize() {
auto maxWidth = qMax(maxActualWidth, scaled.height());
auto minHeight = qMax(scaled.height(), st::minPhotoSize);
if (_parent->hasBubble()) {
const auto botTop = _parent->Get<FakeBotAboutTop>();
const auto captionMaxWidth = _parent->textualMaxWidth();
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
maxWidth = qMin(qMax(maxWidth, maxWithCaption), st::msgMaxWidth);
minHeight = adjustHeightForLessCrop(
dimensions,
{ maxWidth, minHeight });
if (botTop || !_parent->data()->isFakeAboutView()) {
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
maxWidth = qMin(qMax(maxWidth, maxWithCaption), st::msgMaxWidth);
minHeight = adjustHeightForLessCrop(
dimensions,
{ maxWidth, minHeight });
}
}
return { maxWidth, minHeight };
}
@@ -246,11 +249,15 @@ QSize Photo::countCurrentSize(int newWidth) {
if (botTop) {
accumulate_max(captionMaxWidth, botTop->maxWidth);
}
const auto maxWithCaption = qMin(st::msgMaxWidth, captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
newHeight = adjustHeightForLessCrop(
dimensions,
{ newWidth, newHeight });
if (botTop || !_parent->data()->isFakeAboutView()) {
const auto maxWithCaption = qMin(
st::msgMaxWidth,
captionMaxWidth);
newWidth = qMin(qMax(newWidth, maxWithCaption), thumbMaxWidth);
newHeight = adjustHeightForLessCrop(
dimensions,
{ newWidth, newHeight });
}
}
if (newWidth >= maxWidth()) {
newHeight = qMin(newHeight, minHeight());
@@ -529,8 +536,8 @@ QImage Photo::prepareImageCacheWithLarge(QSize outer, Image *large) const {
blurred = thumbnail;
} else if (const auto small = _dataMedia->image(Size::Small)) {
blurred = small;
} else {
blurred = large;
} else {
blurred = large;
}
const auto resize = large
? ::Media::Streaming::DecideFrameResize(outer, large->size())
@@ -539,9 +546,9 @@ QImage Photo::prepareImageCacheWithLarge(QSize outer, Image *large) const {
}
void Photo::paintUserpicFrame(
Painter &p,
QPoint photoPosition,
bool markFrameShown) const {
Painter &p,
QPoint photoPosition,
bool markFrameShown) const {
const auto autoplay = _data->videoCanBePlayed()
&& videoAutoplayEnabled();
const auto startPlay = autoplay && !_streamed;
@@ -596,9 +603,9 @@ void Photo::paintUserpicFrame(
}
void Photo::paintUserpicFrame(
Painter &p,
const PaintContext &context,
QPoint photoPosition) const {
Painter &p,
const PaintContext &context,
QPoint photoPosition) const {
paintUserpicFrame(p, photoPosition, !context.paused);
if (_data->videoCanBePlayed() && !_streamed) {
@@ -628,6 +635,9 @@ void Photo::paintUserpicFrame(
QSize Photo::photoSize() const {
if (_storyId) {
return { kStoryWidth, kStoryHeight };
} else if (_parent->data()->isFakeAboutView()
&& !_parent->Get<FakeBotAboutTop>()) {
return { st::managedBotImageWidth, st::managedBotImageHeight };
}
return QSize(_data->width(), _data->height());
}
@@ -79,6 +79,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/text/text_variant.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
@@ -95,6 +96,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h" // Window::Controller::show.
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h" // st::channelEarnCurrencyCommonMargins
#include "styles/style_info.h"
#include "styles/style_layers.h"
@@ -1218,7 +1220,7 @@ public:
private:
object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user);
object_ptr<Ui::RpWidget> setupInfo();
void setupMainApp();
void setupMainApp(bool suppressBottom = false);
void setupBotPermissions();
void addShowTopicsListButton(
Ui::MultiSlideTracker &tracker,
@@ -2124,7 +2126,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
return result;
}
void DetailsFiller::setupMainApp() {
void DetailsFiller::setupMainApp(bool suppressBottom) {
const auto button = _wrap->add(
object_ptr<Ui::RoundButton>(
_wrap,
@@ -2148,6 +2150,9 @@ void DetailsFiller::setupMainApp() {
});
const auto url = tr::lng_mini_apps_tos_url(tr::now);
const auto parts = suppressBottom
? RectPart::Top
: (RectPart::Top | RectPart::Bottom);
const auto divider = Ui::AddDividerText(
_wrap,
rpl::combine(
@@ -2163,7 +2168,10 @@ void DetailsFiller::setupMainApp() {
text = text.append(u"\n\n"_q).append(verify->description);
}
return text;
}));
}),
st::defaultBoxDividerLabelPadding,
st::defaultDividerLabel,
parts);
divider->setClickHandlerFilter([=](const auto &...) {
UrlClickHandler::Open(url);
return false;
@@ -2370,12 +2378,48 @@ object_ptr<Ui::RpWidget> DetailsFiller::fill() {
if (const auto info = user->botInfo.get()) {
if (info->hasMainApp) {
_dividerOverridden.force_assign(true);
setupMainApp();
const auto managedBotFollows = user->botManagerId()
&& !info->canManageEmojiStatus
&& user->owner().userLoaded(user->botManagerId());
setupMainApp(managedBotFollows);
}
if (info->canManageEmojiStatus) {
_dividerOverridden.force_assign(false);
setupBotPermissions();
}
if (user->botManagerId()) {
if (const auto managerUser = user->owner().userLoaded(
user->botManagerId())) {
if (!info->hasMainApp) {
_dividerOverridden.force_assign(true);
}
const auto botUsername = managerUser->username();
const auto linkText = botUsername.isEmpty()
? managerUser->name()
: (u"@"_q + botUsername);
const auto parts = (info->hasMainApp && !info->canManageEmojiStatus)
? RectPart::Bottom
: (RectPart::Top | RectPart::Bottom);
const auto divider = Ui::AddDividerText(
_wrap,
tr::lng_managed_bot_label(
lt_icon,
rpl::single(Ui::Text::IconEmoji(&st::managedBotIconEmoji)),
lt_bot,
rpl::single(tr::link(linkText)),
tr::marked),
st::defaultBoxDividerLabelPadding,
st::defaultDividerLabel,
parts);
const auto weak = base::make_weak(_controller);
divider->setClickHandlerFilter([=](const auto &...) {
if (const auto strong = weak.get()) {
strong->showPeerInfo(managerUser);
}
return false;
});
}
}
}
if (!user->isSelf() && !_sublist) {
addReportReaction(_mainTracker, &lastButtonTracker);
@@ -17,6 +17,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/random.h"
#include "base/timer_rpl.h"
#include "base/unixtime.h"
#include "boxes/peers/choose_peer_box.h"
#include "boxes/peers/create_managed_bot_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h"
#include "boxes/share_box.h"
@@ -43,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history_item_reply_markup.h"
#include "info/bot/starref/info_bot_starref_common.h" // MakePeerBubbleButton
#include "info/profile/info_profile_values.h"
#include "inline_bots/inline_bot_result.h"
@@ -1975,6 +1978,75 @@ void WebViewInstance::botSendPreparedMessage(
}).send();
}
void WebViewInstance::botRequestChat(
Ui::BotWebView::RequestChatRequest request) {
const auto bot = _bot;
const auto callback = request.callback;
const auto requestId = request.requestId;
if (!_panel) {
callback(u"UNKNOWN_ERROR"_q);
return;
}
const auto show = uiShow();
bot->session().api().request(MTPbots_GetRequestedWebViewButton(
bot->inputUser(),
MTP_string(requestId)
)).done([show, bot, callback, requestId](
const MTPKeyboardButton &result) {
result.match([&](const MTPDkeyboardButtonRequestPeer &data) {
if (!*show) {
callback(u"UNKNOWN_ERROR"_q);
return;
}
const auto buttonId = data.vbutton_id();
const auto sendPeers = [=](
std::vector<not_null<PeerData*>> peers) {
using Flag = MTPmessages_SendBotRequestedPeer::Flag;
bot->session().api().request(
MTPmessages_SendBotRequestedPeer(
MTP_flags(Flag::f_webapp_req_id),
bot->input(),
MTPint(),
MTP_string(requestId),
buttonId,
MTP_vector_from_range(
peers | ranges::views::transform([](
not_null<PeerData*> peer) {
return MTPInputPeer(peer->input());
})))
).done([=](const MTPUpdates &result) {
bot->session().api().applyUpdates(result);
callback(QString());
}).fail([callback](const MTP::Error &error) {
callback(error.type());
}).send();
};
data.vpeer_type().match([&](
const MTPDrequestPeerTypeCreateBot &createData) {
ShowCreateManagedBotBox({
.show = show,
.manager = bot,
.suggestedName = qs(
createData.vsuggested_name().value_or_empty()),
.suggestedUsername = qs(
createData.vsuggested_username()
.value_or_empty()),
.done = [=](not_null<UserData*> createdBot) {
sendPeers({ createdBot });
},
});
}, [&](const auto &) {
const auto query = RequestPeerQueryFromTL(data);
ShowChoosePeerBox(show, bot, query, sendPeers);
});
}, [&](const auto &) {
callback(u"UNSUPPORTED_BUTTON_TYPE"_q);
});
}).fail([callback](const MTP::Error &error) {
callback(error.type());
}).send();
}
void WebViewInstance::botSetEmojiStatus(
Ui::BotWebView::SetEmojiStatusRequest request) {
const auto bot = _bot;
@@ -300,6 +300,8 @@ private:
Ui::BotWebView::CustomMethodRequest request) override;
void botSendPreparedMessage(
Ui::BotWebView::SendPreparedMessageRequest request) override;
void botRequestChat(
Ui::BotWebView::RequestChatRequest request) override;
void botSetEmojiStatus(
Ui::BotWebView::SetEmojiStatusRequest request) override;
void botDownloadFile(
@@ -1027,6 +1027,8 @@ bool Panel::createWebview(const Webview::ThemeParams &params) {
processBottomBarColor(arguments);
} else if (command == "web_app_send_prepared_message") {
processSendMessageRequest(arguments);
} else if (command == "web_app_request_chat") {
processRequestChat(arguments);
} else if (command == "web_app_set_emoji_status") {
processEmojiStatusRequest(arguments);
} else if (command == "web_app_request_emoji_status_access") {
@@ -1214,6 +1216,34 @@ void Panel::processSendMessageRequest(const QJsonObject &args) {
});
}
void Panel::processRequestChat(const QJsonObject &args) {
if (args.isEmpty()) {
_delegate->botClose();
return;
}
const auto requestId = args["req_id"].toString();
if (requestId.isEmpty()) {
return;
}
auto callback = crl::guard(this, [=](QString error) {
if (error.isEmpty()) {
postEvent(
"requested_chat_sent",
u"{ req_id: \"%1\" }"_q.arg(requestId));
} else {
postEvent(
"requested_chat_failed",
u"{ req_id: \"%1\", error: \"%2\" }"_q.arg(
requestId,
error));
}
});
_delegate->botRequestChat({
.requestId = requestId,
.callback = std::move(callback),
});
}
void Panel::processEmojiStatusRequest(const QJsonObject &args) {
if (args.isEmpty()) {
_delegate->botClose();
@@ -79,6 +79,11 @@ struct SendPreparedMessageRequest {
Fn<void(QString)> callback;
};
struct RequestChatRequest {
QString requestId;
Fn<void(QString)> callback;
};
class Delegate {
public:
[[nodiscard]] virtual Webview::ThemeParams botThemeParams() = 0;
@@ -111,6 +116,7 @@ public:
virtual void botDownloadFile(DownloadFileRequest request) = 0;
virtual void botSendPreparedMessage(
SendPreparedMessageRequest request) = 0;
virtual void botRequestChat(RequestChatRequest request) = 0;
virtual void botVerifyAge(int age) = 0;
virtual void botOpenPrivacyPolicy() = 0;
virtual void botClose() = 0;
@@ -176,6 +182,7 @@ private:
void sendDataMessage(const QJsonObject &args);
void switchInlineQueryMessage(const QJsonObject &args);
void processSendMessageRequest(const QJsonObject &args);
void processRequestChat(const QJsonObject &args);
void processEmojiStatusRequest(const QJsonObject &args);
void processEmojiStatusAccessRequest();
void processStorageSaveKey(const QJsonObject &args);
+4
View File
@@ -1551,6 +1551,10 @@ newThreadAboutIconSkip: chatIntroTitleMargin.top;
newThreadAboutIconOuter: 60px;
newThreadAboutIcon: icon{{ "chat/new_thread-40x40", windowFgActive }};
managedBotCodeIcon: icon{{ "chat/code_tags", windowFg }};
managedBotImageWidth: 240px;
managedBotImageHeight: 120px;
stakeBox: Box(defaultBox) {
buttonPadding: margins(24px, 12px, 24px, 24px);
buttonHeight: 42px;