mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Allow single chat history export.
This commit is contained in:
parent
6429e8b532
commit
a99ae76ad4
22 changed files with 424 additions and 62 deletions
|
@ -672,6 +672,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_delete_conversation" = "Delete conversation";
|
||||
"lng_profile_block_user" = "Block user";
|
||||
"lng_profile_unblock_user" = "Unblock user";
|
||||
"lng_profile_export_chat" = "Export chat history";
|
||||
"lng_profile_export_channel" = "Export channel history";
|
||||
"lng_media_selected_photo#one" = "{count} Photo";
|
||||
"lng_media_selected_photo#other" = "{count} Photos";
|
||||
"lng_media_selected_video#one" = "{count} Video";
|
||||
|
|
|
@ -72,12 +72,16 @@ Session::Session(not_null<AuthSession*> session)
|
|||
setupChannelLeavingViewer();
|
||||
}
|
||||
|
||||
void Session::startExport() {
|
||||
void Session::startExport(PeerData *peer) {
|
||||
startExport(peer ? peer->input : MTP_inputPeerEmpty());
|
||||
}
|
||||
|
||||
void Session::startExport(const MTPInputPeer &singlePeer) {
|
||||
if (_exportPanel) {
|
||||
_exportPanel->activatePanel();
|
||||
return;
|
||||
}
|
||||
_export = std::make_unique<Export::ControllerWrap>();
|
||||
_export = std::make_unique<Export::ControllerWrap>(singlePeer);
|
||||
_exportPanel = std::make_unique<Export::View::PanelController>(
|
||||
_export.get());
|
||||
|
||||
|
|
|
@ -56,7 +56,8 @@ public:
|
|||
return *_session;
|
||||
}
|
||||
|
||||
void startExport();
|
||||
void startExport(PeerData *peer = nullptr);
|
||||
void startExport(const MTPInputPeer &singlePeer);
|
||||
void suggestStartExport(TimeId availableAt);
|
||||
void clearExportSuggestion();
|
||||
rpl::producer<Export::View::PanelController*> currentExportView() const;
|
||||
|
|
|
@ -1386,19 +1386,36 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
|
|||
return result;
|
||||
}
|
||||
|
||||
DialogInfo DialogInfoFromUser(const User &data) {
|
||||
auto result = DialogInfo();
|
||||
result.input = (Peer{ data }).input();
|
||||
result.name = data.info.firstName;
|
||||
result.lastName = data.info.lastName;
|
||||
result.peerId = UserPeerId(data.info.userId);
|
||||
result.topMessageDate = 0;
|
||||
result.topMessageId = 0;
|
||||
result.type = DialogTypeFromUser(data);
|
||||
result.isLeftChannel = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
DialogInfo DialogInfoFromChat(const Chat &data) {
|
||||
auto result = DialogInfo();
|
||||
result.input = data.input;
|
||||
result.name = data.title;
|
||||
result.peerId = ChatPeerId(data.id);
|
||||
result.topMessageDate = 0;
|
||||
result.topMessageId = 0;
|
||||
result.type = DialogTypeFromChat(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
|
||||
auto result = DialogsInfo();
|
||||
data.match([&](const auto &data) { //MTPDmessages_chats &data) {
|
||||
result.left.reserve(data.vchats.v.size());
|
||||
for (const auto &single : data.vchats.v) {
|
||||
const auto chat = ParseChat(single);
|
||||
auto info = DialogInfo();
|
||||
info.input = chat.input;
|
||||
info.name = chat.title;
|
||||
info.peerId = ChatPeerId(chat.id);
|
||||
info.topMessageDate = 0;
|
||||
info.topMessageId = 0;
|
||||
info.type = DialogTypeFromChat(chat);
|
||||
auto info = DialogInfoFromChat(ParseChat(single));
|
||||
info.isLeftChannel = true;
|
||||
result.left.push_back(std::move(info));
|
||||
}
|
||||
|
@ -1406,6 +1423,65 @@ DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
|
|||
return result;
|
||||
}
|
||||
|
||||
DialogsInfo ParseDialogsInfo(
|
||||
const MTPInputPeer &singlePeer,
|
||||
const MTPVector<MTPUser> &data) {
|
||||
const auto singleId = singlePeer.match(
|
||||
[](const MTPDinputPeerUser &data) {
|
||||
return data.vuser_id.v;
|
||||
}, [](const MTPDinputPeerSelf &data) {
|
||||
return 0;
|
||||
}, [](const auto &data) -> int {
|
||||
Unexpected("Single peer type in ParseDialogsInfo(users).");
|
||||
});
|
||||
auto result = DialogsInfo();
|
||||
result.chats.reserve(data.v.size());
|
||||
for (const auto &single : data.v) {
|
||||
const auto userId = single.match([&](const auto &data) {
|
||||
return data.vid.v;
|
||||
});
|
||||
if (userId != singleId
|
||||
&& (singleId != 0
|
||||
|| single.type() != mtpc_user
|
||||
|| !single.c_user().is_self())) {
|
||||
continue;
|
||||
}
|
||||
auto info = DialogInfoFromUser(ParseUser(single));
|
||||
result.chats.push_back(std::move(info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DialogsInfo ParseDialogsInfo(
|
||||
const MTPInputPeer &singlePeer,
|
||||
const MTPmessages_Chats &data) {
|
||||
const auto singleId = singlePeer.match(
|
||||
[](const MTPDinputPeerChat &data) {
|
||||
return data.vchat_id.v;
|
||||
}, [](const MTPDinputPeerChannel &data) {
|
||||
return data.vchannel_id.v;
|
||||
}, [](const auto &data) -> int {
|
||||
Unexpected("Single peer type in ParseDialogsInfo(chats).");
|
||||
});
|
||||
auto result = DialogsInfo();
|
||||
data.match([&](const auto &data) { //MTPDmessages_chats &data) {
|
||||
result.chats.reserve(data.vchats.v.size());
|
||||
for (const auto &single : data.vchats.v) {
|
||||
const auto chatId = single.match([&](const auto &data) {
|
||||
return data.vid.v;
|
||||
});
|
||||
if (chatId != singleId) {
|
||||
continue;
|
||||
}
|
||||
const auto chat = ParseChat(single);
|
||||
auto info = DialogInfoFromChat(ParseChat(single));
|
||||
info.isLeftChannel = false;
|
||||
result.chats.push_back(std::move(info));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
|
||||
auto &chats = info.chats;
|
||||
auto &left = info.left;
|
||||
|
@ -1414,7 +1490,9 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
|
|||
auto index = 0;
|
||||
for (auto &dialog : chats) {
|
||||
const auto number = Data::NumberToString(++index, digits, '0');
|
||||
dialog.relativePath = "chats/chat_" + number + '/';
|
||||
dialog.relativePath = settings.onlySinglePeer()
|
||||
? QString()
|
||||
: "chats/chat_" + QString::fromUtf8(number) + '/';
|
||||
|
||||
using DialogType = DialogInfo::Type;
|
||||
using Type = Settings::Type;
|
||||
|
@ -1436,6 +1514,8 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
|
|||
ranges::reverse(dialog.splits);
|
||||
}
|
||||
for (auto &dialog : left) {
|
||||
Assert(!settings.onlySinglePeer());
|
||||
|
||||
const auto number = Data::NumberToString(++index, digits, '0');
|
||||
dialog.relativePath = "chats/chat_" + number + '/';
|
||||
dialog.onlyMyMessages = true;
|
||||
|
|
|
@ -546,6 +546,12 @@ struct DialogsInfo {
|
|||
DialogInfo::Type DialogTypeFromChat(const Chat &chat);
|
||||
|
||||
DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data);
|
||||
DialogsInfo ParseDialogsInfo(
|
||||
const MTPInputPeer &singlePeer,
|
||||
const MTPVector<MTPUser> &data);
|
||||
DialogsInfo ParseDialogsInfo(
|
||||
const MTPInputPeer &singlePeer,
|
||||
const MTPmessages_Chats &data);
|
||||
DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data);
|
||||
void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings);
|
||||
|
||||
|
|
|
@ -369,7 +369,8 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
|
|||
filePartDone(0, MTP_upload_file(MTP_storage_filePartial(),
|
||||
MTP_int(0),
|
||||
MTP_bytes(QByteArray())));
|
||||
} else if (result.type() == qstr("LOCATION_INVALID")) {
|
||||
} else if (result.type() == qstr("LOCATION_INVALID")
|
||||
|| result.type() == qstr("VERSION_INVALID")) {
|
||||
filePartUnavailable();
|
||||
} else {
|
||||
error(std::move(result));
|
||||
|
@ -413,7 +414,9 @@ void ApiWrap::startExport(
|
|||
_startProcess->steps.push_back(Step::DialogsCount);
|
||||
}
|
||||
if (_settings->types & Settings::Type::GroupsChannelsMask) {
|
||||
_startProcess->steps.push_back(Step::LeftChannelsCount);
|
||||
if (!_settings->onlySinglePeer()) {
|
||||
_startProcess->steps.push_back(Step::LeftChannelsCount);
|
||||
}
|
||||
}
|
||||
startMainSession([=] {
|
||||
sendNextStartRequest();
|
||||
|
@ -489,6 +492,15 @@ void ApiWrap::requestSplitRanges() {
|
|||
void ApiWrap::requestDialogsCount() {
|
||||
Expects(_startProcess != nullptr);
|
||||
|
||||
if (_settings->onlySinglePeer()) {
|
||||
_startProcess->info.dialogsCount =
|
||||
(_settings->singlePeer.type() == mtpc_inputPeerChannel
|
||||
? 1
|
||||
: _splits.size());
|
||||
sendNextStartRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto offsetDate = 0;
|
||||
const auto offsetId = 0;
|
||||
const auto offsetPeer = MTP_inputPeerEmpty();
|
||||
|
@ -959,9 +971,58 @@ void ApiWrap::cancelExportFast() {
|
|||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestSinglePeerDialog() {
|
||||
auto doneSinglePeer = [=](const auto &result) {
|
||||
auto info = Data::ParseDialogsInfo(_settings->singlePeer, result);
|
||||
|
||||
_dialogsProcess->processedCount += info.chats.size();
|
||||
appendDialogsSlice(std::move(info));
|
||||
|
||||
const auto last = _dialogsProcess->splitIndexPlusOne - 1;
|
||||
for (auto &info : _dialogsProcess->info.chats) {
|
||||
for (auto i = last; i != 0; --i) {
|
||||
info.splits.push_back(i - 1);
|
||||
info.messagesCountPerSplit.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_dialogsProcess->progress(_dialogsProcess->processedCount)) {
|
||||
return;
|
||||
}
|
||||
finishDialogsList();
|
||||
};
|
||||
const auto requestUser = [&](const MTPInputUser &data) {
|
||||
mainRequest(MTPusers_GetUsers(
|
||||
MTP_vector<MTPInputUser>(1, data)
|
||||
)).done(std::move(doneSinglePeer)).send();
|
||||
};
|
||||
_settings->singlePeer.match([&](const MTPDinputPeerUser &data) {
|
||||
requestUser(MTP_inputUser(data.vuser_id, data.vaccess_hash));
|
||||
}, [&](const MTPDinputPeerChat &data) {
|
||||
mainRequest(MTPmessages_GetChats(
|
||||
MTP_vector<MTPint>(1, data.vchat_id)
|
||||
)).done(std::move(doneSinglePeer)).send();
|
||||
}, [&](const MTPDinputPeerChannel &data) {
|
||||
mainRequest(MTPchannels_GetChannels(
|
||||
MTP_vector<MTPInputChannel>(
|
||||
1,
|
||||
MTP_inputChannel(data.vchannel_id, data.vaccess_hash))
|
||||
)).done(std::move(doneSinglePeer)).send();
|
||||
}, [&](const MTPDinputPeerSelf &data) {
|
||||
requestUser(MTP_inputUserSelf());
|
||||
}, [](const MTPDinputPeerEmpty &data) {
|
||||
Unexpected("Empty peer in ApiWrap::requestSinglePeerDialog.");
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogsSlice() {
|
||||
Expects(_dialogsProcess != nullptr);
|
||||
|
||||
if (_settings->onlySinglePeer()) {
|
||||
requestSinglePeerDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto splitIndex = _dialogsProcess->splitIndexPlusOne - 1;
|
||||
const auto hash = 0;
|
||||
splitRequest(splitIndex, MTPmessages_GetDialogs(
|
||||
|
|
|
@ -124,6 +124,7 @@ private:
|
|||
void requestDialogsSlice();
|
||||
void appendDialogsSlice(Data::DialogsInfo &&info);
|
||||
void finishDialogsList();
|
||||
void requestSinglePeerDialog();
|
||||
|
||||
void requestLeftChannelsIfNeeded();
|
||||
void requestLeftChannelsList(
|
||||
|
|
|
@ -15,12 +15,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "export/output/export_output_stats.h"
|
||||
|
||||
namespace Export {
|
||||
namespace {
|
||||
|
||||
auto kNullStateCallback = [](ProcessingState&) {};
|
||||
const auto kNullStateCallback = [](ProcessingState&) {};
|
||||
|
||||
Settings NormalizeSettings(const Settings &settings) {
|
||||
if (!settings.onlySinglePeer()) {
|
||||
return base::duplicate(settings);
|
||||
}
|
||||
auto result = base::duplicate(settings);
|
||||
result.format = Output::Format::Html;
|
||||
result.types = result.fullChats = Settings::Type::AnyChatsMask;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
Controller(crl::weak_on_queue<Controller> weak);
|
||||
Controller(
|
||||
crl::weak_on_queue<Controller> weak,
|
||||
const MTPInputPeer &peer);
|
||||
|
||||
rpl::producer<State> state() const;
|
||||
|
||||
|
@ -83,8 +98,6 @@ private:
|
|||
|
||||
int substepsInStep(Step step) const;
|
||||
|
||||
bool normalizePath();
|
||||
|
||||
ApiWrap _api;
|
||||
Settings _settings;
|
||||
Environment _environment;
|
||||
|
@ -117,7 +130,9 @@ private:
|
|||
|
||||
};
|
||||
|
||||
Controller::Controller(crl::weak_on_queue<Controller> weak)
|
||||
Controller::Controller(
|
||||
crl::weak_on_queue<Controller> weak,
|
||||
const MTPInputPeer &peer)
|
||||
: _api(weak.runner())
|
||||
, _state(PasswordCheckState{}) {
|
||||
_api.errors(
|
||||
|
@ -134,6 +149,7 @@ Controller::Controller(crl::weak_on_queue<Controller> weak)
|
|||
auto state = PasswordCheckState();
|
||||
state.checked = false;
|
||||
state.requesting = false;
|
||||
state.singlePeer = peer;
|
||||
setState(std::move(state));
|
||||
}
|
||||
|
||||
|
@ -218,10 +234,10 @@ void Controller::startExport(
|
|||
if (!_settings.path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
_settings = base::duplicate(settings);
|
||||
_settings = NormalizeSettings(settings);
|
||||
_environment = environment;
|
||||
|
||||
_settings.path = Output::NormalizePath(_settings.path);
|
||||
_settings.path = Output::NormalizePath(_settings);
|
||||
_writer = Output::CreateWriter(_settings.format);
|
||||
fillExportSteps();
|
||||
exportNext();
|
||||
|
@ -569,7 +585,7 @@ void Controller::setFinishedState() {
|
|||
_stats.bytesCount() });
|
||||
}
|
||||
|
||||
ControllerWrap::ControllerWrap() {
|
||||
ControllerWrap::ControllerWrap(const MTPInputPeer &peer) : _wrapped(peer) {
|
||||
}
|
||||
|
||||
rpl::producer<State> ControllerWrap::state() const {
|
||||
|
|
|
@ -23,6 +23,7 @@ struct PasswordCheckState {
|
|||
bool requesting = true;
|
||||
bool hasPassword = false;
|
||||
bool checked = false;
|
||||
MTPInputPeer singlePeer = MTP_inputPeerEmpty();
|
||||
};
|
||||
|
||||
struct ProcessingState {
|
||||
|
@ -110,7 +111,7 @@ using State = base::optional_variant<
|
|||
|
||||
class ControllerWrap {
|
||||
public:
|
||||
ControllerWrap();
|
||||
explicit ControllerWrap(const MTPInputPeer &peer);
|
||||
|
||||
rpl::producer<State> state() const;
|
||||
|
||||
|
|
|
@ -76,8 +76,14 @@ struct Settings {
|
|||
Types fullChats = DefaultFullChats();
|
||||
MediaSettings media;
|
||||
|
||||
MTPInputPeer singlePeer = MTP_inputPeerEmpty();
|
||||
|
||||
TimeId availableAt = 0;
|
||||
|
||||
bool onlySinglePeer() const {
|
||||
return singlePeer.type() != mtpc_inputPeerEmpty;
|
||||
}
|
||||
|
||||
static inline Types DefaultTypes() {
|
||||
return Type::PersonalInfo
|
||||
| Type::Userpics
|
||||
|
|
|
@ -19,8 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
QString NormalizePath(const QString &source) {
|
||||
QDir folder(source);
|
||||
QString NormalizePath(const Settings &settings) {
|
||||
QDir folder(settings.path);
|
||||
const auto path = folder.absolutePath();
|
||||
auto result = path.endsWith('/') ? path : (path + '/');
|
||||
if (!folder.exists()) {
|
||||
|
@ -32,7 +32,9 @@ QString NormalizePath(const QString &source) {
|
|||
return result;
|
||||
}
|
||||
const auto date = QDate::currentDate();
|
||||
const auto base = QString("DataExport_%1_%2_%3"
|
||||
const auto base = QString(settings.onlySinglePeer()
|
||||
? "ChatExport_%1_%2_%3"
|
||||
: "DataExport_%1_%2_%3"
|
||||
).arg(date.day(), 2, 10, QChar('0')
|
||||
).arg(date.month(), 2, 10, QChar('0')
|
||||
).arg(date.year());
|
||||
|
|
|
@ -27,7 +27,7 @@ struct Environment;
|
|||
|
||||
namespace Output {
|
||||
|
||||
QString NormalizePath(const QString &source);
|
||||
QString NormalizePath(const Settings &settings);
|
||||
|
||||
struct Result;
|
||||
class Stats;
|
||||
|
|
|
@ -1727,7 +1727,6 @@ Result HtmlWriter::start(
|
|||
_settings = base::duplicate(settings);
|
||||
_environment = environment;
|
||||
_stats = stats;
|
||||
_summary = fileWithRelativePath(mainFileRelativePath());
|
||||
|
||||
//const auto result = copyFile(
|
||||
// ":/export/css/bootstrap.min.css",
|
||||
|
@ -1771,6 +1770,11 @@ Result HtmlWriter::start(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_settings.onlySinglePeer()) {
|
||||
return Result::Success();
|
||||
}
|
||||
_summary = fileWithRelativePath(mainFileRelativePath());
|
||||
auto block = _summary->pushHeader("Exported Data");
|
||||
block.append(_summary->pushDiv("page_body"));
|
||||
return _summary->writeBlock(block);
|
||||
|
@ -1810,6 +1814,8 @@ Result HtmlWriter::writeDelayedPersonal(const QString &userpicPath) {
|
|||
Result HtmlWriter::writePreparedPersonal(
|
||||
const Data::PersonalInfo &data,
|
||||
const QString &userpicPath) {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
const auto &info = data.user.info;
|
||||
|
||||
auto userpic = UserpicData{ _selfColorIndex, kPersonalUserpicSize };
|
||||
|
@ -2197,11 +2203,12 @@ Result HtmlWriter::writeOtherData(const Data::File &data) {
|
|||
}
|
||||
|
||||
Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||
Expects(_summary != nullptr);
|
||||
Expects(_chats == nullptr);
|
||||
|
||||
if (data.chats.empty() && data.left.empty()) {
|
||||
return Result::Success();
|
||||
} else if (_settings.onlySinglePeer()) {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
_dialogsRelativePath = "lists/chats.html";
|
||||
|
@ -2293,12 +2300,35 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
|||
return _chat->writeBlock(block);
|
||||
}
|
||||
|
||||
Result HtmlWriter::writeDialogEnd() {
|
||||
Expects(_chats != nullptr);
|
||||
Result HtmlWriter::writeEmptySinglePeer() {
|
||||
Expects(_chat != nullptr);
|
||||
|
||||
if (!_settings.onlySinglePeer() || _messagesCount != 0) {
|
||||
return Result::Success();
|
||||
}
|
||||
Assert(_chatFileEmpty);
|
||||
if (const auto result = writeDialogOpening(0); !result) {
|
||||
return result;
|
||||
}
|
||||
return _chat->writeBlock(_chat->pushServiceMessage(
|
||||
--_dateMessageId,
|
||||
_dialog,
|
||||
_settings.path,
|
||||
"Empty chat"));
|
||||
}
|
||||
|
||||
Result HtmlWriter::writeDialogEnd() {
|
||||
Expects(_settings.onlySinglePeer() || _chats != nullptr);
|
||||
Expects(_chat != nullptr);
|
||||
|
||||
if (const auto result = writeEmptySinglePeer(); !result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const auto closed = base::take(_chat)->close(); !closed) {
|
||||
return closed;
|
||||
} else if (_settings.onlySinglePeer()) {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
using Type = Data::DialogInfo::Type;
|
||||
|
@ -2411,7 +2441,7 @@ Result HtmlWriter::writeDialogOpening(int index) {
|
|||
: (_dialog.name + ' ' + _dialog.lastName);
|
||||
auto block = _chat->pushHeader(
|
||||
name,
|
||||
_dialogsRelativePath);
|
||||
_settings.onlySinglePeer() ? QString() : _dialogsRelativePath);
|
||||
block.append(_chat->pushDiv("page_body chat_page"));
|
||||
block.append(_chat->pushDiv("history"));
|
||||
if (index > 0) {
|
||||
|
@ -2489,7 +2519,11 @@ Result HtmlWriter::switchToNextChatFile(int index) {
|
|||
}
|
||||
|
||||
Result HtmlWriter::finish() {
|
||||
Expects(_summary != nullptr);
|
||||
Expects(_settings.onlySinglePeer() || _summary != nullptr);
|
||||
|
||||
if (_settings.onlySinglePeer()) {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
auto block = QByteArray();
|
||||
if (_haveSections) {
|
||||
|
@ -2516,7 +2550,9 @@ Result HtmlWriter::copyFile(
|
|||
}
|
||||
|
||||
QString HtmlWriter::mainFilePath() {
|
||||
return pathWithRelativePath(mainFileRelativePath());
|
||||
return pathWithRelativePath(_settings.onlySinglePeer()
|
||||
? messagesFile(0)
|
||||
: mainFileRelativePath());
|
||||
}
|
||||
|
||||
QString HtmlWriter::mainFileRelativePath() const {
|
||||
|
|
|
@ -108,6 +108,7 @@ private:
|
|||
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
|
||||
[[nodiscard]] Result writeDialogOpening(int index);
|
||||
[[nodiscard]] Result switchToNextChatFile(int index);
|
||||
[[nodiscard]] Result writeEmptySinglePeer();
|
||||
|
||||
void pushSection(
|
||||
int priority,
|
||||
|
|
|
@ -45,7 +45,7 @@ void SuggestBox::prepare() {
|
|||
|
||||
addButton(langFactory(lng_box_ok), [=] {
|
||||
closeBox();
|
||||
Auth().data().startExport();
|
||||
Auth().data().startExport(Local::ReadExportSettings().singlePeer);
|
||||
});
|
||||
addButton(langFactory(lng_export_suggest_cancel), [=] { closeBox(); });
|
||||
setCloseByOutsideClick(false);
|
||||
|
@ -121,8 +121,11 @@ void PanelController::activatePanel() {
|
|||
}
|
||||
|
||||
void PanelController::createPanel() {
|
||||
const auto singlePeer = _settings->onlySinglePeer();
|
||||
_panel = base::make_unique_q<Ui::SeparatePanel>();
|
||||
_panel->setTitle(Lang::Viewer(lng_export_title));
|
||||
_panel->setTitle(Lang::Viewer(singlePeer
|
||||
? lng_export_header_chats
|
||||
: lng_export_title));
|
||||
_panel->setInnerSize(st::exportPanelSize);
|
||||
_panel->closeRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
|
@ -154,7 +157,6 @@ void PanelController::showSettings() {
|
|||
settings->changes(
|
||||
) | rpl::start_with_next([=](Settings &&settings) {
|
||||
*_settings = std::move(settings);
|
||||
_saveSettingsTimer.callOnce(kSaveSettingsTimeout);
|
||||
}, settings->lifetime());
|
||||
|
||||
_panel->showInner(std::move(settings));
|
||||
|
@ -320,7 +322,14 @@ rpl::producer<> PanelController::stopRequests() const {
|
|||
});
|
||||
}
|
||||
|
||||
void PanelController::fillParams(const PasswordCheckState &state) {
|
||||
_settings->singlePeer = state.singlePeer;
|
||||
}
|
||||
|
||||
void PanelController::updateState(State &&state) {
|
||||
if (const auto start = base::get_if<PasswordCheckState>(&state)) {
|
||||
fillParams(*start);
|
||||
}
|
||||
if (!_panel) {
|
||||
createPanel();
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public:
|
|||
~PanelController();
|
||||
|
||||
private:
|
||||
void fillParams(const PasswordCheckState &state);
|
||||
void stopExport();
|
||||
void createPanel();
|
||||
void updateState(State &&state);
|
||||
|
|
|
@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "auth_session.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_export.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
@ -55,10 +56,25 @@ int SizeLimitByIndex(int index) {
|
|||
return megabytes() * kMegabyte;
|
||||
}
|
||||
|
||||
PeerId ReadPeerId(const MTPInputPeer &data) {
|
||||
return data.match([](const MTPDinputPeerUser &data) {
|
||||
return peerFromUser(data.vuser_id.v);
|
||||
}, [](const MTPDinputPeerChat &data) {
|
||||
return peerFromChat(data.vchat_id.v);
|
||||
}, [](const MTPDinputPeerChannel &data) {
|
||||
return peerFromChannel(data.vchannel_id.v);
|
||||
}, [](const MTPDinputPeerSelf &data) {
|
||||
return Auth().userPeerId();
|
||||
}, [](const MTPDinputPeerEmpty &data) {
|
||||
return PeerId(0);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsWidget::SettingsWidget(QWidget *parent, Settings data)
|
||||
: RpWidget(parent)
|
||||
, _singlePeerId(ReadPeerId(data.singlePeer))
|
||||
, _internal_data(std::move(data)) {
|
||||
setupContent();
|
||||
}
|
||||
|
@ -95,6 +111,17 @@ void SettingsWidget::setupContent() {
|
|||
}
|
||||
|
||||
void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
|
||||
if (!_singlePeerId) {
|
||||
setupFullExportOptions(container);
|
||||
}
|
||||
setupMediaOptions(container);
|
||||
if (!_singlePeerId) {
|
||||
setupOtherOptions(container);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsWidget::setupFullExportOptions(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
addOptionWithAbout(
|
||||
container,
|
||||
lng_export_option_info,
|
||||
|
@ -127,38 +154,21 @@ void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
|
|||
container,
|
||||
lng_export_option_public_channels,
|
||||
Type::PublicChannels);
|
||||
|
||||
setupMediaOptions(container);
|
||||
|
||||
addHeader(container, lng_export_header_other);
|
||||
addOptionWithAbout(
|
||||
container,
|
||||
lng_export_option_sessions,
|
||||
Type::Sessions,
|
||||
lng_export_option_sessions_about);
|
||||
addOptionWithAbout(
|
||||
container,
|
||||
lng_export_option_other,
|
||||
Type::OtherData,
|
||||
lng_export_option_other_about);
|
||||
}
|
||||
|
||||
void SettingsWidget::setupMediaOptions(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
if (_singlePeerId != 0) {
|
||||
addMediaOptions(container);
|
||||
return;
|
||||
}
|
||||
const auto mediaWrap = container->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
||||
container,
|
||||
object_ptr<Ui::VerticalLayout>(container)));
|
||||
const auto media = mediaWrap->entity();
|
||||
addHeader(media, lng_export_header_media);
|
||||
addMediaOption(media, lng_export_option_photos, MediaType::Photo);
|
||||
addMediaOption(media, lng_export_option_video_files, MediaType::Video);
|
||||
addMediaOption(media, lng_export_option_voice_messages, MediaType::VoiceMessage);
|
||||
addMediaOption(media, lng_export_option_video_messages, MediaType::VideoMessage);
|
||||
addMediaOption(media, lng_export_option_stickers, MediaType::Sticker);
|
||||
addMediaOption(media, lng_export_option_gifs, MediaType::GIF);
|
||||
addMediaOption(media, lng_export_option_files, MediaType::File);
|
||||
addSizeSlider(media);
|
||||
addMediaOptions(media);
|
||||
|
||||
value() | rpl::map([](const Settings &data) {
|
||||
return data.types;
|
||||
|
@ -178,8 +188,27 @@ void SettingsWidget::setupMediaOptions(
|
|||
}, mediaWrap->lifetime());
|
||||
}
|
||||
|
||||
void SettingsWidget::setupOtherOptions(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
addHeader(container, lng_export_header_other);
|
||||
addOptionWithAbout(
|
||||
container,
|
||||
lng_export_option_sessions,
|
||||
Type::Sessions,
|
||||
lng_export_option_sessions_about);
|
||||
addOptionWithAbout(
|
||||
container,
|
||||
lng_export_option_other,
|
||||
Type::OtherData,
|
||||
lng_export_option_other_about);
|
||||
}
|
||||
|
||||
void SettingsWidget::setupPathAndFormat(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
if (_singlePeerId != 0) {
|
||||
addLocationLabel(container);
|
||||
return;
|
||||
}
|
||||
const auto formatGroup = std::make_shared<Ui::RadioenumGroup<Format>>(
|
||||
readData().format);
|
||||
formatGroup->setChangedCallback([=](Format format) {
|
||||
|
@ -205,6 +234,7 @@ void SettingsWidget::setupPathAndFormat(
|
|||
|
||||
void SettingsWidget::addLocationLabel(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
#ifndef OS_MAC_STORE
|
||||
auto pathLabel = value() | rpl::map([](const Settings &data) {
|
||||
return data.path;
|
||||
}) | rpl::distinct_until_changed(
|
||||
|
@ -241,6 +271,7 @@ void SettingsWidget::addLocationLabel(
|
|||
chooseFolder();
|
||||
return false;
|
||||
});
|
||||
#endif // OS_MAC_STORE
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> SettingsWidget::setupButtons(
|
||||
|
@ -382,6 +413,30 @@ void SettingsWidget::addChatOption(
|
|||
}
|
||||
}
|
||||
|
||||
void SettingsWidget::addMediaOptions(
|
||||
not_null<Ui::VerticalLayout*> container) {
|
||||
addMediaOption(container, lng_export_option_photos, MediaType::Photo);
|
||||
addMediaOption(
|
||||
container,
|
||||
lng_export_option_video_files,
|
||||
MediaType::Video);
|
||||
addMediaOption(
|
||||
container,
|
||||
lng_export_option_voice_messages,
|
||||
MediaType::VoiceMessage);
|
||||
addMediaOption(
|
||||
container,
|
||||
lng_export_option_video_messages,
|
||||
MediaType::VideoMessage);
|
||||
addMediaOption(
|
||||
container,
|
||||
lng_export_option_stickers,
|
||||
MediaType::Sticker);
|
||||
addMediaOption(container, lng_export_option_gifs, MediaType::GIF);
|
||||
addMediaOption(container, lng_export_option_files, MediaType::File);
|
||||
addSizeSlider(container);
|
||||
}
|
||||
|
||||
void SettingsWidget::addMediaOption(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
LangKey key,
|
||||
|
|
|
@ -42,7 +42,9 @@ private:
|
|||
not_null<Ui::ScrollArea*> scroll,
|
||||
not_null<Ui::RpWidget*> wrap);
|
||||
void setupOptions(not_null<Ui::VerticalLayout*> container);
|
||||
void setupFullExportOptions(not_null<Ui::VerticalLayout*> container);
|
||||
void setupMediaOptions(not_null<Ui::VerticalLayout*> container);
|
||||
void setupOtherOptions(not_null<Ui::VerticalLayout*> container);
|
||||
void setupPathAndFormat(not_null<Ui::VerticalLayout*> container);
|
||||
void addHeader(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
|
@ -60,6 +62,7 @@ private:
|
|||
not_null<Ui::VerticalLayout*> container,
|
||||
LangKey key,
|
||||
Types types);
|
||||
void addMediaOptions(not_null<Ui::VerticalLayout*> container);
|
||||
void addMediaOption(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
LangKey key,
|
||||
|
@ -76,6 +79,8 @@ private:
|
|||
template <typename Callback>
|
||||
void changeData(Callback &&callback);
|
||||
|
||||
PeerId _singlePeerId = 0;
|
||||
|
||||
// Use through readData / changeData wrappers.
|
||||
Settings _internal_data;
|
||||
|
||||
|
|
|
@ -43,6 +43,12 @@ constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
|
|||
constexpr auto kDefaultStickerInstallDate = TimeId(1);
|
||||
constexpr auto kProxyTypeShift = 1024;
|
||||
|
||||
constexpr auto kSinglePeerTypeUser = qint32(1);
|
||||
constexpr auto kSinglePeerTypeChat = qint32(2);
|
||||
constexpr auto kSinglePeerTypeChannel = qint32(3);
|
||||
constexpr auto kSinglePeerTypeSelf = qint32(4);
|
||||
constexpr auto kSinglePeerTypeEmpty = qint32(0);
|
||||
|
||||
using FileKey = quint64;
|
||||
|
||||
constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' };
|
||||
|
@ -4816,7 +4822,8 @@ void WriteExportSettings(const Export::Settings &settings) {
|
|||
&& settings.media.sizeLimit == check.media.sizeLimit
|
||||
&& settings.path == check.path
|
||||
&& settings.format == check.format
|
||||
&& settings.availableAt == check.availableAt) {
|
||||
&& settings.availableAt == check.availableAt
|
||||
&& !settings.onlySinglePeer()) {
|
||||
if (_exportSettingsKey) {
|
||||
clearKey(_exportSettingsKey);
|
||||
_exportSettingsKey = 0;
|
||||
|
@ -4830,7 +4837,8 @@ void WriteExportSettings(const Export::Settings &settings) {
|
|||
_writeMap(WriteMapWhen::Fast);
|
||||
}
|
||||
quint32 size = sizeof(quint32) * 6
|
||||
+ Serialize::stringSize(settings.path);
|
||||
+ Serialize::stringSize(settings.path)
|
||||
+ sizeof(qint32) * 2 + sizeof(quint64);
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream
|
||||
<< quint32(settings.types)
|
||||
|
@ -4840,6 +4848,23 @@ void WriteExportSettings(const Export::Settings &settings) {
|
|||
<< quint32(settings.format)
|
||||
<< settings.path
|
||||
<< quint32(settings.availableAt);
|
||||
settings.singlePeer.match([&](const MTPDinputPeerUser &user) {
|
||||
data.stream
|
||||
<< kSinglePeerTypeUser
|
||||
<< qint32(user.vuser_id.v)
|
||||
<< quint64(user.vaccess_hash.v);
|
||||
}, [&](const MTPDinputPeerChat &chat) {
|
||||
data.stream << kSinglePeerTypeChat << qint32(chat.vchat_id.v);
|
||||
}, [&](const MTPDinputPeerChannel &channel) {
|
||||
data.stream
|
||||
<< kSinglePeerTypeChannel
|
||||
<< qint32(channel.vchannel_id.v)
|
||||
<< quint64(channel.vaccess_hash.v);
|
||||
}, [&](const MTPDinputPeerSelf &) {
|
||||
data.stream << kSinglePeerTypeSelf;
|
||||
}, [&](const MTPDinputPeerEmpty &) {
|
||||
data.stream << kSinglePeerTypeEmpty;
|
||||
});
|
||||
|
||||
FileWriteDescriptor file(_exportSettingsKey);
|
||||
file.writeEncrypted(data);
|
||||
|
@ -4859,6 +4884,8 @@ Export::Settings ReadExportSettings() {
|
|||
quint32 mediaTypes = 0, mediaSizeLimit = 0;
|
||||
quint32 format = 0, availableAt = 0;
|
||||
QString path;
|
||||
qint32 singlePeerType = 0, singlePeerBareId = 0;
|
||||
quint64 singlePeerAccessHash = 0;
|
||||
file.stream
|
||||
>> types
|
||||
>> fullChats
|
||||
|
@ -4867,6 +4894,19 @@ Export::Settings ReadExportSettings() {
|
|||
>> format
|
||||
>> path
|
||||
>> availableAt;
|
||||
if (!file.stream.atEnd()) {
|
||||
file.stream >> singlePeerType;
|
||||
switch (singlePeerType) {
|
||||
case kSinglePeerTypeUser:
|
||||
case kSinglePeerTypeChannel: {
|
||||
file.stream >> singlePeerBareId >> singlePeerAccessHash;
|
||||
} break;
|
||||
case kSinglePeerTypeChat: file.stream >> singlePeerBareId; break;
|
||||
case kSinglePeerTypeSelf:
|
||||
case kSinglePeerTypeEmpty: break;
|
||||
default: return Export::Settings();
|
||||
}
|
||||
}
|
||||
auto result = Export::Settings();
|
||||
result.types = Export::Settings::Types::from_raw(types);
|
||||
result.fullChats = Export::Settings::Types::from_raw(fullChats);
|
||||
|
@ -4875,6 +4915,25 @@ Export::Settings ReadExportSettings() {
|
|||
result.format = Export::Output::Format(format);
|
||||
result.path = path;
|
||||
result.availableAt = availableAt;
|
||||
result.singlePeer = [&] {
|
||||
switch (singlePeerType) {
|
||||
case kSinglePeerTypeUser:
|
||||
return MTP_inputPeerUser(
|
||||
MTP_int(singlePeerBareId),
|
||||
MTP_long(singlePeerAccessHash));
|
||||
case kSinglePeerTypeChat:
|
||||
return MTP_inputPeerChat(MTP_int(singlePeerBareId));
|
||||
case kSinglePeerTypeChannel:
|
||||
return MTP_inputPeerChannel(
|
||||
MTP_int(singlePeerBareId),
|
||||
MTP_long(singlePeerAccessHash));
|
||||
case kSinglePeerTypeSelf:
|
||||
return MTP_inputPeerSelf();
|
||||
case kSinglePeerTypeEmpty:
|
||||
return MTP_inputPeerEmpty();
|
||||
}
|
||||
Unexpected("Type in export data single peer.");
|
||||
}();
|
||||
return (file.stream.status() == QDataStream::Ok && result.validate())
|
||||
? result
|
||||
: Export::Settings();
|
||||
|
|
|
@ -342,6 +342,9 @@ void Filler::addUserActions(not_null<UserData*> user) {
|
|||
lang(lng_profile_invite_to_group),
|
||||
[user] { AddBotToGroupBoxController::Start(user); });
|
||||
}
|
||||
_addAction(
|
||||
lang(lng_profile_export_chat),
|
||||
[=] { PeerMenuExportChat(user); });
|
||||
}
|
||||
_addAction(
|
||||
lang(lng_profile_delete_conversation),
|
||||
|
@ -364,6 +367,9 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
|
|||
lang(lng_profile_add_participant),
|
||||
[chat] { AddChatMembers(chat); });
|
||||
}
|
||||
_addAction(
|
||||
lang(lng_profile_export_chat),
|
||||
[=] { PeerMenuExportChat(chat); });
|
||||
}
|
||||
_addAction(
|
||||
lang(lng_profile_clear_and_exit),
|
||||
|
@ -398,6 +404,11 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
|
|||
lang(lng_channel_add_members),
|
||||
[channel] { PeerMenuAddChannelMembers(channel); });
|
||||
}
|
||||
_addAction(
|
||||
lang(isGroup
|
||||
? lng_profile_export_chat
|
||||
: lng_profile_export_channel),
|
||||
[=] { PeerMenuExportChat(channel); });
|
||||
}
|
||||
if (channel->amIn()) {
|
||||
if (isGroup && !channel->isPublic()) {
|
||||
|
@ -542,6 +553,10 @@ void FeedFiller::addUngroup() {
|
|||
|
||||
} // namespace
|
||||
|
||||
void PeerMenuExportChat(not_null<PeerData*> peer) {
|
||||
Auth().data().startExport(peer);
|
||||
}
|
||||
|
||||
void PeerMenuDeleteContact(not_null<UserData*> user) {
|
||||
auto text = lng_sure_delete_contact(
|
||||
lt_contact,
|
||||
|
|
|
@ -44,6 +44,7 @@ void PeerMenuAddMuteAction(
|
|||
not_null<PeerData*> peer,
|
||||
const PeerMenuCallback &addAction);
|
||||
|
||||
void PeerMenuExportChat(not_null<PeerData*> peer);
|
||||
void PeerMenuDeleteContact(not_null<UserData*> user);
|
||||
void PeerMenuShareContactBox(not_null<UserData*> user);
|
||||
void PeerMenuAddContact(not_null<UserData*> user);
|
||||
|
|
2
Telegram/ThirdParty/crl
vendored
2
Telegram/ThirdParty/crl
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 9bc641f2d4ab140a84aea64c7f2d4669f7633246
|
||||
Subproject commit 527ad273b683d52c5adf5b45b73c6466aa0d0cf0
|
Loading…
Add table
Reference in a new issue