mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-11 09:19:35 +00:00
Export my messages from left channels.
This commit is contained in:
parent
e8d619c740
commit
1bfe409c93
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ private:
|
||||
|
||||
int _dialogsCount = 0;
|
||||
int _dialogIndex = 0;
|
||||
bool _dialogOnlyMy = false;
|
||||
bool _dialogEmpty = true;
|
||||
std::unique_ptr<File> _dialog;
|
||||
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user