mirror of https://github.com/procxx/kepka.git
Add chats list export.
This commit is contained in:
parent
affe9defb5
commit
6776d88688
|
@ -7,12 +7,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "export/data/export_data_types.h"
|
#include "export/data/export_data_types.h"
|
||||||
|
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
|
|
||||||
namespace App { // Hackish..
|
namespace App { // Hackish..
|
||||||
QString formatPhone(QString phone);
|
QString formatPhone(QString phone);
|
||||||
} // namespace App
|
} // namespace App
|
||||||
|
|
||||||
namespace Export {
|
namespace Export {
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kUserPeerIdShift = (1ULL << 32);
|
||||||
|
constexpr auto kChatPeerIdShift = (2ULL << 32);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PeerId UserPeerId(int32 userId) {
|
||||||
|
return kUserPeerIdShift | uint32(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerId ChatPeerId(int32 chatId) {
|
||||||
|
return kChatPeerIdShift | uint32(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 BarePeerId(PeerId peerId) {
|
||||||
|
return int32(peerId & 0xFFFFFFFFULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerId ParsePeerId(const MTPPeer &data) {
|
||||||
|
switch (data.type()) {
|
||||||
|
case mtpc_peerUser:
|
||||||
|
return UserPeerId(data.c_peerUser().vuser_id.v);
|
||||||
|
case mtpc_peerChat:
|
||||||
|
return ChatPeerId(data.c_peerChat().vchat_id.v);
|
||||||
|
case mtpc_peerChannel:
|
||||||
|
return ChatPeerId(data.c_peerChannel().vchannel_id.v);
|
||||||
|
}
|
||||||
|
Unexpected("Type in ParsePeerId.");
|
||||||
|
}
|
||||||
|
|
||||||
Utf8String ParseString(const MTPstring &data) {
|
Utf8String ParseString(const MTPstring &data) {
|
||||||
return data.v;
|
return data.v;
|
||||||
|
@ -85,7 +117,7 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
|
||||||
case mtpc_photo: {
|
case mtpc_photo: {
|
||||||
const auto &photo = data.c_photo();
|
const auto &photo = data.c_photo();
|
||||||
result.id = photo.vid.v;
|
result.id = photo.vid.v;
|
||||||
result.date = QDateTime::fromTime_t(photo.vdate.v);
|
result.date = photo.vdate.v;
|
||||||
result.image = ParseMaxImage(photo.vsizes, suggestedPath);
|
result.image = ParseMaxImage(photo.vsizes, suggestedPath);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
@ -100,18 +132,19 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Utf8String FormatDateTime(
|
Utf8String FormatDateTime(
|
||||||
const QDateTime &date,
|
TimeId date,
|
||||||
QChar dateSeparator,
|
QChar dateSeparator,
|
||||||
QChar timeSeparator,
|
QChar timeSeparator,
|
||||||
QChar separator) {
|
QChar separator) {
|
||||||
|
const auto value = QDateTime::fromTime_t(date);
|
||||||
return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3"
|
return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3"
|
||||||
+ separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6"
|
+ separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6"
|
||||||
).arg(date.date().year()
|
).arg(value.date().year()
|
||||||
).arg(date.date().month(), 2, 10, QChar('0')
|
).arg(value.date().month(), 2, 10, QChar('0')
|
||||||
).arg(date.date().day(), 2, 10, QChar('0')
|
).arg(value.date().day(), 2, 10, QChar('0')
|
||||||
).arg(date.time().hour(), 2, 10, QChar('0')
|
).arg(value.time().hour(), 2, 10, QChar('0')
|
||||||
).arg(date.time().minute(), 2, 10, QChar('0')
|
).arg(value.time().minute(), 2, 10, QChar('0')
|
||||||
).arg(date.time().second(), 2, 10, QChar('0')
|
).arg(value.time().second(), 2, 10, QChar('0')
|
||||||
).toUtf8();
|
).toUtf8();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,11 +182,17 @@ User ParseUser(const MTPUser &data) {
|
||||||
if (fields.has_username()) {
|
if (fields.has_username()) {
|
||||||
result.username = ParseString(fields.vusername);
|
result.username = ParseString(fields.vusername);
|
||||||
}
|
}
|
||||||
|
if (fields.has_access_hash()) {
|
||||||
|
result.input = MTP_inputUser(fields.vid, fields.vaccess_hash);
|
||||||
|
} else {
|
||||||
|
result.input = MTP_inputUserEmpty();
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case mtpc_userEmpty: {
|
case mtpc_userEmpty: {
|
||||||
const auto &fields = data.c_userEmpty();
|
const auto &fields = data.c_userEmpty();
|
||||||
result.id = fields.vid.v;
|
result.id = fields.vid.v;
|
||||||
|
result.input = MTP_inputUserEmpty();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
default: Unexpected("Type in ParseUser.");
|
default: Unexpected("Type in ParseUser.");
|
||||||
|
@ -161,8 +200,8 @@ User ParseUser(const MTPUser &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) {
|
std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data) {
|
||||||
auto result = std::map<int, User>();
|
auto result = std::map<int32, User>();
|
||||||
for (const auto &user : data.v) {
|
for (const auto &user : data.v) {
|
||||||
auto parsed = ParseUser(user);
|
auto parsed = ParseUser(user);
|
||||||
result.emplace(parsed.id, std::move(parsed));
|
result.emplace(parsed.id, std::move(parsed));
|
||||||
|
@ -170,6 +209,152 @@ std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Chat ParseChat(const MTPChat &data) {
|
||||||
|
auto result = Chat();
|
||||||
|
switch (data.type()) {
|
||||||
|
case mtpc_chat: {
|
||||||
|
const auto &fields = data.c_chat();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.title = ParseString(fields.vtitle);
|
||||||
|
result.input = MTP_inputPeerChat(MTP_int(result.id));
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_chatEmpty: {
|
||||||
|
const auto &fields = data.c_chatEmpty();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.input = MTP_inputPeerChat(MTP_int(result.id));
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_chatForbidden: {
|
||||||
|
const auto &fields = data.c_chatForbidden();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.title = ParseString(fields.vtitle);
|
||||||
|
result.input = MTP_inputPeerChat(MTP_int(result.id));
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_channel: {
|
||||||
|
const auto &fields = data.c_channel();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.broadcast = fields.is_broadcast();
|
||||||
|
result.title = ParseString(fields.vtitle);
|
||||||
|
if (fields.has_username()) {
|
||||||
|
result.username = ParseString(fields.vusername);
|
||||||
|
}
|
||||||
|
result.input = MTP_inputPeerChannel(
|
||||||
|
MTP_int(result.id),
|
||||||
|
fields.vaccess_hash);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_channelForbidden: {
|
||||||
|
const auto &fields = data.c_channelForbidden();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.broadcast = fields.is_broadcast();
|
||||||
|
result.title = ParseString(fields.vtitle);
|
||||||
|
result.input = MTP_inputPeerChannel(
|
||||||
|
MTP_int(result.id),
|
||||||
|
fields.vaccess_hash);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: Unexpected("Type in ParseChat.");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int32, Chat> ParseChatsList(const MTPVector<MTPChat> &data) {
|
||||||
|
auto result = std::map<int32, Chat>();
|
||||||
|
for (const auto &chat : data.v) {
|
||||||
|
auto parsed = ParseChat(chat);
|
||||||
|
result.emplace(parsed.id, std::move(parsed));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const User *Peer::user() const {
|
||||||
|
return base::get_if<User>(&data);
|
||||||
|
}
|
||||||
|
const Chat *Peer::chat() const {
|
||||||
|
return base::get_if<Chat>(&data);
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerId Peer::id() const {
|
||||||
|
if (const auto user = this->user()) {
|
||||||
|
return UserPeerId(user->id);
|
||||||
|
} else if (const auto chat = this->chat()) {
|
||||||
|
return ChatPeerId(chat->id);
|
||||||
|
}
|
||||||
|
Unexpected("Variant in Peer::id.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Utf8String Peer::name() const {
|
||||||
|
if (const auto user = this->user()) {
|
||||||
|
return user->firstName + ' ' + user->lastName;
|
||||||
|
} else if (const auto chat = this->chat()) {
|
||||||
|
return chat->title;
|
||||||
|
}
|
||||||
|
Unexpected("Variant in Peer::id.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPInputPeer Peer::input() const {
|
||||||
|
if (const auto user = this->user()) {
|
||||||
|
if (user->input.type() == mtpc_inputUser) {
|
||||||
|
const auto &input = user->input.c_inputUser();
|
||||||
|
return MTP_inputPeerUser(input.vuser_id, input.vaccess_hash);
|
||||||
|
}
|
||||||
|
return MTP_inputPeerEmpty();
|
||||||
|
} else if (const auto chat = this->chat()) {
|
||||||
|
return chat->input;
|
||||||
|
}
|
||||||
|
Unexpected("Variant in Peer::id.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<PeerId, Peer> ParsePeersLists(
|
||||||
|
const MTPVector<MTPUser> &users,
|
||||||
|
const MTPVector<MTPChat> &chats) {
|
||||||
|
auto result = std::map<PeerId, Peer>();
|
||||||
|
for (const auto &user : users.v) {
|
||||||
|
auto parsed = ParseUser(user);
|
||||||
|
result.emplace(UserPeerId(parsed.id), Peer{ std::move(parsed) });
|
||||||
|
}
|
||||||
|
for (const auto &chat : chats.v) {
|
||||||
|
auto parsed = ParseChat(chat);
|
||||||
|
result.emplace(ChatPeerId(parsed.id), Peer{ std::move(parsed) });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message ParseMessage(const MTPMessage &data) {
|
||||||
|
auto result = Message();
|
||||||
|
switch (data.type()) {
|
||||||
|
case mtpc_message: {
|
||||||
|
const auto &fields = data.c_message();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.date = fields.vdate.v;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_messageService: {
|
||||||
|
const auto &fields = data.c_messageService();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
result.date = fields.vdate.v;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case mtpc_messageEmpty: {
|
||||||
|
const auto &fields = data.c_messageEmpty();
|
||||||
|
result.id = fields.vid.v;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int32, Message> ParseMessagesList(
|
||||||
|
const MTPVector<MTPMessage> &data) {
|
||||||
|
auto result = std::map<int32, Message>();
|
||||||
|
for (const auto &message : data.v) {
|
||||||
|
auto parsed = ParseMessage(message);
|
||||||
|
result.emplace(parsed.id, std::move(parsed));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
PersonalInfo ParsePersonalInfo(const MTPUserFull &data) {
|
PersonalInfo ParsePersonalInfo(const MTPUserFull &data) {
|
||||||
Expects(data.type() == mtpc_userFull);
|
Expects(data.type() == mtpc_userFull);
|
||||||
|
|
||||||
|
@ -188,6 +373,7 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) {
|
||||||
auto result = ContactsList();
|
auto result = ContactsList();
|
||||||
const auto &contacts = data.c_contacts_contacts();
|
const auto &contacts = data.c_contacts_contacts();
|
||||||
const auto map = ParseUsersList(contacts.vusers);
|
const auto map = ParseUsersList(contacts.vusers);
|
||||||
|
result.list.reserve(contacts.vcontacts.v.size());
|
||||||
for (const auto &contact : contacts.vcontacts.v) {
|
for (const auto &contact : contacts.vcontacts.v) {
|
||||||
const auto userId = contact.c_contact().vuser_id.v;
|
const auto userId = contact.c_contact().vuser_id.v;
|
||||||
if (const auto i = map.find(userId); i != end(map)) {
|
if (const auto i = map.find(userId); i != end(map)) {
|
||||||
|
@ -226,8 +412,8 @@ Session ParseSession(const MTPAuthorization &data) {
|
||||||
result.systemVersion = ParseString(fields.vsystem_version);
|
result.systemVersion = ParseString(fields.vsystem_version);
|
||||||
result.applicationName = ParseString(fields.vapp_name);
|
result.applicationName = ParseString(fields.vapp_name);
|
||||||
result.applicationVersion = ParseString(fields.vapp_version);
|
result.applicationVersion = ParseString(fields.vapp_version);
|
||||||
result.created = QDateTime::fromTime_t(fields.vdate_created.v);
|
result.created = fields.vdate_created.v;
|
||||||
result.lastActive = QDateTime::fromTime_t(fields.vdate_active.v);
|
result.lastActive = fields.vdate_active.v;
|
||||||
result.ip = ParseString(fields.vip);
|
result.ip = ParseString(fields.vip);
|
||||||
result.country = ParseString(fields.vcountry);
|
result.country = ParseString(fields.vcountry);
|
||||||
result.region = ParseString(fields.vregion);
|
result.region = ParseString(fields.vregion);
|
||||||
|
@ -239,12 +425,62 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) {
|
||||||
|
|
||||||
auto result = SessionsList();
|
auto result = SessionsList();
|
||||||
const auto &list = data.c_account_authorizations().vauthorizations.v;
|
const auto &list = data.c_account_authorizations().vauthorizations.v;
|
||||||
|
result.list.reserve(list.size());
|
||||||
for (const auto &session : list) {
|
for (const auto &session : list) {
|
||||||
result.list.push_back(ParseSession(session));
|
result.list.push_back(ParseSession(session));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) {
|
||||||
|
// const auto process = [&](const MTPDmessages_dialogs &data) {
|
||||||
|
const auto process = [&](const auto &data) {
|
||||||
|
const auto peers = ParsePeersLists(data.vusers, data.vchats);
|
||||||
|
const auto messages = ParseMessagesList(data.vmessages);
|
||||||
|
to.list.reserve(to.list.size() + data.vdialogs.v.size());
|
||||||
|
for (const auto &dialog : data.vdialogs.v) {
|
||||||
|
if (dialog.type() != mtpc_dialog) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &fields = dialog.c_dialog();
|
||||||
|
|
||||||
|
auto info = DialogInfo();
|
||||||
|
const auto peerId = ParsePeerId(fields.vpeer);
|
||||||
|
const auto peerIt = peers.find(peerId);
|
||||||
|
if (peerIt != end(peers)) {
|
||||||
|
const auto &peer = peerIt->second;
|
||||||
|
info.type = peer.user()
|
||||||
|
? DialogInfo::Type::Personal
|
||||||
|
: peer.chat()->broadcast
|
||||||
|
? DialogInfo::Type::Channel
|
||||||
|
: peer.chat()->username.isEmpty()
|
||||||
|
? DialogInfo::Type::PrivateGroup
|
||||||
|
: DialogInfo::Type::PublicGroup;
|
||||||
|
info.name = peer.name();
|
||||||
|
info.input = peer.input();
|
||||||
|
}
|
||||||
|
info.topMessageId = fields.vtop_message.v;
|
||||||
|
const auto messageIt = messages.find(info.topMessageId);
|
||||||
|
if (messageIt != end(messages)) {
|
||||||
|
const auto &message = messageIt->second;
|
||||||
|
info.topMessageDate = message.date;
|
||||||
|
}
|
||||||
|
to.list.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
switch (data.type()) {
|
||||||
|
case mtpc_messages_dialogs:
|
||||||
|
process(data.c_messages_dialogs());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case mtpc_messages_dialogsSlice:
|
||||||
|
process(data.c_messages_dialogsSlice());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: Unexpected("Type in AppendParsedChats.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
|
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
|
||||||
return phoneNumber.isEmpty()
|
return phoneNumber.isEmpty()
|
||||||
? Utf8String()
|
? Utf8String()
|
||||||
|
|
|
@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "scheme.h"
|
#include "scheme.h"
|
||||||
#include "base/optional.h"
|
#include "base/optional.h"
|
||||||
|
#include "base/variant.h"
|
||||||
|
|
||||||
#include <QtCore/QDateTime>
|
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
#include <QtCore/QByteArray>
|
#include <QtCore/QByteArray>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -18,7 +18,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Export {
|
namespace Export {
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
|
using TimeId = int32;
|
||||||
using Utf8String = QByteArray;
|
using Utf8String = QByteArray;
|
||||||
|
using PeerId = uint64;
|
||||||
|
|
||||||
|
PeerId UserPeerId(int32 userId);
|
||||||
|
PeerId ChatPeerId(int32 chatId);
|
||||||
|
int32 BarePeerId(PeerId peerId);
|
||||||
|
|
||||||
Utf8String ParseString(const MTPstring &data);
|
Utf8String ParseString(const MTPstring &data);
|
||||||
|
|
||||||
|
@ -50,7 +56,7 @@ struct File {
|
||||||
|
|
||||||
struct Photo {
|
struct Photo {
|
||||||
uint64 id = 0;
|
uint64 id = 0;
|
||||||
QDateTime date;
|
TimeId date = 0;
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
|
@ -64,15 +70,45 @@ struct UserpicsSlice {
|
||||||
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data);
|
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data);
|
||||||
|
|
||||||
struct User {
|
struct User {
|
||||||
int id = 0;
|
int32 id = 0;
|
||||||
Utf8String firstName;
|
Utf8String firstName;
|
||||||
Utf8String lastName;
|
Utf8String lastName;
|
||||||
Utf8String phoneNumber;
|
Utf8String phoneNumber;
|
||||||
Utf8String username;
|
Utf8String username;
|
||||||
|
|
||||||
|
MTPInputUser input;
|
||||||
};
|
};
|
||||||
|
|
||||||
User ParseUser(const MTPUser &user);
|
User ParseUser(const MTPUser &data);
|
||||||
std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data);
|
std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data);
|
||||||
|
|
||||||
|
struct Chat {
|
||||||
|
int32 id = 0;
|
||||||
|
Utf8String title;
|
||||||
|
Utf8String username;
|
||||||
|
bool broadcast = false;
|
||||||
|
|
||||||
|
MTPInputPeer input;
|
||||||
|
};
|
||||||
|
|
||||||
|
Chat ParseChat(const MTPChat &data);
|
||||||
|
std::map<int32, Chat> ParseChatsList(const MTPVector<MTPChat> &data);
|
||||||
|
|
||||||
|
struct Peer {
|
||||||
|
PeerId id() const;
|
||||||
|
Utf8String name() const;
|
||||||
|
MTPInputPeer input() const;
|
||||||
|
|
||||||
|
const User *user() const;
|
||||||
|
const Chat *chat() const;
|
||||||
|
|
||||||
|
base::variant<User, Chat> data;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<PeerId, Peer> ParsePeersLists(
|
||||||
|
const MTPVector<MTPUser> &users,
|
||||||
|
const MTPVector<MTPChat> &chats);
|
||||||
|
|
||||||
struct PersonalInfo {
|
struct PersonalInfo {
|
||||||
User user;
|
User user;
|
||||||
|
@ -94,8 +130,8 @@ struct Session {
|
||||||
Utf8String systemVersion;
|
Utf8String systemVersion;
|
||||||
Utf8String applicationName;
|
Utf8String applicationName;
|
||||||
Utf8String applicationVersion;
|
Utf8String applicationVersion;
|
||||||
QDateTime created;
|
TimeId created = 0;
|
||||||
QDateTime lastActive;
|
TimeId lastActive = 0;
|
||||||
Utf8String ip;
|
Utf8String ip;
|
||||||
Utf8String country;
|
Utf8String country;
|
||||||
Utf8String region;
|
Utf8String region;
|
||||||
|
@ -107,24 +143,38 @@ struct SessionsList {
|
||||||
|
|
||||||
SessionsList ParseSessionsList(const MTPaccount_Authorizations &data);
|
SessionsList ParseSessionsList(const MTPaccount_Authorizations &data);
|
||||||
|
|
||||||
struct ChatsInfo {
|
struct Message {
|
||||||
int count = 0;
|
int32 id = 0;
|
||||||
|
TimeId date = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChatInfo {
|
Message ParseMessage(const MTPMessage &data);
|
||||||
|
std::map<int32, Message> ParseMessagesList(
|
||||||
|
const MTPVector<MTPMessage> &data);
|
||||||
|
|
||||||
|
struct DialogInfo {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
Unknown,
|
||||||
Personal,
|
Personal,
|
||||||
Group,
|
PrivateGroup,
|
||||||
|
PublicGroup,
|
||||||
Channel,
|
Channel,
|
||||||
};
|
};
|
||||||
Type type = Type::Personal;
|
Type type = Type::Unknown;
|
||||||
QString name;
|
Utf8String name;
|
||||||
|
|
||||||
|
MTPInputPeer input;
|
||||||
|
int32 topMessageId = 0;
|
||||||
|
TimeId topMessageDate = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Message {
|
struct DialogsInfo {
|
||||||
int id = 0;
|
std::vector<DialogInfo> list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data);
|
||||||
|
|
||||||
struct MessagesSlice {
|
struct MessagesSlice {
|
||||||
std::vector<Message> list;
|
std::vector<Message> list;
|
||||||
};
|
};
|
||||||
|
@ -132,22 +182,10 @@ struct MessagesSlice {
|
||||||
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
|
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
|
||||||
|
|
||||||
Utf8String FormatDateTime(
|
Utf8String FormatDateTime(
|
||||||
const QDateTime &date,
|
TimeId date,
|
||||||
QChar dateSeparator = QChar('.'),
|
QChar dateSeparator = QChar('.'),
|
||||||
QChar timeSeparator = QChar(':'),
|
QChar timeSeparator = QChar(':'),
|
||||||
QChar separator = QChar(' '));
|
QChar separator = QChar(' '));
|
||||||
|
|
||||||
inline Utf8String FormatDateTime(
|
|
||||||
int32 date,
|
|
||||||
QChar dateSeparator = QChar('.'),
|
|
||||||
QChar timeSeparator = QChar(':'),
|
|
||||||
QChar separator = QChar(' ')) {
|
|
||||||
return FormatDateTime(
|
|
||||||
QDateTime::fromTime_t(date),
|
|
||||||
dateSeparator,
|
|
||||||
timeSeparator,
|
|
||||||
separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
} // namespace Export
|
} // namespace Export
|
||||||
|
|
|
@ -16,10 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Export {
|
namespace Export {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kUserpicsSliceLimit = 2;
|
constexpr auto kUserpicsSliceLimit = 100;
|
||||||
constexpr auto kFileChunkSize = 128 * 1024;
|
constexpr auto kFileChunkSize = 128 * 1024;
|
||||||
constexpr auto kFileRequestsCount = 2;
|
constexpr auto kFileRequestsCount = 2;
|
||||||
constexpr auto kFileNextRequestDelay = TimeMs(20);
|
constexpr auto kFileNextRequestDelay = TimeMs(20);
|
||||||
|
constexpr auto kChatsSliceLimit = 200;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -54,6 +55,17 @@ struct ApiWrap::FileProcess {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ApiWrap::DialogsProcess {
|
||||||
|
Data::DialogsInfo info;
|
||||||
|
|
||||||
|
FnMut<void(Data::DialogsInfo&&)> done;
|
||||||
|
|
||||||
|
int32 offsetDate = 0;
|
||||||
|
int32 offsetId = 0;
|
||||||
|
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) {
|
ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +263,49 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiWrap::requestDialogs(FnMut<void(Data::DialogsInfo&&)> done) {
|
||||||
|
Expects(_dialogsProcess == nullptr);
|
||||||
|
|
||||||
|
_dialogsProcess = std::make_unique<DialogsProcess>();
|
||||||
|
_dialogsProcess->done = std::move(done);
|
||||||
|
requestDialogsSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::requestDialogsSlice() {
|
||||||
|
Expects(_dialogsProcess != nullptr);
|
||||||
|
|
||||||
|
mainRequest(MTPmessages_GetDialogs(
|
||||||
|
MTP_flags(0),
|
||||||
|
MTP_int(_dialogsProcess->offsetDate),
|
||||||
|
MTP_int(_dialogsProcess->offsetId),
|
||||||
|
_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.");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
Data::AppendParsedDialogs(_dialogsProcess->info, result);
|
||||||
|
if (finished || _dialogsProcess->info.list.empty()) {
|
||||||
|
auto process = base::take(_dialogsProcess);
|
||||||
|
ranges::reverse(process->info.list);
|
||||||
|
process->done(std::move(process->info));
|
||||||
|
} else {
|
||||||
|
const auto &last = _dialogsProcess->info.list.back();
|
||||||
|
_dialogsProcess->offsetId = last.topMessageId;
|
||||||
|
_dialogsProcess->offsetDate = last.topMessageDate;
|
||||||
|
_dialogsProcess->offsetPeer = last.input;
|
||||||
|
requestDialogsSlice();
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
|
void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
|
||||||
Expects(_fileProcess == nullptr);
|
Expects(_fileProcess == nullptr);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct UserpicsInfo;
|
||||||
struct UserpicsSlice;
|
struct UserpicsSlice;
|
||||||
struct ContactsList;
|
struct ContactsList;
|
||||||
struct SessionsList;
|
struct SessionsList;
|
||||||
|
struct DialogsInfo;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
class ApiWrap {
|
class ApiWrap {
|
||||||
|
@ -39,6 +40,8 @@ public:
|
||||||
|
|
||||||
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
|
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
|
||||||
|
|
||||||
|
void requestDialogs(FnMut<void(Data::DialogsInfo&&)> done);
|
||||||
|
|
||||||
~ApiWrap();
|
~ApiWrap();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -48,6 +51,8 @@ private:
|
||||||
void loadUserpicDone(const QString &relativePath);
|
void loadUserpicDone(const QString &relativePath);
|
||||||
void finishUserpics();
|
void finishUserpics();
|
||||||
|
|
||||||
|
void requestDialogsSlice();
|
||||||
|
|
||||||
void loadFile(const Data::File &file, FnMut<void(QString)> done);
|
void loadFile(const Data::File &file, FnMut<void(QString)> done);
|
||||||
void loadFilePart();
|
void loadFilePart();
|
||||||
void filePartDone(int offset, const MTPupload_File &result);
|
void filePartDone(int offset, const MTPupload_File &result);
|
||||||
|
@ -72,6 +77,9 @@ private:
|
||||||
struct FileProcess;
|
struct FileProcess;
|
||||||
std::unique_ptr<FileProcess> _fileProcess;
|
std::unique_ptr<FileProcess> _fileProcess;
|
||||||
|
|
||||||
|
struct DialogsProcess;
|
||||||
|
std::unique_ptr<DialogsProcess> _dialogsProcess;
|
||||||
|
|
||||||
rpl::event_stream<RPCError> _errors;
|
rpl::event_stream<RPCError> _errors;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,7 @@ private:
|
||||||
void exportUserpics();
|
void exportUserpics();
|
||||||
void exportContacts();
|
void exportContacts();
|
||||||
void exportSessions();
|
void exportSessions();
|
||||||
void exportChats();
|
void exportDialogs();
|
||||||
|
|
||||||
bool normalizePath();
|
bool normalizePath();
|
||||||
|
|
||||||
|
@ -206,12 +206,12 @@ void Controller::fillExportSteps() {
|
||||||
if (_settings.types & Type::Sessions) {
|
if (_settings.types & Type::Sessions) {
|
||||||
_steps.push_back(Step::Sessions);
|
_steps.push_back(Step::Sessions);
|
||||||
}
|
}
|
||||||
const auto chatTypes = Type::PersonalChats
|
const auto dialogTypes = Type::PersonalChats
|
||||||
| Type::PrivateGroups
|
| Type::PrivateGroups
|
||||||
| Type::PublicGroups
|
| Type::PublicGroups
|
||||||
| Type::MyChannels;
|
| Type::MyChannels;
|
||||||
if (_settings.types & chatTypes) {
|
if (_settings.types & dialogTypes) {
|
||||||
_steps.push_back(Step::Chats);
|
_steps.push_back(Step::Dialogs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ void Controller::exportNext() {
|
||||||
case Step::Userpics: return exportUserpics();
|
case Step::Userpics: return exportUserpics();
|
||||||
case Step::Contacts: return exportContacts();
|
case Step::Contacts: return exportContacts();
|
||||||
case Step::Sessions: return exportSessions();
|
case Step::Sessions: return exportSessions();
|
||||||
case Step::Chats: return exportChats();
|
case Step::Dialogs: return exportDialogs();
|
||||||
}
|
}
|
||||||
Unexpected("Step in Controller::exportNext.");
|
Unexpected("Step in Controller::exportNext.");
|
||||||
}
|
}
|
||||||
|
@ -267,8 +267,11 @@ void Controller::exportSessions() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::exportChats() {
|
void Controller::exportDialogs() {
|
||||||
exportNext();
|
_api.requestDialogs([=](Data::DialogsInfo &&result) {
|
||||||
|
_writer->writeDialogsStart(result);
|
||||||
|
exportNext();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::setFinishedState() {
|
void Controller::setFinishedState() {
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct ProcessingState {
|
||||||
Userpics,
|
Userpics,
|
||||||
Contacts,
|
Contacts,
|
||||||
Sessions,
|
Sessions,
|
||||||
Chats,
|
Dialogs,
|
||||||
};
|
};
|
||||||
enum class Item {
|
enum class Item {
|
||||||
Other,
|
Other,
|
||||||
|
|
|
@ -16,8 +16,8 @@ struct UserpicsInfo;
|
||||||
struct UserpicsSlice;
|
struct UserpicsSlice;
|
||||||
struct ContactsList;
|
struct ContactsList;
|
||||||
struct SessionsList;
|
struct SessionsList;
|
||||||
struct ChatsInfo;
|
struct DialogsInfo;
|
||||||
struct ChatInfo;
|
struct DialogInfo;
|
||||||
struct MessagesSlice;
|
struct MessagesSlice;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
@ -43,11 +43,11 @@ public:
|
||||||
|
|
||||||
virtual bool writeSessionsList(const Data::SessionsList &data) = 0;
|
virtual bool writeSessionsList(const Data::SessionsList &data) = 0;
|
||||||
|
|
||||||
virtual bool writeChatsStart(const Data::ChatsInfo &data) = 0;
|
virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0;
|
||||||
virtual bool writeChatStart(const Data::ChatInfo &data) = 0;
|
virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
|
||||||
virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0;
|
virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0;
|
||||||
virtual bool writeChatEnd() = 0;
|
virtual bool writeDialogEnd() = 0;
|
||||||
virtual bool writeChatsEnd() = 0;
|
virtual bool writeDialogsEnd() = 0;
|
||||||
|
|
||||||
virtual bool finish() = 0;
|
virtual bool finish() = 0;
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,7 @@ bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
|
||||||
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
|
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
|
||||||
auto lines = QByteArray();
|
auto lines = QByteArray();
|
||||||
for (const auto &userpic : data.list) {
|
for (const auto &userpic : data.list) {
|
||||||
if (!userpic.date.isValid()) {
|
if (!userpic.date) {
|
||||||
lines.append("(empty photo)");
|
lines.append("(empty photo)");
|
||||||
} else {
|
} else {
|
||||||
lines.append(Data::FormatDateTime(userpic.date)).append(" - ");
|
lines.append(Data::FormatDateTime(userpic.date)).append(" - ");
|
||||||
|
@ -153,10 +153,7 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto header = "Contacts "
|
const auto file = std::make_unique<File>(_folder + "contacts.txt");
|
||||||
"(" + Data::NumberToString(data.list.size()) + ")"
|
|
||||||
+ kLineBreak
|
|
||||||
+ kLineBreak;
|
|
||||||
auto list = std::vector<QByteArray>();
|
auto list = std::vector<QByteArray>();
|
||||||
list.reserve(data.list.size());
|
list.reserve(data.list.size());
|
||||||
for (const auto &index : Data::SortedContactsIndices(data)) {
|
for (const auto &index : Data::SortedContactsIndices(data)) {
|
||||||
|
@ -178,15 +175,24 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto full = header + JoinList(kLineBreak, list) + kLineBreak;
|
const auto full = JoinList(kLineBreak, list);
|
||||||
return _result->writeBlock(full) == File::Result::Success;
|
if (file->writeBlock(full) != File::Result::Success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto header = "Contacts "
|
||||||
|
"(" + Data::NumberToString(data.list.size()) + ") - contacts.txt"
|
||||||
|
+ kLineBreak
|
||||||
|
+ kLineBreak;
|
||||||
|
return _result->writeBlock(header) == File::Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||||
const auto header = "Sessions "
|
if (data.list.empty()) {
|
||||||
"(" + Data::NumberToString(data.list.size()) + ")"
|
return true;
|
||||||
+ kLineBreak
|
}
|
||||||
+ kLineBreak;
|
|
||||||
|
const auto file = std::make_unique<File>(_folder + "sessions.txt");
|
||||||
auto list = std::vector<QByteArray>();
|
auto list = std::vector<QByteArray>();
|
||||||
list.reserve(data.list.size());
|
list.reserve(data.list.size());
|
||||||
for (const auto &session : data.list) {
|
for (const auto &session : data.list) {
|
||||||
|
@ -208,15 +214,65 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||||
{ "Created", Data::FormatDateTime(session.created) },
|
{ "Created", Data::FormatDateTime(session.created) },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
const auto full = header + JoinList(kLineBreak, list) + kLineBreak;
|
const auto full = JoinList(kLineBreak, list);
|
||||||
return _result->writeBlock(full) == File::Result::Success;
|
if (file->writeBlock(full) != File::Result::Success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto header = "Sessions "
|
||||||
|
"(" + Data::NumberToString(data.list.size()) + ") - sessions.txt"
|
||||||
|
+ kLineBreak
|
||||||
|
+ kLineBreak;
|
||||||
|
return _result->writeBlock(header) == File::Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWriter::writeChatsStart(const Data::ChatsInfo &data) {
|
bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||||
return true;
|
if (data.list.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using Type = Data::DialogInfo::Type;
|
||||||
|
const auto TypeString = [](Type type) {
|
||||||
|
switch (type) {
|
||||||
|
case Type::Unknown: return "(unknown)";
|
||||||
|
case Type::Personal: return "Personal Chat";
|
||||||
|
case Type::PrivateGroup: return "Private Group";
|
||||||
|
case Type::PublicGroup: return "Public Group";
|
||||||
|
case Type::Channel: return "Channel";
|
||||||
|
}
|
||||||
|
Unexpected("Dialog type in TypeString.");
|
||||||
|
};
|
||||||
|
const auto digits = Data::NumberToString(data.list.size() - 1).size();
|
||||||
|
const auto file = std::make_unique<File>(_folder + "chats.txt");
|
||||||
|
auto list = std::vector<QByteArray>();
|
||||||
|
list.reserve(data.list.size());
|
||||||
|
auto index = 0;
|
||||||
|
for (const auto &dialog : data.list) {
|
||||||
|
auto number = Data::NumberToString(++index);
|
||||||
|
auto path = QByteArray("Chats/chat_");
|
||||||
|
for (auto i = number.size(); i < digits; ++i) {
|
||||||
|
path += '0';
|
||||||
|
}
|
||||||
|
path += number + ".txt";
|
||||||
|
list.push_back(SerializeKeyValue({
|
||||||
|
{ "Name", dialog.name },
|
||||||
|
{ "Type", TypeString(dialog.type) },
|
||||||
|
{ "Content", path }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const auto full = JoinList(kLineBreak, list);
|
||||||
|
if (file->writeBlock(full) != File::Result::Success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto header = "Chats "
|
||||||
|
"(" + Data::NumberToString(data.list.size()) + ") - chats.txt"
|
||||||
|
+ kLineBreak
|
||||||
|
+ kLineBreak;
|
||||||
|
return _result->writeBlock(header) == File::Result::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWriter::writeChatStart(const Data::ChatInfo &data) {
|
bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,11 +280,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWriter::writeChatEnd() {
|
bool TextWriter::writeDialogEnd() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextWriter::writeChatsEnd() {
|
bool TextWriter::writeDialogsEnd() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,11 @@ public:
|
||||||
|
|
||||||
bool writeSessionsList(const Data::SessionsList &data) override;
|
bool writeSessionsList(const Data::SessionsList &data) override;
|
||||||
|
|
||||||
bool writeChatsStart(const Data::ChatsInfo &data) override;
|
bool writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||||
bool writeChatStart(const Data::ChatInfo &data) override;
|
bool writeDialogStart(const Data::DialogInfo &data) override;
|
||||||
bool writeMessagesSlice(const Data::MessagesSlice &data) override;
|
bool writeMessagesSlice(const Data::MessagesSlice &data) override;
|
||||||
bool writeChatEnd() override;
|
bool writeDialogEnd() override;
|
||||||
bool writeChatsEnd() override;
|
bool writeDialogsEnd() override;
|
||||||
|
|
||||||
bool finish() override;
|
bool finish() override;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue