diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 9f74e8eff..f14d6aaad 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -442,23 +442,24 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } else if (_type == Type::BotCommands) { bool listAllSuggestions = _filter.isEmpty(); bool hasUsername = _filter.indexOf('@') > 0; - base::flat_set> bots; + base::flat_map< + not_null, + not_null*>> bots; int32 cnt = 0; if (_chat) { if (_chat->noParticipantInfo()) { _chat->session().api().requestFullPeer(_chat); } else if (!_chat->participants.empty()) { + const auto &commands = _chat->botCommands(); for (const auto user : _chat->participants) { if (!user->isBot()) { continue; - } else if (!user->botInfo->inited) { - user->session().api().requestFullPeer(user); } - if (user->botInfo->commands.empty()) { - continue; + const auto i = commands.find(peerToUser(user->id)); + if (i != end(commands)) { + bots.emplace(user, &i->second); + cnt += i->second.size(); } - bots.emplace(user); - cnt += user->botInfo->commands.size(); } } } else if (_user && _user->isBot()) { @@ -466,24 +467,23 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { _user->session().api().requestFullPeer(_user); } cnt = _user->botInfo->commands.size(); - bots.emplace(_user); + bots.emplace(_user, &_user->botInfo->commands); } else if (_channel && _channel->isMegagroup()) { if (_channel->mgInfo->bots.empty()) { if (!_channel->mgInfo->botStatus) { _channel->session().api().requestBots(_channel); } } else { + const auto &commands = _channel->mgInfo->botCommands(); for (const auto user : _channel->mgInfo->bots) { if (!user->isBot()) { continue; - } else if (!user->botInfo->inited) { - user->session().api().requestFullPeer(user); } - if (user->botInfo->commands.empty()) { - continue; + const auto i = commands.find(peerToUser(user->id)); + if (i != end(commands)) { + bots.emplace(user, &i->second); + cnt += i->second.size(); } - bots.emplace(user); - cnt += user->botInfo->commands.size(); } } } @@ -504,16 +504,12 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { for (const auto &user : _chat->lastAuthors) { if (!user->isBot()) { continue; - } else if (!bots.contains(user)) { - continue; - } else if (!user->botInfo->inited) { - user->session().api().requestFullPeer(user); } - if (user->botInfo->commands.empty()) { + const auto i = bots.find(user); + if (i == end(bots)) { continue; } - bots.remove(user); - for (const auto &command : user->botInfo->commands) { + for (const auto &command : *i->second) { if (!listAllSuggestions) { auto toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? command.command + '@' + user->username @@ -524,12 +520,13 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) { } brows.push_back(make(user, command)); } + bots.erase(i); } } if (!bots.empty()) { for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) { - const auto user = *i; - for (const auto &command : user->botInfo->commands) { + const auto user = i->first; + for (const auto &command : *i->second) { if (!listAllSuggestions) { QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? command.command + '@' + user->username : command.command; if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue; diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 8fbd8460e..805c7e311 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -46,6 +46,10 @@ void MegagroupInfo::setLocation(const ChannelLocation &location) { _location = location; } +bool MegagroupInfo::updateBotCommands(const MTPVector &data) { + return Data::UpdateBotCommands(_botCommands, data); +} + ChannelData::ChannelData(not_null owner, PeerId id) : PeerData(owner, id) , inputChannel( @@ -803,15 +807,6 @@ void ApplyChannelUpdate( const auto chat = channel->owner().chat(migratedFrom->v); Data::ApplyMigration(chat, channel); } - for (const auto &item : update.vbot_info().v) { - auto &owner = channel->owner(); - item.match([&](const MTPDbotInfo &info) { - if (const auto user = owner.userLoaded(info.vuser_id().v)) { - user->setBotInfo(item); - session->api().fullPeerUpdated().notify(user); - } - }); - } channel->setAbout(qs(update.vabout())); channel->setMembersCount(update.vparticipants_count().value_or_empty()); channel->setAdminsCount(update.vadmins_count().value_or_empty()); @@ -867,6 +862,9 @@ void ApplyChannelUpdate( SetTopPinnedMessageId(channel, pinned->v); } if (channel->isMegagroup()) { + if (channel->mgInfo->updateBotCommands(update.vbot_info())) { + channel->owner().botCommandsChanged(channel); + } const auto stickerSet = update.vstickerset(); const auto set = stickerSet ? &stickerSet->c_stickerSet() : nullptr; const auto newSetId = (set ? set->vid().v : 0); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 5987ef7a4..469deda18 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -56,6 +56,12 @@ public: const ChannelLocation *getLocation() const; void setLocation(const ChannelLocation &location); + bool updateBotCommands(const MTPVector &data); + [[nodiscard]] auto botCommands() const + -> const base::flat_map> & { + return _botCommands; + } + std::deque> lastParticipants; base::flat_map, Admin> lastAdmins; base::flat_map, Restricted> lastRestricted; @@ -82,6 +88,7 @@ public: private: ChatData *_migratedFrom = nullptr; ChannelLocation _location; + base::flat_map> _botCommands; }; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index e01bc6ed5..82f14af04 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -246,6 +246,12 @@ PeerId ChatData::groupCallDefaultJoinAs() const { return _callDefaultJoinAs; } +void ChatData::setBotCommands(const MTPVector &data) { + if (Data::UpdateBotCommands(_botCommands, data)) { + owner().botCommandsChanged(this); + } +} + namespace Data { void ApplyChatUpdate( @@ -393,15 +399,9 @@ void ApplyChatUpdate(not_null chat, const MTPDchatFull &update) { chat->setMessagesTTL(update.vttl_period().value_or_empty()); if (const auto info = update.vbot_info()) { - for (const auto &item : info->v) { - item.match([&](const MTPDbotInfo &data) { - const auto userId = data.vuser_id().v; - if (const auto bot = chat->owner().userLoaded(userId)) { - bot->setBotInfo(item); - chat->session().api().fullPeerUpdated().notify(bot); - } - }); - } + chat->setBotCommands(*info); + } else { + chat->setBotCommands(MTP_vector()); } chat->setFullFlags(update.vflags().v); if (const auto photo = update.vchat_photo()) { diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 0c7df890d..f84988805 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -173,6 +173,12 @@ public: void setGroupCallDefaultJoinAs(PeerId peerId); [[nodiscard]] PeerId groupCallDefaultJoinAs() const; + void setBotCommands(const MTPVector &data); + [[nodiscard]] auto botCommands() const + -> const base::flat_map> & { + return _botCommands; + } + // Still public data members. const MTPint inputChat; @@ -198,6 +204,7 @@ private: std::unique_ptr _call; PeerId _callDefaultJoinAs = 0; + base::flat_map> _botCommands; ChannelData *_migratedTo = nullptr; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index b9dbdb0b5..06ca47c95 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -84,6 +84,75 @@ PeerId FakePeerIdForJustName(const QString &name) { : base::crc32(name.constData(), name.size() * sizeof(QChar))); } +bool UpdateBotCommands( + std::vector &commands, + const MTPVector &data) { + const auto &v = data.v; + commands.reserve(v.size()); + auto result = false; + auto index = 0; + for (const auto &command : v) { + command.match([&](const MTPDbotCommand &data) { + const auto command = qs(data.vcommand()); + const auto description = qs(data.vdescription()); + if (commands.size() <= index) { + commands.push_back({ + .command = command, + .description = description, + }); + result = true; + } else { + auto &entry = commands[index]; + if (entry.command != command + || entry.description != description) { + entry.command = command; + entry.description = description; + result = true; + } + } + ++index; + }); + } + if (index < commands.size()) { + result = true; + } + commands.resize(index); + return result; +} + +bool UpdateBotCommands( + base::flat_map> &commands, + const MTPVector &data) { + auto result = false; + auto filled = base::flat_set(); + filled.reserve(data.v.size()); + for (const auto &item : data.v) { + item.match([&](const MTPDbotInfo &data) { + const auto id = UserId(data.vuser_id().v); + if (!filled.emplace(id).second) { + LOG(("API Error: Two BotInfo for a single bot.")); + return; + } + if (data.vcommands().v.isEmpty()) { + if (commands.remove(id)) { + result = true; + } + } else if (UpdateBotCommands(commands[id], data.vcommands())) { + result = true; + } + }); + } + for (auto i = begin(commands); i != end(commands);) { + if (filled.contains(i->first)) { + ++i; + } else { + i = commands.erase(i); + result = true; + } + } + return result; +} + } // namespace Data PeerClickHandler::PeerClickHandler(not_null peer) diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 6605440c7..bfa9df284 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -22,6 +22,11 @@ using ChatRestriction = MTPDchatBannedRights::Flag; using ChatAdminRights = MTPDchatAdminRights::Flags; using ChatRestrictions = MTPDchatBannedRights::Flags; +struct BotCommand { + QString command; + QString description; +}; + namespace Ui { class EmptyUserpic; } // namespace Ui @@ -100,6 +105,13 @@ struct UnavailableReason { [[nodiscard]] TimeId ChatBannedRightsUntilDate( const MTPChatBannedRights &rights); +bool UpdateBotCommands( + std::vector &commands, + const MTPVector &data); +bool UpdateBotCommands( + base::flat_map> &commands, + const MTPVector &data); + } // namespace Data class PeerClickHandler : public ClickHandler { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 5c139ec97..fca539d24 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1088,11 +1088,11 @@ rpl::producer> Session::userIsBotChanges() const { return _userIsBotChanges.events(); } -void Session::botCommandsChanged(not_null user) { - _botCommandsChanges.fire_copy(user); +void Session::botCommandsChanged(not_null peer) { + _botCommandsChanges.fire_copy(peer); } -rpl::producer> Session::botCommandsChanges() const { +rpl::producer> Session::botCommandsChanges() const { return _botCommandsChanges.events(); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index bda612c6f..45547f46d 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -215,8 +215,8 @@ public: void userIsBotChanged(not_null user); [[nodiscard]] rpl::producer> userIsBotChanges() const; - void botCommandsChanged(not_null user); - [[nodiscard]] rpl::producer> botCommandsChanges() const; + void botCommandsChanged(not_null peer); + [[nodiscard]] rpl::producer> botCommandsChanges() const; struct ItemVisibilityQuery { not_null item; @@ -831,7 +831,7 @@ private: rpl::event_stream _chatsListLoadedEvents; rpl::event_stream _chatsListChanged; rpl::event_stream> _userIsBotChanges; - rpl::event_stream> _botCommandsChanges; + rpl::event_stream> _botCommandsChanges; base::Observable _queryItemVisibility; rpl::event_stream _itemIdChanges; rpl::event_stream> _itemLayoutChanges; diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index d075d53f4..9cc9a2c65 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -129,38 +129,9 @@ void UserData::setBotInfo(const MTPBotInfo &info) { botInfo->description = desc; botInfo->text = Ui::Text::String(st::msgMinWidth); } - - auto &v = d.vcommands().v; - botInfo->commands.reserve(v.size()); - auto changedCommands = false; - int32 j = 0; - for (const auto &command : v) { - command.match([&](const MTPDbotCommand &data) { - const auto command = qs(data.vcommand()); - const auto description = qs(data.vdescription()); - if (botInfo->commands.size() <= j) { - botInfo->commands.push_back({ - .command = command, - .description = description, - }); - changedCommands = true; - } else { - if (botInfo->commands[j].command != command) { - botInfo->commands[j].command = command; - changedCommands = true; - } - if (botInfo->commands[j].description != description) { - botInfo->commands[j].description = description; - changedCommands = true; - } - } - ++j; - }); - } - while (j < botInfo->commands.size()) { - botInfo->commands.pop_back(); - changedCommands = true; - } + const auto changedCommands = Data::UpdateBotCommands( + botInfo->commands, + d.vcommands()); botInfo->inited = true; diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 63a654b91..0bff340b2 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -10,11 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "dialogs/dialogs_key.h" -struct BotCommand { - QString command; - QString description; -}; - struct BotInfo { bool inited = false; bool readsAllHistory = false; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 91833a8f4..a2b7946a6 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -489,9 +489,9 @@ HistoryWidget::HistoryWidget( }, lifetime()); session().data().botCommandsChanges( - ) | rpl::filter([=](not_null user) { - return _peer && (_peer == user || !_peer->isUser()); - }) | rpl::start_with_next([=](not_null user) { + ) | rpl::filter([=](not_null peer) { + return _peer && (_peer == peer); + }) | rpl::start_with_next([=] { if (_fieldAutocomplete->clearFilteredBotCommands()) { checkFieldAutocomplete(); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 01809184b..4380894ea 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -1234,10 +1234,9 @@ void ComposeControls::initAutocomplete() { _field->rawTextEdit()->installEventFilter(_autocomplete.get()); _window->session().data().botCommandsChanges( - ) | rpl::filter([=](not_null user) { - const auto peer = _history ? _history->peer.get() : nullptr; - return peer && (peer == user || !peer->isUser()); - }) | rpl::start_with_next([=](not_null user) { + ) | rpl::filter([=](not_null peer) { + return _history && (_history->peer == peer); + }) | rpl::start_with_next([=] { if (_autocomplete->clearFilteredBotCommands()) { checkAutocomplete(); } diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 78f8d0f0b..47721a2e8 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -515,10 +515,10 @@ void ActionsFiller::addBotCommandActions(not_null user) { if (!user->isBot()) { return QString(); } - for_const (auto &data, user->botInfo->commands) { - auto isSame = data.command.compare( + for (const auto &data : user->botInfo->commands) { + const auto isSame = !data.command.compare( command, - Qt::CaseInsensitive) == 0; + Qt::CaseInsensitive); if (isSame) { return data.command; }