From d455d6ee32c0aebf5b68a780bf927444f91fdf06 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 21 Mar 2026 00:12:50 +0700 Subject: [PATCH] Improve managed bot creation. --- Telegram/Resources/langs/lang.strings | 4 + Telegram/SourceFiles/api/api_bot.cpp | 11 +++ Telegram/SourceFiles/api/api_updates.cpp | 12 --- .../boxes/peers/choose_peer_box.cpp | 20 ++++- .../SourceFiles/boxes/peers/choose_peer_box.h | 6 +- .../boxes/peers/create_managed_bot_box.cpp | 77 ++++++++++--------- .../boxes/peers/create_managed_bot_box.h | 3 +- .../core/deep_links/deep_links_new.cpp | 14 +++- .../SourceFiles/core/local_url_handlers.cpp | 18 +++++ .../history/view/history_view_about_view.cpp | 30 +++++++- .../info/profile/info_profile_actions.cpp | 1 + .../inline_bots/bot_attach_web_view.cpp | 17 +++- Telegram/SourceFiles/ui/chat/chat.style | 4 +- 13 files changed, 154 insertions(+), 63 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8542aad728..740e34464a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2557,6 +2557,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_managed_bot_created_title" = "{name} created!"; +"lng_managed_bot_created_text" = "{parent_name} will manage this bot for you."; +"lng_managed_bot_edit_photo" = "You can edit your bot's profile picture {link}"; +"lng_managed_bot_edit_photo_link" = "here {arrow}"; "lng_stake_game_title" = "Emoji Stake"; "lng_stake_game_beta" = "Beta"; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index ea764b8e71..46319947ac 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -585,6 +585,17 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { ).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); }).send(); + controller->showPeerHistory(createdBot); + controller->showToast({ + .title = tr::lng_managed_bot_created_title( + tr::now, + lt_name, + createdBot->name()), + .text = { tr::lng_managed_bot_created_text( + tr::now, + lt_parent_name, + bot->name()) }, + }); }, }); } break; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index de28a78580..bbb0ab721a 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2111,18 +2111,6 @@ 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()))) { diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp index 3d55b4aa12..ffa98e35d5 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp @@ -503,19 +503,22 @@ void ShowChoosePeerBox( not_null navigation, not_null bot, RequestPeerQuery query, - Fn>)> chosen) { + Fn>)> chosen, + Fn cancelled) { ShowChoosePeerBox( navigation->uiShow(), bot, query, - std::move(chosen)); + std::move(chosen), + std::move(cancelled)); } void ShowChoosePeerBox( std::shared_ptr show, not_null bot, RequestPeerQuery query, - Fn>)> chosen) { + Fn>)> chosen, + Fn cancelled) { const auto session = &show->session(); const auto needCommonGroups = query.isBotParticipant && (query.type == RequestPeerQuery::Type::Group) @@ -524,14 +527,16 @@ void ShowChoosePeerBox( const auto weak = std::weak_ptr(show); session->api().requestBotCommonGroups(bot, [=] { if (const auto strong = weak.lock()) { - ShowChoosePeerBox(strong, bot, query, chosen); + ShowChoosePeerBox(strong, bot, query, chosen, cancelled); } }); return; } const auto weak = std::make_shared>(); + const auto sent = std::make_shared(false); auto callback = [=, done = std::move(chosen)]( std::vector> peers) { + *sent = true; done(std::move(peers)); if (const auto strong = weak->get()) { strong->closeBox(); @@ -565,4 +570,11 @@ void ShowChoosePeerBox( *weak = show->show(Box( std::move(controller), std::move(initBox))); + if (const auto strong = weak->get()) { + strong->boxClosing() | rpl::on_next([=] { + if (!*sent && cancelled) { + cancelled(); + } + }, strong->lifetime()); + } } diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h index 3ba5268cef..890a94f534 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h @@ -26,10 +26,12 @@ void ShowChoosePeerBox( not_null navigation, not_null bot, RequestPeerQuery query, - Fn>)> chosen); + Fn>)> chosen, + Fn cancelled = nullptr); void ShowChoosePeerBox( std::shared_ptr show, not_null bot, RequestPeerQuery query, - Fn>)> chosen); + Fn>)> chosen, + Fn cancelled = nullptr); diff --git a/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.cpp b/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.cpp index 68d6cef02d..dd31605904 100644 --- a/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.cpp @@ -35,11 +35,13 @@ void CreateManagedBotBox( QString checkUsername; QString errorText; QString goodText; + bool created = false; }; const auto show = descriptor.show; const auto session = &show->session(); const auto viaDeeplink = descriptor.viaDeeplink; const auto done = std::move(descriptor.done); + const auto cancelled = std::move(descriptor.cancelled); const auto manager = descriptor.manager; const auto api = box->lifetime().make_state( @@ -53,17 +55,15 @@ void CreateManagedBotBox( box->closeBox(); }); - if (manager) { - auto userpic = object_ptr( - box, - manager, - st::defaultUserpicButton); - userpic->setAttribute(Qt::WA_TransparentForMouseEvents); - box->addRow( - std::move(userpic), - st::boxRowPadding + st::createBotUserpicPadding, - style::al_top); - } + auto userpic = object_ptr( + 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( @@ -73,19 +73,17 @@ void CreateManagedBotBox( st::boxRowPadding + st::createBotTitlePadding, style::al_top); - if (manager) { - box->addRow( - object_ptr( - 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); - } + box->addRow( + object_ptr( + 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( box, @@ -100,11 +98,11 @@ void CreateManagedBotBox( const auto botSuffixText = u"bot"_q; auto initialUsername = descriptor.suggestedUsername; - if (initialUsername.startsWith(botPrefixText)) { + while (initialUsername.startsWith(botPrefixText)) { initialUsername.remove(0, botPrefixText.size()); } if (initialUsername.endsWith(botSuffixText, Qt::CaseInsensitive)) { - initialUsername.chop(3); + initialUsername.chop(botSuffixText.size()); } const auto fieldSt = box->lifetime().make_state( @@ -201,10 +199,10 @@ void CreateManagedBotBox( api->request(base::take(state->checkRequestId)).cancel(); auto raw = username->getLastText().trimmed(); - if (raw.startsWith(QChar('@'))) { - raw = raw.mid(1); + while (raw.startsWith(botPrefixText)) { + raw = raw.mid(botPrefixText.size()); } - const auto value = raw + u"bot"_q; + const auto value = raw + botSuffixText; if (value.size() < Ui::EditPeer::kMinUsernameLength) { return; } @@ -252,7 +250,7 @@ void CreateManagedBotBox( } } - if ((value + u"bot"_q).size() < Ui::EditPeer::kMinUsernameLength) { + if ((value + botSuffixText).size() < Ui::EditPeer::kMinUsernameLength) { setError(tr::lng_create_bot_username_too_short(tr::now)); return; } @@ -273,10 +271,10 @@ void CreateManagedBotBox( } auto rawUsername = username->getLastText().trimmed(); - if (rawUsername.startsWith(QChar('@'))) { - rawUsername = rawUsername.mid(1); + while (rawUsername.startsWith(botPrefixText)) { + rawUsername = rawUsername.mid(botPrefixText.size()); } - const auto usernameValue = rawUsername + u"bot"_q; + const auto usernameValue = rawUsername + botSuffixText; if (rawUsername.isEmpty()) { username->showError(); return; @@ -289,19 +287,16 @@ void CreateManagedBotBox( 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 + manager->inputUser() )).done([=](const MTPUser &result) { state->createRequestId = 0; const auto user = session->data().processUser(result); + state->created = true; if (done) { done(user); } @@ -339,6 +334,12 @@ void CreateManagedBotBox( if (!descriptor.suggestedUsername.isEmpty()) { usernameChanged(); } + + box->boxClosing() | rpl::on_next([=] { + if (!state->created && cancelled) { + cancelled(); + } + }, box->lifetime()); } void ShowCreateManagedBotBox(CreateManagedBotDescriptor &&descriptor) { diff --git a/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.h b/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.h index d7bb08f334..56dd50241b 100644 --- a/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.h +++ b/Telegram/SourceFiles/boxes/peers/create_managed_bot_box.h @@ -19,11 +19,12 @@ class GenericBox; struct CreateManagedBotDescriptor { std::shared_ptr show; - UserData *manager = nullptr; + not_null manager; QString suggestedName; QString suggestedUsername; bool viaDeeplink = false; Fn)> done; + Fn cancelled; }; void CreateManagedBotBox( diff --git a/Telegram/SourceFiles/core/deep_links/deep_links_new.cpp b/Telegram/SourceFiles/core/deep_links/deep_links_new.cpp index 3659cac18e..fd3407102b 100644 --- a/Telegram/SourceFiles/core/deep_links/deep_links_new.cpp +++ b/Telegram/SourceFiles/core/deep_links/deep_links_new.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer_id.h" #include "data/data_session.h" #include "data/data_user.h" +#include "lang/lang_keys.h" #include "main/main_session.h" +#include "ui/toast/toast.h" #include "window/window_session_controller.h" namespace Core::DeepLinks { @@ -78,9 +80,19 @@ Result ShowNewBot(const Context &ctx) { .suggestedName = title, .suggestedUsername = username, .viaDeeplink = true, - .done = [weak](not_null createdBot) { + .done = [weak, managerBot](not_null createdBot) { if (const auto strong = weak.get()) { strong->showPeerHistory(createdBot); + strong->showToast({ + .title = tr::lng_managed_bot_created_title( + tr::now, + lt_name, + createdBot->name()), + .text = { tr::lng_managed_bot_created_text( + tr::now, + lt_parent_name, + managerBot->name()) }, + }); } }, }); diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index fa1e04bfe0..d1567d8929 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -1175,6 +1175,20 @@ bool ShowCommonGroups( return true; } +bool EditPeer( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto peerId = PeerId(match->captured(1).toULongLong()); + if (const auto peer = controller->session().data().peerLoaded(peerId)) { + controller->showEditPeerBox(peer); + } + return true; +} + bool ShowStarsExamples( Window::SessionController *controller, const Match &match, @@ -1787,6 +1801,10 @@ const std::vector &InternalUrlHandlers() { u"^common_groups/([0-9]+)$"_q, ShowCommonGroups, }, + { + u"^edit_peer/([0-9]+)$"_q, + EditPeer, + }, { u"^stars_examples$"_q, ShowStarsExamples, diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index 3bd6f439a3..9a2b8a15f7 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -789,11 +789,22 @@ bool AboutView::refresh() { } setItem(makeNewBotThread(), nullptr); return true; - } else if (user->botManagerId() && info->description.isEmpty()) { + } else if (user->botManagerId() + && info + && info->description.isEmpty() + && info->canEditInformation + && _history->isEmpty() + && !_history->lastMessage()) { if (_item) { return false; } setItem(makeManagedBotInfo(user), nullptr); + _history->session().data().newItemAdded( + ) | rpl::on_next([=](not_null item) { + if (item->history() == _history) { + _destroyRequests.fire({}); + } + }, lifetime()); return true; } const auto version = info->descriptionVersion; @@ -1179,7 +1190,7 @@ AdminLog::OwnedItem AboutView::makeManagedBotInfo( const auto parentName = managerUser ? managerUser->name() : QString(); - const auto text = tr::lng_managed_bot_ready( + auto text = tr::lng_managed_bot_ready( tr::now, lt_name, tr::bold(user->name()), @@ -1187,6 +1198,21 @@ AdminLog::OwnedItem AboutView::makeManagedBotInfo( tr::bold(parentName), tr::rich); + const auto url = u"internal:edit_peer/"_q + + QString::number(user->id.value); + text.append(u"\n\n"_q).append(tr::lng_managed_bot_edit_photo( + tr::now, + lt_link, + Ui::Text::Wrapped( + tr::lng_managed_bot_edit_photo_link( + tr::now, + lt_arrow, + Ui::Text::IconEmoji(&st::textMoreIconEmoji), + tr::rich), + EntityType::CustomUrl, + url), + tr::rich)); + return makeAboutSimple(text, nullptr, photo); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index ebd134c918..edf4d836bc 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -2400,6 +2400,7 @@ object_ptr DetailsFiller::fill() { const auto parts = (info->hasMainApp && !info->canManageEmojiStatus) ? RectPart::Bottom : (RectPart::Top | RectPart::Bottom); + Ui::AddSkip(_wrap); const auto divider = Ui::AddDividerText( _wrap, tr::lng_managed_bot_label( diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp index b4570a5f8d..66b2862e3d 100644 --- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp +++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp @@ -2033,11 +2033,26 @@ void WebViewInstance::botRequestChat( .value_or_empty()), .done = [=](not_null createdBot) { sendPeers({ createdBot }); + show->showBox(Ui::MakeInformBox({ + .text = tr::lng_managed_bot_created_text( + tr::now, + lt_parent_name, + bot->name()), + .title = tr::lng_managed_bot_created_title( + tr::now, + lt_name, + createdBot->name()), + })); + }, + .cancelled = [=] { + callback(u"USER_DECLINED"_q); }, }); }, [&](const auto &) { const auto query = RequestPeerQueryFromTL(data); - ShowChoosePeerBox(show, bot, query, sendPeers); + ShowChoosePeerBox(show, bot, query, sendPeers, [=] { + callback(u"USER_DECLINED"_q); + }); }); }, [&](const auto &) { callback(u"UNSUPPORTED_BUTTON_TYPE"_q); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index ccba464478..0bc566b358 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1552,8 +1552,8 @@ newThreadAboutIconOuter: 60px; newThreadAboutIcon: icon{{ "chat/new_thread-40x40", windowFgActive }}; managedBotCodeIcon: icon{{ "chat/code_tags", windowFg }}; -managedBotImageWidth: 240px; -managedBotImageHeight: 120px; +managedBotImageWidth: 280px; +managedBotImageHeight: 140px; stakeBox: Box(defaultBox) { buttonPadding: margins(24px, 12px, 24px, 24px);