From 1bfe409c9380148a8728b98434ba5b3f2ce883fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 17 Jun 2018 21:15:40 +0100 Subject: [PATCH] Export my messages from left channels. --- Telegram/Resources/scheme.tl | 1 + .../export/data/export_data_types.cpp | 83 ++++++++-- .../export/data/export_data_types.h | 9 ++ .../SourceFiles/export/export_api_wrap.cpp | 153 ++++++++++++------ Telegram/SourceFiles/export/export_api_wrap.h | 7 +- .../export/output/export_output_text.cpp | 29 +++- .../export/output/export_output_text.h | 2 + 7 files changed, 216 insertions(+), 68 deletions(-) diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index a16e7d51a1..9faad2bb7d 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -1266,6 +1266,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; +channels.getLeftChannels#90920196 = messages.Chats; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index cf55e70fc2..f957b69be8 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "export/data/export_data_types.h" +#include "export/export_settings.h" #include "core/mime_type.h" #include @@ -891,6 +892,21 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) { return result; } +DialogInfo::Type DialogTypeFromChat(const Chat &chat) { + using Type = DialogInfo::Type; + return chat.username.isEmpty() + ? (chat.broadcast + ? Type::PrivateChannel + : Type::PrivateGroup) + : (chat.broadcast + ? Type::PublicChannel + : Type::PublicGroup); +} + +DialogInfo::Type DialogTypeFromUser(const User &user) { + return user.isBot ? DialogInfo::Type::Bot : DialogInfo::Type::Personal; +} + DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { auto result = DialogsInfo(); const auto folder = QString(); @@ -905,22 +921,14 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { const auto &fields = dialog.c_dialog(); auto info = DialogInfo(); - const auto peerId = ParsePeerId(fields.vpeer); - const auto peerIt = peers.find(peerId); + info.peerId = ParsePeerId(fields.vpeer); + const auto peerIt = peers.find(info.peerId); if (peerIt != end(peers)) { using Type = DialogInfo::Type; const auto &peer = peerIt->second; info.type = peer.user() - ? (peer.user()->isBot - ? Type::Bot - : Type::Personal) - : (peer.chat()->broadcast - ? (peer.chat()->username.isEmpty() - ? Type::PrivateChannel - : Type::PublicChannel) - : (peer.chat()->username.isEmpty() - ? Type::PrivateGroup - : Type::PublicGroup)); + ? DialogTypeFromUser(*peer.user()) + : DialogTypeFromChat(*peer.chat()); info.name = peer.name(); info.input = peer.input(); } @@ -936,6 +944,57 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { return result; } +void InsertLeftDialog( + DialogsInfo &info, + const Chat &chat, + Message &&message) { + const auto projection = [](const DialogInfo &dialog) { + return std::make_tuple( + dialog.topMessageDate, + dialog.topMessageId, + BarePeerId(dialog.peerId)); + }; + const auto i = ranges::lower_bound( + info.list, + std::make_tuple(message.date, message.id, chat.id), + ranges::ordered_less{}, + projection); + + auto insert = DialogInfo(); + insert.input = chat.input; + insert.name = chat.title; + insert.peerId = ChatPeerId(chat.id); + insert.topMessageDate = message.date; + insert.topMessageId = message.id; + insert.type = DialogTypeFromChat(chat); + info.list.insert(i, std::move(insert)); +} + +void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) { + auto &list = info.list; + const auto digits = Data::NumberToString(list.size() - 1).size(); + auto index = 0; + for (auto &dialog : list) { + const auto number = Data::NumberToString(++index, digits, '0'); + dialog.relativePath = "Chats/chat_" + number + '/'; + + using DialogType = DialogInfo::Type; + using Type = Settings::Type; + const auto setting = [&] { + switch (dialog.type) { + case DialogType::Personal: return Type::PersonalChats; + case DialogType::Bot: return Type::BotChats; + case DialogType::PrivateGroup: return Type::PrivateGroups; + case DialogType::PrivateChannel: return Type::PrivateChannels; + case DialogType::PublicGroup: return Type::PublicGroups; + case DialogType::PublicChannel: return Type::PublicChannels; + } + Unexpected("Type in ApiWrap::onlyMyMessages."); + }(); + dialog.onlyMyMessages = ((settings.fullChats & setting) != setting); + } +} + MessagesSlice ParseMessagesSlice( const MTPVector &data, const MTPVector &users, diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 5fa7dc8c56..f005a5ed26 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Export { +struct Settings; namespace Data { using TimeId = int32; @@ -420,7 +421,10 @@ struct DialogInfo { MTPInputPeer input = MTP_inputPeerEmpty(); int32 topMessageId = 0; TimeId topMessageDate = 0; + PeerId peerId = 0; + // Filled after the whole dialogs list is accumulated. + bool onlyMyMessages = false; QString relativePath; }; @@ -430,6 +434,11 @@ struct DialogsInfo { }; DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data); +void InsertLeftDialog( + DialogsInfo &info, + const Chat &chat, + Message &&message); +void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings); struct MessagesSlice { std::vector list; diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index e95abe60c5..c80df6eb05 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -114,6 +114,7 @@ struct ApiWrap::FileProcess { struct ApiWrap::DialogsProcess { Data::DialogsInfo info; + std::map left; FnMut start; Fn startOne; @@ -350,8 +351,9 @@ void ApiWrap::loadNextUserpic() { ? base::none : base::make_optional(list.back().id); - _userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice)); - + if (!list.empty()) { + _userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice)); + } if (_userpicsProcess->lastSlice) { finishUserpics(); return; @@ -431,19 +433,16 @@ void ApiWrap::requestDialogsSlice() { _dialogsProcess->offsetPeer, MTP_int(kChatsSliceLimit) )).done([=](const MTPmessages_Dialogs &result) mutable { - const auto finished = [&] { - switch (result.type()) { - case mtpc_messages_dialogs: return true; - case mtpc_messages_dialogsSlice: { - const auto &data = result.c_messages_dialogsSlice(); - return data.vdialogs.v.isEmpty(); - } break; - default: Unexpected("Type in ApiWrap::requestChatsSlice."); - } - }(); + const auto finished = result.match( + [](const MTPDmessages_dialogs &data) { + return true; + }, [](const MTPDmessages_dialogsSlice &data) { + return data.vdialogs.v.isEmpty(); + }); + auto info = Data::ParseDialogsInfo(result); if (finished || info.list.empty()) { - finishDialogsList(); + requestLeftChannels(); } else { const auto &last = info.list.back(); _dialogsProcess->offsetId = last.topMessageId; @@ -492,28 +491,83 @@ void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) { } } +void ApiWrap::requestLeftChannels() { + Expects(_dialogsProcess != nullptr); + + mainRequest(MTPchannels_GetLeftChannels( + )).done([=](const MTPmessages_Chats &result) mutable { + Expects(_dialogsProcess != nullptr); + + const auto finished = result.match( + [](const MTPDmessages_chats &data) { + return true; + }, [](const MTPDmessages_chatsSlice &data) { + return data.vchats.v.isEmpty(); + }); + + _dialogsProcess->left = Data::ParseChatsList(*result.match( + [](const auto &data) { + return &data.vchats; + })); + requestLeftDialog(); + }).send(); +} + +void ApiWrap::requestLeftDialog() { + Expects(_dialogsProcess != nullptr); + + auto &left = _dialogsProcess->left; + if (true || left.empty()) { // #TODO export + finishDialogsList(); + return; + } + const auto key = std::move(*left.begin()); + left.erase(key.first); + + mainRequest(MTPmessages_Search( + MTP_flags(MTPmessages_Search::Flag::f_from_id), + key.second.input, + MTP_string(""), // query + _user, + MTP_inputMessagesFilterEmpty(), + MTP_int(0), // min_date + MTP_int(0), // max_date + MTP_int(0), // offset_id + MTP_int(0), // add_offset + MTP_int(1), // limit + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_int(0) // hash + )).done([=](const MTPmessages_Messages &result) { + Expects(_dialogsProcess != nullptr); + + result.match([=](const MTPDmessages_messagesNotModified &data) { + error("Unexpected messagesNotModified received."); + }, [=](const auto &data) { + auto messages = Data::ParseMessagesList( + data.vmessages, + QString()); + if (!messages.empty() && messages.begin()->second.date > 0) { + Data::InsertLeftDialog( + _dialogsProcess->info, + key.second, + std::move(messages.begin()->second)); + } + requestLeftDialog(); + }); + }).send(); +} + void ApiWrap::finishDialogsList() { Expects(_dialogsProcess != nullptr); ranges::reverse(_dialogsProcess->info.list); - fillDialogsPaths(); + Data::FinalizeDialogsInfo(_dialogsProcess->info, *_settings); _dialogsProcess->start(_dialogsProcess->info); requestNextDialog(); } -void ApiWrap::fillDialogsPaths() { - Expects(_dialogsProcess != nullptr); - - auto &list = _dialogsProcess->info.list; - const auto digits = Data::NumberToString(list.size() - 1).size(); - auto index = 0; - for (auto &dialog : list) { - const auto number = Data::NumberToString(++index, digits, '0'); - dialog.relativePath = "Chats/chat_" + number + '/'; - } -} - void ApiWrap::requestNextDialog() { Expects(_dialogsProcess != nullptr); Expects(_dialogsProcess->single == nullptr); @@ -534,6 +588,15 @@ void ApiWrap::requestMessagesSlice() { Expects(_dialogsProcess->single != nullptr); const auto process = _dialogsProcess->single.get(); + + // #TODO export + if (process->info.input.match([](const MTPDinputPeerUser &value) { + return !value.vaccess_hash.v; + }, [](const auto &data) { return false; })) { + finishMessages(); + return; + } + auto handleResult = [=](const MTPmessages_Messages &result) mutable { Expects(_dialogsProcess != nullptr); Expects(_dialogsProcess->single != nullptr); @@ -552,7 +615,7 @@ void ApiWrap::requestMessagesSlice() { process->info.relativePath)); }); }; - if (onlyMyMessages()) { + if (process->info.onlyMyMessages) { mainRequest(MTPmessages_Search( MTP_flags(MTPmessages_Search::Flag::f_from_id), process->info.input, @@ -582,26 +645,6 @@ void ApiWrap::requestMessagesSlice() { } } -bool ApiWrap::onlyMyMessages() const { - Expects(_dialogsProcess != nullptr); - Expects(_dialogsProcess->single != nullptr); - - const auto process = _dialogsProcess->single.get(); - using Type = Data::DialogInfo::Type; - const auto setting = [&] { - switch (process->info.type) { - case Type::Personal: return Settings::Type::PersonalChats; - case Type::Bot: return Settings::Type::BotChats; - case Type::PrivateGroup: return Settings::Type::PrivateGroups; - case Type::PrivateChannel: return Settings::Type::PrivateChannels; - case Type::PublicGroup: return Settings::Type::PublicGroups; - case Type::PublicChannel: return Settings::Type::PublicChannels; - } - Unexpected("Type in ApiWrap::onlyMyMessages."); - }(); - return (_settings->fullChats & setting) != setting; -} - void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) { Expects(_dialogsProcess != nullptr); Expects(_dialogsProcess->single != nullptr); @@ -637,12 +680,20 @@ void ApiWrap::loadNextMessageFile() { return; } } + finishMessagesSlice(); +} - if (!list.empty()) { - process->offsetId = list.back().id + 1; +void ApiWrap::finishMessagesSlice() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + Expects(_dialogsProcess->single->slice.has_value()); + + const auto process = _dialogsProcess->single.get(); + auto slice = *base::take(process->slice); + if (!slice.list.empty()) { + process->offsetId = slice.list.back().id + 1; + _dialogsProcess->sliceOne(std::move(slice)); } - _dialogsProcess->sliceOne(*base::take(process->slice)); - if (process->lastSlice) { finishMessages(); } else { diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 367e8c4c3a..9b9a4b0f2b 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Export { namespace Data { struct File; +struct Chat; struct FileLocation; struct PersonalInfo; struct UserpicsInfo; @@ -72,16 +73,16 @@ private: void requestDialogsSlice(); void appendDialogsSlice(Data::DialogsInfo &&info); + void requestLeftChannels(); + void requestLeftDialog(); void finishDialogsList(); - void fillDialogsPaths(); void requestNextDialog(); void requestMessagesSlice(); - bool onlyMyMessages() const; void loadMessagesFiles(Data::MessagesSlice &&slice); void loadNextMessageFile(); - void loadMessageFileDone(const QString &relativePath); + void finishMessagesSlice(); void finishMessages(); void finishDialogs(); diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 7d02f988be..23d2db12c5 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -30,12 +30,17 @@ void SerializeMultiline( auto offset = 0; do { appendTo.append("> "); + const auto win = (newline > 0 && *(data + newline - 1) == '\r'); + if (win) --newline; appendTo.append(data + offset, newline - offset).append(kLineBreak); + if (win) ++newline; offset = newline + 1; newline = value.indexOf('\n', offset); } while (newline > 0); - appendTo.append("> "); - appendTo.append(data + offset).append(kLineBreak); + if (const auto size = value.size(); size > offset) { + appendTo.append("> "); + appendTo.append(data + offset, size - offset).append(kLineBreak); + } } QByteArray JoinList( @@ -467,6 +472,9 @@ bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) { } bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { + Expects(_result != nullptr); + Expects(!data.list.empty()); + auto lines = QByteArray(); for (const auto &userpic : data.list) { if (!userpic.date) { @@ -485,12 +493,16 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { } bool TextWriter::writeUserpicsEnd() { + Expects(_result != nullptr); + return (_userpicsCount > 0) ? _result->writeBlock(kLineBreak) == File::Result::Success : true; } bool TextWriter::writeContactsList(const Data::ContactsList &data) { + Expects(_result != nullptr); + if (data.list.empty()) { return true; } @@ -529,6 +541,8 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { } bool TextWriter::writeSessionsList(const Data::SessionsList &data) { + Expects(_result != nullptr); + if (data.list.empty()) { return true; } @@ -568,6 +582,8 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) { } bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { + Expects(_result != nullptr); + if (data.list.empty()) { return true; } @@ -635,12 +651,16 @@ bool TextWriter::writeDialogStart(const Data::DialogInfo &data) { const auto digits = Data::NumberToString(_dialogsCount - 1).size(); const auto number = Data::NumberToString(++_dialogIndex, digits, '0'); _dialog = fileWithRelativePath(data.relativePath + "messages.txt"); + _dialogEmpty = true; + _dialogOnlyMy = data.onlyMyMessages; return true; } bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) { Expects(_dialog != nullptr); + Expects(!data.list.empty()); + _dialogEmpty = false; auto list = std::vector(); list.reserve(data.list.size()); auto index = 0; @@ -659,6 +679,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) { bool TextWriter::writeDialogEnd() { Expects(_dialog != nullptr); + if (_dialogEmpty) { + _dialog->writeBlock(_dialogOnlyMy + ? "No outgoing messages in this chat." + : "No messages in this chat."); + } _dialog = nullptr; return true; } diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index 714684386c..d6f20ccdf7 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -50,6 +50,8 @@ private: int _dialogsCount = 0; int _dialogIndex = 0; + bool _dialogOnlyMy = false; + bool _dialogEmpty = true; std::unique_ptr _dialog; };