Export full messages information.

Also add some more .match() calls to MTP codegen-ed classes.
This commit is contained in:
John Preston 2018-06-14 21:34:53 +03:00
parent 83786ddeaf
commit 241fee80a7
16 changed files with 888 additions and 149 deletions

View File

@ -767,19 +767,20 @@ for restype in typesList:
else:
typesText += ' = default;\n';
if (withData):
typesText += getters;
if (withType):
typesText += '\n';
typesText += '\ttemplate <typename Method, typename ...Methods>\n';
typesText += '\tdecltype(auto) match(Method &&method, Methods &&...methods) const;\n';
visitorMethods += 'template <typename Method, typename ...Methods>\n';
visitorMethods += 'decltype(auto) MTP' + restype + '::match(Method &&method, Methods &&...methods) const {\n';
visitorMethods += '\tswitch (_type) {\n';
visitorMethods += visitor;
visitorMethods += '\t}\n';
visitorMethods += '\tUnexpected("Type in MTP' + restype + '::match.");\n';
visitorMethods += '}\n\n';
typesText += getters;
typesText += '\n';
typesText += '\ttemplate <typename Method, typename ...Methods>\n';
typesText += '\tdecltype(auto) match(Method &&method, Methods &&...methods) const;\n';
visitorMethods += 'template <typename Method, typename ...Methods>\n';
visitorMethods += 'decltype(auto) MTP' + restype + '::match(Method &&method, Methods &&...methods) const {\n';
if (withType):
visitorMethods += '\tswitch (_type) {\n';
visitorMethods += visitor;
visitorMethods += '\t}\n';
visitorMethods += '\tUnexpected("Type in MTP' + restype + '::match.");\n';
else:
visitorMethods += '\treturn base::match_method(c_' + v[0][0] + '(), std::forward<Method>(method), std::forward<Methods>(methods)...);\n';
visitorMethods += '}\n\n';
typesText += '\n\tuint32 innerLength() const;\n'; # size method
methods += '\nuint32 MTP' + restype + '::innerLength() const {\n';
@ -843,7 +844,7 @@ for restype in typesList:
if (withData):
typesText += constructsText;
methods += constructsBodies;
methods += constructsBodies;
if (friendDecl):
typesText += '\n' + friendDecl;

View File

