init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
786
Telegram/SourceFiles/data/business/data_shortcut_messages.cpp
Normal file
786
Telegram/SourceFiles/data/business/data_shortcut_messages.cpp
Normal file
@@ -0,0 +1,786 @@
|
||||
/*
|
||||
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 "data/business/data_shortcut_messages.h"
|
||||
|
||||
#include "api/api_hash.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
|
||||
|
||||
[[nodiscard]] MsgId RemoteToLocalMsgId(MsgId id) {
|
||||
Expects(IsServerMsgId(id));
|
||||
|
||||
return ScheduledMaxMsgId + id + 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] MsgId LocalToRemoteMsgId(MsgId id) {
|
||||
Expects(IsShortcutMsgId(id));
|
||||
|
||||
return (id - ScheduledMaxMsgId - 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
|
||||
return (received > 0) && (received + kRequestTimeLimit > crl::now());
|
||||
}
|
||||
|
||||
[[nodiscard]] MTPMessage PrepareMessage(
|
||||
BusinessShortcutId shortcutId,
|
||||
const MTPMessage &message) {
|
||||
return message.match([&](const MTPDmessageEmpty &data) {
|
||||
return MTP_messageEmpty(
|
||||
data.vflags(),
|
||||
data.vid(),
|
||||
data.vpeer_id() ? *data.vpeer_id() : MTPPeer());
|
||||
}, [&](const MTPDmessageService &data) {
|
||||
return MTP_messageService(
|
||||
data.vflags(),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
data.vpeer_id(),
|
||||
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
data.vdate(),
|
||||
data.vaction(),
|
||||
data.vreactions() ? *data.vreactions() : MTPMessageReactions(),
|
||||
MTP_int(data.vttl_period().value_or_empty()));
|
||||
}, [&](const MTPDmessage &data) {
|
||||
return MTP_message(
|
||||
MTP_flags(data.vflags().v
|
||||
| MTPDmessage::Flag::f_quick_reply_shortcut_id),
|
||||
data.vid(),
|
||||
data.vfrom_id() ? *data.vfrom_id() : MTPPeer(),
|
||||
MTPint(), // from_boosts_applied
|
||||
data.vpeer_id(),
|
||||
data.vsaved_peer_id() ? *data.vsaved_peer_id() : MTPPeer(),
|
||||
data.vfwd_from() ? *data.vfwd_from() : MTPMessageFwdHeader(),
|
||||
MTP_long(data.vvia_bot_id().value_or_empty()),
|
||||
MTP_long(data.vvia_business_bot_id().value_or_empty()),
|
||||
data.vreply_to() ? *data.vreply_to() : MTPMessageReplyHeader(),
|
||||
data.vdate(),
|
||||
data.vmessage(),
|
||||
data.vmedia() ? *data.vmedia() : MTPMessageMedia(),
|
||||
data.vreply_markup() ? *data.vreply_markup() : MTPReplyMarkup(),
|
||||
(data.ventities()
|
||||
? *data.ventities()
|
||||
: MTPVector<MTPMessageEntity>()),
|
||||
MTP_int(data.vviews().value_or_empty()),
|
||||
MTP_int(data.vforwards().value_or_empty()),
|
||||
data.vreplies() ? *data.vreplies() : MTPMessageReplies(),
|
||||
MTP_int(data.vedit_date().value_or_empty()),
|
||||
MTP_bytes(data.vpost_author().value_or_empty()),
|
||||
MTP_long(data.vgrouped_id().value_or_empty()),
|
||||
MTPMessageReactions(),
|
||||
MTPVector<MTPRestrictionReason>(),
|
||||
MTP_int(data.vttl_period().value_or_empty()),
|
||||
MTP_int(shortcutId),
|
||||
MTP_long(data.veffect().value_or_empty()),
|
||||
(data.vfactcheck() ? *data.vfactcheck() : MTPFactCheck()),
|
||||
MTP_int(data.vreport_delivery_until_date().value_or_empty()),
|
||||
MTP_long(data.vpaid_message_stars().value_or_empty()),
|
||||
(data.vsuggested_post()
|
||||
? *data.vsuggested_post()
|
||||
: MTPSuggestedPost()),
|
||||
MTP_int(data.vschedule_repeat_period().value_or_empty()));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsShortcutMsgId(MsgId id) {
|
||||
return (id > ScheduledMaxMsgId) && (id < ShortcutMaxMsgId);
|
||||
}
|
||||
|
||||
ShortcutMessages::ShortcutMessages(not_null<Session*> owner)
|
||||
: _session(&owner->session())
|
||||
, _history(owner->history(_session->userPeerId()))
|
||||
, _clearTimer([=] { clearOldRequests(); }) {
|
||||
owner->itemRemoved(
|
||||
) | rpl::filter([](not_null<const HistoryItem*> item) {
|
||||
return item->isBusinessShortcut();
|
||||
}) | rpl::on_next([=](not_null<const HistoryItem*> item) {
|
||||
remove(item);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
ShortcutMessages::~ShortcutMessages() {
|
||||
for (const auto &request : _requests) {
|
||||
_session->api().request(request.second.requestId).cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::clearOldRequests() {
|
||||
const auto now = crl::now();
|
||||
while (true) {
|
||||
const auto i = ranges::find_if(_requests, [&](const auto &value) {
|
||||
const auto &request = value.second;
|
||||
return !request.requestId
|
||||
&& (request.lastReceived + kRequestTimeLimit <= now);
|
||||
});
|
||||
if (i == end(_requests)) {
|
||||
break;
|
||||
}
|
||||
_requests.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::updateShortcuts(const QVector<MTPQuickReply> &list) {
|
||||
auto shortcuts = parseShortcuts(list);
|
||||
auto changes = std::vector<ShortcutIdChange>();
|
||||
for (auto &[id, shortcut] : _shortcuts.list) {
|
||||
if (shortcuts.list.contains(id)) {
|
||||
continue;
|
||||
}
|
||||
auto foundId = BusinessShortcutId();
|
||||
for (auto &[realId, real] : shortcuts.list) {
|
||||
if (real.name == shortcut.name) {
|
||||
foundId = realId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundId) {
|
||||
mergeMessagesFromTo(id, foundId);
|
||||
changes.push_back({ .oldId = id, .newId = foundId });
|
||||
} else {
|
||||
shortcuts.list.emplace(id, shortcut);
|
||||
}
|
||||
}
|
||||
const auto changed = !_shortcutsLoaded
|
||||
|| (shortcuts != _shortcuts);
|
||||
if (changed) {
|
||||
_shortcuts = std::move(shortcuts);
|
||||
_shortcutsLoaded = true;
|
||||
for (const auto &change : changes) {
|
||||
_shortcutIdChanges.fire_copy(change);
|
||||
}
|
||||
_shortcutsChanged.fire({});
|
||||
} else {
|
||||
Assert(changes.empty());
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::mergeMessagesFromTo(
|
||||
BusinessShortcutId fromId,
|
||||
BusinessShortcutId toId) {
|
||||
auto &to = _data[toId];
|
||||
const auto i = _data.find(fromId);
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &from = i->second;
|
||||
auto destroy = base::flat_set<not_null<HistoryItem*>>();
|
||||
for (auto &item : from.items) {
|
||||
if (item->isSending() || item->hasFailed()) {
|
||||
item->setRealShortcutId(toId);
|
||||
to.items.push_back(std::move(item));
|
||||
} else {
|
||||
destroy.emplace(item.get());
|
||||
}
|
||||
}
|
||||
for (const auto &item : destroy) {
|
||||
item->destroy();
|
||||
}
|
||||
_data.remove(fromId);
|
||||
|
||||
cancelRequest(fromId);
|
||||
|
||||
_updates.fire_copy(toId);
|
||||
if (!destroy.empty()) {
|
||||
cancelRequest(toId);
|
||||
request(toId);
|
||||
}
|
||||
}
|
||||
|
||||
Shortcuts ShortcutMessages::parseShortcuts(
|
||||
const QVector<MTPQuickReply> &list) const {
|
||||
auto result = Shortcuts();
|
||||
for (const auto &reply : list) {
|
||||
const auto shortcut = parseShortcut(reply);
|
||||
result.list.emplace(shortcut.id, shortcut);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Shortcut ShortcutMessages::parseShortcut(const MTPQuickReply &reply) const {
|
||||
const auto &data = reply.data();
|
||||
return Shortcut{
|
||||
.id = BusinessShortcutId(data.vshortcut_id().v),
|
||||
.count = data.vcount().v,
|
||||
.name = qs(data.vshortcut()),
|
||||
.topMessageId = localMessageId(data.vtop_message().v),
|
||||
};
|
||||
}
|
||||
|
||||
MsgId ShortcutMessages::localMessageId(MsgId remoteId) const {
|
||||
return RemoteToLocalMsgId(remoteId);
|
||||
}
|
||||
|
||||
MsgId ShortcutMessages::lookupId(not_null<const HistoryItem*> item) const {
|
||||
Expects(item->isBusinessShortcut());
|
||||
Expects(!item->isSending());
|
||||
Expects(!item->hasFailed());
|
||||
|
||||
return LocalToRemoteMsgId(item->id);
|
||||
}
|
||||
|
||||
int ShortcutMessages::count(BusinessShortcutId shortcutId) const {
|
||||
const auto i = _data.find(shortcutId);
|
||||
return (i != end(_data)) ? i->second.items.size() : 0;
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(const MTPDupdateQuickReplies &update) {
|
||||
updateShortcuts(update.vquick_replies().v);
|
||||
scheduleShortcutsReload();
|
||||
}
|
||||
|
||||
void ShortcutMessages::scheduleShortcutsReload() {
|
||||
const auto hasUnknownMessages = [&] {
|
||||
const auto selfId = _session->userPeerId();
|
||||
for (const auto &[id, shortcut] : _shortcuts.list) {
|
||||
if (!_session->data().message({ selfId, shortcut.topMessageId })) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (hasUnknownMessages()) {
|
||||
_shortcutsLoaded = false;
|
||||
const auto cancelledId = base::take(_shortcutsRequestId);
|
||||
_session->api().request(cancelledId).cancel();
|
||||
crl::on_main(_session, [=] {
|
||||
if (cancelledId || hasUnknownMessages()) {
|
||||
preloadShortcuts();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(const MTPDupdateNewQuickReply &update) {
|
||||
const auto &reply = update.vquick_reply();
|
||||
auto foundId = BusinessShortcutId();
|
||||
const auto shortcut = parseShortcut(reply);
|
||||
for (auto &[id, existing] : _shortcuts.list) {
|
||||
if (id == shortcut.id) {
|
||||
foundId = id;
|
||||
break;
|
||||
} else if (existing.name == shortcut.name) {
|
||||
foundId = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundId == shortcut.id) {
|
||||
auto &already = _shortcuts.list[shortcut.id];
|
||||
if (already != shortcut) {
|
||||
already = shortcut;
|
||||
_shortcutsChanged.fire({});
|
||||
}
|
||||
return;
|
||||
} else if (foundId) {
|
||||
_shortcuts.list.emplace(shortcut.id, shortcut);
|
||||
mergeMessagesFromTo(foundId, shortcut.id);
|
||||
_shortcuts.list.remove(foundId);
|
||||
_shortcutIdChanges.fire({ foundId, shortcut.id });
|
||||
_shortcutsChanged.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(const MTPDupdateQuickReplyMessage &update) {
|
||||
const auto &message = update.vmessage();
|
||||
const auto shortcutId = BusinessShortcutIdFromMessage(message);
|
||||
if (!shortcutId) {
|
||||
return;
|
||||
}
|
||||
const auto loaded = _data.contains(shortcutId);
|
||||
auto &list = _data[shortcutId];
|
||||
append(shortcutId, list, message);
|
||||
sort(list);
|
||||
_updates.fire_copy(shortcutId);
|
||||
updateCount(shortcutId);
|
||||
if (!loaded) {
|
||||
request(shortcutId);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::updateCount(BusinessShortcutId shortcutId) {
|
||||
const auto i = _data.find(shortcutId);
|
||||
const auto j = _shortcuts.list.find(shortcutId);
|
||||
if (j == end(_shortcuts.list)) {
|
||||
return;
|
||||
}
|
||||
const auto count = (i != end(_data))
|
||||
? int(i->second.itemById.size())
|
||||
: 0;
|
||||
if (j->second.count != count) {
|
||||
_shortcuts.list[shortcutId].count = count;
|
||||
_shortcutsChanged.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(
|
||||
const MTPDupdateDeleteQuickReplyMessages &update) {
|
||||
const auto shortcutId = update.vshortcut_id().v;
|
||||
if (!shortcutId) {
|
||||
return;
|
||||
}
|
||||
auto i = _data.find(shortcutId);
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
for (const auto &id : update.vmessages().v) {
|
||||
const auto &list = i->second;
|
||||
const auto j = list.itemById.find(id.v);
|
||||
if (j != end(list.itemById)) {
|
||||
j->second->destroy();
|
||||
i = _data.find(shortcutId);
|
||||
if (i == end(_data)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_updates.fire_copy(shortcutId);
|
||||
updateCount(shortcutId);
|
||||
|
||||
cancelRequest(shortcutId);
|
||||
request(shortcutId);
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(const MTPDupdateDeleteQuickReply &update) {
|
||||
const auto shortcutId = update.vshortcut_id().v;
|
||||
if (!shortcutId) {
|
||||
return;
|
||||
}
|
||||
auto i = _data.find(shortcutId);
|
||||
while (i != end(_data) && !i->second.itemById.empty()) {
|
||||
i->second.itemById.back().second->destroy();
|
||||
i = _data.find(shortcutId);
|
||||
}
|
||||
_updates.fire_copy(shortcutId);
|
||||
if (_data.contains(shortcutId)) {
|
||||
updateCount(shortcutId);
|
||||
} else {
|
||||
_shortcuts.list.remove(shortcutId);
|
||||
_shortcutIdChanges.fire({ shortcutId, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::apply(
|
||||
const MTPDupdateMessageID &update,
|
||||
not_null<HistoryItem*> local) {
|
||||
const auto id = update.vid().v;
|
||||
const auto i = _data.find(local->shortcutId());
|
||||
Assert(i != end(_data));
|
||||
auto &list = i->second;
|
||||
const auto j = list.itemById.find(id);
|
||||
if (j != end(list.itemById) || !IsServerMsgId(id)) {
|
||||
local->destroy();
|
||||
} else {
|
||||
Assert(!list.itemById.contains(local->id));
|
||||
local->setRealId(localMessageId(id));
|
||||
list.itemById.emplace(id, local);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::appendSending(not_null<HistoryItem*> item) {
|
||||
Expects(item->isSending());
|
||||
Expects(item->isBusinessShortcut());
|
||||
|
||||
const auto shortcutId = item->shortcutId();
|
||||
auto &list = _data[shortcutId];
|
||||
list.items.emplace_back(item);
|
||||
sort(list);
|
||||
_updates.fire_copy(shortcutId);
|
||||
}
|
||||
|
||||
void ShortcutMessages::removeSending(not_null<HistoryItem*> item) {
|
||||
Expects(item->isSending() || item->hasFailed());
|
||||
Expects(item->isBusinessShortcut());
|
||||
|
||||
item->destroy();
|
||||
}
|
||||
|
||||
rpl::producer<> ShortcutMessages::updates(BusinessShortcutId shortcutId) {
|
||||
request(shortcutId);
|
||||
|
||||
return _updates.events(
|
||||
) | rpl::filter([=](BusinessShortcutId value) {
|
||||
return (value == shortcutId);
|
||||
}) | rpl::to_empty;
|
||||
}
|
||||
|
||||
Data::MessagesSlice ShortcutMessages::list(BusinessShortcutId shortcutId) {
|
||||
auto result = Data::MessagesSlice();
|
||||
const auto i = _data.find(shortcutId);
|
||||
if (i == end(_data)) {
|
||||
const auto i = _requests.find(shortcutId);
|
||||
if (i == end(_requests)) {
|
||||
return result;
|
||||
}
|
||||
result.fullCount = result.skippedAfter = result.skippedBefore = 0;
|
||||
return result;
|
||||
}
|
||||
const auto &list = i->second.items;
|
||||
result.skippedAfter = result.skippedBefore = 0;
|
||||
result.fullCount = int(list.size());
|
||||
result.ids = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::transform(
|
||||
&HistoryItem::fullId
|
||||
) | ranges::to_vector;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ShortcutMessages::preloadShortcuts() {
|
||||
if (_shortcutsLoaded || _shortcutsRequestId) {
|
||||
return;
|
||||
}
|
||||
const auto owner = &_session->data();
|
||||
_shortcutsRequestId = owner->session().api().request(
|
||||
MTPmessages_GetQuickReplies(MTP_long(_shortcutsHash))
|
||||
).done([=](const MTPmessages_QuickReplies &result) {
|
||||
result.match([&](const MTPDmessages_quickReplies &data) {
|
||||
owner->processUsers(data.vusers());
|
||||
owner->processChats(data.vchats());
|
||||
updateShortcuts(data.vquick_replies().v);
|
||||
}, [&](const MTPDmessages_quickRepliesNotModified &) {
|
||||
if (!_shortcutsLoaded) {
|
||||
_shortcutsLoaded = true;
|
||||
_shortcutsChanged.fire({});
|
||||
}
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
|
||||
const Shortcuts &ShortcutMessages::shortcuts() const {
|
||||
return _shortcuts;
|
||||
}
|
||||
|
||||
bool ShortcutMessages::shortcutsLoaded() const {
|
||||
return _shortcutsLoaded;
|
||||
}
|
||||
|
||||
rpl::producer<> ShortcutMessages::shortcutsChanged() const {
|
||||
return _shortcutsChanged.events();
|
||||
}
|
||||
|
||||
auto ShortcutMessages::shortcutIdChanged() const
|
||||
-> rpl::producer<ShortcutIdChange> {
|
||||
return _shortcutIdChanges.events();
|
||||
}
|
||||
|
||||
BusinessShortcutId ShortcutMessages::emplaceShortcut(QString name) {
|
||||
Expects(_shortcutsLoaded);
|
||||
|
||||
for (auto &[id, shortcut] : _shortcuts.list) {
|
||||
if (shortcut.name == name) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
const auto result = --_localShortcutId;
|
||||
_shortcuts.list.emplace(result, Shortcut{ .id = result, .name = name });
|
||||
return result;
|
||||
}
|
||||
|
||||
Shortcut ShortcutMessages::lookupShortcut(BusinessShortcutId id) const {
|
||||
const auto i = _shortcuts.list.find(id);
|
||||
|
||||
Ensures(i != end(_shortcuts.list));
|
||||
return i->second;
|
||||
}
|
||||
|
||||
BusinessShortcutId ShortcutMessages::lookupShortcutId(
|
||||
const QString &name) const {
|
||||
for (const auto &[id, shortcut] : _shortcuts.list) {
|
||||
if (!shortcut.name.compare(name, Qt::CaseInsensitive)) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ShortcutMessages::editShortcut(
|
||||
BusinessShortcutId id,
|
||||
QString name,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
name = name.trimmed();
|
||||
if (name.isEmpty()) {
|
||||
fail(QString());
|
||||
return;
|
||||
}
|
||||
const auto finish = [=] {
|
||||
const auto i = _shortcuts.list.find(id);
|
||||
if (i != end(_shortcuts.list)) {
|
||||
i->second.name = name;
|
||||
_shortcutsChanged.fire({});
|
||||
}
|
||||
done();
|
||||
};
|
||||
for (const auto &[existingId, shortcut] : _shortcuts.list) {
|
||||
if (shortcut.name == name) {
|
||||
if (existingId == id) {
|
||||
//done();
|
||||
//return;
|
||||
break;
|
||||
} else if (_data[existingId].items.empty() && !shortcut.count) {
|
||||
removeShortcut(existingId);
|
||||
break;
|
||||
} else {
|
||||
fail(u"SHORTCUT_OCCUPIED"_q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
_session->api().request(MTPmessages_EditQuickReplyShortcut(
|
||||
MTP_int(id),
|
||||
MTP_string(name)
|
||||
)).done(finish).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
if (type == u"SHORTCUT_ID_INVALID"_q) {
|
||||
// Not on the server (yet).
|
||||
finish();
|
||||
} else {
|
||||
fail(type);
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ShortcutMessages::removeShortcut(BusinessShortcutId shortcutId) {
|
||||
auto i = _data.find(shortcutId);
|
||||
while (i != end(_data)) {
|
||||
if (i->second.items.empty()) {
|
||||
_data.erase(i);
|
||||
} else {
|
||||
i->second.items.front()->destroy();
|
||||
}
|
||||
i = _data.find(shortcutId);
|
||||
}
|
||||
_shortcuts.list.remove(shortcutId);
|
||||
_shortcutIdChanges.fire({ shortcutId, 0 });
|
||||
|
||||
_session->api().request(MTPmessages_DeleteQuickReplyShortcut(
|
||||
MTP_int(shortcutId)
|
||||
)).send();
|
||||
}
|
||||
|
||||
void ShortcutMessages::cancelRequest(BusinessShortcutId shortcutId) {
|
||||
const auto j = _requests.find(shortcutId);
|
||||
if (j != end(_requests)) {
|
||||
_session->api().request(j->second.requestId).cancel();
|
||||
_requests.erase(j);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::request(BusinessShortcutId shortcutId) {
|
||||
auto &request = _requests[shortcutId];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto i = _data.find(shortcutId);
|
||||
const auto hash = (i != end(_data))
|
||||
? countListHash(i->second)
|
||||
: uint64(0);
|
||||
request.requestId = _session->api().request(
|
||||
MTPmessages_GetQuickReplyMessages(
|
||||
MTP_flags(0),
|
||||
MTP_int(shortcutId),
|
||||
MTPVector<MTPint>(),
|
||||
MTP_long(hash))
|
||||
).done([=](const MTPmessages_Messages &result) {
|
||||
parse(shortcutId, result);
|
||||
}).fail([=] {
|
||||
_requests.remove(shortcutId);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void ShortcutMessages::parse(
|
||||
BusinessShortcutId shortcutId,
|
||||
const MTPmessages_Messages &list) {
|
||||
auto &request = _requests[shortcutId];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
if (!_clearTimer.isActive()) {
|
||||
_clearTimer.callOnce(kRequestTimeLimit * 2);
|
||||
}
|
||||
|
||||
list.match([&](const MTPDmessages_messagesNotModified &data) {
|
||||
}, [&](const auto &data) {
|
||||
_session->data().processUsers(data.vusers());
|
||||
_session->data().processChats(data.vchats());
|
||||
|
||||
const auto &messages = data.vmessages().v;
|
||||
if (messages.isEmpty()) {
|
||||
clearNotSending(shortcutId);
|
||||
return;
|
||||
}
|
||||
auto received = base::flat_set<not_null<HistoryItem*>>();
|
||||
auto clear = base::flat_set<not_null<HistoryItem*>>();
|
||||
auto &list = _data.emplace(shortcutId, List()).first->second;
|
||||
for (const auto &message : messages) {
|
||||
if (const auto item = append(shortcutId, list, message)) {
|
||||
received.emplace(item);
|
||||
}
|
||||
}
|
||||
for (const auto &owned : list.items) {
|
||||
const auto item = owned.get();
|
||||
if (!item->isSending() && !received.contains(item)) {
|
||||
clear.emplace(item);
|
||||
}
|
||||
}
|
||||
updated(shortcutId, received, clear);
|
||||
});
|
||||
}
|
||||
|
||||
HistoryItem *ShortcutMessages::append(
|
||||
BusinessShortcutId shortcutId,
|
||||
List &list,
|
||||
const MTPMessage &message) {
|
||||
const auto id = message.match([&](const auto &data) {
|
||||
return data.vid().v;
|
||||
});
|
||||
const auto i = list.itemById.find(id);
|
||||
if (i != end(list.itemById)) {
|
||||
const auto existing = i->second;
|
||||
message.match([&](const MTPDmessage &data) {
|
||||
if (data.is_edit_hide()) {
|
||||
existing->applyEdition(HistoryMessageEdition(_session, data));
|
||||
} else {
|
||||
existing->updateSentContent({
|
||||
qs(data.vmessage()),
|
||||
Api::EntitiesFromMTP(
|
||||
_session,
|
||||
data.ventities().value_or_empty())
|
||||
}, data.vmedia());
|
||||
existing->updateReplyMarkup(
|
||||
HistoryMessageMarkupData(data.vreply_markup()));
|
||||
existing->updateForwardedInfo(data.vfwd_from());
|
||||
}
|
||||
existing->updateDate(data.vdate().v);
|
||||
_history->owner().requestItemTextRefresh(existing);
|
||||
}, [&](const auto &data) {});
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (!IsServerMsgId(id)) {
|
||||
LOG(("API Error: Bad id in quick reply messages: %1.").arg(id));
|
||||
return nullptr;
|
||||
}
|
||||
const auto item = _session->data().addNewMessage(
|
||||
localMessageId(id),
|
||||
PrepareMessage(shortcutId, message),
|
||||
MessageFlags(), // localFlags
|
||||
NewMessageType::Existing);
|
||||
if (!item
|
||||
|| item->history() != _history
|
||||
|| item->shortcutId() != shortcutId) {
|
||||
LOG(("API Error: Bad data received in quick reply messages."));
|
||||
return nullptr;
|
||||
}
|
||||
list.items.emplace_back(item);
|
||||
list.itemById.emplace(id, item);
|
||||
return item;
|
||||
}
|
||||
|
||||
void ShortcutMessages::clearNotSending(BusinessShortcutId shortcutId) {
|
||||
const auto i = _data.find(shortcutId);
|
||||
if (i == end(_data)) {
|
||||
return;
|
||||
}
|
||||
auto clear = base::flat_set<not_null<HistoryItem*>>();
|
||||
for (const auto &owned : i->second.items) {
|
||||
if (!owned->isSending() && !owned->hasFailed()) {
|
||||
clear.emplace(owned.get());
|
||||
}
|
||||
}
|
||||
updated(shortcutId, {}, clear);
|
||||
}
|
||||
|
||||
void ShortcutMessages::updated(
|
||||
BusinessShortcutId shortcutId,
|
||||
const base::flat_set<not_null<HistoryItem*>> &added,
|
||||
const base::flat_set<not_null<HistoryItem*>> &clear) {
|
||||
if (!clear.empty()) {
|
||||
for (const auto &item : clear) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
const auto i = _data.find(shortcutId);
|
||||
if (i != end(_data)) {
|
||||
sort(i->second);
|
||||
}
|
||||
if (!added.empty() || !clear.empty()) {
|
||||
_updates.fire_copy(shortcutId);
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutMessages::sort(List &list) {
|
||||
ranges::sort(list.items, ranges::less(), &HistoryItem::position);
|
||||
}
|
||||
|
||||
void ShortcutMessages::remove(not_null<const HistoryItem*> item) {
|
||||
const auto shortcutId = item->shortcutId();
|
||||
const auto i = _data.find(shortcutId);
|
||||
Assert(i != end(_data));
|
||||
auto &list = i->second;
|
||||
|
||||
if (!item->isSending() && !item->hasFailed()) {
|
||||
list.itemById.remove(lookupId(item));
|
||||
}
|
||||
const auto k = ranges::find(list.items, item, &OwnedItem::get);
|
||||
Assert(k != list.items.end());
|
||||
k->release();
|
||||
list.items.erase(k);
|
||||
|
||||
if (list.items.empty()) {
|
||||
_data.erase(i);
|
||||
}
|
||||
_updates.fire_copy(shortcutId);
|
||||
updateCount(shortcutId);
|
||||
}
|
||||
|
||||
uint64 ShortcutMessages::countListHash(const List &list) const {
|
||||
using namespace Api;
|
||||
|
||||
auto hash = HashInit();
|
||||
auto &&serverside = ranges::views::all(
|
||||
list.items
|
||||
) | ranges::views::filter([](const OwnedItem &item) {
|
||||
return !item->isSending() && !item->hasFailed();
|
||||
}) | ranges::views::reverse;
|
||||
for (const auto &item : serverside) {
|
||||
HashUpdate(hash, lookupId(item.get()).bare);
|
||||
if (const auto edited = item->Get<HistoryMessageEdited>()) {
|
||||
HashUpdate(hash, edited->date);
|
||||
} else {
|
||||
HashUpdate(hash, TimeId(0));
|
||||
}
|
||||
}
|
||||
return HashFinalize(hash);
|
||||
}
|
||||
|
||||
MTPInputQuickReplyShortcut ShortcutIdToMTP(
|
||||
not_null<Main::Session*> session,
|
||||
BusinessShortcutId id) {
|
||||
return id
|
||||
? MTP_inputQuickReplyShortcut(MTP_string(
|
||||
session->data().shortcutMessages().lookupShortcut(id).name))
|
||||
: MTPInputQuickReplyShortcut();
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
Reference in New Issue
Block a user