From 820099821a109ae855b1fd9fe327e919af41bc8c Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Tue, 11 Jan 2022 00:18:02 +0000 Subject: [PATCH] Implement workaround for combined emoji (#3469) --- CHANGELOG.md | 1 + src/controllers/commands/CommandController.cpp | 10 +++++++++- src/providers/twitch/IrcMessageHandler.cpp | 17 +++++++++++++---- src/providers/twitch/TwitchChannel.cpp | 10 ++++++++-- src/providers/twitch/TwitchChannel.hpp | 16 ++++++++++++++++ 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c396ff64e70..eb6af5e6223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426) - Minor: Add search to emote popup. (#3404) - Minor: Messages can now be highlighted by subscriber or founder badges. (#3445) +- Minor: Add workaround for multipart emoji as described in [the RFC](https://mm2pl.github.io/emoji_rfc.pdf). (#3469) - Bugfix: Fix Split Input hotkeys not being available when input is hidden (#3362) - Bugfix: Fixed colored usernames sometimes not working. (#3170) - Bugfix: Restored ability to send duplicate `/me` messages. (#3166) diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp index 98625c3f48d..9cdd4ee881c 100644 --- a/src/controllers/commands/CommandController.cpp +++ b/src/controllers/commands/CommandController.cpp @@ -66,7 +66,15 @@ void sendWhisperMessage(const QString &text) // (hemirt) pajlada: "we should not be sending whispers through jtv, but // rather to your own username" auto app = getApp(); - app->twitch.server->sendMessage("jtv", text.simplified()); + QString toSend = text.simplified(); + + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + // Constants used here are defined in TwitchChannel.hpp + toSend.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG); + + app->twitch.server->sendMessage("jtv", toSend); } bool appendWhisperMessageWordsLocally(const QStringList &words) diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 8fa9a619d18..ed1e870568a 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -230,8 +230,15 @@ std::vector IrcMessageHandler::parsePrivMessage( void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchIrcServer &server) { - this->addMessage(message, message->target(), message->content(), server, - false, message->isAction()); + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + // Constants used here are defined in TwitchChannel.hpp + + this->addMessage( + message, message->target(), + message->content().replace(COMBINED_FIXER, ZERO_WIDTH_JOINER), server, + false, message->isAction()); } void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, @@ -560,8 +567,10 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) auto c = getApp()->twitch.server->whispersChannel.get(); - TwitchMessageBuilder builder(c, message, args, message->parameter(1), - false); + TwitchMessageBuilder builder( + c, message, args, + message->parameter(1).replace(COMBINED_FIXER, ZERO_WIDTH_JOINER), + false); if (builder.isIgnored()) { diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index 6adf70b2e0b..d8c5387715d 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -106,9 +106,11 @@ namespace { for (const auto jsonMessage : jsonMessages) { - auto content = jsonMessage.toString().toUtf8(); + auto content = jsonMessage.toString(); + content.replace(COMBINED_FIXER, ZERO_WIDTH_JOINER); - auto message = Communi::IrcMessage::fromData(content, nullptr); + auto message = + Communi::IrcMessage::fromData(content.toUtf8(), nullptr); if (message->command() == "CLEARCHAT") { @@ -365,6 +367,10 @@ void TwitchChannel::sendMessage(const QString &message) // Do last message processing QString parsedMessage = app->emotes->emojis.replaceShortCodes(message); + // This is to make sure that combined emoji go through properly, see + // https://github.com/Chatterino/chatterino2/issues/3384 and + // https://mm2pl.github.io/emoji_rfc.pdf for more details + parsedMessage.replace(ZERO_WIDTH_JOINER, ESCAPE_TAG); parsedMessage = parsedMessage.simplified(); if (parsedMessage.isEmpty()) diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp index f51ca7ca1cd..9c7ecb2586d 100644 --- a/src/providers/twitch/TwitchChannel.hpp +++ b/src/providers/twitch/TwitchChannel.hpp @@ -23,6 +23,22 @@ namespace chatterino { +// This is to make sure that combined emoji go through properly, see +// https://github.com/Chatterino/chatterino2/issues/3384 and +// https://mm2pl.github.io/emoji_rfc.pdf for more details +const QString ZERO_WIDTH_JOINER = QString(QChar(0x200D)); + +// Here be MSVC: Do NOT replace with "\U" literal, it will fail silently. +namespace { + const QChar ESCAPE_TAG_CHARS[2] = {QChar::highSurrogate(0xE0002), + QChar::lowSurrogate(0xE0002)}; +} +const QString ESCAPE_TAG = QString(ESCAPE_TAG_CHARS, 2); + +const static QRegularExpression COMBINED_FIXER( + QString("(?