Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reactions to export chat history #28252

Merged
merged 22 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
19c06a7
Add reactions to export chat history
BohdanTkachenko Jun 20, 2024
281e08a
Add MTPDreactionPaid support
BohdanTkachenko Sep 1, 2024
fbb5e29
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
e2bd535
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
7c4ba74
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
9d745d9
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
6f03f77
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
0885b59
Update Telegram/SourceFiles/export/output/export_output_html.cpp
BohdanTkachenko Sep 21, 2024
f75e00e
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
b6b0cab
Update Telegram/SourceFiles/export/output/export_output_html.cpp
BohdanTkachenko Sep 21, 2024
4a1eb9d
Update Telegram/SourceFiles/export/data/export_data_types.cpp
BohdanTkachenko Sep 21, 2024
6708962
Rename renderCustomEmoji to getCustomEmoji
BohdanTkachenko Sep 21, 2024
bfff0b1
Fix HTML exporting being stuck
BohdanTkachenko Sep 21, 2024
2a34366
Fix rendering of paid reactions in HTML export
BohdanTkachenko Sep 21, 2024
0c42af8
Use waving hand emoji for all custom emojis reactions
BohdanTkachenko Sep 21, 2024
3b47c76
Update Telegram/SourceFiles/export/output/export_output_html.cpp
23rd Sep 30, 2024
58a4e31
Update Telegram/SourceFiles/export/output/export_output_html.cpp
23rd Sep 30, 2024
64d63bb
Update Telegram/SourceFiles/export/output/export_output_html.cpp
23rd Sep 30, 2024
309d0d1
Update Telegram/SourceFiles/export/output/export_output_json.cpp
23rd Sep 30, 2024
dab5d6d
Update Telegram/SourceFiles/export/output/export_output_json.cpp
23rd Sep 30, 2024
6324970
Update Telegram/SourceFiles/export/output/export_output_json.cpp
23rd Sep 30, 2024
439cf6a
Update Telegram/SourceFiles/export/output/export_output_json.cpp
23rd Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -318,6 +318,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";
BohdanTkachenko marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1688,6 +1762,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 @@ -672,6 +672,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 @@ -744,6 +768,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
Loading