Export my messages from left channels.

This commit is contained in:
John Preston 2018-06-17 21:15:40 +01:00
parent e8d619c740
commit 1bfe409c93
7 changed files with 216 additions and 68 deletions

View File

@ -1266,6 +1266,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet =
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = 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;

View File

@ -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 <QtCore/QDateTime>
@ -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<MTPMessage> &data,
const MTPVector<MTPUser> &users,

View File

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <vector>
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<Message> list;

View File

@ -114,6 +114,7 @@ struct ApiWrap::FileProcess {
struct ApiWrap::DialogsProcess {
Data::DialogsInfo info;
std::map<int32, Data::Chat> left;
FnMut<void(const Data::DialogsInfo&)> start;
Fn<void(const Data::DialogInfo&)> 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 {

View File

@ -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();

View File

@ -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<QByteArray>();
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;
}

View File

@ -50,6 +50,8 @@ private:
int _dialogsCount = 0;
int _dialogIndex = 0;
bool _dialogOnlyMy = false;
bool _dialogEmpty = true;
std::unique_ptr<File> _dialog;
};