mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Initial support for managed bot creation.
This commit is contained in:
@@ -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 |
@@ -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.";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()))) {
|
||||
|
||||
@@ -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+)"
|
||||
"("
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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 ¶ms) {
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
Submodule Telegram/lib_ui updated: a0d830f2bd...bcd1aaf92c
Reference in New Issue
Block a user