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

feat: make usernames in USERNOTICEs clickable #5686

Merged
merged 5 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- Minor: Proxy URL information is now included in the `/debug-env` command. (#5648)
- Minor: Make raid entry message usernames clickable. (#5651)
- Minor: Tabs unhighlight when their content is read in other tabs. (#5649)
- Minor: Made usernames in bits and sub messages clickable. (#5686)
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426, #5612)
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)
- Bugfix: Fixed restricted users usernames not being clickable. (#5405)
Expand Down
103 changes: 86 additions & 17 deletions src/messages/MessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ const QRegularExpression mentionRegex("^@" + regexHelpString);
// if findAllUsernames setting is enabled, matches strings like in the examples above, but without @ symbol at the beginning
const QRegularExpression allUsernamesMentionRegex("^" + regexHelpString);

const QRegularExpression SPACE_REGEX("\\s");

const QSet<QString> zeroWidthEmotes{
"SoSnowy", "IceCold", "SantaHat", "TopHat",
"ReinDeer", "CandyCane", "cvMask", "cvHazmat",
Expand Down Expand Up @@ -512,7 +514,7 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text,
// check system message for links
// (e.g. needed for sub ticket message in sub only mode)
const QStringList textFragments =
text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts);
text.split(SPACE_REGEX, Qt::SkipEmptyParts);
for (const auto &word : textFragments)
{
auto link = linkparser::parse(word);
Expand All @@ -531,33 +533,100 @@ MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text,
this->message().searchText = text;
}

MessageBuilder::MessageBuilder(RaidEntryMessageTag, const QString &text,
const QString &loginName,
const QString &displayName,
const MessageColor &userColor, const QTime &time)
: MessageBuilder()
MessagePtrMut MessageBuilder::makeSystemMessageWithUser(
const QString &text, const QString &loginName, const QString &displayName,
const MessageColor &userColor, const QTime &time)
{
this->emplace<TimestampElement>(time);
MessageBuilder builder;
builder.emplace<TimestampElement>(time);

const QStringList textFragments =
text.split(QRegularExpression("\\s"), Qt::SkipEmptyParts);
const auto textFragments = text.split(SPACE_REGEX, Qt::SkipEmptyParts);
for (const auto &word : textFragments)
{
if (word == displayName)
{
this->emplace<MentionElement>(displayName, loginName,
MessageColor::System, userColor);
builder.emplace<MentionElement>(displayName, loginName,
MessageColor::System, userColor);
continue;
}

this->emplace<TextElement>(word, MessageElementFlag::Text,
MessageColor::System);
builder.emplace<TextElement>(word, MessageElementFlag::Text,
MessageColor::System);
}

this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
this->message().messageText = text;
this->message().searchText = text;
builder->flags.set(MessageFlag::System);
builder->flags.set(MessageFlag::DoNotTriggerNotification);
builder->messageText = text;
builder->searchText = text;

return builder.release();
}

MessagePtrMut MessageBuilder::makeSubgiftMessage(const QString &text,
const QVariantMap &tags,
const QTime &time)
{
MessageBuilder builder;
builder.emplace<TimestampElement>(time);

auto gifterLogin = tags.value("login").toString();
auto gifterDisplayName = tags.value("display-name").toString();
if (gifterDisplayName.isEmpty())
{
gifterDisplayName = gifterLogin;
}
MessageColor gifterColor = MessageColor::System;
if (auto colorTag = tags.value("color").value<QColor>(); colorTag.isValid())
{
gifterColor = MessageColor(colorTag);
}

auto recipientLogin =
tags.value("msg-param-recipient-user-name").toString();
if (recipientLogin.isEmpty())
{
recipientLogin = tags.value("msg-param-recipient-name").toString();
}
auto recipientDisplayName =
tags.value("msg-param-recipient-display-name").toString();
if (recipientDisplayName.isEmpty())
{
recipientDisplayName = recipientLogin;
}

const auto textFragments = text.split(SPACE_REGEX, Qt::SkipEmptyParts);
for (const auto &word : textFragments)
{
if (word == gifterDisplayName)
{
builder.emplace<MentionElement>(gifterDisplayName, gifterLogin,
MessageColor::System, gifterColor);
continue;
}
if (word.endsWith('!') &&
word.size() == recipientDisplayName.size() + 1 &&
word.startsWith(recipientDisplayName))
{
builder
.emplace<MentionElement>(recipientDisplayName, recipientLogin,
MessageColor::System,
MessageColor::System)
->setTrailingSpace(false);
builder.emplace<TextElement>(u"!"_s, MessageElementFlag::Text,
MessageColor::System);
continue;
}

builder.emplace<TextElement>(word, MessageElementFlag::Text,
MessageColor::System);
}

builder->flags.set(MessageFlag::System);
builder->flags.set(MessageFlag::DoNotTriggerNotification);
builder->messageText = text;
builder->searchText = text;

return builder.release();
}

MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &timeoutUser,
Expand Down
15 changes: 9 additions & 6 deletions src/messages/MessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ namespace linkparser {

struct SystemMessageTag {
};
struct RaidEntryMessageTag {
};
struct TimeoutMessageTag {
};
struct LiveUpdatesUpdateEmoteMessageTag {
Expand All @@ -69,7 +67,6 @@ struct ImageUploaderResultTag {

// NOLINTBEGIN(readability-identifier-naming)
const SystemMessageTag systemMessage{};
const RaidEntryMessageTag raidEntryMessage{};
const TimeoutMessageTag timeoutMessage{};
const LiveUpdatesUpdateEmoteMessageTag liveUpdatesUpdateEmoteMessage{};
const LiveUpdatesRemoveEmoteMessageTag liveUpdatesRemoveEmoteMessage{};
Expand Down Expand Up @@ -109,9 +106,6 @@ class MessageBuilder

MessageBuilder(SystemMessageTag, const QString &text,
const QTime &time = QTime::currentTime());
MessageBuilder(RaidEntryMessageTag, const QString &text,
const QString &loginName, const QString &displayName,
const MessageColor &userColor, const QTime &time);
MessageBuilder(TimeoutMessageTag, const QString &timeoutUser,
const QString &sourceUser, const QString &systemMessageText,
int times, const QTime &time = QTime::currentTime());
Expand Down Expand Up @@ -255,6 +249,15 @@ class MessageBuilder
const std::shared_ptr<MessageThread> &thread = {},
const MessagePtr &parent = {});

static MessagePtrMut makeSystemMessageWithUser(
const QString &text, const QString &loginName,
const QString &displayName, const MessageColor &userColor,
const QTime &time);

static MessagePtrMut makeSubgiftMessage(const QString &text,
const QVariantMap &tags,
const QTime &time);

private:
struct TextState {
TwitchChannel *twitchChannel = nullptr;
Expand Down
75 changes: 40 additions & 35 deletions src/providers/twitch/IrcMessageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -699,35 +699,6 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message,
{
messageText = "Announcement";
}
else if (msgType == "raid")
{
auto login = tags.value("login").toString();
auto displayName = tags.value("msg-param-displayName").toString();

if (!login.isEmpty() && !displayName.isEmpty())
{
MessageColor color = MessageColor::System;
if (auto colorTag = tags.value("color").value<QColor>();
colorTag.isValid())
{
color = MessageColor(colorTag);
}

auto b = MessageBuilder(
raidEntryMessage, parseTagString(messageText), login,
displayName, color, calculateMessageTime(message).time());

b->flags.set(MessageFlag::Subscription);
if (mirrored)
{
b->flags.set(MessageFlag::SharedMessage);
}
auto newMessage = b.release();

sink.addMessage(newMessage, MessageContext::Original);
return;
}
}
else if (msgType == "subgift")
{
if (auto monthsIt = tags.find("msg-param-gift-months");
Expand Down Expand Up @@ -762,6 +733,20 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message,
}
}
}

// subgifts are special because they include two users
auto msg = MessageBuilder::makeSubgiftMessage(
parseTagString(messageText), tags,
calculateMessageTime(message).time());

msg->flags.set(MessageFlag::Subscription);
if (mirrored)
{
msg->flags.set(MessageFlag::SharedMessage);
}

sink.addMessage(msg, MessageContext::Original);
return;
}
else if (msgType == "sub" || msgType == "resub")
{
Expand Down Expand Up @@ -795,17 +780,37 @@ void IrcMessageHandler::parseUserNoticeMessageInto(Communi::IrcMessage *message,
}
}

auto b = MessageBuilder(systemMessage, parseTagString(messageText),
calculateMessageTime(message).time());
auto displayName = [&] {
if (msgType == u"raid")
{
return tags.value("msg-param-displayName").toString();
}
return tags.value("display-name").toString();
}();
auto login = tags.value("login").toString();
if (displayName.isEmpty())
{
displayName = login;
}

MessageColor userColor = MessageColor::System;
if (auto colorTag = tags.value("color").value<QColor>();
colorTag.isValid())
{
userColor = MessageColor(colorTag);
}

auto msg = MessageBuilder::makeSystemMessageWithUser(
parseTagString(messageText), login, displayName, userColor,
calculateMessageTime(message).time());

b->flags.set(MessageFlag::Subscription);
msg->flags.set(MessageFlag::Subscription);
if (mirrored)
{
b->flags.set(MessageFlag::SharedMessage);
msg->flags.set(MessageFlag::SharedMessage);
}
auto newMessage = b.release();

sink.addMessage(newMessage, MessageContext::Original);
sink.addMessage(msg, MessageContext::Original);
}
}

Expand Down
9 changes: 6 additions & 3 deletions tests/snapshots/IrcMessageHandler/bitsbadge.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,19 @@
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"color": "Text",
"fallbackColor": "System",
"flags": "Text|Mention",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"type": "MentionElement",
"userColor": "#ffff4500",
"userLoginName": "whoopiix",
"words": [
"whoopiix"
]
Expand Down
29 changes: 25 additions & 4 deletions tests/snapshots/IrcMessageHandler/sub-first-gift.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,19 @@
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"color": "Text",
"fallbackColor": "System",
"flags": "Text|Mention",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"type": "MentionElement",
"userColor": "#ff00ff7f",
"userLoginName": "hyperbolicxd",
"words": [
"hyperbolicxd"
]
Expand Down Expand Up @@ -142,6 +145,24 @@
"to"
]
},
{
"color": "Text",
"fallbackColor": "System",
"flags": "Text|Mention",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": false,
"type": "MentionElement",
"userColor": "System",
"userLoginName": "quote_if_nam",
"words": [
"quote_if_nam"
]
},
{
"color": "System",
"flags": "Text",
Expand All @@ -154,7 +175,7 @@
"trailingSpace": true,
"type": "TextElement",
"words": [
"quote_if_nam!"
"!"
]
},
{
Expand Down
9 changes: 6 additions & 3 deletions tests/snapshots/IrcMessageHandler/sub-first-time.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,19 @@
"type": "TimestampElement"
},
{
"color": "System",
"flags": "Text",
"color": "Text",
"fallbackColor": "System",
"flags": "Text|Mention",
"link": {
"type": "None",
"value": ""
},
"style": "ChatMedium",
"tooltip": "",
"trailingSpace": true,
"type": "TextElement",
"type": "MentionElement",
"userColor": "#ff0000ff",
"userLoginName": "byebyeheart",
"words": [
"byebyeheart"
]
Expand Down
Loading
Loading