| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224 |
- /*
- 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/data_histories.h"
- #include "api/api_text_entities.h"
- #include "data/business/data_shortcut_messages.h"
- #include "data/components/scheduled_messages.h"
- #include "data/data_session.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_folder.h"
- #include "data/data_forum.h"
- #include "data/data_forum_topic.h"
- #include "data/data_user.h"
- #include "base/unixtime.h"
- #include "base/random.h"
- #include "main/main_session.h"
- #include "window/notifications_manager.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h"
- #include "history/view/history_view_element.h"
- #include "core/application.h"
- #include "apiwrap.h"
- namespace Data {
- namespace {
- constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
- constexpr auto kReportDeliveriesPerRequest = 50;
- } // namespace
- MTPInputReplyTo ReplyToForMTP(
- not_null<History*> history,
- FullReplyTo replyTo) {
- const auto owner = &history->owner();
- if (replyTo.storyId) {
- if (const auto peer = owner->peerLoaded(replyTo.storyId.peer)) {
- return MTP_inputReplyToStory(
- peer->input,
- MTP_int(replyTo.storyId.story));
- }
- } else if (replyTo.messageId || replyTo.topicRootId) {
- const auto to = LookupReplyTo(history, replyTo.messageId);
- const auto replyingToTopic = replyTo.topicRootId
- ? history->peer->forumTopicFor(replyTo.topicRootId)
- : nullptr;
- const auto replyingToTopicId = replyTo.topicRootId
- ? (replyingToTopic
- ? replyingToTopic->rootId()
- : Data::ForumTopic::kGeneralId)
- : (to ? to->topicRootId() : Data::ForumTopic::kGeneralId);
- const auto replyToTopicId = (to
- && (to->history() != history || to->id != replyingToTopicId))
- ? to->topicRootId()
- : replyingToTopicId;
- const auto external = replyTo.messageId
- && (replyTo.messageId.peer != history->peer->id
- || replyingToTopicId != replyToTopicId);
- const auto quoteEntities = Api::EntitiesToMTP(
- &history->session(),
- replyTo.quote.entities,
- Api::ConvertOption::SkipLocal);
- using Flag = MTPDinputReplyToMessage::Flag;
- return MTP_inputReplyToMessage(
- MTP_flags((replyTo.topicRootId ? Flag::f_top_msg_id : Flag())
- | (external ? Flag::f_reply_to_peer_id : Flag())
- | (replyTo.quote.text.isEmpty()
- ? Flag()
- : (Flag::f_quote_text | Flag::f_quote_offset))
- | (quoteEntities.v.isEmpty()
- ? Flag()
- : Flag::f_quote_entities)),
- MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0),
- MTP_int(replyTo.topicRootId),
- (external
- ? owner->peer(replyTo.messageId.peer)->input
- : MTPInputPeer()),
- MTP_string(replyTo.quote.text),
- quoteEntities,
- MTP_int(replyTo.quoteOffset));
- }
- return MTPInputReplyTo();
- }
- MTPInputMedia WebPageForMTP(
- const Data::WebPageDraft &draft,
- bool required) {
- using Flag = MTPDinputMediaWebPage::Flag;
- return MTP_inputMediaWebPage(
- MTP_flags(((false && required) ? Flag() : Flag::f_optional)
- | (draft.forceLargeMedia ? Flag::f_force_large_media : Flag())
- | (draft.forceSmallMedia ? Flag::f_force_small_media : Flag())),
- MTP_string(draft.url));
- }
- Histories::Histories(not_null<Session*> owner)
- : _owner(owner)
- , _readRequestsTimer([=] { sendReadRequests(); }) {
- }
- Session &Histories::owner() const {
- return *_owner;
- }
- Main::Session &Histories::session() const {
- return _owner->session();
- }
- History *Histories::find(PeerId peerId) {
- const auto i = peerId ? _map.find(peerId) : end(_map);
- return (i != end(_map)) ? i->second.get() : nullptr;
- }
- not_null<History*> Histories::findOrCreate(PeerId peerId) {
- Expects(peerId != 0);
- if (const auto result = find(peerId)) {
- return result;
- }
- const auto &[i, ok] = _map.emplace(
- peerId,
- std::make_unique<History>(&owner(), peerId));
- return i->second.get();
- }
- void Histories::unloadAll() {
- for (const auto &[peerId, history] : _map) {
- history->clear(History::ClearType::Unload);
- }
- }
- void Histories::clearAll() {
- _map.clear();
- }
- void Histories::readInbox(not_null<History*> history) {
- DEBUG_LOG(("Reading: readInbox called."));
- if (history->lastServerMessageKnown()) {
- const auto last = history->lastServerMessage();
- DEBUG_LOG(("Reading: last known, reading till %1."
- ).arg(last ? last->id.bare : 0));
- readInboxTill(history, last ? last->id : 0);
- return;
- } else if (history->loadedAtBottom()) {
- if (const auto lastId = history->maxMsgId()) {
- DEBUG_LOG(("Reading: loaded at bottom, maxMsgId %1."
- ).arg(lastId.bare));
- readInboxTill(history, lastId);
- return;
- } else if (history->loadedAtTop()) {
- DEBUG_LOG(("Reading: loaded at bottom, loaded at top."));
- readInboxTill(history, 0);
- return;
- }
- DEBUG_LOG(("Reading: loaded at bottom, but requesting entry."));
- }
- requestDialogEntry(history, [=] {
- Expects(history->lastServerMessageKnown());
- const auto last = history->lastServerMessage();
- DEBUG_LOG(("Reading: got entry, reading till %1."
- ).arg(last ? last->id.bare : 0));
- readInboxTill(history, last ? last->id : 0);
- });
- }
- void Histories::readInboxTill(not_null<HistoryItem*> item) {
- const auto history = item->history();
- if (!item->isRegular()) {
- readClientSideMessage(item);
- auto view = item->mainView();
- if (!view) {
- return;
- }
- auto block = view->block();
- auto blockIndex = block->indexInHistory();
- auto itemIndex = view->indexInBlock();
- while (blockIndex > 0 || itemIndex > 0) {
- if (itemIndex > 0) {
- view = block->messages[--itemIndex].get();
- } else {
- while (blockIndex > 0) {
- block = history->blocks[--blockIndex].get();
- itemIndex = block->messages.size();
- if (itemIndex > 0) {
- view = block->messages[--itemIndex].get();
- break;
- }
- }
- }
- item = view->data();
- if (item->isRegular()) {
- break;
- }
- }
- if (!item->isRegular()) {
- LOG(("App Error: "
- "Can't read history till unknown local message."));
- return;
- }
- }
- readInboxTill(history, item->id);
- }
- void Histories::readInboxTill(not_null<History*> history, MsgId tillId) {
- readInboxTill(history, tillId, false);
- }
- void Histories::readInboxTill(
- not_null<History*> history,
- MsgId tillId,
- bool force) {
- Expects(IsServerMsgId(tillId) || (!tillId && !force));
- DEBUG_LOG(("Reading: readInboxTill %1, force %2."
- ).arg(tillId.bare
- ).arg(Logs::b(force)));
- const auto syncGuard = gsl::finally([&] {
- DEBUG_LOG(("Reading: in guard, unread %1."
- ).arg(history->unreadCount()));
- if (history->unreadCount() > 0) {
- if (const auto last = history->lastServerMessage()) {
- DEBUG_LOG(("Reading: checking last %1 and %2."
- ).arg(last->id.bare
- ).arg(tillId.bare));
- if (last->id == tillId) {
- DEBUG_LOG(("Reading: locally marked as read."));
- history->setUnreadCount(0);
- history->updateChatListEntry();
- }
- }
- }
- });
- Core::App().notifications().clearIncomingFromHistory(history);
- const auto needsRequest = history->readInboxTillNeedsRequest(tillId);
- if (!needsRequest && !force) {
- DEBUG_LOG(("Reading: readInboxTill finish 1."));
- return;
- } else if (!history->trackUnreadMessages()) {
- DEBUG_LOG(("Reading: readInboxTill finish 2."));
- return;
- }
- const auto maybeState = lookup(history);
- if (maybeState && maybeState->sentReadTill >= tillId) {
- DEBUG_LOG(("Reading: readInboxTill finish 3 with %1."
- ).arg(maybeState->sentReadTill.bare));
- return;
- } else if (maybeState && maybeState->willReadTill >= tillId) {
- DEBUG_LOG(("Reading: readInboxTill finish 4 with %1 and force %2."
- ).arg(maybeState->sentReadTill.bare
- ).arg(Logs::b(force)));
- if (force) {
- sendPendingReadInbox(history);
- }
- return;
- } else if (!needsRequest
- && (!maybeState || !maybeState->willReadTill)) {
- return;
- }
- const auto stillUnread = history->countStillUnreadLocal(tillId);
- if (!force
- && stillUnread
- && history->unreadCountKnown()
- && *stillUnread == history->unreadCount()) {
- DEBUG_LOG(("Reading: count didn't change so just update till %1"
- ).arg(tillId.bare));
- history->setInboxReadTill(tillId);
- return;
- }
- auto &state = maybeState ? *maybeState : _states[history];
- state.willReadTill = tillId;
- if (force || !stillUnread || !*stillUnread) {
- DEBUG_LOG(("Reading: will read till %1 with still unread %2"
- ).arg(tillId.bare
- ).arg(stillUnread.value_or(-666)));
- state.willReadWhen = 0;
- sendReadRequests();
- if (!stillUnread) {
- return;
- }
- } else if (!state.willReadWhen) {
- DEBUG_LOG(("Reading: will read till %1 with postponed"
- ).arg(tillId.bare));
- state.willReadWhen = crl::now() + kReadRequestTimeout;
- if (!_readRequestsTimer.isActive()) {
- _readRequestsTimer.callOnce(kReadRequestTimeout);
- }
- } else {
- DEBUG_LOG(("Reading: will read till %1 postponed already"
- ).arg(tillId.bare));
- }
- DEBUG_LOG(("Reading: marking now with till %1 and still %2"
- ).arg(tillId.bare
- ).arg(*stillUnread));
- history->setInboxReadTill(tillId);
- history->setUnreadCount(*stillUnread);
- history->updateChatListEntry();
- }
- void Histories::readInboxOnNewMessage(not_null<HistoryItem*> item) {
- if (!item->isRegular()) {
- readClientSideMessage(item);
- } else {
- readInboxTill(item->history(), item->id, true);
- }
- }
- void Histories::readClientSideMessage(not_null<HistoryItem*> item) {
- if (item->out() || !item->unread(item->history())) {
- return;
- }
- const auto history = item->history();
- item->markClientSideAsRead();
- if (const auto unread = history->unreadCount()) {
- history->setUnreadCount(unread - 1);
- }
- }
- void Histories::requestDialogEntry(not_null<Data::Folder*> folder) {
- if (_dialogFolderRequests.contains(folder)) {
- return;
- }
- _dialogFolderRequests.emplace(folder);
- auto peers = QVector<MTPInputDialogPeer>(
- 1,
- MTP_inputDialogPeerFolder(MTP_int(folder->id())));
- session().api().request(MTPmessages_GetPeerDialogs(
- MTP_vector(std::move(peers))
- )).done([=](const MTPmessages_PeerDialogs &result) {
- applyPeerDialogs(result);
- _dialogFolderRequests.remove(folder);
- }).fail([=] {
- _dialogFolderRequests.remove(folder);
- }).send();
- }
- void Histories::requestDialogEntry(
- not_null<History*> history,
- Fn<void()> callback) {
- const auto i = _dialogRequests.find(history);
- if (i != end(_dialogRequests)) {
- if (callback) {
- i->second.push_back(std::move(callback));
- }
- return;
- }
- const auto &[j, ok] = _dialogRequestsPending.try_emplace(history);
- if (callback) {
- j->second.push_back(std::move(callback));
- }
- if (!ok) {
- return;
- }
- postponeRequestDialogEntries();
- }
- void Histories::postponeRequestDialogEntries() {
- if (_dialogRequestsPending.size() > 1) {
- return;
- }
- Core::App().postponeCall(crl::guard(&session(), [=] {
- sendDialogRequests();
- }));
- }
- void Histories::sendDialogRequests() {
- if (_dialogRequestsPending.empty()) {
- return;
- }
- const auto histories = ranges::views::all(
- _dialogRequestsPending
- ) | ranges::views::transform([](const auto &pair) {
- return pair.first;
- }) | ranges::views::filter([&](not_null<History*> history) {
- const auto state = lookup(history);
- if (!state) {
- return true;
- } else if (!postponeEntryRequest(*state)) {
- return true;
- }
- state->postponedRequestEntry = true;
- return false;
- }) | ranges::to_vector;
- auto peers = QVector<MTPInputDialogPeer>();
- const auto dialogPeer = [](not_null<History*> history) {
- return MTP_inputDialogPeer(history->peer->input);
- };
- ranges::transform(
- histories,
- ranges::back_inserter(peers),
- dialogPeer);
- for (auto &[history, callbacks] : base::take(_dialogRequestsPending)) {
- _dialogRequests.emplace(history, std::move(callbacks));
- }
- const auto finalize = [=] {
- for (const auto &history : histories) {
- const auto state = lookup(history);
- if (!state || !state->postponedRequestEntry) {
- dialogEntryApplied(history);
- history->updateChatListExistence();
- }
- }
- };
- session().api().request(MTPmessages_GetPeerDialogs(
- MTP_vector(std::move(peers))
- )).done([=](const MTPmessages_PeerDialogs &result) {
- applyPeerDialogs(result);
- finalize();
- }).fail([=] {
- finalize();
- }).send();
- }
- void Histories::dialogEntryApplied(not_null<History*> history) {
- const auto state = lookup(history);
- if (state && state->postponedRequestEntry) {
- return;
- }
- history->dialogEntryApplied();
- if (const auto callbacks = _dialogRequestsPending.take(history)) {
- for (const auto &callback : *callbacks) {
- callback();
- }
- }
- if (const auto callbacks = _dialogRequests.take(history)) {
- for (const auto &callback : *callbacks) {
- callback();
- }
- }
- if (state && state->sentReadTill && state->sentReadDone) {
- history->setInboxReadTill(base::take(state->sentReadTill));
- checkEmptyState(history);
- }
- }
- void Histories::applyPeerDialogs(const MTPmessages_PeerDialogs &dialogs) {
- Expects(dialogs.type() == mtpc_messages_peerDialogs);
- const auto &data = dialogs.c_messages_peerDialogs();
- _owner->processUsers(data.vusers());
- _owner->processChats(data.vchats());
- _owner->processMessages(data.vmessages(), NewMessageType::Last);
- for (const auto &dialog : data.vdialogs().v) {
- dialog.match([&](const MTPDdialog &data) {
- if (const auto peerId = peerFromMTP(data.vpeer())) {
- _owner->history(peerId)->applyDialog(nullptr, data);
- }
- }, [&](const MTPDdialogFolder &data) {
- const auto folder = _owner->processFolder(data.vfolder());
- folder->applyDialog(data);
- });
- }
- _owner->sendHistoryChangeNotifications();
- }
- void Histories::changeDialogUnreadMark(
- not_null<History*> history,
- bool unread) {
- history->setUnreadMark(unread);
- using Flag = MTPmessages_MarkDialogUnread::Flag;
- session().api().request(MTPmessages_MarkDialogUnread(
- MTP_flags(unread ? Flag::f_unread : Flag(0)),
- MTP_inputDialogPeer(history->peer->input)
- )).send();
- }
- void Histories::requestFakeChatListMessage(
- not_null<History*> history) {
- if (_fakeChatListRequests.contains(history)) {
- return;
- }
- _fakeChatListRequests.emplace(history);
- sendRequest(history, RequestType::History, [=](Fn<void()> finish) {
- return session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(0), // offset_id
- MTP_int(0), // offset_date
- MTP_int(0), // add_offset
- MTP_int(2), // limit
- MTP_int(0), // max_id
- MTP_int(0), // min_id
- MTP_long(0) // hash
- )).done([=](const MTPmessages_Messages &result) {
- _fakeChatListRequests.erase(history);
- history->setFakeChatListMessageFrom(result);
- finish();
- }).fail([=] {
- _fakeChatListRequests.erase(history);
- history->setFakeChatListMessageFrom(MTP_messages_messages(
- MTP_vector<MTPMessage>(0),
- MTP_vector<MTPChat>(0),
- MTP_vector<MTPUser>(0)));
- finish();
- }).send();
- });
- }
- void Histories::requestGroupAround(not_null<HistoryItem*> item) {
- const auto history = item->history();
- const auto id = item->id;
- const auto key = GroupRequestKey{ history, item->topicRootId() };
- const auto i = _chatListGroupRequests.find(key);
- if (i != end(_chatListGroupRequests)) {
- if (i->second.aroundId == id) {
- return;
- } else {
- cancelRequest(i->second.requestId);
- _chatListGroupRequests.erase(i);
- }
- }
- constexpr auto kMaxAlbumCount = 10;
- const auto requestId = sendRequest(history, RequestType::History, [=](
- Fn<void()> finish) {
- return session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(id),
- MTP_int(0), // offset_date
- MTP_int(-kMaxAlbumCount),
- MTP_int(2 * kMaxAlbumCount - 1),
- MTP_int(0), // max_id
- MTP_int(0), // min_id
- MTP_long(0) // hash
- )).done([=](const MTPmessages_Messages &result) {
- _owner->processExistingMessages(
- history->peer->asChannel(),
- result);
- _chatListGroupRequests.remove(key);
- history->migrateToOrMe()->applyChatListGroup(
- history->peer->id,
- result);
- finish();
- }).fail([=] {
- _chatListGroupRequests.remove(key);
- finish();
- }).send();
- });
- _chatListGroupRequests.emplace(
- key,
- ChatListGroupRequest{ .aroundId = id, .requestId = requestId });
- }
- void Histories::sendPendingReadInbox(not_null<History*> history) {
- if (const auto state = lookup(history)) {
- DEBUG_LOG(("Reading: send pending now with till %1 and when %2"
- ).arg(state->willReadTill.bare
- ).arg(state->willReadWhen));
- if (state->willReadTill && state->willReadWhen) {
- state->willReadWhen = 0;
- sendReadRequests();
- }
- }
- }
- void Histories::reportDelivery(not_null<HistoryItem*> item) {
- auto &set = _pendingDeliveryReport[item->history()->peer];
- if (!set.emplace(item->id).second) {
- return;
- }
- crl::on_main(&session(), [=] {
- reportPendingDeliveries();
- });
- }
- void Histories::reportPendingDeliveries() {
- auto &pending = _pendingDeliveryReport;
- for (auto i = begin(pending); i != end(pending);) {
- auto &[peer, ids] = *i;
- auto list = QVector<MTPint>();
- if (_deliveryReportSent.contains(peer)) {
- ++i;
- continue;
- } else if (ids.size() > kReportDeliveriesPerRequest) {
- const auto count = kReportDeliveriesPerRequest;
- list.reserve(count);
- for (auto j = begin(ids), till = j + count; j != till; ++j) {
- list.push_back(MTP_int(*j));
- }
- ids.erase(begin(ids), begin(ids) + count);
- } else if (!ids.empty()) {
- list.reserve(ids.size());
- for (const auto &id : ids) {
- list.push_back(MTP_int(id));
- }
- ids.clear();
- }
- if (ids.empty()) {
- i = pending.erase(i);
- } else {
- ++i;
- }
- _deliveryReportSent.emplace(peer);
- const auto finish = [=] {
- _deliveryReportSent.remove(peer);
- if (_pendingDeliveryReport.contains(peer)) {
- reportPendingDeliveries();
- }
- };
- session().api().request(MTPmessages_ReportMessagesDelivery(
- MTP_flags(0),
- peer->input,
- MTP_vector(std::move(list))
- )).done(finish).fail(finish).send();
- }
- }
- void Histories::sendReadRequests() {
- DEBUG_LOG(("Reading: send requests with count %1.").arg(_states.size()));
- if (_states.empty()) {
- return;
- }
- const auto now = crl::now();
- auto next = std::optional<crl::time>();
- for (auto &[history, state] : _states) {
- if (!state.willReadTill) {
- DEBUG_LOG(("Reading: skipping zero till."));
- continue;
- } else if (state.willReadWhen <= now) {
- DEBUG_LOG(("Reading: sending with till %1."
- ).arg(state.willReadTill.bare));
- sendReadRequest(history, state);
- } else if (!next || *next > state.willReadWhen) {
- DEBUG_LOG(("Reading: scheduling for later send."));
- next = state.willReadWhen;
- }
- }
- if (next.has_value()) {
- _readRequestsTimer.callOnce(*next - now);
- } else {
- _readRequestsTimer.cancel();
- }
- }
- void Histories::sendReadRequest(not_null<History*> history, State &state) {
- Expects(state.willReadTill > state.sentReadTill);
- const auto tillId = state.sentReadTill = base::take(state.willReadTill);
- state.willReadWhen = 0;
- state.sentReadDone = false;
- DEBUG_LOG(("Reading: sending request now with till %1."
- ).arg(tillId.bare));
- sendRequest(history, RequestType::ReadInbox, [=](Fn<void()> finish) {
- DEBUG_LOG(("Reading: sending request invoked with till %1."
- ).arg(tillId.bare));
- const auto finished = [=] {
- const auto state = lookup(history);
- Assert(state != nullptr);
- if (state->sentReadTill == tillId) {
- state->sentReadDone = true;
- if (history->unreadCountRefreshNeeded(tillId)) {
- requestDialogEntry(history);
- } else {
- state->sentReadTill = 0;
- }
- } else {
- Assert(!state->sentReadTill || state->sentReadTill > tillId);
- }
- sendReadRequests();
- finish();
- };
- if (const auto channel = history->peer->asChannel()) {
- return session().api().request(MTPchannels_ReadHistory(
- channel->inputChannel,
- MTP_int(tillId)
- )).done(finished).fail(finished).send();
- } else {
- return session().api().request(MTPmessages_ReadHistory(
- history->peer->input,
- MTP_int(tillId)
- )).done([=](const MTPmessages_AffectedMessages &result) {
- session().api().applyAffectedMessages(history->peer, result);
- finished();
- }).fail([=] {
- finished();
- }).send();
- }
- });
- }
- void Histories::checkEmptyState(not_null<History*> history) {
- const auto empty = [](const State &state) {
- return state.postponed.empty()
- && !state.postponedRequestEntry
- && state.sent.empty()
- && (state.willReadTill == 0)
- && (state.sentReadTill == 0);
- };
- const auto i = _states.find(history);
- if (i != end(_states) && empty(i->second)) {
- _states.erase(i);
- }
- }
- bool Histories::postponeHistoryRequest(const State &state) const {
- const auto proj = [](const auto &pair) {
- return pair.second.type;
- };
- const auto i = ranges::find(state.sent, RequestType::Delete, proj);
- return (i != end(state.sent));
- }
- bool Histories::postponeEntryRequest(const State &state) const {
- return ranges::any_of(state.sent, [](const auto &pair) {
- return pair.second.type != RequestType::History;
- });
- }
- void Histories::deleteMessages(
- not_null<History*> history,
- const QVector<MTPint> &ids,
- bool revoke) {
- sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
- const auto done = [=](const MTPmessages_AffectedMessages &result) {
- session().api().applyAffectedMessages(history->peer, result);
- finish();
- history->requestChatListMessage();
- };
- if (const auto channel = history->peer->asChannel()) {
- return session().api().request(MTPchannels_DeleteMessages(
- channel->inputChannel,
- MTP_vector<MTPint>(ids)
- )).done(done).fail(finish).send();
- } else {
- using Flag = MTPmessages_DeleteMessages::Flag;
- return session().api().request(MTPmessages_DeleteMessages(
- MTP_flags(revoke ? Flag::f_revoke : Flag(0)),
- MTP_vector<MTPint>(ids)
- )).done(done).fail(finish).send();
- }
- });
- }
- void Histories::deleteAllMessages(
- not_null<History*> history,
- MsgId deleteTillId,
- bool justClear,
- bool revoke) {
- sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
- const auto peer = history->peer;
- const auto chat = peer->asChat();
- const auto channel = peer->asChannel();
- if (!justClear && revoke && channel && channel->canDelete()) {
- return session().api().request(MTPchannels_DeleteChannel(
- channel->inputChannel
- )).done([=](const MTPUpdates &result) {
- session().api().applyUpdates(result);
- //}).fail([=](const MTP::Error &error) {
- // if (error.type() == u"CHANNEL_TOO_LARGE"_q) {
- // Ui::show(Box<Ui::InformBox>(tr::lng_cant_delete_channel(tr::now)));
- // }
- }).send();
- } else if (channel) {
- using Flag = MTPchannels_DeleteHistory::Flag;
- return session().api().request(MTPchannels_DeleteHistory(
- MTP_flags(revoke ? Flag::f_for_everyone : Flag(0)),
- channel->inputChannel,
- MTP_int(deleteTillId)
- )).done(finish).fail(finish).send();
- } else if (revoke && chat && chat->amCreator()) {
- return session().api().request(MTPmessages_DeleteChat(
- chat->inputChat
- )).done(finish).fail([=](const MTP::Error &error) {
- if (error.type() == "PEER_ID_INVALID") {
- // Try to join and delete,
- // while delete fails for non-joined.
- session().api().request(MTPmessages_AddChatUser(
- chat->inputChat,
- MTP_inputUserSelf(),
- MTP_int(0)
- )).done([=](const MTPmessages_InvitedUsers &result) {
- const auto &data = result.data();
- session().api().applyUpdates(data.vupdates());
- deleteAllMessages(
- history,
- deleteTillId,
- justClear,
- revoke);
- }).send();
- }
- finish();
- }).send();
- } else {
- using Flag = MTPmessages_DeleteHistory::Flag;
- const auto flags = Flag(0)
- | (justClear ? Flag::f_just_clear : Flag(0))
- | (revoke ? Flag::f_revoke : Flag(0));
- return session().api().request(MTPmessages_DeleteHistory(
- MTP_flags(flags),
- peer->input,
- MTP_int(0),
- MTPint(), // min_date
- MTPint() // max_date
- )).done([=](const MTPmessages_AffectedHistory &result) {
- const auto offset = session().api().applyAffectedHistory(
- peer,
- result);
- if (offset > 0) {
- deleteAllMessages(
- history,
- deleteTillId,
- justClear,
- revoke);
- }
- finish();
- }).fail(finish).send();
- }
- });
- }
- void Histories::deleteMessagesByDates(
- not_null<History*> history,
- QDate firstDayToDelete,
- QDate lastDayToDelete,
- bool revoke) {
- const auto firstSecondToDelete = base::unixtime::serialize(
- { firstDayToDelete, QTime(0, 0) }
- );
- const auto lastSecondToDelete = base::unixtime::serialize(
- { lastDayToDelete, QTime(23, 59, 59) }
- );
- deleteMessagesByDates(
- history,
- firstSecondToDelete - 1,
- lastSecondToDelete + 1,
- revoke);
- }
- void Histories::deleteMessagesByDates(
- not_null<History*> history,
- TimeId minDate,
- TimeId maxDate,
- bool revoke) {
- sendRequest(history, RequestType::Delete, [=](Fn<void()> finish) {
- const auto peer = history->peer;
- using Flag = MTPmessages_DeleteHistory::Flag;
- const auto flags = Flag::f_just_clear
- | Flag::f_min_date
- | Flag::f_max_date
- | (revoke ? Flag::f_revoke : Flag(0));
- return session().api().request(MTPmessages_DeleteHistory(
- MTP_flags(flags),
- peer->input,
- MTP_int(0),
- MTP_int(minDate),
- MTP_int(maxDate)
- )).done([=](const MTPmessages_AffectedHistory &result) {
- const auto offset = session().api().applyAffectedHistory(
- peer,
- result);
- if (offset > 0) {
- deleteMessagesByDates(history, minDate, maxDate, revoke);
- }
- finish();
- }).fail(finish).send();
- });
- history->destroyMessagesByDates(minDate, maxDate);
- }
- void Histories::deleteMessages(const MessageIdsList &ids, bool revoke) {
- auto remove = std::vector<not_null<HistoryItem*>>();
- remove.reserve(ids.size());
- base::flat_map<not_null<History*>, QVector<MTPint>> idsByPeer;
- base::flat_map<not_null<PeerData*>, QVector<MTPint>> scheduledIdsByPeer;
- base::flat_map<BusinessShortcutId, QVector<MTPint>> quickIdsByShortcut;
- for (const auto &itemId : ids) {
- if (const auto item = _owner->message(itemId)) {
- const auto history = item->history();
- if (item->isScheduled()) {
- const auto wasOnServer = !item->isSending()
- && !item->hasFailed();
- auto &scheduled = _owner->session().scheduledMessages();
- if (wasOnServer) {
- scheduledIdsByPeer[history->peer].push_back(
- MTP_int(scheduled.lookupId(item)));
- } else {
- scheduled.removeSending(item);
- }
- continue;
- } else if (item->isBusinessShortcut()) {
- const auto wasOnServer = !item->isSending()
- && !item->hasFailed();
- if (wasOnServer) {
- quickIdsByShortcut[item->shortcutId()].push_back(MTP_int(
- _owner->shortcutMessages().lookupId(item)));
- } else {
- _owner->shortcutMessages().removeSending(item);
- }
- continue;
- }
- remove.push_back(item);
- if (item->isRegular()) {
- idsByPeer[history].push_back(MTP_int(itemId.msg));
- }
- }
- }
- for (const auto &[history, ids] : idsByPeer) {
- history->owner().histories().deleteMessages(history, ids, revoke);
- }
- for (const auto &[peer, ids] : scheduledIdsByPeer) {
- peer->session().api().request(MTPmessages_DeleteScheduledMessages(
- peer->input,
- MTP_vector<MTPint>(ids)
- )).done([peer = peer](const MTPUpdates &result) {
- peer->session().api().applyUpdates(result);
- }).send();
- }
- for (const auto &[shortcutId, ids] : quickIdsByShortcut) {
- const auto api = &_owner->session().api();
- api->request(MTPmessages_DeleteQuickReplyMessages(
- MTP_int(shortcutId),
- MTP_vector<MTPint>(ids)
- )).done([=](const MTPUpdates &result) {
- api->applyUpdates(result);
- }).send();
- }
- for (const auto item : remove) {
- const auto history = item->history();
- const auto wasLast = (history->lastMessage() == item);
- const auto wasInChats = (history->chatListMessage() == item);
- item->destroy();
- if (wasLast || wasInChats) {
- history->requestChatListMessage();
- }
- }
- }
- int Histories::sendRequest(
- not_null<History*> history,
- RequestType type,
- Fn<mtpRequestId(Fn<void()> finish)> generator) {
- Expects(type != RequestType::None);
- auto &state = _states[history];
- const auto id = ++_requestAutoincrement;
- _historyByRequest.emplace(id, history);
- if (type == RequestType::History && postponeHistoryRequest(state)) {
- state.postponed.emplace(
- id,
- PostponedHistoryRequest{ std::move(generator) });
- return id;
- }
- const auto requestId = generator([=] { checkPostponed(history, id); });
- state.sent.emplace(id, SentRequest{
- std::move(generator),
- requestId,
- type
- });
- if (!state.postponedRequestEntry
- && postponeEntryRequest(state)
- && _dialogRequests.contains(history)) {
- state.postponedRequestEntry = true;
- }
- if (postponeHistoryRequest(state)) {
- const auto resendHistoryRequest = [&](auto &pair) {
- auto &[id, sent] = pair;
- if (sent.type != RequestType::History) {
- return false;
- }
- state.postponed.emplace(
- id,
- PostponedHistoryRequest{ std::move(sent.generator) });
- session().api().request(sent.id).cancel();
- return true;
- };
- state.sent.erase(
- ranges::remove_if(state.sent, resendHistoryRequest),
- end(state.sent));
- }
- return id;
- }
- void Histories::sendCreateTopicRequest(
- not_null<History*> history,
- MsgId rootId) {
- Expects(history->peer->isChannel());
- const auto forum = history->asForum();
- Assert(forum != nullptr);
- const auto topic = forum->topicFor(rootId);
- Assert(topic != nullptr);
- const auto randomId = base::RandomValue<uint64>();
- session().data().registerMessageRandomId(
- randomId,
- { history->peer->id, rootId });
- const auto api = &session().api();
- using Flag = MTPchannels_CreateForumTopic::Flag;
- api->request(MTPchannels_CreateForumTopic(
- MTP_flags(Flag::f_icon_color
- | (topic->iconId() ? Flag::f_icon_emoji_id : Flag(0))),
- history->peer->asChannel()->inputChannel,
- MTP_string(topic->title()),
- MTP_int(topic->colorId()),
- MTP_long(topic->iconId()),
- MTP_long(randomId),
- MTPInputPeer() // send_as
- )).done([=](const MTPUpdates &result) {
- api->applyUpdates(result, randomId);
- }).fail([=](const MTP::Error &error) {
- api->sendMessageFail(error, history->peer, randomId);
- }).send();
- }
- bool Histories::isCreatingTopic(
- not_null<History*> history,
- MsgId rootId) const {
- const auto forum = history->asForum();
- return forum && forum->creating(rootId);
- }
- int Histories::sendPreparedMessage(
- not_null<History*> history,
- FullReplyTo replyTo,
- uint64 randomId,
- Fn<PreparedMessage(not_null<History*>, FullReplyTo)> message,
- Fn<void(const MTPUpdates&, const MTP::Response&)> done,
- Fn<void(const MTP::Error&, const MTP::Response&)> fail) {
- if (isCreatingTopic(history, replyTo.topicRootId)) {
- const auto id = ++_requestAutoincrement;
- const auto creatingId = FullMsgId(
- history->peer->id,
- replyTo.topicRootId);
- auto i = _creatingTopics.find(creatingId);
- if (i == end(_creatingTopics)) {
- sendCreateTopicRequest(history, replyTo.topicRootId);
- i = _creatingTopics.emplace(creatingId).first;
- }
- i->second.push_back({
- .randomId = randomId,
- .replyTo = replyTo.messageId,
- .message = std::move(message),
- .done = std::move(done),
- .fail = std::move(fail),
- .requestId = id,
- });
- _creatingTopicRequests.emplace(id);
- return id;
- }
- const auto realReplyTo = FullReplyTo{
- .messageId = convertTopicReplyToId(history, replyTo.messageId),
- .quote = replyTo.quote,
- .storyId = replyTo.storyId,
- .topicRootId = convertTopicReplyToId(history, replyTo.topicRootId),
- .quoteOffset = replyTo.quoteOffset,
- };
- return v::match(message(history, realReplyTo), [&](const auto &request) {
- const auto type = RequestType::Send;
- return sendRequest(history, type, [=](Fn<void()> finish) {
- const auto session = &_owner->session();
- const auto api = &session->api();
- history->sendRequestId = api->request(
- base::duplicate(request)
- ).done([=](
- const MTPUpdates &result,
- const MTP::Response &response) {
- api->applyUpdates(result, randomId);
- done(result, response);
- finish();
- }).fail([=](
- const MTP::Error &error,
- const MTP::Response &response) {
- fail(error, response);
- finish();
- }).afterRequest(
- history->sendRequestId
- ).send();
- return history->sendRequestId;
- });
- });
- }
- void Histories::checkTopicCreated(FullMsgId rootId, MsgId realRoot) {
- const auto i = _creatingTopics.find(rootId);
- if (i != end(_creatingTopics)) {
- auto scheduled = base::take(i->second);
- _creatingTopics.erase(i);
- _createdTopicIds.emplace(rootId, realRoot);
- const auto history = _owner->history(rootId.peer);
- if (const auto forum = history->asForum()) {
- forum->created(rootId.msg, realRoot);
- }
- for (auto &entry : scheduled) {
- _creatingTopicRequests.erase(entry.requestId);
- sendPreparedMessage(
- history,
- FullReplyTo{
- .messageId = entry.replyTo,
- .topicRootId = realRoot,
- },
- entry.randomId,
- std::move(entry.message),
- std::move(entry.done),
- std::move(entry.fail));
- }
- for (const auto &item : history->clientSideMessages()) {
- const auto replace = [&](MsgId nowId) {
- return (nowId == rootId.msg) ? realRoot : nowId;
- };
- if (item->topicRootId() == rootId.msg) {
- item->setReplyFields(
- replace(item->replyToId()),
- realRoot,
- true);
- }
- }
- }
- }
- FullMsgId Histories::convertTopicReplyToId(
- not_null<History*> history,
- FullMsgId replyToId) const {
- const auto id = (history->peer->id == replyToId.peer)
- ? convertTopicReplyToId(history, replyToId.msg)
- : replyToId.msg;
- return { replyToId.peer, id };
- }
- MsgId Histories::convertTopicReplyToId(
- not_null<History*> history,
- MsgId replyToId) const {
- if (!replyToId) {
- return {};
- }
- const auto i = _createdTopicIds.find({ history->peer->id, replyToId });
- return (i != end(_createdTopicIds)) ? i->second : replyToId;
- }
- void Histories::checkPostponed(not_null<History*> history, int id) {
- if (const auto state = lookup(history)) {
- finishSentRequest(history, state, id);
- }
- }
- void Histories::cancelRequest(int id) {
- if (!id) {
- return;
- } else if (_creatingTopicRequests.contains(id)) {
- cancelDelayedByTopicRequest(id);
- return;
- }
- const auto history = _historyByRequest.take(id);
- if (!history) {
- return;
- }
- const auto state = lookup(*history);
- if (!state) {
- return;
- }
- state->postponed.remove(id);
- finishSentRequest(*history, state, id);
- }
- void Histories::cancelDelayedByTopicRequest(int id) {
- for (auto &[rootId, messages] : _creatingTopics) {
- messages.erase(
- ranges::remove(messages, id, &DelayedByTopicMessage::requestId),
- end(messages));
- }
- _creatingTopicRequests.remove(id);
- }
- void Histories::finishSentRequest(
- not_null<History*> history,
- not_null<State*> state,
- int id) {
- _historyByRequest.remove(id);
- const auto i = state->sent.find(id);
- if (i != end(state->sent)) {
- session().api().request(i->second.id).cancel();
- state->sent.erase(i);
- }
- if (!state->postponed.empty() && !postponeHistoryRequest(*state)) {
- for (auto &[id, postponed] : base::take(state->postponed)) {
- const auto requestId = postponed.generator([=, id=id] {
- checkPostponed(history, id);
- });
- state->sent.emplace(id, SentRequest{
- std::move(postponed.generator),
- requestId,
- RequestType::History
- });
- }
- }
- if (state->postponedRequestEntry && !postponeEntryRequest(*state)) {
- const auto i = _dialogRequests.find(history);
- Assert(i != end(_dialogRequests));
- const auto &[j, ok] = _dialogRequestsPending.emplace(
- history,
- std::move(i->second));
- Assert(ok);
- _dialogRequests.erase(i);
- state->postponedRequestEntry = false;
- postponeRequestDialogEntries();
- }
- checkEmptyState(history);
- }
- Histories::State *Histories::lookup(not_null<History*> history) {
- const auto i = _states.find(history);
- return (i != end(_states)) ? &i->second : nullptr;
- }
- } // namespace Data
|