diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index f5012194916320..50fa21b0dfac98 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -100,19 +100,18 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) { } Utf8String FormatDateTime( - const int32 date, + const QDateTime &date, QChar dateSeparator, QChar timeSeparator, QChar separator) { - const auto value = QDateTime::fromTime_t(date); return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3" + separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6" - ).arg(value.date().year() - ).arg(value.date().month(), 2, 10, QChar('0') - ).arg(value.date().day(), 2, 10, QChar('0') - ).arg(value.time().hour(), 2, 10, QChar('0') - ).arg(value.time().minute(), 2, 10, QChar('0') - ).arg(value.time().second(), 2, 10, QChar('0') + ).arg(date.date().year() + ).arg(date.date().month(), 2, 10, QChar('0') + ).arg(date.date().day(), 2, 10, QChar('0') + ).arg(date.time().hour(), 2, 10, QChar('0') + ).arg(date.time().minute(), 2, 10, QChar('0') + ).arg(date.time().second(), 2, 10, QChar('0') ).toUtf8(); } @@ -200,6 +199,52 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) { return result; } +std::vector SortedContactsIndices(const ContactsList &data) { + const auto names = ranges::view::all( + data.list + ) | ranges::view::transform([](const Data::User &user) { + return (QString::fromUtf8(user.firstName) + + ' ' + + QString::fromUtf8(user.lastName)).toLower(); + }) | ranges::to_vector; + + auto indices = ranges::view::ints(0, int(data.list.size())) + | ranges::to_vector; + ranges::sort(indices, [&](int i, int j) { + return names[i] < names[j]; + }); + return indices; +} + +Session ParseSession(const MTPAuthorization &data) { + Expects(data.type() == mtpc_authorization); + + const auto &fields = data.c_authorization(); + auto result = Session(); + result.platform = ParseString(fields.vplatform); + result.deviceModel = ParseString(fields.vdevice_model); + result.systemVersion = ParseString(fields.vsystem_version); + result.applicationName = ParseString(fields.vapp_name); + result.applicationVersion = ParseString(fields.vapp_version); + result.created = QDateTime::fromTime_t(fields.vdate_created.v); + result.lastActive = QDateTime::fromTime_t(fields.vdate_active.v); + result.ip = ParseString(fields.vip); + result.country = ParseString(fields.vcountry); + result.region = ParseString(fields.vregion); + return result; +} + +SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) { + Expects(data.type() == mtpc_account_authorizations); + + auto result = SessionsList(); + const auto &list = data.c_account_authorizations().vauthorizations.v; + for (const auto &session : list) { + result.list.push_back(ParseSession(session)); + } + return result; +} + Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { return phoneNumber.isEmpty() ? Utf8String() diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 8b4722ee6a528c..8ea0ff73942d8e 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -86,6 +86,7 @@ struct ContactsList { }; ContactsList ParseContactsList(const MTPcontacts_Contacts &data); +std::vector SortedContactsIndices(const ContactsList &data); struct Session { Utf8String platform; @@ -104,6 +105,8 @@ struct SessionsList { std::vector list; }; +SessionsList ParseSessionsList(const MTPaccount_Authorizations &data); + struct ChatsInfo { int count = 0; }; @@ -127,11 +130,24 @@ struct MessagesSlice { }; Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); + Utf8String FormatDateTime( - const int32 date, + const QDateTime &date, QChar dateSeparator = QChar('.'), QChar timeSeparator = 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 Export diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 5050a590810146..2ceda3677f1e49 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -243,6 +243,14 @@ void ApiWrap::requestContacts(FnMut done) { }).send(); } +void ApiWrap::requestSessions(FnMut done) { + mainRequest(MTPaccount_GetAuthorizations( + )).done([=, done = std::move(done)]( + const MTPaccount_Authorizations &result) mutable { + done(Data::ParseSessionsList(result)); + }).send(); +} + void ApiWrap::loadFile(const Data::File &file, FnMut done) { Expects(_fileProcess == nullptr); diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 407f79267d896b..e826ddc91910b3 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -17,6 +17,7 @@ struct PersonalInfo; struct UserpicsInfo; struct UserpicsSlice; struct ContactsList; +struct SessionsList; } // namespace Data class ApiWrap { @@ -36,6 +37,8 @@ class ApiWrap { void requestContacts(FnMut done); + void requestSessions(FnMut done); + ~ApiWrap(); private: diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index 80f1848c2ecb27..b5318239de65c6 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -261,7 +261,10 @@ void Controller::exportContacts() { } void Controller::exportSessions() { - exportNext(); + _api.requestSessions([=](Data::SessionsList &&result) { + _writer->writeSessionsList(result); + exportNext(); + }); } void Controller::exportChats() { diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 0b7dd3ddc96dcc..0a5c99889729a1 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -130,8 +130,7 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { if (!userpic.date.isValid()) { lines.append("(empty photo)"); } else { - lines.append(Data::FormatDateTime(userpic.date.toTime_t())); - lines.append(" - "); + lines.append(Data::FormatDateTime(userpic.date)).append(" - "); if (userpic.image.relativePath.isEmpty()) { lines.append("(file unavailable)"); } else { @@ -154,28 +153,13 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { return true; } - // Get sorted by name indices. - const auto names = ranges::view::all( - data.list - ) | ranges::view::transform([](const Data::User &user) { - return (QString::fromUtf8(user.firstName) - + ' ' - + QString::fromUtf8(user.lastName)).toLower(); - }) | ranges::to_vector; - - auto indices = ranges::view::ints(0, int(data.list.size())) - | ranges::to_vector; - ranges::sort(indices, [&](int i, int j) { - return names[i] < names[j]; - }); - const auto header = "Contacts " "(" + Data::NumberToString(data.list.size()) + ")" + kLineBreak + kLineBreak; auto list = std::vector(); list.reserve(data.list.size()); - for (const auto &index : indices) { + for (const auto &index : Data::SortedContactsIndices(data)) { const auto &contact = data.list[index]; if (!contact.id) { list.push_back("(user unavailable)"); @@ -199,7 +183,33 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { } bool TextWriter::writeSessionsList(const Data::SessionsList &data) { - return true; + const auto header = "Sessions " + "(" + Data::NumberToString(data.list.size()) + ")" + + kLineBreak + + kLineBreak; + auto list = std::vector(); + list.reserve(data.list.size()); + for (const auto &session : data.list) { + list.push_back(SerializeKeyValue({ + { "Last active", Data::FormatDateTime(session.lastActive) }, + { "Last IP address", session.ip }, + { "Last country", session.country }, + { "Last region", session.region }, + { + "Application name", + (session.applicationName.isEmpty() + ? Data::Utf8String("(unknown)") + : session.applicationName) + }, + { "Application version", session.applicationVersion }, + { "Device model", session.deviceModel }, + { "Platform", session.platform }, + { "System version", session.systemVersion }, + { "Created", Data::FormatDateTime(session.created) }, + })); + } + const auto full = header + JoinList(kLineBreak, list) + kLineBreak; + return _result->writeBlock(full) == File::Result::Success; } bool TextWriter::writeChatsStart(const Data::ChatsInfo &data) {