Track bot commands separately in different chats.

This commit is contained in:
John Preston 2021-07-01 14:05:15 +03:00
parent 93d99d6173
commit b930bc0e6d
14 changed files with 149 additions and 94 deletions

View file

@ -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<not_null<UserData*>> bots;
base::flat_map<
not_null<UserData*>,
not_null<const std::vector<BotCommand>*>> 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;

View file

@ -46,6 +46,10 @@ void MegagroupInfo::setLocation(const ChannelLocation &location) {
_location = location;
}
bool MegagroupInfo::updateBotCommands(const MTPVector<MTPBotInfo> &data) {
return Data::UpdateBotCommands(_botCommands, data);
}
ChannelData::ChannelData(not_null<Data::Session*> 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);

View file

@ -56,6 +56,12 @@ public:
const ChannelLocation *getLocation() const;
void setLocation(const ChannelLocation &location);
bool updateBotCommands(const MTPVector<MTPBotInfo> &data);
[[nodiscard]] auto botCommands() const
-> const base::flat_map<UserId, std::vector<BotCommand>> & {
return _botCommands;
}
std::deque<not_null<UserData*>> lastParticipants;
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
@ -82,6 +88,7 @@ public:
private:
ChatData *_migratedFrom = nullptr;
ChannelLocation _location;
base::flat_map<UserId, std::vector<BotCommand>> _botCommands;
};

View file

@ -246,6 +246,12 @@ PeerId ChatData::groupCallDefaultJoinAs() const {
return _callDefaultJoinAs;
}
void ChatData::setBotCommands(const MTPVector<MTPBotInfo> &data) {
if (Data::UpdateBotCommands(_botCommands, data)) {
owner().botCommandsChanged(this);
}
}
namespace Data {
void ApplyChatUpdate(
@ -393,15 +399,9 @@ void ApplyChatUpdate(not_null<ChatData*> 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<MTPBotInfo>());
}
chat->setFullFlags(update.vflags().v);
if (const auto photo = update.vchat_photo()) {

View file

@ -173,6 +173,12 @@ public:
void setGroupCallDefaultJoinAs(PeerId peerId);
[[nodiscard]] PeerId groupCallDefaultJoinAs() const;
void setBotCommands(const MTPVector<MTPBotInfo> &data);
[[nodiscard]] auto botCommands() const
-> const base::flat_map<UserId, std::vector<BotCommand>> & {
return _botCommands;
}
// Still public data members.
const MTPint inputChat;
@ -198,6 +204,7 @@ private:
std::unique_ptr<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0;
base::flat_map<UserId, std::vector<BotCommand>> _botCommands;
ChannelData *_migratedTo = nullptr;
rpl::lifetime _lifetime;

View file

@ -84,6 +84,75 @@ PeerId FakePeerIdForJustName(const QString &name) {
: base::crc32(name.constData(), name.size() * sizeof(QChar)));
}
bool UpdateBotCommands(
std::vector<BotCommand> &commands,
const MTPVector<MTPBotCommand> &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<UserId, std::vector<BotCommand>> &commands,
const MTPVector<MTPBotInfo> &data) {
auto result = false;
auto filled = base::flat_set<UserId>();
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<PeerData*> peer)

View file

@ -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<BotCommand> &commands,
const MTPVector<MTPBotCommand> &data);
bool UpdateBotCommands(
base::flat_map<UserId, std::vector<BotCommand>> &commands,
const MTPVector<MTPBotInfo> &data);
} // namespace Data
class PeerClickHandler : public ClickHandler {

View file

@ -1088,11 +1088,11 @@ rpl::producer<not_null<UserData*>> Session::userIsBotChanges() const {
return _userIsBotChanges.events();
}
void Session::botCommandsChanged(not_null<UserData*> user) {
_botCommandsChanges.fire_copy(user);
void Session::botCommandsChanged(not_null<PeerData*> peer) {
_botCommandsChanges.fire_copy(peer);
}
rpl::producer<not_null<UserData*>> Session::botCommandsChanges() const {
rpl::producer<not_null<PeerData*>> Session::botCommandsChanges() const {
return _botCommandsChanges.events();
}

View file

@ -215,8 +215,8 @@ public:
void userIsBotChanged(not_null<UserData*> user);
[[nodiscard]] rpl::producer<not_null<UserData*>> userIsBotChanges() const;
void botCommandsChanged(not_null<UserData*> user);
[[nodiscard]] rpl::producer<not_null<UserData*>> botCommandsChanges() const;
void botCommandsChanged(not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<not_null<PeerData*>> botCommandsChanges() const;
struct ItemVisibilityQuery {
not_null<HistoryItem*> item;
@ -831,7 +831,7 @@ private:
rpl::event_stream<Data::Folder*> _chatsListLoadedEvents;
rpl::event_stream<Data::Folder*> _chatsListChanged;
rpl::event_stream<not_null<UserData*>> _userIsBotChanges;
rpl::event_stream<not_null<UserData*>> _botCommandsChanges;
rpl::event_stream<not_null<PeerData*>> _botCommandsChanges;
base::Observable<ItemVisibilityQuery> _queryItemVisibility;
rpl::event_stream<IdChange> _itemIdChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;

View file

@ -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;

View file

@ -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;

View file

@ -489,9 +489,9 @@ HistoryWidget::HistoryWidget(
}, lifetime());
session().data().botCommandsChanges(
) | rpl::filter([=](not_null<UserData*> user) {
return _peer && (_peer == user || !_peer->isUser());
}) | rpl::start_with_next([=](not_null<UserData*> user) {
) | rpl::filter([=](not_null<PeerData*> peer) {
return _peer && (_peer == peer);
}) | rpl::start_with_next([=] {
if (_fieldAutocomplete->clearFilteredBotCommands()) {
checkFieldAutocomplete();
}

View file

@ -1234,10 +1234,9 @@ void ComposeControls::initAutocomplete() {
_field->rawTextEdit()->installEventFilter(_autocomplete.get());
_window->session().data().botCommandsChanges(
) | rpl::filter([=](not_null<UserData*> user) {
const auto peer = _history ? _history->peer.get() : nullptr;
return peer && (peer == user || !peer->isUser());
}) | rpl::start_with_next([=](not_null<UserData*> user) {
) | rpl::filter([=](not_null<PeerData*> peer) {
return _history && (_history->peer == peer);
}) | rpl::start_with_next([=] {
if (_autocomplete->clearFilteredBotCommands()) {
checkAutocomplete();
}

View file

@ -515,10 +515,10 @@ void ActionsFiller::addBotCommandActions(not_null<UserData*> 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;
}