Implement workaround for combined emoji (#3469)

This commit is contained in:
Mm2PL 2022-01-11 00:18:02 +00:00 committed by GitHub
parent dfa3818a70
commit 820099821a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 7 deletions

View file

@ -44,6 +44,7 @@
- Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426) - Minor: Mod list, VIP list, and Users joined/parted messages are now searchable. (#3426)
- Minor: Add search to emote popup. (#3404) - Minor: Add search to emote popup. (#3404)
- Minor: Messages can now be highlighted by subscriber or founder badges. (#3445) - 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: Fix Split Input hotkeys not being available when input is hidden (#3362)
- Bugfix: Fixed colored usernames sometimes not working. (#3170) - Bugfix: Fixed colored usernames sometimes not working. (#3170)
- Bugfix: Restored ability to send duplicate `/me` messages. (#3166) - Bugfix: Restored ability to send duplicate `/me` messages. (#3166)

View file

@ -66,7 +66,15 @@ void sendWhisperMessage(const QString &text)
// (hemirt) pajlada: "we should not be sending whispers through jtv, but // (hemirt) pajlada: "we should not be sending whispers through jtv, but
// rather to your own username" // rather to your own username"
auto app = getApp(); 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) bool appendWhisperMessageWordsLocally(const QStringList &words)

View file

@ -230,7 +230,14 @@ std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchIrcServer &server) TwitchIrcServer &server)
{ {
this->addMessage(message, message->target(), message->content(), server, // 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()); false, message->isAction());
} }
@ -560,7 +567,9 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
auto c = getApp()->twitch.server->whispersChannel.get(); auto c = getApp()->twitch.server->whispersChannel.get();
TwitchMessageBuilder builder(c, message, args, message->parameter(1), TwitchMessageBuilder builder(
c, message, args,
message->parameter(1).replace(COMBINED_FIXER, ZERO_WIDTH_JOINER),
false); false);
if (builder.isIgnored()) if (builder.isIgnored())

View file

@ -106,9 +106,11 @@ namespace {
for (const auto jsonMessage : jsonMessages) 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") if (message->command() == "CLEARCHAT")
{ {
@ -365,6 +367,10 @@ void TwitchChannel::sendMessage(const QString &message)
// Do last message processing // Do last message processing
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message); 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(); parsedMessage = parsedMessage.simplified();
if (parsedMessage.isEmpty()) if (parsedMessage.isEmpty())

View file

@ -23,6 +23,22 @@
namespace chatterino { 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("(?<!%1)%1").arg(ESCAPE_TAG),
QRegularExpression::UseUnicodePropertiesOption);
enum class HighlightState; enum class HighlightState;
struct Emote; struct Emote;