feat: add badges, emotes, and filters for suspicious messages (#5060)
* feat: show chat badges on suspicious user messages * feat: display emotes in suspicious user messages * feat: add search filters for suspicious messages * chore: update changelog * refactor: resolve initial nits * fix: finish adding new filter identifier * Comment the new message flags * Add a list of known issues to low trust update messages * fix: Keep shared-pointerness of the channel Without this change, we would have the possibility of using the TwitchChannel after the Channel itself has gone out of scope, albeit not realistically since we just post this to a thread and parse it - there's no networking or big delays involved. but this shows the intent better --------- Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- Major: Allow use of Twitch follower emotes in other channels if subscribed. (#4922)
|
- Major: Allow use of Twitch follower emotes in other channels if subscribed. (#4922)
|
||||||
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026)
|
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026)
|
||||||
- Major: Show restricted chat messages and suspicious treatment updates. (#5056)
|
- Major: Show restricted chat messages and suspicious treatment updates. (#5056, #5060)
|
||||||
- Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809)
|
- Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809)
|
||||||
- Minor: The account switcher is now styled to match your theme. (#4817)
|
- Minor: The account switcher is now styled to match your theme. (#4817)
|
||||||
- Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795)
|
- Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795)
|
||||||
|
|||||||
+12
-4
@@ -525,12 +525,20 @@ void Application::initPubSub()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
postToThread([chan, action] {
|
auto twitchChannel =
|
||||||
|
std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||||
|
if (!twitchChannel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([twitchChannel, action] {
|
||||||
const auto p =
|
const auto p =
|
||||||
TwitchMessageBuilder::makeLowTrustUserMessage(
|
TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||||
action, chan->getName());
|
action, twitchChannel->getName(),
|
||||||
chan->addMessage(p.first);
|
twitchChannel.get());
|
||||||
chan->addMessage(p.second);
|
twitchChannel->addMessage(p.first);
|
||||||
|
twitchChannel->addMessage(p.second);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||||||
* flags.whisper
|
* flags.whisper
|
||||||
* flags.reply
|
* flags.reply
|
||||||
* flags.automod
|
* flags.automod
|
||||||
|
* flags.restricted
|
||||||
|
* flags.monitored
|
||||||
*
|
*
|
||||||
* message.content
|
* message.content
|
||||||
* message.length
|
* message.length
|
||||||
@@ -101,6 +103,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||||||
{"flags.whisper", m->flags.has(MessageFlag::Whisper)},
|
{"flags.whisper", m->flags.has(MessageFlag::Whisper)},
|
||||||
{"flags.reply", m->flags.has(MessageFlag::ReplyMessage)},
|
{"flags.reply", m->flags.has(MessageFlag::ReplyMessage)},
|
||||||
{"flags.automod", m->flags.has(MessageFlag::AutoMod)},
|
{"flags.automod", m->flags.has(MessageFlag::AutoMod)},
|
||||||
|
{"flags.restricted", m->flags.has(MessageFlag::RestrictedMessage)},
|
||||||
|
{"flags.monitored", m->flags.has(MessageFlag::MonitoredMessage)},
|
||||||
|
|
||||||
{"message.content", m->messageText},
|
{"message.content", m->messageText},
|
||||||
{"message.length", m->messageText.length()},
|
{"message.length", m->messageText.length()},
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ static const QMap<QString, Type> MESSAGE_TYPING_CONTEXT = {
|
|||||||
{"flags.whisper", Type::Bool},
|
{"flags.whisper", Type::Bool},
|
||||||
{"flags.reply", Type::Bool},
|
{"flags.reply", Type::Bool},
|
||||||
{"flags.automod", Type::Bool},
|
{"flags.automod", Type::Bool},
|
||||||
|
{"flags.restricted", Type::Bool},
|
||||||
|
{"flags.monitored", Type::Bool},
|
||||||
{"message.content", Type::String},
|
{"message.content", Type::String},
|
||||||
{"message.length", Type::Int},
|
{"message.length", Type::Int},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ static const QMap<QString, QString> validIdentifiersMap = {
|
|||||||
{"flags.whisper", "whisper message?"},
|
{"flags.whisper", "whisper message?"},
|
||||||
{"flags.reply", "reply message?"},
|
{"flags.reply", "reply message?"},
|
||||||
{"flags.automod", "automod message?"},
|
{"flags.automod", "automod message?"},
|
||||||
|
{"flags.restricted", "restricted message?"},
|
||||||
|
{"flags.monitored", "monitored message?"},
|
||||||
{"message.content", "message text"},
|
{"message.content", "message text"},
|
||||||
{"message.length", "message length"}};
|
{"message.length", "message length"}};
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ enum class MessageFlag : int64_t {
|
|||||||
/// The message caught by AutoMod containing the user who sent the message & its contents
|
/// The message caught by AutoMod containing the user who sent the message & its contents
|
||||||
AutoModOffendingMessage = (1LL << 31),
|
AutoModOffendingMessage = (1LL << 31),
|
||||||
LowTrustUsers = (1LL << 32),
|
LowTrustUsers = (1LL << 32),
|
||||||
|
/// The message is sent by a user marked as restricted with Twitch's "Low Trust"/"Suspicious User" feature
|
||||||
|
RestrictedMessage = (1LL << 33),
|
||||||
|
/// The message is sent by a user marked as monitor with Twitch's "Low Trust"/"Suspicious User" feature
|
||||||
|
MonitoredMessage = (1LL << 34),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ MessageFlagsPredicate::MessageFlagsPredicate(const QString &flags, bool negate)
|
|||||||
{
|
{
|
||||||
this->flags_.set(MessageFlag::ReplyMessage);
|
this->flags_.set(MessageFlag::ReplyMessage);
|
||||||
}
|
}
|
||||||
|
else if (flag == "restricted")
|
||||||
|
{
|
||||||
|
this->flags_.set(MessageFlag::RestrictedMessage);
|
||||||
|
}
|
||||||
|
else if (flag == "monitored")
|
||||||
|
{
|
||||||
|
this->flags_.set(MessageFlag::MonitoredMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,119 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<EmotePtr> getTwitchBadge(const Badge &badge,
|
||||||
|
const TwitchChannel *twitchChannel)
|
||||||
|
{
|
||||||
|
if (auto channelBadge =
|
||||||
|
twitchChannel->twitchBadge(badge.key_, badge.value_))
|
||||||
|
{
|
||||||
|
return channelBadge;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto globalBadge =
|
||||||
|
TwitchBadges::instance()->badge(badge.key_, badge.value_))
|
||||||
|
{
|
||||||
|
return globalBadge;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendBadges(MessageBuilder *builder, const std::vector<Badge> &badges,
|
||||||
|
const std::unordered_map<QString, QString> &badgeInfos,
|
||||||
|
const TwitchChannel *twitchChannel)
|
||||||
|
{
|
||||||
|
if (twitchChannel == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &badge : badges)
|
||||||
|
{
|
||||||
|
auto badgeEmote = getTwitchBadge(badge, twitchChannel);
|
||||||
|
if (!badgeEmote)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto tooltip = (*badgeEmote)->tooltip.string;
|
||||||
|
|
||||||
|
if (badge.key_ == "bits")
|
||||||
|
{
|
||||||
|
const auto &cheerAmount = badge.value_;
|
||||||
|
tooltip = QString("Twitch cheer %0").arg(cheerAmount);
|
||||||
|
}
|
||||||
|
else if (badge.key_ == "moderator" &&
|
||||||
|
getSettings()->useCustomFfzModeratorBadges)
|
||||||
|
{
|
||||||
|
if (auto customModBadge = twitchChannel->ffzCustomModBadge())
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
->emplace<ModBadgeElement>(
|
||||||
|
*customModBadge,
|
||||||
|
MessageElementFlag::BadgeChannelAuthority)
|
||||||
|
->setTooltip((*customModBadge)->tooltip.string);
|
||||||
|
// early out, since we have to add a custom badge element here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (badge.key_ == "vip" &&
|
||||||
|
getSettings()->useCustomFfzVipBadges)
|
||||||
|
{
|
||||||
|
if (auto customVipBadge = twitchChannel->ffzCustomVipBadge())
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
->emplace<VipBadgeElement>(
|
||||||
|
*customVipBadge,
|
||||||
|
MessageElementFlag::BadgeChannelAuthority)
|
||||||
|
->setTooltip((*customVipBadge)->tooltip.string);
|
||||||
|
// early out, since we have to add a custom badge element here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (badge.flag_ == MessageElementFlag::BadgeSubscription)
|
||||||
|
{
|
||||||
|
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
||||||
|
if (badgeInfoIt != badgeInfos.end())
|
||||||
|
{
|
||||||
|
// badge.value_ is 4 chars long if user is subbed on higher tier
|
||||||
|
// (tier + amount of months with leading zero if less than 100)
|
||||||
|
// e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub
|
||||||
|
const auto &subTier =
|
||||||
|
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
|
||||||
|
const auto &subMonths = badgeInfoIt->second;
|
||||||
|
tooltip += QString(" (%1%2 months)")
|
||||||
|
.arg(subTier != '1'
|
||||||
|
? QString("Tier %1, ").arg(subTier)
|
||||||
|
: "")
|
||||||
|
.arg(subMonths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (badge.flag_ == MessageElementFlag::BadgePredictions)
|
||||||
|
{
|
||||||
|
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
||||||
|
if (badgeInfoIt != badgeInfos.end())
|
||||||
|
{
|
||||||
|
auto infoValue = badgeInfoIt->second;
|
||||||
|
auto predictionText =
|
||||||
|
infoValue
|
||||||
|
.replace(R"(\s)", " ") // standard IRC escapes
|
||||||
|
.replace(R"(\:)", ";")
|
||||||
|
.replace(R"(\\)", R"(\)")
|
||||||
|
.replace("⸝", ","); // twitch's comma escape
|
||||||
|
// Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma
|
||||||
|
|
||||||
|
tooltip = QString("Predicted %1").arg(predictionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder->emplace<BadgeElement>(*badgeEmote, badge.flag_)
|
||||||
|
->setTooltip(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder->message().badges = badges;
|
||||||
|
builder->message().badgeInfos = badgeInfos;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TwitchMessageBuilder::TwitchMessageBuilder(
|
TwitchMessageBuilder::TwitchMessageBuilder(
|
||||||
@@ -1113,24 +1226,6 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<EmotePtr> TwitchMessageBuilder::getTwitchBadge(
|
|
||||||
const Badge &badge) const
|
|
||||||
{
|
|
||||||
if (auto channelBadge =
|
|
||||||
this->twitchChannel->twitchBadge(badge.key_, badge.value_))
|
|
||||||
{
|
|
||||||
return channelBadge;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto globalBadge =
|
|
||||||
TwitchBadges::instance()->badge(badge.key_, badge.value_))
|
|
||||||
{
|
|
||||||
return globalBadge;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<QString, QString> TwitchMessageBuilder::parseBadgeInfoTag(
|
std::unordered_map<QString, QString> TwitchMessageBuilder::parseBadgeInfoTag(
|
||||||
const QVariantMap &tags)
|
const QVariantMap &tags)
|
||||||
{
|
{
|
||||||
@@ -1189,88 +1284,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
|
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
|
||||||
auto badges = this->parseBadgeTag(this->tags);
|
auto badges = TwitchMessageBuilder::parseBadgeTag(this->tags);
|
||||||
|
appendBadges(this, badges, badgeInfos, this->twitchChannel);
|
||||||
for (const auto &badge : badges)
|
|
||||||
{
|
|
||||||
auto badgeEmote = this->getTwitchBadge(badge);
|
|
||||||
if (!badgeEmote)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto tooltip = (*badgeEmote)->tooltip.string;
|
|
||||||
|
|
||||||
if (badge.key_ == "bits")
|
|
||||||
{
|
|
||||||
const auto &cheerAmount = badge.value_;
|
|
||||||
tooltip = QString("Twitch cheer %0").arg(cheerAmount);
|
|
||||||
}
|
|
||||||
else if (badge.key_ == "moderator" &&
|
|
||||||
getSettings()->useCustomFfzModeratorBadges)
|
|
||||||
{
|
|
||||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
|
||||||
{
|
|
||||||
this->emplace<ModBadgeElement>(
|
|
||||||
*customModBadge,
|
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
|
||||||
->setTooltip((*customModBadge)->tooltip.string);
|
|
||||||
// early out, since we have to add a custom badge element here
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (badge.key_ == "vip" && getSettings()->useCustomFfzVipBadges)
|
|
||||||
{
|
|
||||||
if (auto customVipBadge = this->twitchChannel->ffzCustomVipBadge())
|
|
||||||
{
|
|
||||||
this->emplace<VipBadgeElement>(
|
|
||||||
*customVipBadge,
|
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
|
||||||
->setTooltip((*customVipBadge)->tooltip.string);
|
|
||||||
// early out, since we have to add a custom badge element here
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (badge.flag_ == MessageElementFlag::BadgeSubscription)
|
|
||||||
{
|
|
||||||
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
|
||||||
if (badgeInfoIt != badgeInfos.end())
|
|
||||||
{
|
|
||||||
// badge.value_ is 4 chars long if user is subbed on higher tier
|
|
||||||
// (tier + amount of months with leading zero if less than 100)
|
|
||||||
// e.g. 3054 - tier 3 4,5-year sub. 2108 - tier 2 9-year sub
|
|
||||||
const auto &subTier =
|
|
||||||
badge.value_.length() > 3 ? badge.value_.at(0) : '1';
|
|
||||||
const auto &subMonths = badgeInfoIt->second;
|
|
||||||
tooltip +=
|
|
||||||
QString(" (%1%2 months)")
|
|
||||||
.arg(subTier != '1' ? QString("Tier %1, ").arg(subTier)
|
|
||||||
: "")
|
|
||||||
.arg(subMonths);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (badge.flag_ == MessageElementFlag::BadgePredictions)
|
|
||||||
{
|
|
||||||
auto badgeInfoIt = badgeInfos.find(badge.key_);
|
|
||||||
if (badgeInfoIt != badgeInfos.end())
|
|
||||||
{
|
|
||||||
auto predictionText =
|
|
||||||
badgeInfoIt->second
|
|
||||||
.replace(R"(\s)", " ") // standard IRC escapes
|
|
||||||
.replace(R"(\:)", ";")
|
|
||||||
.replace(R"(\\)", R"(\)")
|
|
||||||
.replace("⸝", ","); // twitch's comma escape
|
|
||||||
// Careful, the first character is RIGHT LOW PARAPHRASE BRACKET or U+2E1D, which just looks like a comma
|
|
||||||
|
|
||||||
tooltip = QString("Predicted %1").arg(predictionText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->emplace<BadgeElement>(*badgeEmote, badge.flag_)
|
|
||||||
->setTooltip(tooltip);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->message().badges = badges;
|
|
||||||
this->message().badgeInfos = badgeInfos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchMessageBuilder::appendChatterinoBadges()
|
void TwitchMessageBuilder::appendChatterinoBadges()
|
||||||
@@ -1936,6 +1951,12 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeAutomodMessage(
|
|||||||
MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
||||||
const PubSubLowTrustUsersMessage &action)
|
const PubSubLowTrustUsersMessage &action)
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Known issues:
|
||||||
|
* - Non-Twitch badges are not shown
|
||||||
|
* - Non-Twitch emotes are not shown
|
||||||
|
*/
|
||||||
|
|
||||||
MessageBuilder builder;
|
MessageBuilder builder;
|
||||||
builder.emplace<TimestampElement>();
|
builder.emplace<TimestampElement>();
|
||||||
builder.message().flags.set(MessageFlag::System);
|
builder.message().flags.set(MessageFlag::System);
|
||||||
@@ -2006,7 +2027,8 @@ MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||||
const PubSubLowTrustUsersMessage &action, const QString &channelName)
|
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||||
|
const TwitchChannel *twitchChannel)
|
||||||
{
|
{
|
||||||
MessageBuilder builder, builder2;
|
MessageBuilder builder, builder2;
|
||||||
|
|
||||||
@@ -2029,10 +2051,12 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
|||||||
if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted)
|
if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||||
{
|
{
|
||||||
headerMessage = "Restricted";
|
headerMessage = "Restricted";
|
||||||
|
builder2.message().flags.set(MessageFlag::RestrictedMessage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
headerMessage = "Monitored";
|
headerMessage = "Monitored";
|
||||||
|
builder2.message().flags.set(MessageFlag::MonitoredMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.restrictionTypes.has(
|
if (action.restrictionTypes.has(
|
||||||
@@ -2089,6 +2113,9 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
|||||||
builder2.message().flags.set(MessageFlag::PubSub);
|
builder2.message().flags.set(MessageFlag::PubSub);
|
||||||
builder2.message().flags.set(MessageFlag::LowTrustUsers);
|
builder2.message().flags.set(MessageFlag::LowTrustUsers);
|
||||||
|
|
||||||
|
// sender badges
|
||||||
|
appendBadges(&builder2, action.senderBadges, {}, twitchChannel);
|
||||||
|
|
||||||
// sender username
|
// sender username
|
||||||
builder2
|
builder2
|
||||||
.emplace<TextElement>(action.suspiciousUserDisplayName + ":",
|
.emplace<TextElement>(action.suspiciousUserDisplayName + ":",
|
||||||
@@ -2103,8 +2130,23 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
|||||||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||||
|
|
||||||
// sender's message caught by AutoMod
|
// sender's message caught by AutoMod
|
||||||
builder2.emplace<TextElement>(action.text, MessageElementFlag::Text,
|
for (const auto &fragment : action.fragments)
|
||||||
MessageColor::Text);
|
{
|
||||||
|
if (fragment.emoteID.isEmpty())
|
||||||
|
{
|
||||||
|
builder2.emplace<TextElement>(
|
||||||
|
fragment.text, MessageElementFlag::Text, MessageColor::Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto emotePtr =
|
||||||
|
getIApp()->getEmotes()->getTwitchEmotes()->getOrCreateEmote(
|
||||||
|
EmoteId{fragment.emoteID}, EmoteName{fragment.text});
|
||||||
|
builder2.emplace<EmoteElement>(
|
||||||
|
emotePtr, MessageElementFlag::TwitchEmote, MessageColor::Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto text =
|
auto text =
|
||||||
QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text);
|
QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text);
|
||||||
builder2.message().messageText = text;
|
builder2.message().messageText = text;
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ public:
|
|||||||
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
||||||
|
|
||||||
static std::pair<MessagePtr, MessagePtr> makeLowTrustUserMessage(
|
static std::pair<MessagePtr, MessagePtr> makeLowTrustUserMessage(
|
||||||
const PubSubLowTrustUsersMessage &action, const QString &channelName);
|
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||||
|
const TwitchChannel *twitchChannel);
|
||||||
static MessagePtr makeLowTrustUpdateMessage(
|
static MessagePtr makeLowTrustUpdateMessage(
|
||||||
const PubSubLowTrustUsersMessage &action);
|
const PubSubLowTrustUsersMessage &action);
|
||||||
|
|
||||||
@@ -119,7 +120,6 @@ private:
|
|||||||
|
|
||||||
void runIgnoreReplaces(std::vector<TwitchEmoteOccurrence> &twitchEmotes);
|
void runIgnoreReplaces(std::vector<TwitchEmoteOccurrence> &twitchEmotes);
|
||||||
|
|
||||||
std::optional<EmotePtr> getTwitchBadge(const Badge &badge) const;
|
|
||||||
Outcome tryAppendEmote(const EmoteName &name) override;
|
Outcome tryAppendEmote(const EmoteName &name) override;
|
||||||
|
|
||||||
void addWords(const QStringList &words,
|
void addWords(const QStringList &words,
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||||||
{
|
{
|
||||||
this->msgID = data.value("message_id").toString();
|
this->msgID = data.value("message_id").toString();
|
||||||
this->sentAt = data.value("sent_at").toString();
|
this->sentAt = data.value("sent_at").toString();
|
||||||
this->text =
|
const auto content = data.value("message_content").toObject();
|
||||||
data.value("message_content").toObject().value("text").toString();
|
this->text = content.value("text").toString();
|
||||||
|
for (const auto &part : content.value("fragments").toArray())
|
||||||
|
{
|
||||||
|
this->fragments.emplace_back(part.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
// the rest of the data is within a nested object
|
// the rest of the data is within a nested object
|
||||||
data = data.value("low_trust_user").toObject();
|
data = data.value("low_trust_user").toObject();
|
||||||
@@ -35,23 +39,22 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||||||
this->suspiciousUserColor =
|
this->suspiciousUserColor =
|
||||||
QColor(sender.value("chat_color").toString());
|
QColor(sender.value("chat_color").toString());
|
||||||
|
|
||||||
std::vector<LowTrustUserChatBadge> badges;
|
|
||||||
for (const auto &badge : sender.value("badges").toArray())
|
for (const auto &badge : sender.value("badges").toArray())
|
||||||
{
|
{
|
||||||
badges.emplace_back(badge.toObject());
|
const auto badgeObj = badge.toObject();
|
||||||
|
const auto badgeID = badgeObj.value("id").toString();
|
||||||
|
const auto badgeVersion = badgeObj.value("version").toString();
|
||||||
|
this->senderBadges.emplace_back(Badge{badgeID, badgeVersion});
|
||||||
}
|
}
|
||||||
this->senderBadges = badges;
|
|
||||||
|
|
||||||
const auto sharedValue = data.value("shared_ban_channel_ids");
|
const auto sharedValue = data.value("shared_ban_channel_ids");
|
||||||
std::vector<QString> sharedIDs;
|
|
||||||
if (!sharedValue.isNull())
|
if (!sharedValue.isNull())
|
||||||
{
|
{
|
||||||
for (const auto &id : sharedValue.toArray())
|
for (const auto &id : sharedValue.toArray())
|
||||||
{
|
{
|
||||||
sharedIDs.emplace_back(id.toString());
|
this->sharedBanChannelIDs.emplace_back(id.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->sharedBanChannelIDs = sharedIDs;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -88,17 +91,15 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||||||
this->evasionEvaluation = oEvaluation.value();
|
this->evasionEvaluation = oEvaluation.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
FlagsEnum<RestrictionType> restrictions;
|
|
||||||
for (const auto &rType : data.value("types").toArray())
|
for (const auto &rType : data.value("types").toArray())
|
||||||
{
|
{
|
||||||
if (const auto oRestriction = magic_enum::enum_cast<RestrictionType>(
|
if (const auto oRestriction = magic_enum::enum_cast<RestrictionType>(
|
||||||
rType.toString().toStdString());
|
rType.toString().toStdString());
|
||||||
oRestriction.has_value())
|
oRestriction.has_value())
|
||||||
{
|
{
|
||||||
restrictions.set(oRestriction.value());
|
this->restrictionTypes.set(oRestriction.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->restrictionTypes = restrictions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
|
|
||||||
#include <common/FlagsEnum.hpp>
|
#include <common/FlagsEnum.hpp>
|
||||||
#include <magic_enum/magic_enum.hpp>
|
#include <magic_enum/magic_enum.hpp>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
@@ -8,18 +10,21 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct LowTrustUserChatBadge {
|
|
||||||
QString id;
|
|
||||||
QString version;
|
|
||||||
|
|
||||||
explicit LowTrustUserChatBadge(const QJsonObject &obj)
|
|
||||||
: id(obj.value("id").toString())
|
|
||||||
, version(obj.value("version").toString())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PubSubLowTrustUsersMessage {
|
struct PubSubLowTrustUsersMessage {
|
||||||
|
struct Fragment {
|
||||||
|
QString text;
|
||||||
|
QString emoteID;
|
||||||
|
|
||||||
|
explicit Fragment(const QJsonObject &obj)
|
||||||
|
: text(obj.value("text").toString())
|
||||||
|
, emoteID(obj.value("emoticon")
|
||||||
|
.toObject()
|
||||||
|
.value("emoticonID")
|
||||||
|
.toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of low trust message update
|
* The type of low trust message update
|
||||||
*/
|
*/
|
||||||
@@ -102,6 +107,12 @@ struct PubSubLowTrustUsersMessage {
|
|||||||
*/
|
*/
|
||||||
QString text;
|
QString text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-parsed components of the message.
|
||||||
|
* Only used for the UserMessage type.
|
||||||
|
*/
|
||||||
|
std::vector<Fragment> fragments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the message.
|
* ID of the message.
|
||||||
* Only used for the UserMessage type.
|
* Only used for the UserMessage type.
|
||||||
@@ -130,7 +141,7 @@ struct PubSubLowTrustUsersMessage {
|
|||||||
* A list of badges of the user who sent the message.
|
* A list of badges of the user who sent the message.
|
||||||
* Only used for the UserMessage type.
|
* Only used for the UserMessage type.
|
||||||
*/
|
*/
|
||||||
std::vector<LowTrustUserChatBadge> senderBadges;
|
std::vector<Badge> senderBadges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the string value of `type`
|
* Stores the string value of `type`
|
||||||
|
|||||||
Reference in New Issue
Block a user