Skip to content

Commit

Permalink
Add reactions to export chat history (#28252)
Browse files Browse the repository at this point in the history
Co-authored-by: 23rd <23rd@vivaldi.net>
  • Loading branch information
BohdanTkachenko and 23rd authored Sep 30, 2024
1 parent 91f5c72 commit a970fe9
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 54 deletions.
52 changes: 52 additions & 0 deletions Telegram/Resources/export_html/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -582,3 +582,55 @@ div.toast_shown {
.bot_button_column_separator {
width: 2px
}

.reactions {
margin: 5px 0;
}

.reactions .reaction {
display: inline-flex;
height: 20px;
border-radius: 15px;
background-color: #e8f5fc;
color: #168acd;
font-weight: bold;
margin-bottom: 5px;
}

.reactions .reaction.active {
background-color: #40a6e2;
color: #fff;
}

.reactions .reaction.paid {
background-color: #fdf6e1;
color: #c58523;
}

.reactions .reaction.active.paid {
background-color: #ecae0a;
color: #fdf6e1;
}

.reactions .reaction .emoji {
line-height: 20px;
margin: 0 5px;
font-size: 15px;
}

.reactions .reaction .userpic:not(:first-child) {
margin-left: -8px;
}

.reactions .reaction .userpic {
display: inline-block;
}

.reactions .reaction .userpic .initials {
font-size: 8px;
}

.reactions .reaction .count {
margin-right: 8px;
line-height: 20px;
}
77 changes: 77 additions & 0 deletions Telegram/SourceFiles/export/data/export_data_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,80 @@ std::vector<TextPart> ParseText(
return result;
}

Utf8String Reaction::TypeToString(const Reaction &reaction) {
switch (reaction.type) {
case Reaction::Type::Empty: return "empty";
case Reaction::Type::Emoji: return "emoji";
case Reaction::Type::CustomEmoji: return "custom_emoji";
case Reaction::Type::Paid: return "paid";
}
Unexpected("Type in Reaction::Type.");
}

Utf8String Reaction::Id(const Reaction &reaction) {
auto id = Utf8String();
switch (reaction.type) {
case Reaction::Type::Emoji:
id = reaction.emoji.toUtf8();
break;
case Reaction::Type::CustomEmoji:
id = reaction.documentId;
break;
}
return Reaction::TypeToString(reaction) + id;
}

Reaction ParseReaction(const MTPReaction& reaction) {
auto result = Reaction();
reaction.match([&](const MTPDreactionEmoji &data) {
result.type = Reaction::Type::Emoji;
result.emoji = qs(data.vemoticon());
}, [&](const MTPDreactionCustomEmoji &data) {
result.type = Reaction::Type::CustomEmoji;
result.documentId = NumberToString(data.vdocument_id().v);
}, [&](const MTPDreactionPaid &data) {
result.type = Reaction::Type::Paid;
}, [&](const MTPDreactionEmpty &data) {
result.type = Reaction::Type::Empty;
});
return result;
}

std::vector<Reaction> ParseReactions(const MTPMessageReactions &data) {
auto reactionsMap = std::map<QString, Reaction>();
auto reactionsOrder = std::vector<Utf8String>();
for (const auto &single : data.data().vresults().v) {
auto reaction = ParseReaction(single.data().vreaction());
reaction.count = single.data().vcount().v;
auto id = Reaction::Id(reaction);
auto const &[_, inserted] = reactionsMap.try_emplace(id, reaction);
if (inserted) {
reactionsOrder.push_back(id);
}
}
if (data.data().vrecent_reactions().has_value()) {
if (const auto list = data.data().vrecent_reactions()) {
for (const auto &single : list->v) {
auto reaction = ParseReaction(single.data().vreaction());
auto id = Reaction::Id(reaction);
auto const &[it, inserted] = reactionsMap.try_emplace(id, reaction);
if (inserted) {
reactionsOrder.push_back(id);
}
it->second.recent.push_back({
.peerId = ParsePeerId(single.data().vpeer_id()),
.date = single.data().vdate().v,
});
}
}
}
std::vector<Reaction> results;
for (const auto &id : reactionsOrder) {
results.push_back(reactionsMap[id]);
}
return results;
}

Utf8String FillLeft(const Utf8String &data, int length, char filler) {
if (length <= data.size()) {
return data;
Expand Down Expand Up @@ -1739,6 +1813,9 @@ Message ParseMessage(
result.text = ParseText(
data.vmessage(),
data.ventities().value_or_empty());
if (data.vreactions().has_value()) {
result.reactions = ParseReactions(*data.vreactions());
}
}, [&](const MTPDmessageService &data) {
result.action = ParseServiceAction(
context,
Expand Down
25 changes: 25 additions & 0 deletions Telegram/SourceFiles/export/data/export_data_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,30 @@ struct TextPart {
}
};

struct Reaction {
enum class Type {
Empty,
Emoji,
CustomEmoji,
Paid,
};

static Utf8String TypeToString(const Reaction &);

static Utf8String Id(const Reaction &);

struct Recent {
PeerId peerId = 0;
TimeId date = 0;
};

Type type;
QString emoji;
Utf8String documentId;
uint32 count = 0;
std::vector<Recent> recent;
};

struct MessageId {
ChannelId channelId;
int32 msgId = 0;
Expand Down Expand Up @@ -775,6 +799,7 @@ struct Message {
int32 replyToMsgId = 0;
PeerId replyToPeerId = 0;
std::vector<TextPart> text;
std::vector<Reaction> reactions;
Media media;
ServiceAction action;
bool out = false;
Expand Down
86 changes: 57 additions & 29 deletions Telegram/SourceFiles/export/export_api_wrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,15 @@ void ApiWrap::collectMessagesCustomEmoji(const Data::MessagesSlice &slice) {
}
}
}
for (const auto &reaction : message.reactions) {
if (reaction.type == Data::Reaction::Type::CustomEmoji) {
if (const auto id = reaction.documentId.toULongLong()) {
if (!_resolvedCustomEmoji.contains(id)) {
_unresolvedCustomEmoji.emplace(id);
}
}
}
}
}
}

Expand Down Expand Up @@ -1803,38 +1812,57 @@ Data::FileOrigin ApiWrap::currentFileMessageOrigin() const {
return result;
}

std::optional<QByteArray> ApiWrap::getCustomEmoji(QByteArray &data) {
if (const auto id = data.toULongLong()) {
const auto i = _resolvedCustomEmoji.find(id);
if (i == end(_resolvedCustomEmoji)) {
return Data::TextPart::UnavailableEmoji();
}
auto &file = i->second.file;
const auto fileProgress = [=](FileProgress value) {
return loadMessageEmojiProgress(value);
};
const auto ready = processFileLoad(
file,
{ .customEmojiId = id },
fileProgress,
[=](const QString &path) {
loadMessageEmojiDone(id, path);
});
if (!ready) {
return std::nullopt;
}
using SkipReason = Data::File::SkipReason;
if (file.skipReason == SkipReason::Unavailable) {
return Data::TextPart::UnavailableEmoji();
} else if (file.skipReason == SkipReason::FileType
|| file.skipReason == SkipReason::FileSize) {
return QByteArray();
} else {
return file.relativePath.toUtf8();
}
}
return data;
}

bool ApiWrap::messageCustomEmojiReady(Data::Message &message) {
for (auto &part : message.text) {
if (part.type == Data::TextPart::Type::CustomEmoji) {
if (const auto id = part.additional.toULongLong()) {
const auto i = _resolvedCustomEmoji.find(id);
if (i == end(_resolvedCustomEmoji)) {
part.additional = Data::TextPart::UnavailableEmoji();
} else {
auto &file = i->second.file;
const auto fileProgress = [=](FileProgress value) {
return loadMessageEmojiProgress(value);
};
const auto ready = processFileLoad(
file,
{ .customEmojiId = id },
fileProgress,
[=](const QString &path) {
loadMessageEmojiDone(id, path);
});
if (!ready) {
return false;
}
using SkipReason = Data::File::SkipReason;
if (file.skipReason == SkipReason::Unavailable) {
part.additional = Data::TextPart::UnavailableEmoji();
} else if (file.skipReason == SkipReason::FileType
|| file.skipReason == SkipReason::FileSize) {
part.additional = QByteArray();
} else {
part.additional = file.relativePath.toUtf8();
}
}
auto data = getCustomEmoji(part.additional);
if (data.has_value()) {
part.additional = *data;
} else {
return false;
}
}
}
for (auto &reaction : message.reactions) {
if (reaction.type == Data::Reaction::Type::CustomEmoji) {
auto data = getCustomEmoji(reaction.documentId);
if (data.has_value()) {
reaction.documentId = *data;
} else {
return false;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Telegram/SourceFiles/export/export_api_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class ApiWrap {
void resolveCustomEmoji();
void loadMessagesFiles(Data::MessagesSlice &&slice);
void loadNextMessageFile();
std::optional<QByteArray> getCustomEmoji(QByteArray &data);
bool messageCustomEmojiReady(Data::Message &message);
bool loadMessageFileProgress(FileProgress value);
void loadMessageFileDone(const QString &relativePath);
Expand Down
Loading

0 comments on commit a970fe9

Please sign in to comment.