diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index a6d5afaba..f4e5b0032 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -36,6 +36,7 @@ namespace { constexpr auto kUserPeerIdShift = (1ULL << 32); constexpr auto kChatPeerIdShift = (2ULL << 32); constexpr auto kMaxImageSize = 10000; +constexpr auto kMigratedMessagesIdShift = -1'000'000'000; QString PrepareFileNameDatePart(TimeId date) { return date @@ -712,6 +713,12 @@ Chat ParseChat(const MTPChat &data) { result.id = data.vid().v; result.title = ParseString(data.vtitle()); result.input = MTP_inputPeerChat(MTP_int(result.id)); + if (const auto migratedTo = data.vmigrated_to()) { + result.migratedToChannelId = migratedTo->match( + [](const MTPDinputChannel &data) { + return data.vchannel_id().v; + }, [](auto&&) { return 0; }); + } }, [&](const MTPDchatEmpty &data) { result.id = data.vid().v; result.input = MTP_inputPeerChat(MTP_int(result.id)); @@ -1477,6 +1484,9 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { ? peer.user()->info.lastName : Utf8String(); info.input = peer.input(); + info.migratedToChannelId = peer.chat() + ? peer.chat()->migratedToChannelId + : 0; } info.topMessageId = fields.vtop_message().v; const auto shift = IsChatPeerId(info.peerId) @@ -1515,6 +1525,7 @@ DialogInfo DialogInfoFromChat(const Chat &data) { result.topMessageDate = 0; result.topMessageId = 0; result.type = DialogTypeFromChat(data); + result.migratedToChannelId = data.migratedToChannelId; return result; } @@ -1590,6 +1601,34 @@ DialogsInfo ParseDialogsInfo( return result; } +bool AddMigrateFromSlice( + DialogInfo &to, + const DialogInfo &from, + int splitIndex, + int splitsCount) { + Expects(splitIndex < splitsCount); + + const auto good = to.migratedFromInput.match([]( + const MTPDinputPeerEmpty &) { + return true; + }, [&](const MTPDinputPeerChat & data) { + return (ChatPeerId(data.vchat_id().v) == from.peerId); + }, [](auto&&) { return false; }); + if (!good) { + return false; + } + Assert(from.splits.size() == from.messagesCountPerSplit.size()); + for (auto i = 0, count = int(from.splits.size()); i != count; ++i) { + Assert(from.splits[i] < splitsCount); + to.splits.push_back(from.splits[i] - splitsCount); + to.messagesCountPerSplit.push_back(from.messagesCountPerSplit[i]); + } + to.migratedFromInput = from.input; + to.splits.push_back(splitIndex - splitsCount); + to.messagesCountPerSplit.push_back(0); + return true; +} + void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) { auto &chats = info.chats; auto &left = info.left; @@ -1619,7 +1658,7 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) { }(); dialog.onlyMyMessages = ((settings.fullChats & setting) != setting); - ranges::reverse(dialog.splits); + ranges::sort(dialog.splits); } for (auto &dialog : left) { Assert(!settings.onlySinglePeer()); @@ -1647,6 +1686,16 @@ MessagesSlice ParseMessagesSlice( return result; } +MessagesSlice AdjustMigrateMessageIds(MessagesSlice slice) { + for (auto &message : slice.list) { + message.id += kMigratedMessagesIdShift; + if (message.replyToMsgId) { + message.replyToMsgId += kMigratedMessagesIdShift; + } + } + return slice; +} + TimeId SingleMessageDate(const MTPmessages_Messages &data) { return data.match([&](const MTPDmessages_messagesNotModified &data) { return 0; diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 573f7c0af..5abe768db 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -220,6 +220,7 @@ std::map ParseUsersList(const MTPVector &data); struct Chat { int32 id = 0; + int32 migratedToChannelId = 0; Utf8String title; Utf8String username; bool isBroadcast = false; @@ -564,6 +565,9 @@ struct DialogInfo { TimeId topMessageDate = 0; PeerId peerId = 0; + MTPInputPeer migratedFromInput = MTP_inputPeerEmpty(); + int32 migratedToChannelId = 0; + // User messages splits which contained that dialog. std::vector splits; @@ -594,6 +598,11 @@ DialogsInfo ParseDialogsInfo( const MTPInputPeer &singlePeer, const MTPmessages_Chats &data); DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data); +bool AddMigrateFromSlice( + DialogInfo &to, + const DialogInfo &from, + int splitIndex, + int splitsCount); void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings); struct MessagesSlice { @@ -607,6 +616,7 @@ MessagesSlice ParseMessagesSlice( const MTPVector &users, const MTPVector &chats, const QString &mediaFolder); +MessagesSlice AdjustMigrateMessageIds(MessagesSlice slice); bool SingleMessageBefore( const MTPmessages_Messages &data, diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index b2fc69789..b036089cb 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -1047,34 +1047,9 @@ void ApiWrap::cancelExportFast() { } void ApiWrap::requestSinglePeerDialog() { - const auto isChannelType = [](Data::DialogInfo::Type type) { - using Type = Data::DialogInfo::Type; - return (type == Type::PrivateSupergroup) - || (type == Type::PublicSupergroup) - || (type == Type::PrivateChannel) - || (type == Type::PublicChannel); - }; auto doneSinglePeer = [=](const auto &result) { - auto info = Data::ParseDialogsInfo(_settings->singlePeer, result); - - _dialogsProcess->processedCount += info.chats.size(); - appendDialogsSlice(std::move(info)); - - const auto last = _dialogsProcess->splitIndexPlusOne - 1; - for (auto &info : _dialogsProcess->info.chats) { - if (isChannelType(info.type)) { - continue; - } - for (auto i = last; i != 0; --i) { - info.splits.push_back(i - 1); - info.messagesCountPerSplit.push_back(0); - } - } - - if (!_dialogsProcess->progress(_dialogsProcess->processedCount)) { - return; - } - finishDialogsList(); + appendSinglePeerDialogs( + Data::ParseDialogsInfo(_settings->singlePeer, result)); }; const auto requestUser = [&](const MTPInputUser &data) { mainRequest(MTPusers_GetUsers( @@ -1104,6 +1079,76 @@ void ApiWrap::requestSinglePeerDialog() { }); } +mtpRequestId ApiWrap::requestSinglePeerMigrated( + const Data::DialogInfo &info) { + const auto input = info.input.match([&]( + const MTPDinputPeerChannel & data) { + return MTP_inputChannel( + data.vchannel_id(), + data.vaccess_hash()); + }, [](auto&&) -> MTPinputChannel { + Unexpected("Peer type in a supergroup."); + }); + return mainRequest(MTPchannels_GetFullChannel( + input + )).done([=](const MTPmessages_ChatFull &result) { + auto info = result.match([&]( + const MTPDmessages_chatFull &data) { + const auto migratedChatId = data.vfull_chat().match([&]( + const MTPDchannelFull &data) { + return data.vmigrated_from_chat_id().value_or_empty(); + }, [](auto &&other) { + return 0; + }); + return migratedChatId + ? Data::ParseDialogsInfo( + MTP_inputPeerChat(MTP_int(migratedChatId)), + MTP_messages_chats(data.vchats())) + : Data::DialogsInfo(); + }); + appendSinglePeerDialogs(std::move(info)); + }).send(); +} + +void ApiWrap::appendSinglePeerDialogs(Data::DialogsInfo &&info) { + const auto isSupergroupType = [](Data::DialogInfo::Type type) { + using Type = Data::DialogInfo::Type; + return (type == Type::PrivateSupergroup) + || (type == Type::PublicSupergroup); + }; + const auto isChannelType = [](Data::DialogInfo::Type type) { + using Type = Data::DialogInfo::Type; + return (type == Type::PrivateChannel) + || (type == Type::PublicChannel); + }; + + auto migratedRequestId = mtpRequestId(0); + const auto last = _dialogsProcess->splitIndexPlusOne - 1; + for (auto &info : info.chats) { + if (isSupergroupType(info.type) && !migratedRequestId) { + migratedRequestId = requestSinglePeerMigrated(info); + continue; + } else if (isChannelType(info.type)) { + continue; + } + for (auto i = last; i != 0; --i) { + info.splits.push_back(i - 1); + info.messagesCountPerSplit.push_back(0); + } + } + + if (!migratedRequestId) { + _dialogsProcess->processedCount += info.chats.size(); + } + appendDialogsSlice(std::move(info)); + + if (migratedRequestId + || !_dialogsProcess->progress(_dialogsProcess->processedCount)) { + return; + } + finishDialogsList(); +} + void ApiWrap::requestDialogsSlice() { Expects(_dialogsProcess != nullptr); @@ -1261,15 +1306,41 @@ void ApiWrap::appendChatsSlice( Expects(_settings != nullptr); const auto types = _settings->types; + const auto goodByTypes = [&](const Data::DialogInfo &info) { + return ((types & SettingsFromDialogsType(info.type)) != 0); + }; auto filtered = ranges::view::all( from ) | ranges::view::filter([&](const Data::DialogInfo &info) { - return (types & SettingsFromDialogsType(info.type)) != 0; + if (goodByTypes(info)) { + return true; + } else if (info.migratedToChannelId + && (((types & Settings::Type::PublicGroups) != 0) + || ((types & Settings::Type::PrivateGroups) != 0))) { + return true; + } + return false; }); to.reserve(to.size() + from.size()); for (auto &info : filtered) { const auto nextIndex = to.size(); - const auto [i, ok] = process.indexByPeer.emplace(info.peerId, nextIndex); + if (info.migratedToChannelId) { + const auto toPeerId = Data::ChatPeerId(info.migratedToChannelId); + const auto i = process.indexByPeer.find(toPeerId); + if (i != process.indexByPeer.end() + && Data::AddMigrateFromSlice( + to[i->second], + info, + splitIndex, + int(_splits.size()))) { + continue; + } else if (!goodByTypes(info)) { + continue; + } + } + const auto [i, ok] = process.indexByPeer.emplace( + info.peerId, + nextIndex); if (ok) { to.push_back(std::move(info)); } @@ -1325,10 +1396,17 @@ void ApiWrap::requestChatMessages( base::take(_chatProcess->requestDone)(std::move(result)); }; + const auto splitsCount = int(_splits.size()); + const auto realPeerInput = (splitIndex >= 0) + ? _chatProcess->info.input + : _chatProcess->info.migratedFromInput; + const auto realSplitIndex = (splitIndex >= 0) + ? splitIndex + : (splitsCount + splitIndex); if (_chatProcess->info.onlyMyMessages) { - splitRequest(splitIndex, MTPmessages_Search( + splitRequest(realSplitIndex, MTPmessages_Search( MTP_flags(MTPmessages_Search::Flag::f_from_id), - _chatProcess->info.input, + realPeerInput, MTP_string(), // query _user, MTP_inputMessagesFilterEmpty(), @@ -1342,8 +1420,8 @@ void ApiWrap::requestChatMessages( MTP_int(0) // hash )).done(doneHandler).send(); } else { - splitRequest(splitIndex, MTPmessages_GetHistory( - _chatProcess->info.input, + splitRequest(realSplitIndex, MTPmessages_GetHistory( + realPeerInput, MTP_int(offsetId), MTP_int(0), // offset_date MTP_int(addOffset), @@ -1355,7 +1433,7 @@ void ApiWrap::requestChatMessages( Expects(_chatProcess != nullptr); if (error.type() == qstr("CHANNEL_PRIVATE")) { - if (_chatProcess->info.input.type() == mtpc_inputPeerChannel + if (realPeerInput.type() == mtpc_inputPeerChannel && !_chatProcess->info.onlyMyMessages) { // Perhaps we just left / were kicked from channel. @@ -1399,10 +1477,16 @@ Data::FileOrigin ApiWrap::currentFileMessageOrigin() const { Expects(_chatProcess != nullptr); Expects(_chatProcess->slice.has_value()); + const auto splitIndex = _chatProcess->info.splits[ + _chatProcess->localSplitIndex]; auto result = Data::FileOrigin(); result.messageId = currentFileMessage()->id; - result.peer = _chatProcess->info.input; - result.split = _chatProcess->info.splits[_chatProcess->localSplitIndex]; + result.split = (splitIndex >= 0) + ? splitIndex + : (int(_splits.size()) + splitIndex); + result.peer = (splitIndex >= 0) + ? _chatProcess->info.input + : _chatProcess->info.migratedFromInput; return result; } @@ -1452,6 +1536,11 @@ void ApiWrap::finishMessagesSlice() { auto slice = *base::take(_chatProcess->slice); if (!slice.list.empty()) { _chatProcess->largestIdPlusOne = slice.list.back().id + 1; + const auto splitIndex = _chatProcess->info.splits[ + _chatProcess->localSplitIndex]; + if (splitIndex < 0) { + slice = AdjustMigrateMessageIds(std::move(slice)); + } if (!_chatProcess->handleSlice(std::move(slice))) { return; } diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 0e47e9be3..db3457194 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -126,6 +126,8 @@ private: void appendDialogsSlice(Data::DialogsInfo &&info); void finishDialogsList(); void requestSinglePeerDialog(); + mtpRequestId requestSinglePeerMigrated(const Data::DialogInfo &info); + void appendSinglePeerDialogs(Data::DialogsInfo &&info); void requestLeftChannelsIfNeeded(); void requestLeftChannelsList( diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index 2870e1bca..022abd810 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -352,7 +352,9 @@ void ControllerObject::initialized(const ApiWrap::StartInfo &info) { void ControllerObject::collectDialogsList() { setState(stateDialogsList(0)); _api.requestDialogsList([=](int count) { - setState(stateDialogsList(count)); + if (count > 0) { + setState(stateDialogsList(count - 1)); + } return true; }, [=](Data::DialogsInfo &&result) { _dialogsInfo = std::move(result);