init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,499 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_abstract.h"
#include "export/output/export_output_html_and_json.h"
#include "export/output/export_output_html.h"
#include "export/output/export_output_json.h"
#include "export/output/export_output_stats.h"
#include "export/output/export_output_result.h"
#include <QtCore/QDir>
#include <QtCore/QDate>
namespace Export {
namespace Output {
QString NormalizePath(const Settings &settings) {
QDir folder(settings.path);
const auto path = folder.absolutePath();
auto result = path.endsWith('/') ? path : (path + '/');
if (!folder.exists() && !settings.forceSubPath) {
return result;
}
const auto mode = QDir::AllEntries | QDir::NoDotAndDotDot;
const auto list = folder.entryInfoList(mode);
if (list.isEmpty() && !settings.forceSubPath) {
return result;
}
const auto date = QDate::currentDate();
const auto base = QString(settings.onlySinglePeer()
? "ChatExport_%1"
: "DataExport_%1"
).arg(date.toString(Qt::ISODate));
const auto add = [&](int i) {
return base + (i ? " (" + QString::number(i) + ')' : QString());
};
auto index = 0;
while (QDir(result + add(index)).exists()) {
++index;
}
result += add(index) + '/';
return result;
}
std::unique_ptr<AbstractWriter> CreateWriter(Format format) {
switch (format) {
case Format::Html: return std::make_unique<HtmlWriter>();
case Format::Json: return std::make_unique<JsonWriter>();
case Format::HtmlAndJson: return std::make_unique<HtmlAndJsonWriter>();
}
Unexpected("Format in Export::Output::CreateWriter.");
}
Stats AbstractWriter::produceTestExample(
const QString &path,
const Environment &environment) {
auto result = Stats();
const auto folder = QDir(path).absolutePath();
auto settings = Settings();
settings.format = format();
settings.path = (folder.endsWith('/') ? folder : (folder + '/'))
+ "ExportExample/";
settings.types = Settings::Type::AllMask;
settings.fullChats = Settings::Type::AllMask
& ~(Settings::Type::PublicChannels | Settings::Type::PublicGroups);
settings.media.types = MediaSettings::Type::AllMask;
settings.media.sizeLimit = 1024 * 1024;
const auto check = [](Result result) {
Assert(result.isSuccess());
};
check(start(settings, environment, &result));
const auto counter = [&] {
static auto GlobalCounter = 0;
return ++GlobalCounter;
};
const auto date = [&] {
return time(nullptr) - 86400 + counter();
};
const auto prevdate = [&] {
return date() - 86400;
};
auto personal = Data::PersonalInfo();
personal.bio = "Nice text about me.";
personal.user.info.firstName = "John";
personal.user.info.lastName = "Preston";
personal.user.info.phoneNumber = "447400000000";
personal.user.info.date = date();
personal.user.username = "preston";
personal.user.info.userId = counter();
personal.user.isBot = false;
personal.user.isSelf = true;
check(writePersonal(personal));
const auto generatePhoto = [&] {
static auto index = 0;
auto result = Data::Photo();
result.date = date();
result.id = counter();
result.image.width = 512;
result.image.height = 512;
result.image.file.relativePath = "files/photo_"
+ QString::number(++index)
+ ".jpg";
return result;
};
auto userpics = Data::UserpicsInfo();
userpics.count = 3;
auto userpicsSlice1 = Data::UserpicsSlice();
userpicsSlice1.list.push_back(generatePhoto());
userpicsSlice1.list.push_back(generatePhoto());
auto userpicsSlice2 = Data::UserpicsSlice();
userpicsSlice2.list.push_back(generatePhoto());
check(writeUserpicsStart(userpics));
check(writeUserpicsSlice(userpicsSlice1));
check(writeUserpicsSlice(userpicsSlice2));
check(writeUserpicsEnd());
auto contacts = Data::ContactsList();
auto topUser = Data::TopPeer();
auto user = personal.user;
auto peerUser = Data::Peer{ user };
topUser.peer = peerUser;
topUser.rating = 0.5;
auto topChat = Data::TopPeer();
auto chat = Data::Chat();
chat.bareId = counter();
chat.title = "Group chat";
auto peerChat = Data::Peer{ chat };
topChat.peer = peerChat;
topChat.rating = 0.25;
auto topBot = Data::TopPeer();
auto bot = Data::User();
bot.info.date = date();
bot.isBot = true;
bot.info.firstName = "Bot";
bot.info.lastName = "Father";
bot.info.userId = counter();
bot.username = "botfather";
auto peerBot = Data::Peer{ bot };
topBot.peer = peerBot;
topBot.rating = 0.125;
auto peers = std::map<PeerId, Data::Peer>();
peers.emplace(peerUser.id(), peerUser);
peers.emplace(peerBot.id(), peerBot);
peers.emplace(peerChat.id(), peerChat);
contacts.correspondents.push_back(topUser);
contacts.correspondents.push_back(topChat);
contacts.inlineBots.push_back(topBot);
contacts.inlineBots.push_back(topBot);
contacts.phoneCalls.push_back(topUser);
contacts.list.push_back(user.info);
contacts.list.push_back(bot.info);
check(writeContactsList(contacts));
auto sessions = Data::SessionsList();
auto session = Data::Session();
session.applicationName = "Telegram Desktop";
session.applicationVersion = "1.3.8";
session.country = "GB";
session.created = date();
session.deviceModel = "PC";
session.ip = "127.0.0.1";
session.lastActive = date();
session.platform = "Windows";
session.region = "London";
session.systemVersion = "10";
sessions.list.push_back(session);
sessions.list.push_back(session);
auto webSession = Data::WebSession();
webSession.botUsername = "botfather";
webSession.browser = "Google Chrome";
webSession.created = date();
webSession.domain = "telegram.org";
webSession.ip = "127.0.0.1";
webSession.lastActive = date();
webSession.platform = "Windows";
webSession.region = "London, GB";
sessions.webList.push_back(webSession);
sessions.webList.push_back(webSession);
check(writeSessionsList(sessions));
auto sampleMessage = [&] {
auto message = Data::Message();
message.id = counter();
message.date = prevdate();
message.edited = date();
static auto count = 0;
if (++count % 3 == 0) {
message.forwardedFromId = peerFromUser(user.info.userId);
message.forwardedDate = date();
} else if (count % 3 == 2) {
message.forwardedFromName = "Test hidden forward";
message.forwardedDate = date();
}
message.fromId = user.info.userId;
message.replyToMsgId = counter();
message.viaBotId = bot.info.userId;
message.text.push_back(Data::TextPart{
Data::TextPart::Type::Text,
("Text message " + QString::number(counter())).toUtf8()
});
return message;
};
auto sliceBot1 = Data::MessagesSlice();
sliceBot1.peers = peers;
sliceBot1.list.push_back(sampleMessage());
sliceBot1.list.push_back([&] {
auto message = sampleMessage();
message.media.content = generatePhoto();
message.media.ttl = counter();
return message;
}());
sliceBot1.list.push_back([&] {
auto message = sampleMessage();
auto document = Data::Document();
document.date = prevdate();
document.duration = counter();
auto photo = generatePhoto();
document.file = photo.image.file;
document.width = photo.image.width;
document.height = photo.image.height;
document.id = counter();
message.media.content = document;
return message;
}());
sliceBot1.list.push_back([&] {
auto message = sampleMessage();
auto contact = Data::SharedContact();
contact.info = user.info;
message.media.content = contact;
return message;
}());
auto sliceBot2 = Data::MessagesSlice();
sliceBot2.peers = peers;
sliceBot2.list.push_back([&] {
auto message = sampleMessage();
auto point = Data::GeoPoint();
point.latitude = 1.5;
point.longitude = 2.8;
point.valid = true;
message.media.content = point;
message.media.ttl = counter();
return message;
}());
sliceBot2.list.push_back([&] {
auto message = sampleMessage();
message.replyToMsgId = sliceBot1.list.back().id;
auto venue = Data::Venue();
venue.point.latitude = 1.5;
venue.point.longitude = 2.8;
venue.point.valid = true;
venue.address = "Test address";
venue.title = "Test venue";
message.media.content = venue;
return message;
}());
sliceBot2.list.push_back([&] {
auto message = sampleMessage();
auto game = Data::Game();
game.botId = bot.info.userId;
game.title = "Test game";
game.description = "Test game description";
game.id = counter();
game.shortName = "testgame";
message.media.content = game;
return message;
}());
sliceBot2.list.push_back([&] {
auto message = sampleMessage();
auto invoice = Data::Invoice();
invoice.amount = counter();
invoice.currency = "GBP";
invoice.title = "Huge invoice.";
invoice.description = "So money.";
invoice.receiptMsgId = sliceBot2.list.front().id;
message.media.content = invoice;
return message;
}());
auto serviceMessage = [&] {
auto message = Data::Message();
message.id = counter();
message.date = prevdate();
message.fromId = user.info.userId;
return message;
};
auto sliceChat1 = Data::MessagesSlice();
sliceChat1.peers = peers;
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatCreate();
action.title = "Test chat";
action.userIds.push_back(user.info.userId);
action.userIds.push_back(bot.info.userId);
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatEditTitle();
action.title = "New title";
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatEditPhoto();
action.photo = generatePhoto();
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatDeletePhoto();
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatAddUser();
action.userIds.push_back(user.info.userId);
action.userIds.push_back(bot.info.userId);
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatDeleteUser();
action.userId = bot.info.userId;
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatJoinedByLink();
action.inviterId = bot.info.userId;
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChannelCreate();
action.title = "Channel name";
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChatMigrateTo();
action.channelId = ChannelId(chat.bareId);
message.action.content = action;
return message;
}());
sliceChat1.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionChannelMigrateFrom();
action.chatId = ChatId(chat.bareId);
action.title = "Supergroup now";
message.action.content = action;
return message;
}());
auto sliceChat2 = Data::MessagesSlice();
sliceChat2.peers = peers;
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionPinMessage();
message.replyToMsgId = sliceChat1.list.back().id;
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionHistoryClear();
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionGameScore();
action.score = counter();
action.gameId = counter();
message.replyToMsgId = sliceChat2.list.back().id;
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionPaymentSent();
action.amount = counter();
action.currency = "GBP";
message.replyToMsgId = sliceChat2.list.front().id;
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionPhoneCall();
action.duration = counter();
action.state = Data::ActionPhoneCall::State::Busy;
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionScreenshotTaken();
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionCustomAction();
action.message = "Custom chat action.";
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionBotAllowed();
action.domain = "telegram.org";
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionSecureValuesSent();
using Type = Data::ActionSecureValuesSent::Type;
action.types.push_back(Type::BankStatement);
action.types.push_back(Type::Phone);
message.action.content = action;
return message;
}());
sliceChat2.list.push_back([&] {
auto message = serviceMessage();
auto action = Data::ActionContactSignUp();
message.action.content = action;
return message;
}());
auto dialogs = Data::DialogsInfo();
auto dialogBot = Data::DialogInfo();
dialogBot.messagesCountPerSplit.push_back(sliceBot1.list.size());
dialogBot.messagesCountPerSplit.push_back(sliceBot2.list.size());
dialogBot.type = Data::DialogInfo::Type::Bot;
dialogBot.name = peerBot.name();
dialogBot.onlyMyMessages = false;
dialogBot.peerId = peerBot.id();
dialogBot.relativePath = "chats/chat_"
+ QString::number(counter())
+ '/';
dialogBot.splits.push_back(0);
dialogBot.splits.push_back(1);
dialogBot.topMessageDate = sliceBot2.list.back().date;
dialogBot.topMessageId = sliceBot2.list.back().id;
auto dialogChat = Data::DialogInfo();
dialogChat.messagesCountPerSplit.push_back(sliceChat1.list.size());
dialogChat.messagesCountPerSplit.push_back(sliceChat2.list.size());
dialogChat.type = Data::DialogInfo::Type::PrivateGroup;
dialogChat.name = peerChat.name();
dialogChat.onlyMyMessages = true;
dialogChat.peerId = peerChat.id();
dialogChat.relativePath = "chats/chat_"
+ QString::number(counter())
+ '/';
dialogChat.splits.push_back(0);
dialogChat.splits.push_back(1);
dialogChat.topMessageDate = sliceChat2.list.back().date;
dialogChat.topMessageId = sliceChat2.list.back().id;
dialogs.chats.push_back(dialogBot);
dialogs.chats.push_back(dialogChat);
check(writeDialogsStart(dialogs));
check(writeDialogStart(dialogBot));
check(writeDialogSlice(sliceBot1));
check(writeDialogSlice(sliceBot2));
check(writeDialogEnd());
check(writeDialogStart(dialogChat));
check(writeDialogSlice(sliceChat1));
check(writeDialogSlice(sliceChat2));
check(writeDialogEnd());
check(writeDialogsEnd());
check(finish());
return result;
}
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,108 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <QtCore/QString>
namespace Export {
namespace Data {
struct PersonalInfo;
struct UserpicsInfo;
struct UserpicsSlice;
struct StoriesInfo;
struct StoriesSlice;
struct ProfileMusicInfo;
struct ProfileMusicSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
struct DialogInfo;
struct MessagesSlice;
struct File;
} // namespace Data
struct Settings;
struct Environment;
namespace Output {
QString NormalizePath(const Settings &settings);
struct Result;
class Stats;
enum class Format {
Html,
Json,
HtmlAndJson,
};
class AbstractWriter {
public:
[[nodiscard]] virtual Format format() = 0;
[[nodiscard]] virtual Result start(
const Settings &settings,
const Environment &environment,
Stats *stats) = 0;
[[nodiscard]] virtual Result writePersonal(
const Data::PersonalInfo &data) = 0;
[[nodiscard]] virtual Result writeUserpicsStart(
const Data::UserpicsInfo &data) = 0;
[[nodiscard]] virtual Result writeUserpicsSlice(
const Data::UserpicsSlice &data) = 0;
[[nodiscard]] virtual Result writeUserpicsEnd() = 0;
[[nodiscard]] virtual Result writeStoriesStart(
const Data::StoriesInfo &data) = 0;
[[nodiscard]] virtual Result writeStoriesSlice(
const Data::StoriesSlice &data) = 0;
[[nodiscard]] virtual Result writeStoriesEnd() = 0;
[[nodiscard]] virtual Result writeProfileMusicStart(
const Data::ProfileMusicInfo &data) = 0;
[[nodiscard]] virtual Result writeProfileMusicSlice(
const Data::ProfileMusicSlice &data) = 0;
[[nodiscard]] virtual Result writeProfileMusicEnd() = 0;
[[nodiscard]] virtual Result writeContactsList(
const Data::ContactsList &data) = 0;
[[nodiscard]] virtual Result writeSessionsList(
const Data::SessionsList &data) = 0;
[[nodiscard]] virtual Result writeOtherData(
const Data::File &data) = 0;
[[nodiscard]] virtual Result writeDialogsStart(
const Data::DialogsInfo &data) = 0;
[[nodiscard]] virtual Result writeDialogStart(
const Data::DialogInfo &data) = 0;
[[nodiscard]] virtual Result writeDialogSlice(
const Data::MessagesSlice &data) = 0;
[[nodiscard]] virtual Result writeDialogEnd() = 0;
[[nodiscard]] virtual Result writeDialogsEnd() = 0;
[[nodiscard]] virtual Result finish() = 0;
[[nodiscard]] virtual QString mainFilePath() = 0;
virtual ~AbstractWriter() = default;
Stats produceTestExample(
const QString &path,
const Environment &environment);
};
std::unique_ptr<AbstractWriter> CreateWriter(Format format);
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,140 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_file.h"
#include "export/output/export_output_result.h"
#include "export/output/export_output_stats.h"
#include "base/qt/qt_string_view.h"
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <gsl/util>
namespace Export {
namespace Output {
File::File(const QString &path, Stats *stats) : _path(path), _stats(stats) {
}
int64 File::size() const {
return _offset;
}
bool File::empty() const {
return !_offset;
}
Result File::writeBlock(const QByteArray &block) {
const auto result = writeBlockAttempt(block);
if (!result) {
_file.reset();
}
return result;
}
Result File::writeBlockAttempt(const QByteArray &block) {
if (_stats && !_inStats) {
_inStats = true;
_stats->incrementFiles();
}
if (const auto result = reopen(); !result) {
return result;
}
const auto size = block.size();
if (!size) {
return Result::Success();
}
if (_file->write(block) == size && _file->flush()) {
_offset += size;
if (_stats) {
_stats->incrementBytes(size);
}
return Result::Success();
}
return error();
}
Result File::reopen() {
if (_file && _file->isOpen()) {
return Result::Success();
}
_file.emplace(_path);
if (_file->exists()) {
if (_file->size() < _offset) {
return fatalError();
} else if (!_file->resize(_offset)) {
return error();
}
} else if (_offset > 0) {
return fatalError();
}
if (_file->open(QIODevice::Append)) {
return Result::Success();
}
const auto info = QFileInfo(_path);
const auto dir = info.absoluteDir();
return (!dir.exists()
&& dir.mkpath(dir.absolutePath())
&& _file->open(QIODevice::Append))
? Result::Success()
: error();
}
Result File::error() const {
return Result(Result::Type::Error, _path);
}
Result File::fatalError() const {
return Result(Result::Type::FatalError, _path);
}
QString File::PrepareRelativePath(
const QString &folder,
const QString &suggested) {
if (!QFile::exists(folder + suggested)) {
return suggested;
}
// Not lastIndexOf('.') so that "file.tar.xz" won't be messed up.
const auto position = suggested.indexOf('.');
const auto base = suggested.mid(0, position);
const auto extension = (position >= 0)
? base::StringViewMid(suggested, position)
: QStringView();
const auto relativePart = [&](int attempt) {
auto result = base + QString(" (%1)").arg(attempt);
result.append(extension);
return result;
};
auto attempt = 0;
while (true) {
const auto relativePath = relativePart(++attempt);
if (!QFile::exists(folder + relativePath)) {
return relativePath;
}
}
}
Result File::Copy(
const QString &source,
const QString &path,
Stats *stats) {
QFile f(source);
if (!f.exists() || !f.open(QIODevice::ReadOnly)) {
return Result(Result::Type::FatalError, source);
}
const auto bytes = f.readAll();
if (bytes.size() != f.size()) {
return Result(Result::Type::FatalError, source);
}
return File(path, stats).writeBlock(bytes);
}
} // namespace Output
} // namespace File

View File

@@ -0,0 +1,57 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/optional.h"
#include <QtCore/QFile>
#include <QtCore/QString>
#include <QtCore/QByteArray>
namespace Export {
namespace Output {
struct Result;
class Stats;
class File {
public:
File(const QString &path, Stats *stats);
[[nodiscard]] int64 size() const;
[[nodiscard]] bool empty() const;
[[nodiscard]] Result writeBlock(const QByteArray &block);
[[nodiscard]] static QString PrepareRelativePath(
const QString &folder,
const QString &suggested);
[[nodiscard]] static Result Copy(
const QString &source,
const QString &path,
Stats *stats);
private:
[[nodiscard]] Result reopen();
[[nodiscard]] Result writeBlockAttempt(const QByteArray &block);
[[nodiscard]] Result error() const;
[[nodiscard]] Result fatalError() const;
QString _path;
int64 _offset = 0;
std::optional<QFile> _file;
Stats *_stats = nullptr;
bool _inStats = false;
};
} // namespace Output
} // namespace File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "export/output/export_output_abstract.h"
#include "export/output/export_output_file.h"
#include "export/export_settings.h"
#include "export/data/export_data_types.h"
namespace Export {
namespace Output {
namespace details {
class HtmlContext {
public:
[[nodiscard]] QByteArray pushTag(
const QByteArray &tag,
std::map<QByteArray, QByteArray> &&attributes = {});
[[nodiscard]] QByteArray popTag();
[[nodiscard]] QByteArray indent() const;
[[nodiscard]] bool empty() const;
private:
struct Tag {
QByteArray name;
bool block = true;
};
std::vector<Tag> _tags;
};
struct UserpicData;
struct StoryData;
class PeersMap;
struct MediaData;
} // namespace details
class HtmlWriter : public AbstractWriter {
public:
HtmlWriter();
Format format() override {
return Format::Html;
}
Result start(
const Settings &settings,
const Environment &environment,
Stats *stats) override;
Result writePersonal(const Data::PersonalInfo &data) override;
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
Result writeUserpicsEnd() override;
Result writeStoriesStart(const Data::StoriesInfo &data) override;
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override;
Result writeDialogEnd() override;
Result writeDialogsEnd() override;
Result finish() override;
QString mainFilePath() override;
~HtmlWriter();
private:
using Context = details::HtmlContext;
using UserpicData = details::UserpicData;
using MediaData = details::MediaData;
class Wrap;
struct MessageInfo;
enum class DialogsMode {
None,
Chats,
Left,
};
[[nodiscard]] Result copyFile(
const QString &source,
const QString &relativePath) const;
[[nodiscard]] QString mainFileRelativePath() const;
[[nodiscard]] QString pathWithRelativePath(const QString &path) const;
[[nodiscard]] std::unique_ptr<Wrap> fileWithRelativePath(
const QString &path) const;
[[nodiscard]] QString messagesFile(int index) const;
[[nodiscard]] Result writeSavedContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeFrequentContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeSessions(const Data::SessionsList &data);
[[nodiscard]] Result writeWebSessions(const Data::SessionsList &data);
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
[[nodiscard]] Result writeDialogOpening(int index);
[[nodiscard]] Result switchToNextChatFile(int index);
[[nodiscard]] Result writeEmptySinglePeer();
void pushSection(
int priority,
const QByteArray &label,
const QByteArray &type,
int count,
const QString &path);
[[nodiscard]] Result writeSections();
[[nodiscard]] Result writeDefaultPersonal(
const Data::PersonalInfo &data);
[[nodiscard]] Result writeDelayedPersonal(const QString &userpicPath);
[[nodiscard]] Result writePreparedPersonal(
const Data::PersonalInfo &data,
const QString &userpicPath);
void pushUserpicsSection();
void pushStoriesSection();
void pushProfileMusicSection();
[[nodiscard]] QString userpicsFilePath() const;
[[nodiscard]] QString storiesFilePath() const;
[[nodiscard]] QString profileMusicFilePath() const;
[[nodiscard]] QByteArray wrapMessageLink(
int messageId,
QByteArray text);
Settings _settings;
Environment _environment;
Stats *_stats = nullptr;
struct SavedSection;
std::vector<SavedSection> _savedSections;
std::unique_ptr<Wrap> _summary;
bool _summaryNeedDivider = false;
bool _haveSections = false;
uint8 _selfColorIndex = 0;
std::unique_ptr<Data::PersonalInfo> _delayedPersonalInfo;
int _userpicsCount = 0;
std::unique_ptr<Wrap> _userpics;
int _storiesCount = 0;
std::unique_ptr<Wrap> _stories;
int _profileMusicCount = 0;
std::unique_ptr<Wrap> _profileMusic;
QString _dialogsRelativePath;
Data::DialogInfo _dialog;
DialogsMode _dialogsMode = DialogsMode::None;
int _messagesCount = 0;
std::unique_ptr<MessageInfo> _lastMessageInfo;
int _dateMessageId = 0;
std::unique_ptr<Wrap> _chats;
std::unique_ptr<Wrap> _chat;
std::vector<int> _lastMessageIdsPerFile;
bool _chatFileEmpty = false;
};
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,166 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_html_and_json.h"
#include "export/output/export_output_html.h"
#include "export/output/export_output_json.h"
#include "export/output/export_output_result.h"
namespace Export::Output {
HtmlAndJsonWriter::HtmlAndJsonWriter() {
_writers.push_back(CreateWriter(Format::Html));
_writers.push_back(CreateWriter(Format::Json));
}
Format HtmlAndJsonWriter::format() {
return Format::HtmlAndJson;
}
Result HtmlAndJsonWriter::start(
const Settings &settings,
const Environment &environment,
Stats *stats) {
return invoke([&](WriterPtr w) {
return w->start(settings, environment, stats);
});
}
Result HtmlAndJsonWriter::writePersonal(const Data::PersonalInfo &data) {
return invoke([&](WriterPtr w) {
return w->writePersonal(data);
});
}
Result HtmlAndJsonWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeUserpicsStart(data);
});
}
Result HtmlAndJsonWriter::writeUserpicsSlice(const Data::UserpicsSlice &d) {
return invoke([&](WriterPtr w) {
return w->writeUserpicsSlice(d);
});
}
Result HtmlAndJsonWriter::writeUserpicsEnd() {
return invoke([&](WriterPtr w) {
return w->writeUserpicsEnd();
});
}
Result HtmlAndJsonWriter::writeStoriesStart(const Data::StoriesInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeStoriesStart(data);
});
}
Result HtmlAndJsonWriter::writeStoriesSlice(const Data::StoriesSlice &data) {
return invoke([&](WriterPtr w) {
return w->writeStoriesSlice(data);
});
}
Result HtmlAndJsonWriter::writeStoriesEnd() {
return invoke([&](WriterPtr w) {
return w->writeStoriesEnd();
});
}
Result HtmlAndJsonWriter::writeProfileMusicStart(const Data::ProfileMusicInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicStart(data);
});
}
Result HtmlAndJsonWriter::writeProfileMusicSlice(const Data::ProfileMusicSlice &data) {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicSlice(data);
});
}
Result HtmlAndJsonWriter::writeProfileMusicEnd() {
return invoke([&](WriterPtr w) {
return w->writeProfileMusicEnd();
});
}
Result HtmlAndJsonWriter::writeContactsList(const Data::ContactsList &data) {
return invoke([&](WriterPtr w) {
return w->writeContactsList(data);
});
}
Result HtmlAndJsonWriter::writeSessionsList(const Data::SessionsList &data) {
return invoke([&](WriterPtr w) {
return w->writeSessionsList(data);
});
}
Result HtmlAndJsonWriter::writeOtherData(const Data::File &data) {
return invoke([&](WriterPtr w) {
return w->writeOtherData(data);
});
}
Result HtmlAndJsonWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeDialogsStart(data);
});
}
Result HtmlAndJsonWriter::writeDialogStart(const Data::DialogInfo &data) {
return invoke([&](WriterPtr w) {
return w->writeDialogStart(data);
});
}
Result HtmlAndJsonWriter::writeDialogSlice(const Data::MessagesSlice &data) {
return invoke([&](WriterPtr w) {
return w->writeDialogSlice(data);
});
}
Result HtmlAndJsonWriter::writeDialogEnd() {
return invoke([&](WriterPtr w) {
return w->writeDialogEnd();
});
}
Result HtmlAndJsonWriter::writeDialogsEnd() {
return invoke([&](WriterPtr w) {
return w->writeDialogsEnd();
});
}
Result HtmlAndJsonWriter::finish() {
return invoke([&](WriterPtr w) {
return w->finish();
});
}
QString HtmlAndJsonWriter::mainFilePath() {
return _writers.front()->mainFilePath();
}
HtmlAndJsonWriter::~HtmlAndJsonWriter() = default;
Result HtmlAndJsonWriter::invoke(Fn<Result(WriterPtr)> method) const {
auto result = Result(Result::Type::Success, QString());
for (const auto &writer : _writers) {
const auto current = method(writer);
if (!current) {
result = current;
}
}
return result;
}
} // namespace Export::Output