@ -7,14 +7,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <cmath>
#include <set>
#include "logs.h"
#include "core/basic_types.h"
#include "base/flags.h"
#include "base/algorithm.h"
#include "base/assertion.h"
#include <QtCore/QReadWriteLock>
#include <QtCore/QRegularExpression>
#include <QtNetwork/QNetworkProxy>
#include <cmath>
#include <set>
// Define specializations for QByteArray for Qt 5.3.2, because
// QByteArray in Qt 5.3.2 doesn't declare "pointer" subtype.
#ifdef OS_MAC_OLD

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace App { // Hackish..
QString formatPhone(QString phone);
} // namespace App
QString FillAmountAndCurrency(uint64 amount, const QString &currency);
namespace Export {
namespace Data {
@ -272,21 +273,46 @@ Document ParseDocument(
return result;
}
Utf8String FormatDateTime(
TimeId 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')
).toUtf8();
GeoPoint ParseGeoPoint(const MTPGeoPoint &data) {
auto result = GeoPoint();
data.match([&](const MTPDgeoPoint &data) {
result.latitude = data.vlat.v;
result.longitude = data.vlong.v;
result.valid = true;
}, [](const MTPDgeoPointEmpty &data) {});
return result;
}
Venue ParseVenue(const MTPDmessageMediaVenue &data) {
auto result = Venue();
result.point = ParseGeoPoint(data.vgeo);
result.title = ParseString(data.vtitle);
result.address = ParseString(data.vaddress);
return result;
}
Game ParseGame(const MTPGame &data, int32 botId) {
return data.match([&](const MTPDgame &data) {
auto result = Game();
result.id = data.vid.v;
result.title = ParseString(data.vtitle);
result.description = ParseString(data.vdescription);
result.shortName = ParseString(data.vshort_name);
result.botId = botId;
return result;
});
}
Invoice ParseInvoice(const MTPDmessageMediaInvoice &data) {
auto result = Invoice();
result.title = ParseString(data.vtitle);
result.description = ParseString(data.vdescription);
result.currency = ParseString(data.vcurrency);
result.amount = data.vtotal_amount.v;
if (data.has_receipt_msg_id()) {
result.receiptMsgId = data.vreceipt_msg_id.v;
}
return result;
}
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data) {
@ -303,10 +329,10 @@ UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data) {
return result;
}
User ParseUser(const MTPUser &data) {
auto result = User();
ContactInfo ParseContactInfo(const MTPUser &data) {
auto result = ContactInfo();
data.match([&](const MTPDuser &data) {
result.id = data.vid.v;
result.userId = data.vid.v;
if (data.has_first_name()) {
result.firstName = ParseString(data.vfirst_name);
}
@ -316,15 +342,36 @@ User ParseUser(const MTPUser &data) {
if (data.has_phone()) {
result.phoneNumber = ParseString(data.vphone);
}
}, [&](const MTPDuserEmpty &data) {
result.userId = data.vid.v;
});
return result;
}
ContactInfo ParseContactInfo(const MTPDmessageMediaContact &data) {
auto result = ContactInfo();
result.userId = data.vuser_id.v;
result.firstName = ParseString(data.vfirst_name);
result.lastName = ParseString(data.vlast_name);
result.phoneNumber = ParseString(data.vphone_number);
return result;
}
User ParseUser(const MTPUser &data) {
auto result = User();
result.info = ParseContactInfo(data);
data.match([&](const MTPDuser &data) {
if (data.has_username()) {
result.username = ParseString(data.vusername);
}
if (data.has_bot_info_version()) {
result.isBot = true;
}
const auto access_hash = data.has_access_hash()
? data.vaccess_hash
: MTP_long(0);
result.input = MTP_inputUser(data.vid, access_hash);
}, [&](const MTPDuserEmpty &data) {
result.id = data.vid.v;
result.input = MTP_inputUser(data.vid, MTP_long(0));
});
return result;
@ -334,7 +381,7 @@ std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data) {
auto result = std::map<int32, User>();
for (const auto &user : data.v) {
auto parsed = ParseUser(user);
result.emplace(parsed.id, std::move(parsed));
result.emplace(parsed.info.userId, std::move(parsed));
}
return result;
}
@ -382,6 +429,21 @@ std::map<int32, Chat> ParseChatsList(const MTPVector<MTPChat> &data) {
return result;
}
Utf8String ContactInfo::name() const {
return firstName.isEmpty()
? (lastName.isEmpty()
? Utf8String()
: lastName)
: (lastName.isEmpty()
? firstName
: firstName + ' ' + lastName);
}
Utf8String User::name() const {
return info.name();
}
const User *Peer::user() const {
return base::get_if<User>(&data);
}
@ -391,7 +453,7 @@ const Chat *Peer::chat() const {
PeerId Peer::id() const {
if (const auto user = this->user()) {
return UserPeerId(user->id);
return UserPeerId(user->info.userId);
} else if (const auto chat = this->chat()) {
return ChatPeerId(chat->id);
}
@ -400,13 +462,7 @@ PeerId Peer::id() const {
Utf8String Peer::name() const {
if (const auto user = this->user()) {
return user->firstName.isEmpty()
? (user->lastName.isEmpty()
? Utf8String()
: user->lastName)
: (user->lastName.isEmpty()
? user->firstName
: user->firstName + ' ' + user->lastName);
return user->name();
} else if (const auto chat = this->chat()) {
return chat->title;
}
@ -432,7 +488,9 @@ std::map<PeerId, Peer> ParsePeersLists(
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) });
result.emplace(
UserPeerId(parsed.info.userId),
Peer{ std::move(parsed) });
}
for (const auto &chat : chats.v) {
auto parsed = ParseChat(chat);
@ -446,7 +504,7 @@ File &Media::file() {
return data.image.file;
}, [](Document &data) -> File& {
return data.file;
}, [](base::none_type &) -> File& {
}, [](auto&) -> File& {
static File result;
return result;
});
@ -457,7 +515,7 @@ const File &Media::file() const {
return data.image.file;
}, [](const Document &data) -> const File& {
return data.file;
}, [](const base::none_type &) -> const File& {
}, [](const auto &) -> const File& {
static const File result;
return result;
});
@ -466,7 +524,8 @@ const File &Media::file() const {
Media ParseMedia(
const MTPMessageMedia &data,
const QString &folder,
TimeId date) {
TimeId date,
int32 botId) {
Expects(folder.isEmpty() || folder.endsWith(QChar('/')));
auto result = Media();
@ -480,8 +539,11 @@ Media ParseMedia(
result.ttl = data.vttl_seconds.v;
}
}, [&](const MTPDmessageMediaGeo &data) {
result.content = ParseGeoPoint(data.vgeo);
}, [&](const MTPDmessageMediaContact &data) {
result.content = ParseContactInfo(data);
}, [&](const MTPDmessageMediaUnsupported &data) {
result.content = UnsupportedMedia();
}, [&](const MTPDmessageMediaDocument &data) {
result.content = data.has_document()
? ParseDocument(
@ -489,12 +551,20 @@ Media ParseMedia(
folder + "Files/",
date)
: Document();
if (data.has_ttl_seconds()) {
result.ttl = data.vttl_seconds.v;
}
}, [&](const MTPDmessageMediaWebPage &data) {
// Ignore web pages.
}, [&](const MTPDmessageMediaVenue &data) {
result.content = ParseVenue(data);
}, [&](const MTPDmessageMediaGame &data) {
result.content = ParseGame(data.vgame, botId);
}, [&](const MTPDmessageMediaInvoice &data) {
result.content = ParseInvoice(data);
}, [&](const MTPDmessageMediaGeoLive &data) {
result.content = ParseGeoPoint(data.vgeo);
result.ttl = data.vperiod.v;
}, [](const MTPDmessageMediaEmpty &data) {});
return result;
}
@ -505,30 +575,155 @@ ServiceAction ParseServiceAction(
TimeId date) {
auto result = ServiceAction();
data.match([&](const MTPDmessageActionChatCreate &data) {
auto content = ActionChatCreate();
content.title = ParseString(data.vtitle);
content.userIds.reserve(data.vusers.v.size());
for (const auto &userId : data.vusers.v) {
content.userIds.push_back(userId.v);
}
result.content = content;
}, [&](const MTPDmessageActionChatEditTitle &data) {
auto content = ActionChatEditTitle();
content.title = ParseString(data.vtitle);
result.content = content;
}, [&](const MTPDmessageActionChatEditPhoto &data) {
auto content = ActionChatEditPhoto();
content.photo = ParsePhoto(
data.vphoto,
mediaFolder + "Photos/" + PreparePhotoFileName(date));
result.content = content;
}, [&](const MTPDmessageActionChatDeletePhoto &data) {
result.content = ActionChatDeletePhoto();
}, [&](const MTPDmessageActionChatAddUser &data) {
auto content = ActionChatAddUser();
content.userIds.reserve(data.vusers.v.size());
for (const auto &user : data.vusers.v) {
content.userIds.push_back(user.v);
}
result.content = content;
}, [&](const MTPDmessageActionChatDeleteUser &data) {
auto content = ActionChatDeleteUser();
content.userId = data.vuser_id.v;
result.content = content;
}, [&](const MTPDmessageActionChatJoinedByLink &data) {
auto content = ActionChatJoinedByLink();
content.inviterId = data.vinviter_id.v;
result.content = content;
}, [&](const MTPDmessageActionChannelCreate &data) {
auto content = ActionChannelCreate();
content.title = ParseString(data.vtitle);
result.content = content;
}, [&](const MTPDmessageActionChatMigrateTo &data) {
auto content = ActionChatMigrateTo();
content.channelId = data.vchannel_id.v;
result.content = content;
}, [&](const MTPDmessageActionChannelMigrateFrom &data) {
auto content = ActionChannelMigrateFrom();
content.title = ParseString(data.vtitle);
content.chatId = data.vchat_id.v;
result.content = content;
}, [&](const MTPDmessageActionPinMessage &data) {
result.content = ActionPinMessage();
}, [&](const MTPDmessageActionHistoryClear &data) {
result.content = ActionHistoryClear();
}, [&](const MTPDmessageActionGameScore &data) {
auto content = ActionGameScore();
content.gameId = data.vgame_id.v;
content.score = data.vscore.v;
result.content = content;
}, [&](const MTPDmessageActionPaymentSentMe &data) {
// Should not be in user inbox.
}, [&](const MTPDmessageActionPaymentSent &data) {
auto content = ActionPaymentSent();
content.currency = ParseString(data.vcurrency);
content.amount = data.vtotal_amount.v;
result.content = content;
}, [&](const MTPDmessageActionPhoneCall &data) {
auto content = ActionPhoneCall();
if (data.has_duration()) {
content.duration = data.vduration.v;
}
if (data.has_reason()) {
using Reason = ActionPhoneCall::DiscardReason;
content.discardReason = data.vreason.match(
[](const MTPDphoneCallDiscardReasonMissed &data) {
return Reason::Missed;
}, [](const MTPDphoneCallDiscardReasonDisconnect &data) {
return Reason::Disconnect;
}, [](const MTPDphoneCallDiscardReasonHangup &data) {
return Reason::Hangup;
}, [](const MTPDphoneCallDiscardReasonBusy &data) {
return Reason::Busy;
});
}
result.content = content;
}, [&](const MTPDmessageActionScreenshotTaken &data) {
result.content = ActionScreenshotTaken();
}, [&](const MTPDmessageActionCustomAction &data) {
auto content = ActionCustomAction();
content.message = ParseString(data.vmessage);
result.content = content;
}, [&](const MTPDmessageActionBotAllowed &data) {
auto content = ActionBotAllowed();
content.domain = ParseString(data.vdomain);
result.content = content;
}, [&](const MTPDmessageActionSecureValuesSentMe &data) {
// Should not be in user inbox.
}, [&](const MTPDmessageActionSecureValuesSent &data) {
auto content = ActionSecureValuesSent();
content.types.reserve(data.vtypes.v.size());
using Type = ActionSecureValuesSent::Type;
for (const auto &type : data.vtypes.v) {
content.types.push_back(type.match(
[](const MTPDsecureValueTypePersonalDetails &data) {
return Type::PersonalDetails;
}, [](const MTPDsecureValueTypePassport &data) {
return Type::Passport;
}, [](const MTPDsecureValueTypeDriverLicense &data) {
return Type::DriverLicense;
}, [](const MTPDsecureValueTypeIdentityCard &data) {
return Type::IdentityCard;
}, [](const MTPDsecureValueTypeInternalPassport &data) {
return Type::InternalPassport;
}, [](const MTPDsecureValueTypeAddress &data) {
return Type::Address;
}, [](const MTPDsecureValueTypeUtilityBill &data) {
return Type::UtilityBill;
}, [](const MTPDsecureValueTypeBankStatement &data) {
return Type::BankStatement;
}, [](const MTPDsecureValueTypeRentalAgreement &data) {
return Type::RentalAgreement;
}, [](const MTPDsecureValueTypePassportRegistration &data) {
return Type::PassportRegistration;
}, [](const MTPDsecureValueTypeTemporaryRegistration &data) {
return Type::TemporaryRegistration;
}, [](const MTPDsecureValueTypePhone &data) {
return Type::Phone;
}, [](const MTPDsecureValueTypeEmail &data) {
return Type::Email;
}));
}
result.content = content;
}, [](const MTPDmessageActionEmpty &data) {});
return result;
}
File &Message::file() {
const auto service = &action.content;
if (const auto photo = base::get_if<ActionChatEditPhoto>(service)) {
return photo->photo.image.file;
}
return media.file();
}
const File &Message::file() const {
const auto service = &action.content;
if (const auto photo = base::get_if<ActionChatEditPhoto>(service)) {
return photo->photo.image.file;
}
return media.file();
}
Message ParseMessage(const MTPMessage &data, const QString &mediaFolder) {
auto result = Message();
data.match([&](const MTPDmessage &data) {
@ -540,6 +735,22 @@ Message ParseMessage(const MTPMessage &data, const QString &mediaFolder) {
if (data.has_from_id()) {
result.fromId = data.vfrom_id.v;
}
if (data.has_fwd_from()) {
result.forwardedFromId = data.vfwd_from.match(
[](const MTPDmessageFwdHeader &data) {
if (data.has_channel_id()) {
return ChatPeerId(data.vchannel_id.v);
} else if (data.has_saved_from_peer()) {
return ParsePeerId(data.vsaved_from_peer);
} else if (data.has_from_id()) {
return UserPeerId(data.vfrom_id.v);
}
return PeerId(0);
});
}
if (data.has_post_author()) {
result.signature = ParseString(data.vpost_author);
}
if (data.has_reply_to_msg_id()) {
result.replyToMsgId = data.vreply_to_msg_id.v;
}
@ -547,7 +758,15 @@ Message ParseMessage(const MTPMessage &data, const QString &mediaFolder) {
result.viaBotId = data.vvia_bot_id.v;
}
if (data.has_media()) {
result.media = ParseMedia(data.vmedia, mediaFolder, date);
result.media = ParseMedia(
data.vmedia,
mediaFolder,
date,
(result.viaBotId
? result.viaBotId
: result.forwardedFromId
? result.forwardedFromId
: result.fromId));
}
result.text = ParseString(data.vmessage);
}, [&](const MTPDmessageService &data) {
@ -599,9 +818,9 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) {
for (const auto &contact : contacts.vcontacts.v) {
const auto userId = contact.c_contact().vuser_id.v;
if (const auto i = map.find(userId); i != end(map)) {
result.list.push_back(i->second);
result.list.push_back(i->second.info);
} else {
result.list.push_back(User());
result.list.push_back(ContactInfo());
}
}
return result;
@ -610,10 +829,10 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) {
std::vector<int> SortedContactsIndices(const ContactsList &data) {
const auto names = ranges::view::all(
data.list
) | ranges::view::transform([](const Data::User &user) {
return (QString::fromUtf8(user.firstName)
) | ranges::view::transform([](const Data::ContactInfo &info) {
return (QString::fromUtf8(info.firstName)
+ ' '
+ QString::fromUtf8(user.lastName)).toLower();
+ QString::fromUtf8(info.lastName)).toLower();
}) | ranges::to_vector;
auto indices = ranges::view::ints(0, int(data.list.size()))
@ -716,5 +935,28 @@ Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
: App::formatPhone(QString::fromUtf8(phoneNumber)).toUtf8();
}
Utf8String FormatDateTime(
TimeId 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')
).toUtf8();
}
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency) {
return FillAmountAndCurrency(
amount,
QString::fromUtf8(currency)).toUtf8();
}
} // namespace Data
} // namespace Export

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <vector>
namespace Export {
@ -95,20 +96,61 @@ struct Document {
bool isAudioFile = false;
};
struct GeoPoint {
float64 latitude = 0.;
float64 longitude = 0.;
bool valid = false;
};
struct Venue {
GeoPoint point;
Utf8String title;
Utf8String address;
};
struct Game {
uint64 id = 0;
Utf8String shortName;
Utf8String title;
Utf8String description;
int32 botId = 0;
};
struct Invoice {
Utf8String title;
Utf8String description;
Utf8String currency;
uint64 amount = 0;
int32 receiptMsgId = 0;
};
struct UserpicsSlice {
std::vector<Photo> list;
};
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data);
struct User {
int32 id = 0;
struct ContactInfo {
int32 userId = 0;
Utf8String firstName;
Utf8String lastName;
Utf8String phoneNumber;
Utf8String username;
MTPInputUser input;
Utf8String name() const;
};
ContactInfo ParseContactInfo(const MTPUser &data);
ContactInfo ParseContactInfo(const MTPDmessageMediaContact &data);
struct User {
ContactInfo info;
Utf8String username;
bool isBot = false;
MTPInputUser input = MTP_inputUserEmpty();
Utf8String name() const;
};
User ParseUser(const MTPUser &data);
@ -120,7 +162,7 @@ struct Chat {
Utf8String username;
bool broadcast = false;
MTPInputPeer input;
MTPInputPeer input = MTP_inputPeerEmpty();
};
Chat ParseChat(const MTPChat &data);
@ -150,7 +192,7 @@ struct PersonalInfo {
PersonalInfo ParsePersonalInfo(const MTPUserFull &data);
struct ContactsList {
std::vector<User> list;
std::vector<ContactInfo> list;
};
ContactsList ParseContactsList(const MTPcontacts_Contacts &data);
@ -175,8 +217,19 @@ struct SessionsList {
SessionsList ParseSessionsList(const MTPaccount_Authorizations &data);
struct UnsupportedMedia {
};
struct Media {
base::optional_variant<Photo, Document> content;
base::optional_variant<
Photo,
Document,
ContactInfo,
GeoPoint,
Venue,
Game,
Invoice,
UnsupportedMedia> content;
TimeId ttl = 0;
File &file();
@ -188,8 +241,126 @@ Media ParseMedia(
const QString &folder,
TimeId date);
struct ActionChatCreate {
Utf8String title;
std::vector<int32> userIds;
};
struct ActionChatEditTitle {
Utf8String title;
};
struct ActionChatEditPhoto {
Photo photo;
};
struct ActionChatDeletePhoto {
};
struct ActionChatAddUser {
std::vector<int32> userIds;
};
struct ActionChatDeleteUser {
int32 userId = 0;
};
struct ActionChatJoinedByLink {
int32 inviterId = 0;
};
struct ActionChannelCreate {
Utf8String title;
};
struct ActionChatMigrateTo {
int32 channelId = 0;
};
struct ActionChannelMigrateFrom {
Utf8String title;
int32 chatId = 0;
};
struct ActionPinMessage {
};
struct ActionHistoryClear {
};
struct ActionGameScore {
uint64 gameId = 0;
int score = 0;
};
struct ActionPaymentSent {
Utf8String currency;
uint64 amount = 0;
};
struct ActionPhoneCall {
enum class DiscardReason {
Unknown,
Missed,
Disconnect,
Hangup,
Busy,
};
DiscardReason discardReason = DiscardReason::Unknown;
int duration = 0;
};
struct ActionScreenshotTaken {
};
struct ActionCustomAction {
Utf8String message;
};
struct ActionBotAllowed {
Utf8String domain;
};
struct ActionSecureValuesSent {
enum class Type {
PersonalDetails,
Passport,
DriverLicense,
IdentityCard,
InternalPassport,
Address,
UtilityBill,
BankStatement,
RentalAgreement,
PassportRegistration,
TemporaryRegistration,
Phone,
Email,
};
std::vector<Type> types;
};
struct ServiceAction {
base::optional_variant<> data;
base::optional_variant<
ActionChatCreate,
ActionChatEditTitle,
ActionChatEditPhoto,
ActionChatDeletePhoto,
ActionChatAddUser,
ActionChatDeleteUser,
ActionChatJoinedByLink,
ActionChannelCreate,
ActionChatMigrateTo,
ActionChannelMigrateFrom,
ActionPinMessage,
ActionHistoryClear,
ActionGameScore,
ActionPaymentSent,
ActionPhoneCall,
ActionScreenshotTaken,
ActionCustomAction,
ActionBotAllowed,
ActionSecureValuesSent> content;
};
ServiceAction ParseServiceAction(
@ -202,12 +373,16 @@ struct Message {
TimeId date = 0;
TimeId edited = 0;
int32 fromId = 0;
PeerId forwardedFromId = 0;
Utf8String signature;
int32 viaBotId = 0;
int32 replyToMsgId = 0;
Utf8String text;
Media media;
ServiceAction action;
File &file();
const File &file() const;
};
Message ParseMessage(const MTPMessage &data, const QString &mediaFolder);
@ -226,7 +401,7 @@ struct DialogInfo {
Type type = Type::Unknown;
Utf8String name;
MTPInputPeer input;
MTPInputPeer input = MTP_inputPeerEmpty();
int32 topMessageId = 0;
TimeId topMessageDate = 0;
@ -259,5 +434,7 @@ Utf8String FormatDateTime(
QChar timeSeparator = QChar(':'),
QChar separator = QChar(' '));
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String &currency);
} // namespace Data
} // namespace Export

View File

@ -470,7 +470,7 @@ void ApiWrap::loadNextMessageFile() {
if (index >= list.size()) {
break;
}
const auto &file = list[index].media.file();
const auto &file = list[index].file();
if (WillLoadFile(file)) {
loadFile(
file,
@ -479,16 +479,16 @@ void ApiWrap::loadNextMessageFile() {
}
}
if (!list.empty()) {
process->offsetId = list.back().id + 1;
}
_dialogsProcess->sliceOne(*base::take(process->slice));
if (process->lastSlice) {
finishMessages();
return;
} else {
requestMessagesSlice();
}
Assert(!list.empty());
process->offsetId = list.back().id + 1;
requestMessagesSlice();
}
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
@ -501,7 +501,7 @@ void ApiWrap::loadMessageFileDone(const QString &relativePath) {
const auto process = _dialogsProcess->single.get();
const auto index = process->fileIndex;
process->slice->list[index].media.file().relativePath = relativePath;
process->slice->list[index].file().relativePath = relativePath;
loadNextMessageFile();
}

View File

@ -217,7 +217,7 @@ void Controller::fillExportSteps() {
void Controller::exportNext() {
if (!++_stepIndex) {
_writer->start(_settings.path);
_writer->start(_settings);
}
if (_stepIndex >= _steps.size()) {
_writer->finish();

View File

@ -11,8 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QDir>
#include <QtCore/QTextStream>
#include <QtCore/QDateTime>
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <QtCore/QReadWriteLock>
#include <QtCore/QRegularExpression>
#include <crl/crl.h>
#include <rpl/rpl.h>
#include <vector>
#include <map>
#include <deque>

View File

@ -50,6 +50,7 @@ struct Settings {
friend inline constexpr auto is_flag_type(Type) { return true; };
QString path;
QString internalLinksDomain;
Output::Format format = Output::Format();
Types types = DefaultTypes();
@ -62,7 +63,8 @@ struct Settings {
| Type::Userpics
| Type::Contacts
| Type::Sessions
| Type::PersonalChats;
| Type::PersonalChats
| Type::PrivateGroups;
}
static inline Types DefaultFullChats() {

View File

@ -21,6 +21,8 @@ struct DialogInfo;
struct MessagesSlice;
} // namespace Data
struct Settings;
namespace Output {
enum class Format {
@ -31,7 +33,7 @@ enum class Format {
class AbstractWriter {
public:
virtual bool start(const QString &folder) = 0;
virtual bool start(const Settings &settings) = 0;
virtual bool writePersonal(const Data::PersonalInfo &data) = 0;

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <gsl/gsl_util>
namespace Export {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/output/export_output_text.h"
#include "export/data/export_data_types.h"
#include "core/utils.h"
#include <QtCore/QFile>
@ -83,12 +84,312 @@ Data::Utf8String FormatUsername(const Data::Utf8String &username) {
return username.isEmpty() ? username : ('@' + username);
}
QByteArray FormatFilePath(const Data::File &file) {
return file.relativePath.toUtf8();
}
QByteArray SerializeMessage(
const Data::Message &message,
const std::map<Data::PeerId, Data::Peer> &peers,
const QString &internalLinksDomain) {
using namespace Data;
if (message.media.content.is<UnsupportedMedia>()) {
return "Error! This message is not supported "
"by this version of Telegram Desktop. "
"Please update the application.";
}
const auto peer = [&](PeerId peerId) -> const Peer& {
if (const auto i = peers.find(peerId); i != end(peers)) {
return i->second;
}
static auto empty = Peer{ User() };
return empty;
};
const auto user = [&](int32 userId) -> const User& {
if (const auto result = peer(UserPeerId(userId)).user()) {
return *result;
}
static auto empty = User();
return empty;
};
const auto chat = [&](int32 chatId) -> const Chat& {
if (const auto result = peer(ChatPeerId(chatId)).chat()) {
return *result;
}
static auto empty = Chat();
return empty;
};
auto values = std::vector<std::pair<QByteArray, QByteArray>>{
{ "ID", NumberToString(message.id) },
{ "Date", FormatDateTime(message.date) },
{ "Edited", FormatDateTime(message.edited) },
};
const auto push = [&](const QByteArray &key, const QByteArray &value) {
if (!value.isEmpty()) {
values.emplace_back(key, value);
}
};
const auto wrapPeerName = [&](PeerId peerId) {
const auto result = peer(peerId).name();
return result.isEmpty() ? QByteArray("(unknown peer)") : result;
};
const auto wrapUserName = [&](int32 userId) {
const auto result = user(userId).name();
return result.isEmpty() ? QByteArray("(unknown user)") : result;
};
const auto pushFrom = [&](const QByteArray &label = "From") {
if (message.fromId) {
push(label, wrapUserName(message.fromId));
}
};
const auto pushReplyToMsgId = [&](
const QByteArray &label = "Reply to message") {
if (message.replyToMsgId) {
push(label, "ID-" + NumberToString(message.replyToMsgId));
}
};
const auto pushUserNames = [&](
const std::vector<int32> &data,
const QByteArray &labelOne = "Member",
const QByteArray &labelMany = "Members") {
auto list = std::vector<QByteArray>();
for (const auto userId : data) {
list.push_back(wrapUserName(userId));
}
if (list.size() == 1) {
push(labelOne, list[0]);
} else if (!list.empty()) {
push(labelMany, JoinList(", ", list));
}
};
const auto pushActor = [&] {
pushFrom("Actor");
};
const auto pushAction = [&](const QByteArray &action) {
push("Action", action);
};
message.action.content.match([&](const ActionChatCreate &data) {
pushActor();
pushAction("Create group");
push("Title", data.title);
pushUserNames(data.userIds);
}, [&](const ActionChatEditTitle &data) {
pushActor();
pushAction("Edit group title");
push("New title", data.title);
}, [&](const ActionChatEditPhoto &data) {
pushActor();
pushAction("Edit group photo");
push("Photo", FormatFilePath(data.photo.image.file));
}, [&](const ActionChatDeletePhoto &data) {
pushActor();
pushAction("Delete group photo");
}, [&](const ActionChatAddUser &data) {
pushActor();
pushAction("Invite members");
pushUserNames(data.userIds);
}, [&](const ActionChatDeleteUser &data) {
pushActor();
pushAction("Remove members");
push("Member", wrapUserName(data.userId));
}, [&](const ActionChatJoinedByLink &data) {
pushActor();
pushAction("Join group by link");
push("Inviter", wrapUserName(data.inviterId));
}, [&](const ActionChannelCreate &data) {
pushActor();
pushAction("Create channel");
push("Title", data.title);
}, [&](const ActionChatMigrateTo &data) {
pushActor();
pushAction("Migrate this group to supergroup");
}, [&](const ActionChannelMigrateFrom &data) {
pushActor();
pushAction("Migrate this supergroup from group");
push("Title", data.title);
}, [&](const ActionPinMessage &data) {
pushActor();
pushAction("Pin message");
pushReplyToMsgId("Message");
}, [&](const ActionHistoryClear &data) {
pushActor();
pushAction("Clear history");
}, [&](const ActionGameScore &data) {
pushActor();
pushAction("Score in a game");
pushReplyToMsgId("Game message");
push("Score", NumberToString(data.score));
}, [&](const ActionPaymentSent &data) {
pushAction("Send payment");
push(
"Amount",
Data::FormatMoneyAmount(data.amount, data.currency));
pushReplyToMsgId("Invoice message");
}, [&](const ActionPhoneCall &data) {
pushActor();
pushAction("Phone call");
if (data.duration) {
push("Duration", NumberToString(data.duration) + " sec.");
}
using Reason = ActionPhoneCall::DiscardReason;
push("Discard reason", [&] {
switch (data.discardReason) {
case Reason::Busy: return "Busy";
case Reason::Disconnect: return "Disconnect";
case Reason::Hangup: return "Hangup";
case Reason::Missed: return "Missed";
}
return "";
}());
}, [&](const ActionScreenshotTaken &data) {
pushActor();
pushAction("Take screenshot");
}, [&](const ActionCustomAction &data) {
pushActor();
push("Information", data.message);
}, [&](const ActionBotAllowed &data) {
pushAction("Allow sending messages");
push("Reason", "Login on \"" + data.domain + "\"");
}, [&](const ActionSecureValuesSent &data) {
pushAction("Send Telegram Passport values");
auto list = std::vector<QByteArray>();
for (const auto type : data.types) {
list.push_back([&] {
using Type = ActionSecureValuesSent::Type;
switch (type) {
case Type::PersonalDetails: return "Personal details";
case Type::Passport: return "Passport";
case Type::DriverLicense: return "Driver license";
case Type::IdentityCard: return "Identity card";
case Type::InternalPassport: return "Internal passport";
case Type::Address: return "Address information";
case Type::UtilityBill: return "Utility bill";
case Type::BankStatement: return "Bank statement";
case Type::RentalAgreement: return "Rental agreement";
case Type::PassportRegistration:
return "Passport registration";
case Type::TemporaryRegistration:
return "Temporary registration";
case Type::Phone: return "Phone number";
case Type::Email: return "Email";
}
return "";
}());
}
if (list.size() == 1) {
push("Value", list[0]);
} else if (!list.empty()) {
push("Values", JoinList(", ", list));
}
}, [](const base::none_type &) {});
if (!message.action.content) {
pushFrom();
push("Author", message.signature);
if (message.forwardedFromId) {
push("Forwarded from", wrapPeerName(message.forwardedFromId));
}
pushReplyToMsgId();
if (message.viaBotId) {
push("Via", user(message.viaBotId).username);
}
}
message.media.content.match([&](const Photo &photo) {
}, [&](const Document &data) {
const auto pushPath = [&](const QByteArray &label) {
push(label, FormatFilePath(data.file));
};
if (!data.stickerEmoji.isEmpty()) {
pushPath("Sticker");
push("Emoji", data.stickerEmoji);
} else if (data.isVideoMessage) {
pushPath("Video message");
} else if (data.isVoiceMessage) {
pushPath("Voice message");
} else if (data.isAnimated) {
pushPath("Animation");
} else if (data.isVideoFile) {
pushPath("Video file");
} else if (data.isAudioFile) {
pushPath("Audio file");
push("Performer", data.songPerformer);
push("Title", data.songTitle);
} else {
pushPath("File");
}
if (data.stickerEmoji.isEmpty()) {
push("Mime type", data.mime);
}
if (data.duration) {
push("Duration", NumberToString(data.duration) + " sec.");
}
if (data.width && data.height) {
push("Width", NumberToString(data.width));
push("Height", NumberToString(data.height));
}
}, [&](const ContactInfo &data) {
push("Contact information", SerializeKeyValue({
{ "First name", data.firstName },
{ "Last name", data.lastName },
{ "Phone number", FormatPhoneNumber(data.phoneNumber) },
}));
}, [&](const GeoPoint &data) {
push("Location", data.valid ? SerializeKeyValue({
{ "Latitude", NumberToString(data.latitude) },
{ "Longitude", NumberToString(data.longitude) },
}) : QByteArray("(empty value)"));
}, [&](const Venue &data) {
push("Place name", data.title);
push("Address", data.address);
if (data.point.valid) {
push("Location", SerializeKeyValue({
{ "Latitude", NumberToString(data.point.latitude) },
{ "Longitude", NumberToString(data.point.longitude) },
}));
}
}, [&](const Game &data) {
push("Game", data.title);
push("Description", data.description);
if (data.botId != 0 && !data.shortName.isEmpty()) {
const auto bot = user(data.botId);
if (bot.isBot && !bot.username.isEmpty()) {
push("Link", internalLinksDomain.toUtf8()
+ bot.username
+ "?game="
+ data.shortName);
}
}
}, [&](const Invoice &data) {
push("Invoice", SerializeKeyValue({
{ "Title", data.title },
{ "Description", data.description },
{
"Amount",
Data::FormatMoneyAmount(data.amount, data.currency)
},
{ "Receipt message", (data.receiptMsgId
? "ID-" + NumberToString(data.receiptMsgId)
: QByteArray()) }
}));
}, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message.");
}, [](const base::none_type &) {});
push("Text", message.text);
return SerializeKeyValue(std::move(values));
}
} // namespace
bool TextWriter::start(const QString &folder) {
Expects(folder.endsWith('/'));
bool TextWriter::start(const Settings &settings) {
Expects(settings.path.endsWith('/'));
_folder = folder;
_settings = base::duplicate(settings);
_result = fileWithRelativePath(mainFileRelativePath());
return true;
}
@ -96,13 +397,14 @@ bool TextWriter::start(const QString &folder) {
bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_result != nullptr);
const auto &info = data.user.info;
const auto serialized = "Personal information"
+ kLineBreak
+ kLineBreak
+ SerializeKeyValue({
{ "First name", data.user.firstName },
{ "Last name", data.user.lastName },
{ "Phone number", Data::FormatPhoneNumber(data.user.phoneNumber) },
{ "First name", info.firstName },
{ "Last name", info.lastName },
{ "Phone number", Data::FormatPhoneNumber(info.phoneNumber) },
{ "Username", FormatUsername(data.user.username) },
{ "Bio", data.bio },
})
@ -158,7 +460,7 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
list.reserve(data.list.size());
for (const auto &index : Data::SortedContactsIndices(data)) {
const auto &contact = data.list[index];
if (!contact.id) {
if (!contact.userId) {
list.push_back("(user unavailable)" + kLineBreak);
} else if (contact.firstName.isEmpty()
&& contact.lastName.isEmpty()
@ -300,11 +602,10 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
list.reserve(data.list.size());
auto index = 0;
for (const auto &message : data.list) {
list.push_back(SerializeKeyValue({
{ "ID", Data::NumberToString(message.id) },
{ "Date", Data::FormatDateTime(message.date) },
{ "Text", message.text }
}));
list.push_back(SerializeMessage(
message,
data.peers,
_settings.internalLinksDomain));
}
const auto full = _dialog->empty()
? JoinList(kLineBreak, list)
@ -336,12 +637,12 @@ QString TextWriter::mainFileRelativePath() const {
}
QString TextWriter::pathWithRelativePath(const QString &path) const {
return _folder + path;
return _settings.path + path;
}
std::unique_ptr<File> TextWriter::fileWithRelativePath(
const QString &path) const {
return std::make_unique<File>(_folder + path);
return std::make_unique<File>(pathWithRelativePath(path));
}
} // namespace Output

View File

@ -9,13 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/output/export_output_abstract.h"
#include "export/output/export_output_file.h"
#include "export/export_settings.h"
namespace Export {
namespace Output {
class TextWriter : public AbstractWriter {
public:
bool start(const QString &folder) override;
bool start(const Settings &settings) override;
bool writePersonal(const Data::PersonalInfo &data) override;
@ -42,7 +43,7 @@ private:
QString pathWithRelativePath(const QString &path) const;
std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
QString _folder;
Settings _settings;
std::unique_ptr<File> _result;
int _userpicsCount = 0;

View File

@ -29,6 +29,8 @@ SettingsWidget::SettingsWidget(QWidget *parent)
} else {
_data.path = Global::DownloadPath();
}
_data.internalLinksDomain = Global::InternalLinksDomain();
setupContent();
}

View File

@ -97,6 +97,61 @@ std::unique_ptr<HistoryMedia> CreateAttach(
} // namespace
QString FillAmountAndCurrency(uint64 amount, const QString &currency) {
static const auto ShortCurrencyNames = QMap<QString, QString> {
{ qsl("USD"), QString::fromUtf8("\x24") },
{ qsl("GBP"), QString::fromUtf8("\xC2\xA3") },
{ qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") },
{ qsl("JPY"), QString::fromUtf8("\xC2\xA5") },
};
static const auto Denominators = QMap<QString, int> {
{ qsl("CLF"), 10000 },
{ qsl("BHD"), 1000 },
{ qsl("IQD"), 1000 },
{ qsl("JOD"), 1000 },
{ qsl("KWD"), 1000 },
{ qsl("LYD"), 1000 },
{ qsl("OMR"), 1000 },
{ qsl("TND"), 1000 },
{ qsl("BIF"), 1 },
{ qsl("BYR"), 1 },
{ qsl("CLP"), 1 },
{ qsl("CVE"), 1 },
{ qsl("DJF"), 1 },
{ qsl("GNF"), 1 },
{ qsl("ISK"), 1 },
{ qsl("JPY"), 1 },
{ qsl("KMF"), 1 },
{ qsl("KRW"), 1 },
{ qsl("MGA"), 1 },
{ qsl("PYG"), 1 },
{ qsl("RWF"), 1 },
{ qsl("UGX"), 1 },
{ qsl("UYI"), 1 },
{ qsl("VND"), 1 },
{ qsl("VUV"), 1 },
{ qsl("XAF"), 1 },
{ qsl("XOF"), 1 },
{ qsl("XPF"), 1 },
{ qsl("MRO"), 10 },
};
const auto currencyText = ShortCurrencyNames.value(currency, currency);
const auto denominator = Denominators.value(currency, 100);
const auto currencyValue = amount / float64(denominator);
const auto digits = [&] {
auto result = 0;
for (auto test = 1; test < denominator; test *= 10) {
++result;
}
return result;
}();
return QLocale::system().toCurrencyString(currencyValue, currencyText);
//auto amountBucks = amount / 100;
//auto amountCents = amount % 100;
//auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0'));
//return currencyText + amountText;
}
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (p == _savel || p == _cancell) {
if (active && !dataLoaded()) {
@ -4259,63 +4314,6 @@ HistoryInvoice::HistoryInvoice(
fillFromData(invoice);
}
QString HistoryInvoice::fillAmountAndCurrency(
uint64 amount,
const QString &currency) {
static const auto ShortCurrencyNames = QMap<QString, QString> {
{ qsl("USD"), QString::fromUtf8("\x24") },
{ qsl("GBP"), QString::fromUtf8("\xC2\xA3") },
{ qsl("EUR"), QString::fromUtf8("\xE2\x82\xAC") },
{ qsl("JPY"), QString::fromUtf8("\xC2\xA5") },
};
static const auto Denominators = QMap<QString, int> {
{ qsl("CLF"), 10000 },
{ qsl("BHD"), 1000 },
{ qsl("IQD"), 1000 },
{ qsl("JOD"), 1000 },
{ qsl("KWD"), 1000 },
{ qsl("LYD"), 1000 },
{ qsl("OMR"), 1000 },
{ qsl("TND"), 1000 },
{ qsl("BIF"), 1 },
{ qsl("BYR"), 1 },
{ qsl("CLP"), 1 },
{ qsl("CVE"), 1 },
{ qsl("DJF"), 1 },
{ qsl("GNF"), 1 },
{ qsl("ISK"), 1 },
{ qsl("JPY"), 1 },
{ qsl("KMF"), 1 },
{ qsl("KRW"), 1 },
{ qsl("MGA"), 1 },
{ qsl("PYG"), 1 },
{ qsl("RWF"), 1 },
{ qsl("UGX"), 1 },
{ qsl("UYI"), 1 },
{ qsl("VND"), 1 },
{ qsl("VUV"), 1 },
{ qsl("XAF"), 1 },
{ qsl("XOF"), 1 },
{ qsl("XPF"), 1 },
{ qsl("MRO"), 10 },
};
const auto currencyText = ShortCurrencyNames.value(currency, currency);
const auto denominator = Denominators.value(currency, 100);
const auto currencyValue = amount / float64(denominator);
const auto digits = [&] {
auto result = 0;
for (auto test = 1; test < denominator; test *= 10) {
++result;
}
return result;
}();
return QLocale::system().toCurrencyString(currencyValue, currencyText);
//auto amountBucks = amount / 100;
//auto amountCents = amount % 100;
//auto amountText = qsl("%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0'));
//return currencyText + amountText;
}
void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
// init attach
auto labelText = [&] {
@ -4330,7 +4328,7 @@ void HistoryInvoice::fillFromData(not_null<Data::Invoice*> invoice) {
return lang(lng_payments_invoice_label);
};
auto statusText = TextWithEntities {
fillAmountAndCurrency(invoice->amount, invoice->currency),
FillAmountAndCurrency(invoice->amount, invoice->currency),
EntitiesInText()
};
statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size()));

View File

@ -41,6 +41,8 @@ namespace Ui {
class EmptyUserpic;
} // namespace Ui
QString FillAmountAndCurrency(uint64 amount, const QString &currency);
class HistoryFileMedia : public HistoryMedia {
public:
using HistoryMedia::HistoryMedia;
@ -850,7 +852,6 @@ public:
QString getTitle() const {
return _title.originalText();
}
static QString fillAmountAndCurrency(uint64 amount, const QString &currency);
bool hideMessageText() const override {
return false;

View File

@ -608,7 +608,7 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
UpdateComponents(HistoryServicePayment::Bit());
auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v;
auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency);
Get<HistoryServicePayment>()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency);
Get<HistoryServicePayment>()->amount = FillAmountAndCurrency(amount, currency);
}
if (message.has_reply_to_msg_id()) {
if (message.vaction.type() == mtpc_messageActionPinMessage) {