mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add stories to data export.
This commit is contained in:
parent
08c4f1f67a
commit
2a1631247d
19 changed files with 812 additions and 29 deletions
|
@ -111,6 +111,11 @@ pre {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.story {
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.userpic .initials {
|
.userpic .initials {
|
||||||
display: block;
|
display: block;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -194,6 +199,10 @@ a.block_link:hover {
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
background-color: #f5f7f8;
|
background-color: #f5f7f8;
|
||||||
}
|
}
|
||||||
|
a.expanded {
|
||||||
|
padding: 2px 8px;
|
||||||
|
margin: -2px -8px;
|
||||||
|
}
|
||||||
.sections {
|
.sections {
|
||||||
padding: 11px 0;
|
padding: 11px 0;
|
||||||
}
|
}
|
||||||
|
@ -428,6 +437,9 @@ div.toast_shown {
|
||||||
.section.sessions {
|
.section.sessions {
|
||||||
background-image: url(../images/section_sessions.png);
|
background-image: url(../images/section_sessions.png);
|
||||||
}
|
}
|
||||||
|
.section.stories {
|
||||||
|
background-image: url(../images/section_stories.png);
|
||||||
|
}
|
||||||
.section.web {
|
.section.web {
|
||||||
background-image: url(../images/section_web.png);
|
background-image: url(../images/section_web.png);
|
||||||
}
|
}
|
||||||
|
@ -489,6 +501,9 @@ div.toast_shown {
|
||||||
.section.sessions {
|
.section.sessions {
|
||||||
background-image: url(../images/section_sessions@2x.png);
|
background-image: url(../images/section_sessions@2x.png);
|
||||||
}
|
}
|
||||||
|
.section.stories {
|
||||||
|
background-image: url(../images/section_stories@2x.png);
|
||||||
|
}
|
||||||
.section.web {
|
.section.web {
|
||||||
background-image: url(../images/section_web@2x.png);
|
background-image: url(../images/section_web@2x.png);
|
||||||
}
|
}
|
||||||
|
|
BIN
Telegram/Resources/export_html/images/section_stories.png
Normal file
BIN
Telegram/Resources/export_html/images/section_stories.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 605 B |
BIN
Telegram/Resources/export_html/images/section_stories@2x.png
Normal file
BIN
Telegram/Resources/export_html/images/section_stories@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -3409,6 +3409,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_export_option_info_about" = "Your chosen screen name, username, phone number and profile pictures.";
|
"lng_export_option_info_about" = "Your chosen screen name, username, phone number and profile pictures.";
|
||||||
"lng_export_option_contacts" = "Contacts list";
|
"lng_export_option_contacts" = "Contacts list";
|
||||||
"lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices.";
|
"lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices.";
|
||||||
|
"lng_export_option_stories" = "Stories archive";
|
||||||
|
"lng_export_option_stories_about" = "All stories you posted from Telegram mobile apps.";
|
||||||
"lng_export_option_sessions" = "Active sessions";
|
"lng_export_option_sessions" = "Active sessions";
|
||||||
"lng_export_option_sessions_about" = "We store this to display your connected devices in Settings > Privacy & Security > Active Sessions.";
|
"lng_export_option_sessions_about" = "We store this to display your connected devices in Settings > Privacy & Security > Active Sessions.";
|
||||||
"lng_export_header_other" = "Other";
|
"lng_export_header_other" = "Other";
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
<file alias="images/section_photos@2x.png">../../export_html/images/section_photos@2x.png</file>
|
<file alias="images/section_photos@2x.png">../../export_html/images/section_photos@2x.png</file>
|
||||||
<file alias="images/section_sessions.png">../../export_html/images/section_sessions.png</file>
|
<file alias="images/section_sessions.png">../../export_html/images/section_sessions.png</file>
|
||||||
<file alias="images/section_sessions@2x.png">../../export_html/images/section_sessions@2x.png</file>
|
<file alias="images/section_sessions@2x.png">../../export_html/images/section_sessions@2x.png</file>
|
||||||
|
<file alias="images/section_stories.png">../../export_html/images/section_stories.png</file>
|
||||||
|
<file alias="images/section_stories@2x.png">../../export_html/images/section_stories@2x.png</file>
|
||||||
<file alias="images/section_web.png">../../export_html/images/section_web.png</file>
|
<file alias="images/section_web.png">../../export_html/images/section_web.png</file>
|
||||||
<file alias="images/section_web@2x.png">../../export_html/images/section_web@2x.png</file>
|
<file alias="images/section_web@2x.png">../../export_html/images/section_web@2x.png</file>
|
||||||
<file alias="js/script.js">../../export_html/js/script.js</file>
|
<file alias="js/script.js">../../export_html/js/script.js</file>
|
||||||
|
|
|
@ -42,6 +42,16 @@ QString PreparePhotoFileName(int index, TimeId date) {
|
||||||
+ ".jpg";
|
+ ".jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PrepareStoryFileName(
|
||||||
|
int index,
|
||||||
|
TimeId date,
|
||||||
|
const Utf8String &extension) {
|
||||||
|
return "story_"
|
||||||
|
+ QString::number(index)
|
||||||
|
+ PrepareFileNameDatePart(date)
|
||||||
|
+ extension;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int PeerColorIndex(BareId bareId) {
|
int PeerColorIndex(BareId bareId) {
|
||||||
|
@ -584,6 +594,99 @@ UserpicsSlice ParseUserpicsSlice(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File &Story::file() {
|
||||||
|
return media.file();
|
||||||
|
}
|
||||||
|
|
||||||
|
const File &Story::file() const {
|
||||||
|
return media.file();
|
||||||
|
}
|
||||||
|
|
||||||
|
Image &Story::thumb() {
|
||||||
|
return media.thumb();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image &Story::thumb() const {
|
||||||
|
return media.thumb();
|
||||||
|
}
|
||||||
|
|
||||||
|
StoriesSlice ParseStoriesSlice(
|
||||||
|
const MTPVector<MTPStoryItem> &data,
|
||||||
|
int baseIndex) {
|
||||||
|
const auto &list = data.v;
|
||||||
|
auto result = StoriesSlice();
|
||||||
|
result.list.reserve(list.size());
|
||||||
|
for (const auto &story : list) {
|
||||||
|
result.lastId = story.match([](const auto &data) {
|
||||||
|
return data.vid().v;
|
||||||
|
});
|
||||||
|
++result.skipped;
|
||||||
|
story.match([&](const MTPDstoryItem &data) {
|
||||||
|
const auto date = data.vdate().v;
|
||||||
|
const auto expires = data.vexpire_date().v;
|
||||||
|
auto media = Media();
|
||||||
|
data.vmedia().match([&](const MTPDmessageMediaPhoto &data) {
|
||||||
|
const auto suggestedPath = "stories/"
|
||||||
|
+ PrepareStoryFileName(
|
||||||
|
++baseIndex,
|
||||||
|
date,
|
||||||
|
".jpg"_q);
|
||||||
|
const auto photo = data.vphoto();
|
||||||
|
auto content = photo
|
||||||
|
? ParsePhoto(*photo, suggestedPath)
|
||||||
|
: Photo();
|
||||||
|
media.content = content;
|
||||||
|
}, [&](const MTPDmessageMediaDocument &data) {
|
||||||
|
const auto document = data.vdocument();
|
||||||
|
auto fake = ParseMediaContext();
|
||||||
|
auto content = document
|
||||||
|
? ParseDocument(fake, *document, "stories", date)
|
||||||
|
: Document();
|
||||||
|
const auto extension = (content.mime == "image/jpeg")
|
||||||
|
? ".jpg"_q
|
||||||
|
: (content.mime == "image/png")
|
||||||
|
? ".png"_q
|
||||||
|
: [&] {
|
||||||
|
const auto mimeType = Core::MimeTypeForName(
|
||||||
|
content.mime);
|
||||||
|
QStringList patterns = mimeType.globPatterns();
|
||||||
|
if (!patterns.isEmpty()) {
|
||||||
|
return patterns.front().replace(
|
||||||
|
'*',
|
||||||
|
QString()).toUtf8();
|
||||||
|
}
|
||||||
|
return QByteArray();
|
||||||
|
}();
|
||||||
|
const auto path = content.file.suggestedPath = "stories/"
|
||||||
|
+ PrepareStoryFileName(
|
||||||
|
++baseIndex,
|
||||||
|
date,
|
||||||
|
extension);
|
||||||
|
content.thumb.file.suggestedPath = path + "_thumb.jpg";
|
||||||
|
media.content = content;
|
||||||
|
}, [&](const auto &data) {
|
||||||
|
media.content = UnsupportedMedia();
|
||||||
|
});
|
||||||
|
if (!v::is<UnsupportedMedia>(media.content)) {
|
||||||
|
result.list.push_back(Story{
|
||||||
|
.id = data.vid().v,
|
||||||
|
.date = date,
|
||||||
|
.expires = data.vexpire_date().v,
|
||||||
|
.media = std::move(media),
|
||||||
|
.pinned = data.is_pinned(),
|
||||||
|
.caption = (data.vcaption()
|
||||||
|
? ParseText(
|
||||||
|
*data.vcaption(),
|
||||||
|
data.ventities().value_or_empty())
|
||||||
|
: std::vector<TextPart>()),
|
||||||
|
});
|
||||||
|
--result.skipped;
|
||||||
|
}
|
||||||
|
}, [](const auto &) {});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<QString, QSize> WriteImageThumb(
|
std::pair<QString, QSize> WriteImageThumb(
|
||||||
const QString &basePath,
|
const QString &basePath,
|
||||||
const QString &largePath,
|
const QString &largePath,
|
||||||
|
|
|
@ -48,6 +48,10 @@ struct UserpicsInfo {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StoriesInfo {
|
||||||
|
int count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct FileLocation {
|
struct FileLocation {
|
||||||
int dcId = 0;
|
int dcId = 0;
|
||||||
MTPInputFileLocation data;
|
MTPInputFileLocation data;
|
||||||
|
@ -663,9 +667,34 @@ struct FileOrigin {
|
||||||
int split = 0;
|
int split = 0;
|
||||||
MTPInputPeer peer;
|
MTPInputPeer peer;
|
||||||
int32 messageId = 0;
|
int32 messageId = 0;
|
||||||
|
int32 storyId = 0;
|
||||||
uint64 customEmojiId = 0;
|
uint64 customEmojiId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Story {
|
||||||
|
int32 id = 0;
|
||||||
|
TimeId date = 0;
|
||||||
|
TimeId expires = 0;
|
||||||
|
Media media;
|
||||||
|
bool pinned = false;
|
||||||
|
std::vector<TextPart> caption;
|
||||||
|
|
||||||
|
File &file();
|
||||||
|
const File &file() const;
|
||||||
|
Image &thumb();
|
||||||
|
const Image &thumb() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoriesSlice {
|
||||||
|
std::vector<Story> list;
|
||||||
|
int32 lastId = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
StoriesSlice ParseStoriesSlice(
|
||||||
|
const MTPVector<MTPStoryItem> &data,
|
||||||
|
int baseIndex);
|
||||||
|
|
||||||
Message ParseMessage(
|
Message ParseMessage(
|
||||||
ParseMediaContext &context,
|
ParseMediaContext &context,
|
||||||
const MTPMessage &data,
|
const MTPMessage &data,
|
||||||
|
|
|
@ -30,6 +30,7 @@ constexpr auto kTopPeerSliceLimit = 100;
|
||||||
constexpr auto kFileMaxSize = 4000 * int64(1024 * 1024);
|
constexpr auto kFileMaxSize = 4000 * int64(1024 * 1024);
|
||||||
constexpr auto kLocationCacheSize = 100'000;
|
constexpr auto kLocationCacheSize = 100'000;
|
||||||
constexpr auto kMaxEmojiPerRequest = 100;
|
constexpr auto kMaxEmojiPerRequest = 100;
|
||||||
|
constexpr auto kStoriesSliceLimit = 100;
|
||||||
|
|
||||||
struct LocationKey {
|
struct LocationKey {
|
||||||
uint64 type;
|
uint64 type;
|
||||||
|
@ -109,6 +110,7 @@ struct ApiWrap::StartProcess {
|
||||||
|
|
||||||
enum class Step {
|
enum class Step {
|
||||||
UserpicsCount,
|
UserpicsCount,
|
||||||
|
StoriesCount,
|
||||||
SplitRanges,
|
SplitRanges,
|
||||||
DialogsCount,
|
DialogsCount,
|
||||||
LeftChannelsCount,
|
LeftChannelsCount,
|
||||||
|
@ -139,6 +141,19 @@ struct ApiWrap::UserpicsProcess {
|
||||||
int fileIndex = 0;
|
int fileIndex = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ApiWrap::StoriesProcess {
|
||||||
|
FnMut<bool(Data::StoriesInfo&&)> start;
|
||||||
|
Fn<bool(DownloadProgress)> fileProgress;
|
||||||
|
Fn<bool(Data::StoriesSlice&&)> handleSlice;
|
||||||
|
FnMut<void()> finish;
|
||||||
|
|
||||||
|
int processed = 0;
|
||||||
|
std::optional<Data::StoriesSlice> slice;
|
||||||
|
int offsetId = 0;
|
||||||
|
bool lastSlice = false;
|
||||||
|
int fileIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct ApiWrap::OtherDataProcess {
|
struct ApiWrap::OtherDataProcess {
|
||||||
Data::File file;
|
Data::File file;
|
||||||
FnMut<void(Data::File&&)> done;
|
FnMut<void(Data::File&&)> done;
|
||||||
|
@ -417,6 +432,9 @@ void ApiWrap::startExport(
|
||||||
if (_settings->types & Settings::Type::Userpics) {
|
if (_settings->types & Settings::Type::Userpics) {
|
||||||
_startProcess->steps.push_back(Step::UserpicsCount);
|
_startProcess->steps.push_back(Step::UserpicsCount);
|
||||||
}
|
}
|
||||||
|
if (_settings->types & Settings::Type::Stories) {
|
||||||
|
_startProcess->steps.push_back(Step::StoriesCount);
|
||||||
|
}
|
||||||
if (_settings->types & Settings::Type::AnyChatsMask) {
|
if (_settings->types & Settings::Type::AnyChatsMask) {
|
||||||
_startProcess->steps.push_back(Step::SplitRanges);
|
_startProcess->steps.push_back(Step::SplitRanges);
|
||||||
}
|
}
|
||||||
|
@ -447,6 +465,8 @@ void ApiWrap::sendNextStartRequest() {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case Step::UserpicsCount:
|
case Step::UserpicsCount:
|
||||||
return requestUserpicsCount();
|
return requestUserpicsCount();
|
||||||
|
case Step::StoriesCount:
|
||||||
|
return requestStoriesCount();
|
||||||
case Step::SplitRanges:
|
case Step::SplitRanges:
|
||||||
return requestSplitRanges();
|
return requestSplitRanges();
|
||||||
case Step::DialogsCount:
|
case Step::DialogsCount:
|
||||||
|
@ -480,6 +500,22 @@ void ApiWrap::requestUserpicsCount() {
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiWrap::requestStoriesCount() {
|
||||||
|
Expects(_startProcess != nullptr);
|
||||||
|
|
||||||
|
mainRequest(MTPstories_GetStoriesArchive(
|
||||||
|
MTP_int(0), // offset_id
|
||||||
|
MTP_int(0) // limit
|
||||||
|
)).done([=](const MTPstories_Stories &result) {
|
||||||
|
Expects(_settings != nullptr);
|
||||||
|
Expects(_startProcess != nullptr);
|
||||||
|
|
||||||
|
_startProcess->info.storiesCount = result.data().vcount().v;
|
||||||
|
|
||||||
|
sendNextStartRequest();
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::requestSplitRanges() {
|
void ApiWrap::requestSplitRanges() {
|
||||||
Expects(_startProcess != nullptr);
|
Expects(_startProcess != nullptr);
|
||||||
|
|
||||||
|
@ -616,7 +652,8 @@ void ApiWrap::startMainSession(FnMut<void()> done) {
|
||||||
using Type = Settings::Type;
|
using Type = Settings::Type;
|
||||||
const auto sizeLimit = _settings->media.sizeLimit;
|
const auto sizeLimit = _settings->media.sizeLimit;
|
||||||
const auto hasFiles = ((_settings->media.types != 0) && (sizeLimit > 0))
|
const auto hasFiles = ((_settings->media.types != 0) && (sizeLimit > 0))
|
||||||
|| (_settings->types & Type::Userpics);
|
|| (_settings->types & Type::Userpics)
|
||||||
|
|| (_settings->types & Type::Stories);
|
||||||
|
|
||||||
using Flag = MTPaccount_InitTakeoutSession::Flag;
|
using Flag = MTPaccount_InitTakeoutSession::Flag;
|
||||||
const auto flags = Flag(0)
|
const auto flags = Flag(0)
|
||||||
|
@ -856,6 +893,171 @@ void ApiWrap::finishUserpics() {
|
||||||
base::take(_userpicsProcess)->finish();
|
base::take(_userpicsProcess)->finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiWrap::requestStories(
|
||||||
|
FnMut<bool(Data::StoriesInfo&&)> start,
|
||||||
|
Fn<bool(DownloadProgress)> progress,
|
||||||
|
Fn<bool(Data::StoriesSlice&&)> slice,
|
||||||
|
FnMut<void()> finish) {
|
||||||
|
Expects(_storiesProcess == nullptr);
|
||||||
|
|
||||||
|
_storiesProcess = std::make_unique<StoriesProcess>();
|
||||||
|
_storiesProcess->start = std::move(start);
|
||||||
|
_storiesProcess->fileProgress = std::move(progress);
|
||||||
|
_storiesProcess->handleSlice = std::move(slice);
|
||||||
|
_storiesProcess->finish = std::move(finish);
|
||||||
|
|
||||||
|
mainRequest(MTPstories_GetStoriesArchive(
|
||||||
|
MTP_int(_storiesProcess->offsetId),
|
||||||
|
MTP_int(kStoriesSliceLimit)
|
||||||
|
)).done([=](const MTPstories_Stories &result) mutable {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
|
||||||
|
auto startInfo = Data::StoriesInfo{ result.data().vcount().v };
|
||||||
|
if (!_storiesProcess->start(std::move(startInfo))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStoriesSlice(result);
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::handleStoriesSlice(const MTPstories_Stories &result) {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
|
||||||
|
loadStoriesFiles(Data::ParseStoriesSlice(
|
||||||
|
result.data().vstories(),
|
||||||
|
_storiesProcess->processed));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::loadStoriesFiles(Data::StoriesSlice &&slice) {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(!_storiesProcess->slice.has_value());
|
||||||
|
|
||||||
|
if (!slice.lastId) {
|
||||||
|
_storiesProcess->lastSlice = true;
|
||||||
|
}
|
||||||
|
_storiesProcess->slice = std::move(slice);
|
||||||
|
_storiesProcess->fileIndex = 0;
|
||||||
|
loadNextStory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::loadNextStory() {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(_storiesProcess->slice.has_value());
|
||||||
|
|
||||||
|
for (auto &list = _storiesProcess->slice->list
|
||||||
|
; _storiesProcess->fileIndex < list.size()
|
||||||
|
; ++_storiesProcess->fileIndex) {
|
||||||
|
auto &story = list[_storiesProcess->fileIndex];
|
||||||
|
const auto origin = Data::FileOrigin{ .storyId = story.id };
|
||||||
|
const auto ready = processFileLoad(
|
||||||
|
story.file(),
|
||||||
|
origin,
|
||||||
|
[=](FileProgress value) { return loadStoryProgress(value); },
|
||||||
|
[=](const QString &path) { loadStoryDone(path); });
|
||||||
|
if (!ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto thumbProgress = [=](FileProgress value) {
|
||||||
|
return loadStoryThumbProgress(value);
|
||||||
|
};
|
||||||
|
const auto thumbReady = processFileLoad(
|
||||||
|
story.thumb().file,
|
||||||
|
origin,
|
||||||
|
thumbProgress,
|
||||||
|
[=](const QString &path) { loadStoryThumbDone(path); },
|
||||||
|
nullptr,
|
||||||
|
&story);
|
||||||
|
if (!thumbReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishStoriesSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::finishStoriesSlice() {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(_storiesProcess->slice.has_value());
|
||||||
|
|
||||||
|
auto slice = *base::take(_storiesProcess->slice);
|
||||||
|
if (slice.lastId) {
|
||||||
|
_storiesProcess->processed += slice.list.size();
|
||||||
|
_storiesProcess->offsetId = slice.lastId;
|
||||||
|
if (!_storiesProcess->handleSlice(std::move(slice))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_storiesProcess->lastSlice) {
|
||||||
|
finishStories();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainRequest(MTPstories_GetStoriesArchive(
|
||||||
|
MTP_int(_storiesProcess->offsetId),
|
||||||
|
MTP_int(kStoriesSliceLimit)
|
||||||
|
)).done([=](const MTPstories_Stories &result) {
|
||||||
|
handleStoriesSlice(result);
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiWrap::loadStoryProgress(FileProgress progress) {
|
||||||
|
Expects(_fileProcess != nullptr);
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(_storiesProcess->slice.has_value());
|
||||||
|
Expects((_storiesProcess->fileIndex >= 0)
|
||||||
|
&& (_storiesProcess->fileIndex
|
||||||
|
< _storiesProcess->slice->list.size()));
|
||||||
|
|
||||||
|
return _storiesProcess->fileProgress(DownloadProgress{
|
||||||
|
_fileProcess->randomId,
|
||||||
|
_fileProcess->relativePath,
|
||||||
|
_storiesProcess->fileIndex,
|
||||||
|
progress.ready,
|
||||||
|
progress.total });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::loadStoryDone(const QString &relativePath) {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(_storiesProcess->slice.has_value());
|
||||||
|
Expects((_storiesProcess->fileIndex >= 0)
|
||||||
|
&& (_storiesProcess->fileIndex
|
||||||
|
< _storiesProcess->slice->list.size()));
|
||||||
|
|
||||||
|
const auto index = _storiesProcess->fileIndex;
|
||||||
|
auto &file = _storiesProcess->slice->list[index].file();
|
||||||
|
file.relativePath = relativePath;
|
||||||
|
if (relativePath.isEmpty()) {
|
||||||
|
file.skipReason = Data::File::SkipReason::Unavailable;
|
||||||
|
}
|
||||||
|
loadNextStory();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApiWrap::loadStoryThumbProgress(FileProgress progress) {
|
||||||
|
return loadStoryProgress(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::loadStoryThumbDone(const QString &relativePath) {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
Expects(_storiesProcess->slice.has_value());
|
||||||
|
Expects((_storiesProcess->fileIndex >= 0)
|
||||||
|
&& (_storiesProcess->fileIndex
|
||||||
|
< _storiesProcess->slice->list.size()));
|
||||||
|
|
||||||
|
const auto index = _storiesProcess->fileIndex;
|
||||||
|
auto &file = _storiesProcess->slice->list[index].thumb().file;
|
||||||
|
file.relativePath = relativePath;
|
||||||
|
if (relativePath.isEmpty()) {
|
||||||
|
file.skipReason = Data::File::SkipReason::Unavailable;
|
||||||
|
}
|
||||||
|
loadNextStory();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiWrap::finishStories() {
|
||||||
|
Expects(_storiesProcess != nullptr);
|
||||||
|
|
||||||
|
base::take(_storiesProcess)->finish();
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::requestContacts(FnMut<void(Data::ContactsList&&)> done) {
|
void ApiWrap::requestContacts(FnMut<void(Data::ContactsList&&)> done) {
|
||||||
Expects(_contactsProcess == nullptr);
|
Expects(_contactsProcess == nullptr);
|
||||||
|
|
||||||
|
@ -1753,7 +1955,8 @@ bool ApiWrap::processFileLoad(
|
||||||
const Data::FileOrigin &origin,
|
const Data::FileOrigin &origin,
|
||||||
Fn<bool(FileProgress)> progress,
|
Fn<bool(FileProgress)> progress,
|
||||||
FnMut<void(QString)> done,
|
FnMut<void(QString)> done,
|
||||||
Data::Message *message) {
|
Data::Message *message,
|
||||||
|
Data::Story *story) {
|
||||||
using SkipReason = Data::File::SkipReason;
|
using SkipReason = Data::File::SkipReason;
|
||||||
|
|
||||||
if (!file.relativePath.isEmpty()
|
if (!file.relativePath.isEmpty()
|
||||||
|
@ -1767,7 +1970,12 @@ bool ApiWrap::processFileLoad(
|
||||||
}
|
}
|
||||||
|
|
||||||
using Type = MediaSettings::Type;
|
using Type = MediaSettings::Type;
|
||||||
const auto type = message ? v::match(message->media.content, [&](
|
const auto media = message
|
||||||
|
? &message->media
|
||||||
|
: story
|
||||||
|
? &story->media
|
||||||
|
: nullptr;
|
||||||
|
const auto type = media ? v::match(media->content, [&](
|
||||||
const Data::Document &data) {
|
const Data::Document &data) {
|
||||||
if (data.isSticker) {
|
if (data.isSticker) {
|
||||||
return Type::Sticker;
|
return Type::Sticker;
|
||||||
|
@ -1786,14 +1994,18 @@ bool ApiWrap::processFileLoad(
|
||||||
return Type::Photo;
|
return Type::Photo;
|
||||||
}) : Type(0);
|
}) : Type(0);
|
||||||
|
|
||||||
const auto limit = _settings->media.sizeLimit;
|
const auto fullSize = message
|
||||||
|
? message->file().size
|
||||||
|
: story
|
||||||
|
? story->file().size
|
||||||
|
: file.size;
|
||||||
if (message && Data::SkipMessageByDate(*message, *_settings)) {
|
if (message && Data::SkipMessageByDate(*message, *_settings)) {
|
||||||
file.skipReason = SkipReason::DateLimits;
|
file.skipReason = SkipReason::DateLimits;
|
||||||
return true;
|
return true;
|
||||||
} else if ((_settings->media.types & type) != type) {
|
} else if (!story && (_settings->media.types & type) != type) {
|
||||||
file.skipReason = SkipReason::FileType;
|
file.skipReason = SkipReason::FileType;
|
||||||
return true;
|
return true;
|
||||||
} else if ((message ? message->file().size : file.size) >= limit) {
|
} else if (!story && fullSize >= _settings->media.sizeLimit) {
|
||||||
// Don't load thumbs for large files that we skip.
|
// Don't load thumbs for large files that we skip.
|
||||||
file.skipReason = SkipReason::FileSize;
|
file.skipReason = SkipReason::FileSize;
|
||||||
return true;
|
return true;
|
||||||
|
@ -1972,7 +2184,20 @@ void ApiWrap::filePartRefreshReference(int64 offset) {
|
||||||
Expects(_fileProcess->requestId == 0);
|
Expects(_fileProcess->requestId == 0);
|
||||||
|
|
||||||
const auto &origin = _fileProcess->origin;
|
const auto &origin = _fileProcess->origin;
|
||||||
if (!origin.messageId) {
|
if (origin.storyId) {
|
||||||
|
_fileProcess->requestId = mainRequest(MTPstories_GetStoriesByID(
|
||||||
|
MTP_inputUserSelf(),
|
||||||
|
MTP_vector<MTPint>(1, MTP_int(origin.storyId))
|
||||||
|
)).fail([=](const MTP::Error &error) {
|
||||||
|
_fileProcess->requestId = 0;
|
||||||
|
filePartUnavailable();
|
||||||
|
return true;
|
||||||
|
}).done([=](const MTPstories_Stories &result) {
|
||||||
|
_fileProcess->requestId = 0;
|
||||||
|
filePartExtractReference(offset, result);
|
||||||
|
}).send();
|
||||||
|
return;
|
||||||
|
} else if (!origin.messageId) {
|
||||||
error("FILE_REFERENCE error for non-message file.");
|
error("FILE_REFERENCE error for non-message file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2061,6 +2286,38 @@ void ApiWrap::filePartExtractReference(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApiWrap::filePartExtractReference(
|
||||||
|
int64 offset,
|
||||||
|
const MTPstories_Stories &result) {
|
||||||
|
Expects(_fileProcess != nullptr);
|
||||||
|
Expects(_fileProcess->requestId == 0);
|
||||||
|
|
||||||
|
const auto stories = Data::ParseStoriesSlice(
|
||||||
|
result.data().vstories(),
|
||||||
|
0);
|
||||||
|
for (const auto &story : stories.list) {
|
||||||
|
if (story.id == _fileProcess->origin.storyId) {
|
||||||
|
const auto refresh1 = Data::RefreshFileReference(
|
||||||
|
_fileProcess->location,
|
||||||
|
story.file().location);
|
||||||
|
const auto refresh2 = Data::RefreshFileReference(
|
||||||
|
_fileProcess->location,
|
||||||
|
story.thumb().file.location);
|
||||||
|
if (refresh1 || refresh2) {
|
||||||
|
_fileProcess->requestId = fileRequest(
|
||||||
|
_fileProcess->location,
|
||||||
|
offset
|
||||||
|
).done([=](const MTPupload_File &result) {
|
||||||
|
_fileProcess->requestId = 0;
|
||||||
|
filePartDone(offset, result);
|
||||||
|
}).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filePartUnavailable();
|
||||||
|
}
|
||||||
|
|
||||||
void ApiWrap::filePartUnavailable() {
|
void ApiWrap::filePartUnavailable() {
|
||||||
Expects(_fileProcess != nullptr);
|
Expects(_fileProcess != nullptr);
|
||||||
Expects(!_fileProcess->requests.empty());
|
Expects(!_fileProcess->requests.empty());
|
||||||
|
|
|
@ -19,12 +19,15 @@ struct FileLocation;
|
||||||
struct PersonalInfo;
|
struct PersonalInfo;
|
||||||
struct UserpicsInfo;
|
struct UserpicsInfo;
|
||||||
struct UserpicsSlice;
|
struct UserpicsSlice;
|
||||||
|
struct StoriesInfo;
|
||||||
|
struct StoriesSlice;
|
||||||
struct ContactsList;
|
struct ContactsList;
|
||||||
struct SessionsList;
|
struct SessionsList;
|
||||||
struct DialogsInfo;
|
struct DialogsInfo;
|
||||||
struct DialogInfo;
|
struct DialogInfo;
|
||||||
struct MessagesSlice;
|
struct MessagesSlice;
|
||||||
struct Message;
|
struct Message;
|
||||||
|
struct Story;
|
||||||
struct FileOrigin;
|
struct FileOrigin;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
@ -44,6 +47,7 @@ public:
|
||||||
|
|
||||||
struct StartInfo {
|
struct StartInfo {
|
||||||
int userpicsCount = 0;
|
int userpicsCount = 0;
|
||||||
|
int storiesCount = 0;
|
||||||
int dialogsCount = 0;
|
int dialogsCount = 0;
|
||||||
};
|
};
|
||||||
void startExport(
|
void startExport(
|
||||||
|
@ -74,6 +78,12 @@ public:
|
||||||
Fn<bool(Data::UserpicsSlice&&)> slice,
|
Fn<bool(Data::UserpicsSlice&&)> slice,
|
||||||
FnMut<void()> finish);
|
FnMut<void()> finish);
|
||||||
|
|
||||||
|
void requestStories(
|
||||||
|
FnMut<bool(Data::StoriesInfo&&)> start,
|
||||||
|
Fn<bool(DownloadProgress)> progress,
|
||||||
|
Fn<bool(Data::StoriesSlice&&)> slice,
|
||||||
|
FnMut<void()> finish);
|
||||||
|
|
||||||
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
|
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
|
||||||
|
|
||||||
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
|
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
|
||||||
|
@ -96,6 +106,7 @@ private:
|
||||||
struct StartProcess;
|
struct StartProcess;
|
||||||
struct ContactsProcess;
|
struct ContactsProcess;
|
||||||
struct UserpicsProcess;
|
struct UserpicsProcess;
|
||||||
|
struct StoriesProcess;
|
||||||
struct OtherDataProcess;
|
struct OtherDataProcess;
|
||||||
struct FileProcess;
|
struct FileProcess;
|
||||||
struct FileProgress;
|
struct FileProgress;
|
||||||
|
@ -107,6 +118,7 @@ private:
|
||||||
void startMainSession(FnMut<void()> done);
|
void startMainSession(FnMut<void()> done);
|
||||||
void sendNextStartRequest();
|
void sendNextStartRequest();
|
||||||
void requestUserpicsCount();
|
void requestUserpicsCount();
|
||||||
|
void requestStoriesCount();
|
||||||
void requestSplitRanges();
|
void requestSplitRanges();
|
||||||
void requestDialogsCount();
|
void requestDialogsCount();
|
||||||
void requestLeftChannelsCount();
|
void requestLeftChannelsCount();
|
||||||
|
@ -122,6 +134,16 @@ private:
|
||||||
void finishUserpicsSlice();
|
void finishUserpicsSlice();
|
||||||
void finishUserpics();
|
void finishUserpics();
|
||||||
|
|
||||||
|
void handleStoriesSlice(const MTPstories_Stories &result);
|
||||||
|
void loadStoriesFiles(Data::StoriesSlice &&slice);
|
||||||
|
void loadNextStory();
|
||||||
|
bool loadStoryProgress(FileProgress value);
|
||||||
|
void loadStoryDone(const QString &relativePath);
|
||||||
|
bool loadStoryThumbProgress(FileProgress value);
|
||||||
|
void loadStoryThumbDone(const QString &relativePath);
|
||||||
|
void finishStoriesSlice();
|
||||||
|
void finishStories();
|
||||||
|
|
||||||
void otherDataDone(const QString &relativePath);
|
void otherDataDone(const QString &relativePath);
|
||||||
|
|
||||||
bool useOnlyLastSplit() const;
|
bool useOnlyLastSplit() const;
|
||||||
|
@ -179,7 +201,8 @@ private:
|
||||||
const Data::FileOrigin &origin,
|
const Data::FileOrigin &origin,
|
||||||
Fn<bool(FileProgress)> progress,
|
Fn<bool(FileProgress)> progress,
|
||||||
FnMut<void(QString)> done,
|
FnMut<void(QString)> done,
|
||||||
Data::Message *message = nullptr);
|
Data::Message *message = nullptr,
|
||||||
|
Data::Story *story = nullptr);
|
||||||
std::unique_ptr<FileProcess> prepareFileProcess(
|
std::unique_ptr<FileProcess> prepareFileProcess(
|
||||||
const Data::File &file,
|
const Data::File &file,
|
||||||
const Data::FileOrigin &origin) const;
|
const Data::FileOrigin &origin) const;
|
||||||
|
@ -198,6 +221,9 @@ private:
|
||||||
void filePartExtractReference(
|
void filePartExtractReference(
|
||||||
int64 offset,
|
int64 offset,
|
||||||
const MTPmessages_Messages &result);
|
const MTPmessages_Messages &result);
|
||||||
|
void filePartExtractReference(
|
||||||
|
int64 offset,
|
||||||
|
const MTPstories_Stories &result);
|
||||||
|
|
||||||
template <typename Request>
|
template <typename Request>
|
||||||
class RequestBuilder;
|
class RequestBuilder;
|
||||||
|
@ -228,6 +254,7 @@ private:
|
||||||
std::unique_ptr<LoadedFileCache> _fileCache;
|
std::unique_ptr<LoadedFileCache> _fileCache;
|
||||||
std::unique_ptr<ContactsProcess> _contactsProcess;
|
std::unique_ptr<ContactsProcess> _contactsProcess;
|
||||||
std::unique_ptr<UserpicsProcess> _userpicsProcess;
|
std::unique_ptr<UserpicsProcess> _userpicsProcess;
|
||||||
|
std::unique_ptr<StoriesProcess> _storiesProcess;
|
||||||
std::unique_ptr<OtherDataProcess> _otherDataProcess;
|
std::unique_ptr<OtherDataProcess> _otherDataProcess;
|
||||||
std::unique_ptr<FileProcess> _fileProcess;
|
std::unique_ptr<FileProcess> _fileProcess;
|
||||||
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
|
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
|
||||||
|
|
|
@ -75,6 +75,7 @@ private:
|
||||||
void collectDialogsList();
|
void collectDialogsList();
|
||||||
void exportPersonalInfo();
|
void exportPersonalInfo();
|
||||||
void exportUserpics();
|
void exportUserpics();
|
||||||
|
void exportStories();
|
||||||
void exportContacts();
|
void exportContacts();
|
||||||
void exportSessions();
|
void exportSessions();
|
||||||
void exportOtherData();
|
void exportOtherData();
|
||||||
|
@ -89,6 +90,7 @@ private:
|
||||||
ProcessingState stateDialogsList(int processed) const;
|
ProcessingState stateDialogsList(int processed) const;
|
||||||
ProcessingState statePersonalInfo() const;
|
ProcessingState statePersonalInfo() const;
|
||||||
ProcessingState stateUserpics(const DownloadProgress &progress) const;
|
ProcessingState stateUserpics(const DownloadProgress &progress) const;
|
||||||
|
ProcessingState stateStories(const DownloadProgress &progress) const;
|
||||||
ProcessingState stateContacts() const;
|
ProcessingState stateContacts() const;
|
||||||
ProcessingState stateSessions() const;
|
ProcessingState stateSessions() const;
|
||||||
ProcessingState stateOtherData() const;
|
ProcessingState stateOtherData() const;
|
||||||
|
@ -114,6 +116,9 @@ private:
|
||||||
int _userpicsWritten = 0;
|
int _userpicsWritten = 0;
|
||||||
int _userpicsCount = 0;
|
int _userpicsCount = 0;
|
||||||
|
|
||||||
|
int _storiesWritten = 0;
|
||||||
|
int _storiesCount = 0;
|
||||||
|
|
||||||
// rpl::variable<State> fails to compile in MSVC :(
|
// rpl::variable<State> fails to compile in MSVC :(
|
||||||
State _state;
|
State _state;
|
||||||
rpl::event_stream<State> _stateChanges;
|
rpl::event_stream<State> _stateChanges;
|
||||||
|
@ -273,6 +278,9 @@ void ControllerObject::fillExportSteps() {
|
||||||
if (_settings.types & Type::Userpics) {
|
if (_settings.types & Type::Userpics) {
|
||||||
_steps.push_back(Step::Userpics);
|
_steps.push_back(Step::Userpics);
|
||||||
}
|
}
|
||||||
|
if (_settings.types & Type::Stories) {
|
||||||
|
_steps.push_back(Step::Stories);
|
||||||
|
}
|
||||||
if (_settings.types & Type::Contacts) {
|
if (_settings.types & Type::Contacts) {
|
||||||
_steps.push_back(Step::Contacts);
|
_steps.push_back(Step::Contacts);
|
||||||
}
|
}
|
||||||
|
@ -306,6 +314,9 @@ void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
|
||||||
if (_settings.types & Settings::Type::Userpics) {
|
if (_settings.types & Settings::Type::Userpics) {
|
||||||
push(Step::Userpics, 1);
|
push(Step::Userpics, 1);
|
||||||
}
|
}
|
||||||
|
if (_settings.types & Settings::Type::Stories) {
|
||||||
|
push(Step::Stories, 1);
|
||||||
|
}
|
||||||
if (_settings.types & Settings::Type::Contacts) {
|
if (_settings.types & Settings::Type::Contacts) {
|
||||||
push(Step::Contacts, 1);
|
push(Step::Contacts, 1);
|
||||||
}
|
}
|
||||||
|
@ -344,6 +355,7 @@ void ControllerObject::exportNext() {
|
||||||
case Step::DialogsList: return collectDialogsList();
|
case Step::DialogsList: return collectDialogsList();
|
||||||
case Step::PersonalInfo: return exportPersonalInfo();
|
case Step::PersonalInfo: return exportPersonalInfo();
|
||||||
case Step::Userpics: return exportUserpics();
|
case Step::Userpics: return exportUserpics();
|
||||||
|
case Step::Stories: return exportStories();
|
||||||
case Step::Contacts: return exportContacts();
|
case Step::Contacts: return exportContacts();
|
||||||
case Step::Sessions: return exportSessions();
|
case Step::Sessions: return exportSessions();
|
||||||
case Step::OtherData: return exportOtherData();
|
case Step::OtherData: return exportOtherData();
|
||||||
|
@ -416,6 +428,32 @@ void ControllerObject::exportUserpics() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControllerObject::exportStories() {
|
||||||
|
_api.requestStories([=](Data::StoriesInfo &&start) {
|
||||||
|
if (ioCatchError(_writer->writeStoriesStart(start))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_storiesWritten = 0;
|
||||||
|
_storiesCount = start.count;
|
||||||
|
return true;
|
||||||
|
}, [=](DownloadProgress progress) {
|
||||||
|
setState(stateStories(progress));
|
||||||
|
return true;
|
||||||
|
}, [=](Data::StoriesSlice &&slice) {
|
||||||
|
if (ioCatchError(_writer->writeStoriesSlice(slice))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_storiesWritten += slice.list.size();
|
||||||
|
setState(stateStories(DownloadProgress()));
|
||||||
|
return true;
|
||||||
|
}, [=] {
|
||||||
|
if (ioCatchError(_writer->writeStoriesEnd())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exportNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void ControllerObject::exportContacts() {
|
void ControllerObject::exportContacts() {
|
||||||
setState(stateContacts());
|
setState(stateContacts());
|
||||||
_api.requestContacts([=](Data::ContactsList &&result) {
|
_api.requestContacts([=](Data::ContactsList &&result) {
|
||||||
|
@ -533,7 +571,21 @@ ProcessingState ControllerObject::stateUserpics(
|
||||||
return prepareState(Step::Userpics, [&](ProcessingState &result) {
|
return prepareState(Step::Userpics, [&](ProcessingState &result) {
|
||||||
result.entityIndex = _userpicsWritten + progress.itemIndex;
|
result.entityIndex = _userpicsWritten + progress.itemIndex;
|
||||||
result.entityCount = std::max(_userpicsCount, result.entityIndex);
|
result.entityCount = std::max(_userpicsCount, result.entityIndex);
|
||||||
result.bytesType = ProcessingState::FileType::Photo;
|
result.bytesRandomId = progress.randomId;
|
||||||
|
if (!progress.path.isEmpty()) {
|
||||||
|
const auto last = progress.path.lastIndexOf('/');
|
||||||
|
result.bytesName = progress.path.mid(last + 1);
|
||||||
|
}
|
||||||
|
result.bytesLoaded = progress.ready;
|
||||||
|
result.bytesCount = progress.total;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessingState ControllerObject::stateStories(
|
||||||
|
const DownloadProgress &progress) const {
|
||||||
|
return prepareState(Step::Stories, [&](ProcessingState &result) {
|
||||||
|
result.entityIndex = _storiesWritten + progress.itemIndex;
|
||||||
|
result.entityCount = std::max(_storiesCount, result.entityIndex);
|
||||||
result.bytesRandomId = progress.randomId;
|
result.bytesRandomId = progress.randomId;
|
||||||
if (!progress.path.isEmpty()) {
|
if (!progress.path.isEmpty()) {
|
||||||
const auto last = progress.path.lastIndexOf('/');
|
const auto last = progress.path.lastIndexOf('/');
|
||||||
|
@ -586,7 +638,6 @@ void ControllerObject::fillMessagesState(
|
||||||
: ProcessingState::EntityType::Chat;
|
: ProcessingState::EntityType::Chat;
|
||||||
result.itemIndex = _messagesWritten + progress.itemIndex;
|
result.itemIndex = _messagesWritten + progress.itemIndex;
|
||||||
result.itemCount = std::max(_messagesCount, result.itemIndex);
|
result.itemCount = std::max(_messagesCount, result.itemIndex);
|
||||||
result.bytesType = ProcessingState::FileType::File; // TODO
|
|
||||||
result.bytesRandomId = progress.randomId;
|
result.bytesRandomId = progress.randomId;
|
||||||
if (!progress.path.isEmpty()) {
|
if (!progress.path.isEmpty()) {
|
||||||
const auto last = progress.path.lastIndexOf('/');
|
const auto last = progress.path.lastIndexOf('/');
|
||||||
|
|
|
@ -38,21 +38,12 @@ struct ProcessingState {
|
||||||
DialogsList,
|
DialogsList,
|
||||||
PersonalInfo,
|
PersonalInfo,
|
||||||
Userpics,
|
Userpics,
|
||||||
|
Stories,
|
||||||
Contacts,
|
Contacts,
|
||||||
Sessions,
|
Sessions,
|
||||||
OtherData,
|
OtherData,
|
||||||
Dialogs,
|
Dialogs,
|
||||||
};
|
};
|
||||||
enum class FileType {
|
|
||||||
None,
|
|
||||||
Photo,
|
|
||||||
Video,
|
|
||||||
VoiceMessage,
|
|
||||||
VideoMessage,
|
|
||||||
Sticker,
|
|
||||||
GIF,
|
|
||||||
File,
|
|
||||||
};
|
|
||||||
enum class EntityType {
|
enum class EntityType {
|
||||||
Chat,
|
Chat,
|
||||||
SavedMessages,
|
SavedMessages,
|
||||||
|
@ -75,7 +66,6 @@ struct ProcessingState {
|
||||||
int itemCount = 0;
|
int itemCount = 0;
|
||||||
|
|
||||||
uint64 bytesRandomId = 0;
|
uint64 bytesRandomId = 0;
|
||||||
FileType bytesType = FileType::None;
|
|
||||||
QString bytesName;
|
QString bytesName;
|
||||||
int64 bytesLoaded = 0;
|
int64 bytesLoaded = 0;
|
||||||
int64 bytesCount = 0;
|
int64 bytesCount = 0;
|
||||||
|
|
|
@ -57,13 +57,18 @@ struct Settings {
|
||||||
PublicGroups = 0x100,
|
PublicGroups = 0x100,
|
||||||
PrivateChannels = 0x200,
|
PrivateChannels = 0x200,
|
||||||
PublicChannels = 0x400,
|
PublicChannels = 0x400,
|
||||||
|
Stories = 0x800,
|
||||||
|
|
||||||
GroupsMask = PrivateGroups | PublicGroups,
|
GroupsMask = PrivateGroups | PublicGroups,
|
||||||
ChannelsMask = PrivateChannels | PublicChannels,
|
ChannelsMask = PrivateChannels | PublicChannels,
|
||||||
GroupsChannelsMask = GroupsMask | ChannelsMask,
|
GroupsChannelsMask = GroupsMask | ChannelsMask,
|
||||||
NonChannelChatsMask = PersonalChats | BotChats | PrivateGroups,
|
NonChannelChatsMask = PersonalChats | BotChats | PrivateGroups,
|
||||||
AnyChatsMask = PersonalChats | BotChats | GroupsChannelsMask,
|
AnyChatsMask = PersonalChats | BotChats | GroupsChannelsMask,
|
||||||
NonChatsMask = PersonalInfo | Userpics | Contacts | Sessions,
|
NonChatsMask = (PersonalInfo
|
||||||
|
| Userpics
|
||||||
|
| Contacts
|
||||||
|
| Stories
|
||||||
|
| Sessions),
|
||||||
AllMask = NonChatsMask | OtherData | AnyChatsMask,
|
AllMask = NonChatsMask | OtherData | AnyChatsMask,
|
||||||
};
|
};
|
||||||
using Types = base::flags<Type>;
|
using Types = base::flags<Type>;
|
||||||
|
@ -91,6 +96,7 @@ struct Settings {
|
||||||
return Type::PersonalInfo
|
return Type::PersonalInfo
|
||||||
| Type::Userpics
|
| Type::Userpics
|
||||||
| Type::Contacts
|
| Type::Contacts
|
||||||
|
| Type::Stories
|
||||||
| Type::PersonalChats
|
| Type::PersonalChats
|
||||||
| Type::PrivateGroups;
|
| Type::PrivateGroups;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ namespace Data {
|
||||||
struct PersonalInfo;
|
struct PersonalInfo;
|
||||||
struct UserpicsInfo;
|
struct UserpicsInfo;
|
||||||
struct UserpicsSlice;
|
struct UserpicsSlice;
|
||||||
|
struct StoriesInfo;
|
||||||
|
struct StoriesSlice;
|
||||||
struct ContactsList;
|
struct ContactsList;
|
||||||
struct SessionsList;
|
struct SessionsList;
|
||||||
struct DialogsInfo;
|
struct DialogsInfo;
|
||||||
|
@ -55,6 +57,12 @@ public:
|
||||||
const Data::UserpicsSlice &data) = 0;
|
const Data::UserpicsSlice &data) = 0;
|
||||||
[[nodiscard]] virtual Result writeUserpicsEnd() = 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 writeContactsList(
|
[[nodiscard]] virtual Result writeContactsList(
|
||||||
const Data::ContactsList &data) = 0;
|
const Data::ContactsList &data) = 0;
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,23 @@ constexpr auto kStickerMaxWidth = 384;
|
||||||
constexpr auto kStickerMaxHeight = 384;
|
constexpr auto kStickerMaxHeight = 384;
|
||||||
constexpr auto kStickerMinWidth = 80;
|
constexpr auto kStickerMinWidth = 80;
|
||||||
constexpr auto kStickerMinHeight = 80;
|
constexpr auto kStickerMinHeight = 80;
|
||||||
|
constexpr auto kStoryThumbWidth = 45;
|
||||||
|
constexpr auto kStoryThumbHeight = 80;
|
||||||
|
|
||||||
|
constexpr auto kChatsPriority = 0;
|
||||||
|
constexpr auto kContactsPriority = 2;
|
||||||
|
constexpr auto kFrequentContactsPriority = 3;
|
||||||
|
constexpr auto kUserpicsPriority = 4;
|
||||||
|
constexpr auto kStoriesPriority = 5;
|
||||||
|
constexpr auto kSessionsPriority = 6;
|
||||||
|
constexpr auto kWebSessionsPriority = 7;
|
||||||
|
constexpr auto kOtherPriority = 8;
|
||||||
|
|
||||||
const auto kLineBreak = QByteArrayLiteral("<br>");
|
const auto kLineBreak = QByteArrayLiteral("<br>");
|
||||||
|
|
||||||
using Context = details::HtmlContext;
|
using Context = details::HtmlContext;
|
||||||
using UserpicData = details::UserpicData;
|
using UserpicData = details::UserpicData;
|
||||||
|
using StoryData = details::StoryData;
|
||||||
using PeersMap = details::PeersMap;
|
using PeersMap = details::PeersMap;
|
||||||
using MediaData = details::MediaData;
|
using MediaData = details::MediaData;
|
||||||
|
|
||||||
|
@ -347,6 +359,11 @@ struct UserpicData {
|
||||||
QByteArray lastName;
|
QByteArray lastName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StoryData {
|
||||||
|
QString imageLink;
|
||||||
|
QString largeLink;
|
||||||
|
};
|
||||||
|
|
||||||
class PeersMap {
|
class PeersMap {
|
||||||
public:
|
public:
|
||||||
using Peer = Data::Peer;
|
using Peer = Data::Peer;
|
||||||
|
@ -503,6 +520,14 @@ public:
|
||||||
const QByteArray &details,
|
const QByteArray &details,
|
||||||
const QByteArray &info,
|
const QByteArray &info,
|
||||||
const QString &link = QString());
|
const QString &link = QString());
|
||||||
|
[[nodiscard]] QByteArray pushStoriesListEntry(
|
||||||
|
const StoryData &story,
|
||||||
|
const QByteArray &name,
|
||||||
|
const QByteArrayList &details,
|
||||||
|
const QByteArray &info,
|
||||||
|
const std::vector<Data::TextPart> &caption,
|
||||||
|
const QString &internalLinksDomain,
|
||||||
|
const QString &link = QString());
|
||||||
[[nodiscard]] QByteArray pushSessionListEntry(
|
[[nodiscard]] QByteArray pushSessionListEntry(
|
||||||
int apiId,
|
int apiId,
|
||||||
const QByteArray &name,
|
const QByteArray &name,
|
||||||
|
@ -750,6 +775,75 @@ QByteArray HtmlWriter::Wrap::pushListEntry(
|
||||||
info);
|
info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray HtmlWriter::Wrap::pushStoriesListEntry(
|
||||||
|
const StoryData &story,
|
||||||
|
const QByteArray &name,
|
||||||
|
const QByteArrayList &details,
|
||||||
|
const QByteArray &info,
|
||||||
|
const std::vector<Data::TextPart> &caption,
|
||||||
|
const QString &internalLinksDomain,
|
||||||
|
const QString &link) {
|
||||||
|
auto result = pushDiv("entry clearfix");
|
||||||
|
if (!link.isEmpty()) {
|
||||||
|
result.append(pushTag("a", {
|
||||||
|
{ "class", "pull_left userpic_wrap" },
|
||||||
|
{ "href", relativePath(link).toUtf8() + "#allow_back" },
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
result.append(pushDiv("pull_left userpic_wrap"));
|
||||||
|
}
|
||||||
|
if (!story.imageLink.isEmpty()) {
|
||||||
|
const auto sizeStyle = "width: "
|
||||||
|
+ Data::NumberToString(kStoryThumbWidth)
|
||||||
|
+ "px; height: "
|
||||||
|
+ Data::NumberToString(kStoryThumbHeight)
|
||||||
|
+ "px";
|
||||||
|
result.append(pushTag("img", {
|
||||||
|
{ "class", "story" },
|
||||||
|
{ "style", sizeStyle },
|
||||||
|
{ "src", relativePath(story.imageLink).toUtf8() },
|
||||||
|
{ "empty", "" }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(pushDiv("body"));
|
||||||
|
if (!info.isEmpty()) {
|
||||||
|
result.append(pushDiv("pull_right info details"));
|
||||||
|
result.append(SerializeString(info));
|
||||||
|
result.append(popTag());
|
||||||
|
}
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
if (!link.isEmpty()) {
|
||||||
|
result.append(pushTag("a", {
|
||||||
|
{ "class", "block_link expanded" },
|
||||||
|
{ "href", relativePath(link).toUtf8() + "#allow_back" },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
result.append(pushDiv("name bold"));
|
||||||
|
result.append(SerializeString(name));
|
||||||
|
result.append(popTag());
|
||||||
|
if (!link.isEmpty()) {
|
||||||
|
result.append(popTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto text = caption.empty()
|
||||||
|
? QByteArray()
|
||||||
|
: FormatText(caption, internalLinksDomain, _base);
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
result.append(pushDiv("text"));
|
||||||
|
result.append(text);
|
||||||
|
result.append(popTag());
|
||||||
|
}
|
||||||
|
for (const auto &detail : details) {
|
||||||
|
result.append(pushDiv("details_entry details"));
|
||||||
|
result.append(SerializeString(detail));
|
||||||
|
result.append(popTag());
|
||||||
|
}
|
||||||
|
result.append(popTag());
|
||||||
|
result.append(popTag());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray HtmlWriter::Wrap::pushSessionListEntry(
|
QByteArray HtmlWriter::Wrap::pushSessionListEntry(
|
||||||
int apiId,
|
int apiId,
|
||||||
const QByteArray &name,
|
const QByteArray &name,
|
||||||
|
@ -1980,6 +2074,7 @@ Result HtmlWriter::start(
|
||||||
"images/section_other.png",
|
"images/section_other.png",
|
||||||
"images/section_photos.png",
|
"images/section_photos.png",
|
||||||
"images/section_sessions.png",
|
"images/section_sessions.png",
|
||||||
|
"images/section_stories.png",
|
||||||
"images/section_web.png",
|
"images/section_web.png",
|
||||||
"js/script.js",
|
"js/script.js",
|
||||||
};
|
};
|
||||||
|
@ -2176,13 +2271,114 @@ QString HtmlWriter::userpicsFilePath() const {
|
||||||
|
|
||||||
void HtmlWriter::pushUserpicsSection() {
|
void HtmlWriter::pushUserpicsSection() {
|
||||||
pushSection(
|
pushSection(
|
||||||
4,
|
kUserpicsPriority,
|
||||||
"Profile pictures",
|
"Profile pictures",
|
||||||
"photos",
|
"photos",
|
||||||
_userpicsCount,
|
_userpicsCount,
|
||||||
userpicsFilePath());
|
userpicsFilePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result HtmlWriter::writeStoriesStart(const Data::StoriesInfo &data) {
|
||||||
|
Expects(_summary != nullptr);
|
||||||
|
Expects(_stories == nullptr);
|
||||||
|
|
||||||
|
_storiesCount = data.count;
|
||||||
|
if (!_storiesCount) {
|
||||||
|
return Result::Success();
|
||||||
|
}
|
||||||
|
_stories = fileWithRelativePath(storiesFilePath());
|
||||||
|
|
||||||
|
auto block = _stories->pushHeader(
|
||||||
|
"Stories archive",
|
||||||
|
mainFileRelativePath());
|
||||||
|
block.append(_stories->pushDiv("page_body list_page"));
|
||||||
|
block.append(_stories->pushDiv("entry_list"));
|
||||||
|
if (const auto result = _stories->writeBlock(block); !result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return Result::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result HtmlWriter::writeStoriesSlice(const Data::StoriesSlice &data) {
|
||||||
|
Expects(_stories != nullptr);
|
||||||
|
|
||||||
|
_storiesCount -= data.skipped;
|
||||||
|
if (data.list.empty()) {
|
||||||
|
return Result::Success();
|
||||||
|
}
|
||||||
|
auto block = QByteArray();
|
||||||
|
for (const auto &story : data.list) {
|
||||||
|
auto data = StoryData{};
|
||||||
|
using SkipReason = Data::File::SkipReason;
|
||||||
|
const auto &file = story.file();
|
||||||
|
Assert(!file.relativePath.isEmpty()
|
||||||
|
|| file.skipReason != SkipReason::None);
|
||||||
|
auto status = QByteArrayList();
|
||||||
|
if (story.pinned) {
|
||||||
|
status.append("Saved to Profile");
|
||||||
|
}
|
||||||
|
if (story.expires > 0) {
|
||||||
|
status.append("Expiring: " + Data::FormatDateTime(story.expires));
|
||||||
|
}
|
||||||
|
status.append([&]() -> Data::Utf8String {
|
||||||
|
switch (file.skipReason) {
|
||||||
|
case SkipReason::Unavailable:
|
||||||
|
return "(Story unavailable, please try again later)";
|
||||||
|
case SkipReason::FileSize:
|
||||||
|
return "(Story exceeds maximum size. "
|
||||||
|
"Change data exporting settings to download.)";
|
||||||
|
case SkipReason::FileType:
|
||||||
|
return "(Story not included. "
|
||||||
|
"Change data exporting settings to download.)";
|
||||||
|
case SkipReason::None: return Data::FormatFileSize(file.size);
|
||||||
|
}
|
||||||
|
Unexpected("Skip reason while writing story path.");
|
||||||
|
}());
|
||||||
|
const auto &path = story.file().relativePath;
|
||||||
|
const auto &image = story.thumb().file.relativePath.isEmpty()
|
||||||
|
? story.file().relativePath
|
||||||
|
: story.thumb().file.relativePath;
|
||||||
|
data.imageLink = Data::WriteImageThumb(
|
||||||
|
_settings.path,
|
||||||
|
image,
|
||||||
|
kStoryThumbWidth * 2,
|
||||||
|
kStoryThumbHeight * 2);
|
||||||
|
const auto info = (story.date > 0)
|
||||||
|
? Data::FormatDateTime(story.date)
|
||||||
|
: QByteArray();
|
||||||
|
block.append(_stories->pushStoriesListEntry(
|
||||||
|
data,
|
||||||
|
(path.isEmpty() ? QString("Story unavailable") : path).toUtf8(),
|
||||||
|
status,
|
||||||
|
info,
|
||||||
|
story.caption,
|
||||||
|
_environment.internalLinksDomain,
|
||||||
|
path));
|
||||||
|
}
|
||||||
|
return _stories->writeBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result HtmlWriter::writeStoriesEnd() {
|
||||||
|
pushStoriesSection();
|
||||||
|
if (_stories) {
|
||||||
|
return base::take(_stories)->close();
|
||||||
|
}
|
||||||
|
return Result::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString HtmlWriter::storiesFilePath() const {
|
||||||
|
return "lists/stories.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
void HtmlWriter::pushStoriesSection() {
|
||||||
|
pushSection(
|
||||||
|
kStoriesPriority,
|
||||||
|
"Stories archive",
|
||||||
|
"stories",
|
||||||
|
_storiesCount,
|
||||||
|
storiesFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
Result HtmlWriter::writeContactsList(const Data::ContactsList &data) {
|
Result HtmlWriter::writeContactsList(const Data::ContactsList &data) {
|
||||||
Expects(_summary != nullptr);
|
Expects(_summary != nullptr);
|
||||||
|
|
||||||
|
@ -2228,7 +2424,7 @@ Result HtmlWriter::writeSavedContacts(const Data::ContactsList &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
2,
|
kContactsPriority,
|
||||||
"Contacts",
|
"Contacts",
|
||||||
"contacts",
|
"contacts",
|
||||||
data.list.size(),
|
data.list.size(),
|
||||||
|
@ -2294,7 +2490,7 @@ Result HtmlWriter::writeFrequentContacts(const Data::ContactsList &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
3,
|
kFrequentContactsPriority,
|
||||||
"Frequent contacts",
|
"Frequent contacts",
|
||||||
"frequent",
|
"frequent",
|
||||||
size,
|
size,
|
||||||
|
@ -2360,7 +2556,7 @@ Result HtmlWriter::writeSessions(const Data::SessionsList &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
5,
|
kSessionsPriority,
|
||||||
"Sessions",
|
"Sessions",
|
||||||
"sessions",
|
"sessions",
|
||||||
data.list.size(),
|
data.list.size(),
|
||||||
|
@ -2406,7 +2602,7 @@ Result HtmlWriter::writeWebSessions(const Data::SessionsList &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
6,
|
kWebSessionsPriority,
|
||||||
"Web sessions",
|
"Web sessions",
|
||||||
"web",
|
"web",
|
||||||
data.webList.size(),
|
data.webList.size(),
|
||||||
|
@ -2418,7 +2614,7 @@ Result HtmlWriter::writeOtherData(const Data::File &data) {
|
||||||
Expects(_summary != nullptr);
|
Expects(_summary != nullptr);
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
7,
|
kOtherPriority,
|
||||||
"Other data",
|
"Other data",
|
||||||
"other",
|
"other",
|
||||||
1,
|
1,
|
||||||
|
@ -2447,7 +2643,7 @@ Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSection(
|
pushSection(
|
||||||
0,
|
kChatsPriority,
|
||||||
"Chats",
|
"Chats",
|
||||||
"chats",
|
"chats",
|
||||||
data.chats.size() + data.left.size(),
|
data.chats.size() + data.left.size(),
|
||||||
|
|
|
@ -35,6 +35,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UserpicData;
|
struct UserpicData;
|
||||||
|
struct StoryData;
|
||||||
class PeersMap;
|
class PeersMap;
|
||||||
struct MediaData;
|
struct MediaData;
|
||||||
|
|
||||||
|
@ -59,6 +60,10 @@ public:
|
||||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||||
Result writeUserpicsEnd() override;
|
Result writeUserpicsEnd() override;
|
||||||
|
|
||||||
|
Result writeStoriesStart(const Data::StoriesInfo &data) override;
|
||||||
|
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
|
||||||
|
Result writeStoriesEnd() override;
|
||||||
|
|
||||||
Result writeContactsList(const Data::ContactsList &data) override;
|
Result writeContactsList(const Data::ContactsList &data) override;
|
||||||
|
|
||||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||||
|
@ -125,8 +130,10 @@ private:
|
||||||
const Data::PersonalInfo &data,
|
const Data::PersonalInfo &data,
|
||||||
const QString &userpicPath);
|
const QString &userpicPath);
|
||||||
void pushUserpicsSection();
|
void pushUserpicsSection();
|
||||||
|
void pushStoriesSection();
|
||||||
|
|
||||||
[[nodiscard]] QString userpicsFilePath() const;
|
[[nodiscard]] QString userpicsFilePath() const;
|
||||||
|
[[nodiscard]] QString storiesFilePath() const;
|
||||||
|
|
||||||
[[nodiscard]] QByteArray wrapMessageLink(
|
[[nodiscard]] QByteArray wrapMessageLink(
|
||||||
int messageId,
|
int messageId,
|
||||||
|
@ -149,6 +156,9 @@ private:
|
||||||
int _userpicsCount = 0;
|
int _userpicsCount = 0;
|
||||||
std::unique_ptr<Wrap> _userpics;
|
std::unique_ptr<Wrap> _userpics;
|
||||||
|
|
||||||
|
int _storiesCount = 0;
|
||||||
|
std::unique_ptr<Wrap> _stories;
|
||||||
|
|
||||||
QString _dialogsRelativePath;
|
QString _dialogsRelativePath;
|
||||||
Data::DialogInfo _dialog;
|
Data::DialogInfo _dialog;
|
||||||
DialogsMode _dialogsMode = DialogsMode::None;
|
DialogsMode _dialogsMode = DialogsMode::None;
|
||||||
|
|
|
@ -887,6 +887,77 @@ Result JsonWriter::writeUserpicsEnd() {
|
||||||
return _output->writeBlock(popNesting());
|
return _output->writeBlock(popNesting());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result JsonWriter::writeStoriesStart(const Data::StoriesInfo &data) {
|
||||||
|
Expects(_output != nullptr);
|
||||||
|
|
||||||
|
auto block = prepareObjectItemStart("stories");
|
||||||
|
return _output->writeBlock(block + pushNesting(Context::kArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result JsonWriter::writeStoriesSlice(const Data::StoriesSlice &data) {
|
||||||
|
Expects(_output != nullptr);
|
||||||
|
|
||||||
|
if (data.list.empty()) {
|
||||||
|
return Result::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block = QByteArray();
|
||||||
|
for (const auto &story : data.list) {
|
||||||
|
using SkipReason = Data::File::SkipReason;
|
||||||
|
const auto &file = story.file();
|
||||||
|
Assert(!file.relativePath.isEmpty()
|
||||||
|
|| file.skipReason != SkipReason::None);
|
||||||
|
const auto path = [&]() -> Data::Utf8String {
|
||||||
|
switch (file.skipReason) {
|
||||||
|
case SkipReason::Unavailable:
|
||||||
|
return "(Photo unavailable, please try again later)";
|
||||||
|
case SkipReason::FileSize:
|
||||||
|
return "(Photo exceeds maximum size. "
|
||||||
|
"Change data exporting settings to download.)";
|
||||||
|
case SkipReason::FileType:
|
||||||
|
return "(Photo not included. "
|
||||||
|
"Change data exporting settings to download.)";
|
||||||
|
case SkipReason::None: return FormatFilePath(file);
|
||||||
|
}
|
||||||
|
Unexpected("Skip reason while writing story path.");
|
||||||
|
}();
|
||||||
|
block.append(prepareArrayItemStart());
|
||||||
|
block.append(SerializeObject(_context, {
|
||||||
|
{
|
||||||
|
"date",
|
||||||
|
story.date ? SerializeDate(story.date) : QByteArray()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_unixtime",
|
||||||
|
story.date ? SerializeDateRaw(story.date) : QByteArray()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expires",
|
||||||
|
story.expires ? SerializeDate(story.expires) : QByteArray()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expires_unixtime",
|
||||||
|
story.expires ? SerializeDateRaw(story.expires) : QByteArray()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pinned",
|
||||||
|
story.pinned ? "true" : "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"media",
|
||||||
|
SerializeString(path)
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return _output->writeBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result JsonWriter::writeStoriesEnd() {
|
||||||
|
Expects(_output != nullptr);
|
||||||
|
|
||||||
|
return _output->writeBlock(popNesting());
|
||||||
|
}
|
||||||
|
|
||||||
Result JsonWriter::writeContactsList(const Data::ContactsList &data) {
|
Result JsonWriter::writeContactsList(const Data::ContactsList &data) {
|
||||||
Expects(_output != nullptr);
|
Expects(_output != nullptr);
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,10 @@ public:
|
||||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||||
Result writeUserpicsEnd() override;
|
Result writeUserpicsEnd() override;
|
||||||
|
|
||||||
|
Result writeStoriesStart(const Data::StoriesInfo &data) override;
|
||||||
|
Result writeStoriesSlice(const Data::StoriesSlice &data) override;
|
||||||
|
Result writeStoriesEnd() override;
|
||||||
|
|
||||||
Result writeContactsList(const Data::ContactsList &data) override;
|
Result writeContactsList(const Data::ContactsList &data) override;
|
||||||
|
|
||||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||||
|
|
|
@ -89,6 +89,13 @@ Content ContentFromState(
|
||||||
case Step::Contacts:
|
case Step::Contacts:
|
||||||
pushMain(tr::lng_export_option_contacts(tr::now));
|
pushMain(tr::lng_export_option_contacts(tr::now));
|
||||||
break;
|
break;
|
||||||
|
case Step::Stories:
|
||||||
|
pushMain(tr::lng_export_option_stories(tr::now));
|
||||||
|
pushBytes(
|
||||||
|
"story" + QString::number(state.entityIndex),
|
||||||
|
state.bytesName,
|
||||||
|
state.bytesRandomId);
|
||||||
|
break;
|
||||||
case Step::Sessions:
|
case Step::Sessions:
|
||||||
pushMain(tr::lng_export_option_sessions(tr::now));
|
pushMain(tr::lng_export_option_sessions(tr::now));
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -173,6 +173,11 @@ void SettingsWidget::setupFullExportOptions(
|
||||||
tr::lng_export_option_contacts(tr::now),
|
tr::lng_export_option_contacts(tr::now),
|
||||||
Type::Contacts,
|
Type::Contacts,
|
||||||
tr::lng_export_option_contacts_about(tr::now));
|
tr::lng_export_option_contacts_about(tr::now));
|
||||||
|
addOptionWithAbout(
|
||||||
|
container,
|
||||||
|
tr::lng_export_option_stories(tr::now),
|
||||||
|
Type::Stories,
|
||||||
|
tr::lng_export_option_stories_about(tr::now));
|
||||||
addHeader(container, tr::lng_export_header_chats(tr::now));
|
addHeader(container, tr::lng_export_header_chats(tr::now));
|
||||||
addOption(
|
addOption(
|
||||||
container,
|
container,
|
||||||
|
|
Loading…
Add table
Reference in a new issue