Added initial ability to export topic history.

This commit is contained in:
23rd
2025-11-25 06:19:34 +03:00
parent 4eee00d95e
commit 57411b962f
12 changed files with 608 additions and 11 deletions
+370 -9
View File
@@ -225,25 +225,41 @@ struct ApiWrap::DialogsProcess : ChatsProcess {
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
};
struct ApiWrap::ChatProcess {
Data::DialogInfo info;
FnMut<bool(const Data::DialogInfo &)> start;
struct ApiWrap::AbstractMessagesProcess {
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::MessagesSlice&&)> handleSlice;
FnMut<void()> done;
FnMut<void(MTPmessages_Messages&&)> requestDone;
int localSplitIndex = 0;
int32 largestIdPlusOne = 1;
Data::ParseMediaContext context;
std::optional<Data::MessagesSlice> slice;
bool lastSlice = false;
int fileIndex = 0;
};
struct ApiWrap::ChatProcess : AbstractMessagesProcess {
Data::DialogInfo info;
FnMut<bool(const Data::DialogInfo &)> start;
int localSplitIndex = 0;
int32 largestIdPlusOne = 1;
};
struct ApiWrap::TopicProcess : AbstractMessagesProcess {
PeerId peerId = 0;
MTPInputPeer inputPeer;
int32 topicRootId = 0;
QString relativePath;
FnMut<bool(int count)> start;
int32 offsetId = 0;
int totalCount = 0;
int processedCount = 0;
};
template <typename Request>
class ApiWrap::RequestBuilder {
@@ -2080,13 +2096,18 @@ std::optional<QByteArray> ApiWrap::getCustomEmoji(QByteArray &data) {
}
auto &file = i->second.file;
const auto fileProgress = [=](FileProgress value) {
return loadMessageEmojiProgress(value);
if (_chatProcess) {
return loadMessageEmojiProgress(value);
} else if (_topicProcess) {
return loadTopicEmojiProgress(value);
}
return true;
};
const auto ready = processFileLoad(
file,
{ .customEmojiId = id },
fileProgress,
[=](const QString &path) { loadMessageEmojiDone(id, path); });
[=](const QString &path) { loadCustomEmojiDone(id, path); });
if (!ready) {
return std::nullopt;
}
@@ -2262,6 +2283,36 @@ void ApiWrap::loadMessageEmojiDone(uint64 id, const QString &relativePath) {
loadNextMessageFile();
}
bool ApiWrap::loadTopicEmojiProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
Expects((_topicProcess->fileIndex >= 0)
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
return _topicProcess->fileProgress(DownloadProgress{
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _topicProcess->fileIndex,
.ready = progress.ready,
.total = progress.total });
}
void ApiWrap::loadCustomEmojiDone(uint64 id, const QString &relativePath) {
const auto i = _resolvedCustomEmoji.find(id);
if (i != end(_resolvedCustomEmoji)) {
i->second.file.relativePath = relativePath;
if (relativePath.isEmpty()) {
i->second.file.skipReason = Data::File::SkipReason::Unavailable;
}
}
if (_chatProcess) {
loadNextMessageFile();
} else if (_topicProcess) {
loadNextTopicMessageFile();
}
}
void ApiWrap::finishMessages() {
Expects(_chatProcess != nullptr);
Expects(!_chatProcess->slice.has_value());
@@ -2270,6 +2321,316 @@ void ApiWrap::finishMessages() {
process->done();
}
void ApiWrap::requestTopicMessages(
PeerId peerId,
MTPInputPeer inputPeer,
int32 topicRootId,
FnMut<bool(int count)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done) {
Expects(_topicProcess == nullptr);
Expects(_selfId.has_value());
_topicProcess = std::make_unique<TopicProcess>();
_topicProcess->context.selfPeerId = peerFromUser(*_selfId);
_topicProcess->peerId = peerId;
_topicProcess->inputPeer = inputPeer;
_topicProcess->topicRootId = topicRootId;
_topicProcess->relativePath = "chats/chat_"
+ QString::number(peerId.value)
+ "/topic_"
+ QString::number(topicRootId)
+ "/";
_topicProcess->start = std::move(start);
_topicProcess->fileProgress = std::move(progress);
_topicProcess->handleSlice = std::move(slice);
_topicProcess->done = std::move(done);
mainRequest(MTPchannels_GetMessages(
MTP_inputChannel(
inputPeer.c_inputPeerChannel().vchannel_id(),
inputPeer.c_inputPeerChannel().vaccess_hash()),
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(topicRootId)))
)).done([=](const MTPmessages_Messages &rootResult) {
Expects(_topicProcess != nullptr);
auto rootSlice = rootResult.match([&](
const MTPDmessages_messagesNotModified &) {
return Data::MessagesSlice();
}, [&](const auto &data) {
return Data::ParseMessagesSlice(
_topicProcess->context,
data.vmessages(),
data.vusers(),
data.vchats(),
_topicProcess->relativePath);
});
auto rootSlicePtr = std::make_shared<Data::MessagesSlice>(
std::move(rootSlice));
requestTopicReplies(
0,
0,
kMessagesSliceLimit,
[=](const MTPmessages_Messages &result) {
Expects(_topicProcess != nullptr);
const auto count = result.match(
[](const MTPDmessages_messages &data) {
return int(data.vmessages().v.size());
}, [](const MTPDmessages_messagesSlice &data) {
return data.vcount().v;
}, [](const MTPDmessages_channelMessages &data) {
return data.vcount().v;
}, [](const MTPDmessages_messagesNotModified &data) {
return -1;
});
if (count < 0) {
error("Unexpected messagesNotModified received.");
return;
}
_topicProcess->totalCount = count;
if (!_topicProcess->start(count)) {
return;
}
if (!rootSlicePtr->list.empty()) {
collectMessagesCustomEmoji(*rootSlicePtr);
_topicProcess->slice = std::move(*rootSlicePtr);
_topicProcess->fileIndex = 0;
resolveTopicCustomEmoji();
return;
}
requestTopicMessagesSlice();
});
}).send();
}
void ApiWrap::requestTopicMessagesSlice() {
Expects(_topicProcess != nullptr);
const auto offsetId = (_topicProcess->offsetId == 0)
? 1
: (_topicProcess->offsetId + 1);
requestTopicReplies(
offsetId,
-kMessagesSliceLimit,
kMessagesSliceLimit,
[=](const MTPmessages_Messages &result) {
Expects(_topicProcess != nullptr);
result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
}, [&](const auto &data) {
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
_topicProcess->lastSlice = true;
}
auto slice = Data::ParseMessagesSlice(
_topicProcess->context,
data.vmessages(),
data.vusers(),
data.vchats(),
_topicProcess->relativePath);
if (slice.list.empty()) {
_topicProcess->lastSlice = true;
}
loadTopicMessagesFiles(std::move(slice));
});
});
}
void ApiWrap::requestTopicReplies(
int offsetId,
int addOffset,
int limit,
FnMut<void(MTPmessages_Messages&&)> done) {
Expects(_topicProcess != nullptr);
_topicProcess->requestDone = std::move(done);
const auto doneHandler = [=](MTPmessages_Messages &&result) {
Expects(_topicProcess != nullptr);
base::take(_topicProcess->requestDone)(std::move(result));
};
mainRequest(MTPmessages_GetReplies(
_topicProcess->inputPeer,
MTP_int(_topicProcess->topicRootId),
MTP_int(offsetId),
MTP_int(0),
MTP_int(addOffset),
MTP_int(limit),
MTP_int(0),
MTP_int(0),
MTP_long(0)
)).done(doneHandler).send();
}
void ApiWrap::loadTopicMessagesFiles(Data::MessagesSlice &&slice) {
Expects(_topicProcess != nullptr);
Expects(!_topicProcess->slice.has_value());
collectMessagesCustomEmoji(slice);
if (slice.list.empty()) {
_topicProcess->lastSlice = true;
}
_topicProcess->slice = std::move(slice);
_topicProcess->fileIndex = 0;
resolveTopicCustomEmoji();
}
void ApiWrap::resolveTopicCustomEmoji() {
if (_unresolvedCustomEmoji.empty()) {
loadNextTopicMessageFile();
return;
}
const auto count = std::min(
int(_unresolvedCustomEmoji.size()),
kMaxEmojiPerRequest);
auto v = QVector<MTPlong>();
v.reserve(count);
const auto till = end(_unresolvedCustomEmoji);
const auto from = end(_unresolvedCustomEmoji) - count;
for (auto i = from; i != till; ++i) {
v.push_back(MTP_long(*i));
}
_unresolvedCustomEmoji.erase(from, till);
const auto finalize = [=] {
for (const auto &id : v) {
if (_resolvedCustomEmoji.contains(id.v)) {
continue;
}
_resolvedCustomEmoji.emplace(
id.v,
Data::Document{
.file = {
.skipReason = Data::File::SkipReason::Unavailable,
},
});
}
resolveTopicCustomEmoji();
};
mainRequest(MTPmessages_GetCustomEmojiDocuments(
MTP_vector<MTPlong>(v)
)).fail([=](const MTP::Error &error) {
LOG(("Export Error: Failed to get documents for emoji."));
finalize();
return true;
}).done([=](const MTPVector<MTPDocument> &result) {
for (const auto &entry : result.v) {
auto document = Data::ParseDocument(
_topicProcess->context,
entry,
_topicProcess->relativePath,
TimeId());
_resolvedCustomEmoji.emplace(document.id, std::move(document));
}
finalize();
}).send();
}
void ApiWrap::loadNextTopicMessageFile() {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
const auto makeProgress = [=](FileProgress progress) {
return _topicProcess->fileProgress(DownloadProgress{
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _topicProcess->fileIndex,
.ready = progress.ready,
.total = progress.total,
});
};
for (auto &list = _topicProcess->slice->list
; _topicProcess->fileIndex < list.size()
; ++_topicProcess->fileIndex) {
auto &message = list[_topicProcess->fileIndex];
if (!messageCustomEmojiReady(message)) {
return;
}
const auto origin = Data::FileOrigin{
.peer = _topicProcess->inputPeer,
.messageId = message.id
};
const auto ready = processFileLoad(
message.file(),
origin,
makeProgress,
[=, &message](const QString &path) {
loadTopicMessageFileOrThumbDone(message.file(), path);
},
&message);
if (!ready) {
return;
}
const auto thumbReady = processFileLoad(
message.thumb().file,
origin,
makeProgress,
[=, &message](const QString &path) {
loadTopicMessageFileOrThumbDone(message.thumb().file, path);
},
&message);
if (!thumbReady) {
return;
}
}
finishTopicMessagesSlice();
}
void ApiWrap::finishTopicMessagesSlice() {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
auto slice = *base::take(_topicProcess->slice);
if (!slice.list.empty()) {
_topicProcess->offsetId = slice.list.back().id;
_topicProcess->processedCount += slice.list.size();
if (!_topicProcess->handleSlice(std::move(slice))) {
return;
}
}
const auto reachedTotal = _topicProcess->totalCount > 0
&& _topicProcess->processedCount >= _topicProcess->totalCount;
if (!_topicProcess->lastSlice && !reachedTotal) {
requestTopicMessagesSlice();
} else {
finishTopicMessages();
}
}
void ApiWrap::loadTopicMessageFileOrThumbDone(
Data::File &file,
const QString &relativePath) {
Expects(_topicProcess != nullptr);
Expects(_topicProcess->slice.has_value());
Expects((_topicProcess->fileIndex >= 0)
&& (_topicProcess->fileIndex < _topicProcess->slice->list.size()));
file.relativePath = relativePath;
if (relativePath.isEmpty()) {
file.skipReason = Data::File::SkipReason::Unavailable;
}
loadNextTopicMessageFile();
}
void ApiWrap::finishTopicMessages() {
Expects(_topicProcess != nullptr);
Expects(!_topicProcess->slice.has_value());
const auto process = base::take(_topicProcess);
process->done();
}
bool ApiWrap::processFileLoad(
Data::File &file,
const Data::FileOrigin &origin,