View File

@@ -0,0 +1,69 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "export/output/export_output_abstract.h"
namespace Export::Output {
class HtmlWriter;
class JsonWriter;
struct Result;
class HtmlAndJsonWriter final : public AbstractWriter {
public:
HtmlAndJsonWriter();
Format format() override;
Result start(
const Settings &settings,
const Environment &environment,
Stats *stats) override;
Result writePersonal(const Data::PersonalInfo &data) override;
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
Result writeUserpicsEnd() override;
Result writeStoriesStart(const Data::StoriesInfo &data) override;
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override;
Result writeDialogEnd() override;
Result writeDialogsEnd() override;
Result finish() override;
QString mainFilePath() override;
~HtmlAndJsonWriter();
private:
using WriterPtr = const std::unique_ptr<AbstractWriter> &;
Result invoke(Fn<Result(WriterPtr)> method) const;
std::vector<std::unique_ptr<AbstractWriter>> _writers;
};
} // namespace Export::Output

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "export/output/export_output_abstract.h"
#include "export/output/export_output_file.h"
#include "export/export_settings.h"
#include "export/data/export_data_types.h"
namespace Export {
namespace Output {
namespace details {
struct JsonContext {
using Type = bool;
static constexpr auto kObject = Type(true);
static constexpr auto kArray = Type(false);
// Always fun to use std::vector<bool>.
std::vector<Type> nesting;
};
} // namespace details
class JsonWriter : public AbstractWriter {
public:
Format format() override {
return Format::Json;
}
Result start(
const Settings &settings,
const Environment &environment,
Stats *stats) override;
Result writePersonal(const Data::PersonalInfo &data) override;
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
Result writeUserpicsEnd() override;
Result writeStoriesStart(const Data::StoriesInfo &data) override;
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
Result writeStoriesEnd() override;
Result writeProfileMusicStart(const Data::ProfileMusicInfo &data) override;
Result writeProfileMusicSlice(const Data::ProfileMusicSlice &data) override;
Result writeProfileMusicEnd() override;
Result writeContactsList(const Data::ContactsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override;
Result writeDialogEnd() override;
Result writeDialogsEnd() override;
Result finish() override;
QString mainFilePath() override;
private:
using Context = details::JsonContext;
enum class DialogsMode {
None,
Chats,
Left,
};
[[nodiscard]] QByteArray pushNesting(Context::Type type);
[[nodiscard]] QByteArray prepareObjectItemStart(const QByteArray &key);
[[nodiscard]] QByteArray prepareArrayItemStart();
[[nodiscard]] QByteArray popNesting();
[[nodiscard]] QString mainFileRelativePath() const;
[[nodiscard]] QString pathWithRelativePath(const QString &path) const;
[[nodiscard]] std::unique_ptr<File> fileWithRelativePath(
const QString &path) const;
[[nodiscard]] Result writeSavedContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeFrequentContacts(const Data::ContactsList &data);
[[nodiscard]] Result writeSessions(const Data::SessionsList &data);
[[nodiscard]] Result writeWebSessions(const Data::SessionsList &data);
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
[[nodiscard]] Result writeChatsStart(
const QByteArray &listName,
const QByteArray &about);
[[nodiscard]] Result writeChatsEnd();
Settings _settings;
Environment _environment;
Stats *_stats = nullptr;
Context _context;
bool _currentNestingHadItem = false;
DialogsMode _dialogsMode = DialogsMode::None;
std::unique_ptr<File> _output;
};
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,48 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <QtCore/QString>
namespace Export {
namespace Output {
struct Result {
enum class Type : char {
Success,
Error,
FatalError
};
Result(Type type, QString path) : path(path), type(type) {
}
static Result Success() {
return Result(Type::Success, QString());
}
bool isSuccess() const {
return type == Type::Success;
}
bool isError() const {
return (type == Type::Error) || (type == Type::FatalError);
}
bool isFatalError() const {
return (type == Type::FatalError);
}
explicit operator bool() const {
return isSuccess();
}
QString path;
Type type;
};
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,35 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_stats.h"
namespace Export {
namespace Output {
Stats::Stats(const Stats &other)
: _files(other._files.load())
, _bytes(other._bytes.load()) {
}
void Stats::incrementFiles() {
++_files;
}
void Stats::incrementBytes(int count) {
_bytes += count;
}
int Stats::filesCount() const {
return _files;
}
int64 Stats::bytesCount() const {
return _bytes;
}
} // namespace Output
} // namespace Export

View File

@@ -0,0 +1,33 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <atomic>
namespace Export {
namespace Output {
class Stats {
public:
Stats() = default;
Stats(const Stats &other);
void incrementFiles();
void incrementBytes(int count);
int filesCount() const;
int64 bytesCount() const;
private:
std::atomic<int> _files;
std::atomic<int64> _bytes;
};
} // namespace Output
} // namespace Export