From 4156beaa3cf793a2a3585ea374ce5cb247294c86 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jun 2018 21:40:16 +0100 Subject: [PATCH] Export top peers as frequent contacts. --- .../export/data/export_data_types.cpp | 67 ++++++++++++++++- .../export/data/export_data_types.h | 8 ++ .../SourceFiles/export/export_api_wrap.cpp | 69 +++++++++++++++-- Telegram/SourceFiles/export/export_api_wrap.h | 4 + .../export/output/export_output_text.cpp | 74 ++++++++++++++++++- .../export/output/export_output_text.h | 3 + 6 files changed, 215 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 32f21622b3f81c..2ef20078a8cb63 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -517,6 +517,25 @@ std::map ParsePeersLists( return result; } +User EmptyUser(int32 userId) { + return ParseUser(MTP_userEmpty(MTP_int(userId))); +} + +Chat EmptyChat(int32 chatId) { + return ParseChat(MTP_chatEmpty(MTP_int(chatId))); +} + +Peer EmptyPeer(PeerId peerId) { + if (UserPeerId(BarePeerId(peerId)) == peerId) { + auto user = ParseUser(MTP_userEmpty(MTP_int(BarePeerId(peerId)))); + return Peer{ EmptyUser(BarePeerId(peerId)) }; + } else if (ChatPeerId(BarePeerId(peerId)) == peerId) { + auto chat = ParseChat(MTP_chatEmpty(MTP_int(BarePeerId(peerId)))); + return Peer{ EmptyChat(BarePeerId(peerId)) }; + } + Unexpected("PeerId in EmptyPeer."); +} + File &Media::file() { return content.match([](Photo &data) -> File& { return data.image.file; @@ -835,7 +854,7 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) { if (const auto i = map.find(userId); i != end(map)) { result.list.push_back(i->second.info); } else { - result.list.push_back(ContactInfo()); + result.list.push_back(EmptyUser(userId).info); } } return result; @@ -875,6 +894,52 @@ std::vector SortedContactsIndices(const ContactsList &data) { return indices; } +bool AppendTopPeers(ContactsList &to, const MTPcontacts_TopPeers &data) { + return data.match([](const MTPDcontacts_topPeersNotModified &data) { + return false; + }, [&](const MTPDcontacts_topPeers &data) { + const auto peers = ParsePeersLists(data.vusers, data.vchats); + const auto append = [&]( + std::vector &to, + const MTPVector &list) { + for (const auto &topPeer : list.v) { + to.push_back(topPeer.match([&](const MTPDtopPeer &data) { + const auto peerId = ParsePeerId(data.vpeer); + auto peer = [&] { + const auto i = peers.find(peerId); + return (i != peers.end()) + ? i->second + : EmptyPeer(peerId); + }(); + return TopPeer{ + Peer{ std::move(peer) }, + data.vrating.v + }; + })); + } + }; + for (const auto &list : data.vcategories.v) { + const auto appended = list.match( + [&](const MTPDtopPeerCategoryPeers &data) { + const auto category = data.vcategory.type(); + if (category == mtpc_topPeerCategoryCorrespondents) { + append(to.correspondents, data.vpeers); + return true; + } else if (category == mtpc_topPeerCategoryBotsInline) { + append(to.inlineBots, data.vpeers); + return true; + } else { + return false; + } + }); + if (!appended) { + return false; + } + } + return true; + }); +} + Session ParseSession(const MTPAuthorization &data) { Expects(data.type() == mtpc_authorization); diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 88b90baed8f905..a8e15d80299972 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -205,13 +205,21 @@ struct PersonalInfo { PersonalInfo ParsePersonalInfo(const MTPUserFull &data); +struct TopPeer { + Peer peer; + float64 rating = 0.; +}; + struct ContactsList { std::vector list; + std::vector correspondents; + std::vector inlineBots; }; ContactsList ParseContactsList(const MTPcontacts_Contacts &data); ContactsList ParseContactsList(const MTPVector &data); std::vector SortedContactsIndices(const ContactsList &data); +bool AppendTopPeers(ContactsList &to, const MTPcontacts_TopPeers &data); struct Session { Utf8String platform; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 01ea2e4421e837..35048b70fe7357 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -26,6 +26,7 @@ constexpr auto kFileRequestsCount = 2; constexpr auto kFileNextRequestDelay = TimeMs(20); constexpr auto kChatsSliceLimit = 100; constexpr auto kMessagesSliceLimit = 100; +constexpr auto kTopPeerSliceLimit = 100; constexpr auto kFileMaxSize = 1500 * 1024 * 1024; constexpr auto kLocationCacheSize = 100'000; @@ -111,7 +112,14 @@ struct ApiWrap::StartProcess { }; std::deque steps; StartInfo info; +}; + +struct ApiWrap::ContactsProcess { + FnMut done; + Data::ContactsList result; + + int topPeersOffset = 0; }; struct ApiWrap::UserpicsProcess { @@ -124,7 +132,6 @@ struct ApiWrap::UserpicsProcess { uint64 maxId = 0; bool lastSlice = false; int fileIndex = -1; - }; struct ApiWrap::FileProcess { @@ -145,7 +152,6 @@ struct ApiWrap::FileProcess { QByteArray bytes; }; std::deque requests; - }; struct ApiWrap::FileProgress { @@ -162,7 +168,6 @@ struct ApiWrap::LeftChannelsProcess { int fullCount = 0; int offset = 0; bool finished = false; - }; struct ApiWrap::DialogsProcess { @@ -174,7 +179,6 @@ struct ApiWrap::DialogsProcess { Data::TimeId offsetDate = 0; int32 offsetId = 0; MTPInputPeer offsetPeer = MTP_inputPeerEmpty(); - }; struct ApiWrap::ChatProcess { @@ -190,7 +194,6 @@ struct ApiWrap::ChatProcess { base::optional slice; bool lastSlice = false; int fileIndex = -1; - }; ApiWrap::LoadedFileCache::LoadedFileCache(int limit) : _limit(limit) { @@ -605,10 +608,60 @@ void ApiWrap::finishUserpics() { } void ApiWrap::requestContacts(FnMut done) { + Expects(_contactsProcess == nullptr); + + _contactsProcess = std::make_unique(); + _contactsProcess->done = std::move(done); mainRequest(MTPcontacts_GetSaved( - )).done([=, done = std::move(done)]( - const MTPVector &result) mutable { - done(Data::ParseContactsList(result)); + )).done([=](const MTPVector &result) { + _contactsProcess->result = Data::ParseContactsList(result); + requestTopPeersSlice(); + }).send(); +} + +void ApiWrap::requestTopPeersSlice() { + Expects(_contactsProcess != nullptr); + + using Flag = MTPcontacts_GetTopPeers::Flag; + mainRequest(MTPcontacts_GetTopPeers( + MTP_flags(Flag::f_correspondents | Flag::f_bots_inline), + MTP_int(_contactsProcess->topPeersOffset), + MTP_int(kTopPeerSliceLimit), + MTP_int(0) // hash + )).done([=](const MTPcontacts_TopPeers &result) { + Expects(_contactsProcess != nullptr); + + if (!Data::AppendTopPeers(_contactsProcess->result, result)) { + error("Unexpected data in ApiWrap::requestTopPeersSlice."); + return; + } + + const auto offset = _contactsProcess->topPeersOffset; + const auto loaded = result.match( + [](const MTPDcontacts_topPeersNotModified &data) { + return true; + }, [&](const MTPDcontacts_topPeers &data) { + for (const auto &category : data.vcategories.v) { + const auto loaded = category.match( + [&](const MTPDtopPeerCategoryPeers &data) { + return offset + data.vpeers.v.size() >= data.vcount.v; + }); + if (!loaded) { + return false; + } + } + return true; + }); + + if (loaded) { + auto process = base::take(_contactsProcess); + process->done(std::move(process->result)); + } else { + _contactsProcess->topPeersOffset = std::max( + _contactsProcess->result.correspondents.size(), + _contactsProcess->result.inlineBots.size()); + requestTopPeersSlice(); + } }).send(); } diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index ddfb28faf8705e..ce52f1b13ede29 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -84,6 +84,7 @@ class ApiWrap { private: class LoadedFileCache; struct StartProcess; + struct ContactsProcess; struct UserpicsProcess; struct FileProcess; struct FileProgress; @@ -98,6 +99,8 @@ class ApiWrap { void requestLeftChannelsCount(); void finishStartProcess(); + void requestTopPeersSlice(); + void handleUserpicsSlice(const MTPphotos_Photos &result); void loadUserpicsFiles(Data::UserpicsSlice &&slice); void loadNextUserpic(); @@ -158,6 +161,7 @@ class ApiWrap { std::unique_ptr _startProcess; std::unique_ptr _fileCache; + std::unique_ptr _contactsProcess; std::unique_ptr _userpicsProcess; std::unique_ptr _fileProcess; std::unique_ptr _leftChannelsProcess; diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 769026efccc183..89ae44d67d7b52 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -504,6 +504,15 @@ Result TextWriter::writeUserpicsEnd() { Result TextWriter::writeContactsList(const Data::ContactsList &data) { Expects(_summary != nullptr); + if (const auto result = writeSavedContacts(data); !result) { + return result; + } else if (const auto result = writeFrequentContacts(data); !result) { + return result; + } + return Result::Success(); +} + +Result TextWriter::writeSavedContacts(const Data::ContactsList &data) { if (data.list.empty()) { return Result::Success(); } @@ -511,7 +520,7 @@ Result TextWriter::writeContactsList(const Data::ContactsList &data) { const auto file = fileWithRelativePath("contacts.txt"); auto list = std::vector(); list.reserve(data.list.size()); - for (const auto &index : Data::SortedContactsIndices(data)) { + for (const auto index : Data::SortedContactsIndices(data)) { const auto &contact = data.list[index]; if (contact.firstName.isEmpty() && contact.lastName.isEmpty() @@ -541,6 +550,69 @@ Result TextWriter::writeContactsList(const Data::ContactsList &data) { return _summary->writeBlock(header); } +Result TextWriter::writeFrequentContacts(const Data::ContactsList &data) { + const auto size = data.correspondents.size() + data.inlineBots.size(); + if (data.correspondents.empty() && data.inlineBots.empty()) { + return Result::Success(); + } + + const auto file = fileWithRelativePath("frequent.txt"); + auto list = std::vector(); + list.reserve(size); + const auto writeList = [&]( + const std::vector &peers, + Data::Utf8String category) { + for (const auto &top : peers) { + const auto user = [&]() -> Data::Utf8String { + if (!top.peer.user()) { + return Data::Utf8String(); + } else if (top.peer.name().isEmpty()) { + return "(deleted user)"; + } + return top.peer.name(); + }(); + const auto chatType = [&] { + if (const auto chat = top.peer.chat()) { + return chat->username.isEmpty() + ? (chat->broadcast + ? "Private channel" + : "Private group") + : (chat->broadcast + ? "Public channel" + : "Public group"); + } + return ""; + }(); + const auto chat = [&]() -> Data::Utf8String { + if (!top.peer.chat()) { + return Data::Utf8String(); + } else if (top.peer.name().isEmpty()) { + return "(deleted chat)"; + } + return top.peer.name(); + }(); + list.push_back(SerializeKeyValue({ + { "Category", category }, + { "User", top.peer.user() ? user : QByteArray() }, + { chatType, chat }, + { "Rating", QString::number(top.rating).toUtf8() } + })); + } + }; + writeList(data.correspondents, "Correspondents"); + writeList(data.inlineBots, "Inline bots"); + const auto full = JoinList(kLineBreak, list); + if (const auto result = file->writeBlock(full); !result) { + return result; + } + + const auto header = "Frequent contacts " + "(" + Data::NumberToString(size) + ") - frequent.txt" + + kLineBreak + + kLineBreak; + return _summary->writeBlock(header); +} + Result TextWriter::writeSessionsList(const Data::SessionsList &data) { Expects(_summary != nullptr); diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index 7ca08073681b59..b7c4303daf31cd 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -49,6 +49,9 @@ class TextWriter : public AbstractWriter { QString pathWithRelativePath(const QString &path) const; std::unique_ptr fileWithRelativePath(const QString &path) const; + Result writeSavedContacts(const Data::ContactsList &data); + Result writeFrequentContacts(const Data::ContactsList &data); + Result writeChatsStart( const Data::DialogsInfo &data, const QByteArray &listName,