mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
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:
parent
416806bb0a
commit
693d4f401d
|
@ -4,7 +4,7 @@
|
|||
|
||||
- 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: 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: 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)
|
||||
|
|
|
@ -525,12 +525,20 @@ void Application::initPubSub()
|
|||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
auto twitchChannel =
|
||||
std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||
if (!twitchChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([twitchChannel, action] {
|
||||
const auto p =
|
||||
TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first);
|
||||
chan->addMessage(p.second);
|
||||
action, twitchChannel->getName(),
|
||||
twitchChannel.get());
|
||||
twitchChannel->addMessage(p.first);
|
||||
twitchChannel->addMessage(p.second);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
* flags.whisper
|
||||
* flags.reply
|
||||
* flags.automod
|
||||
* flags.restricted
|
||||
* flags.monitored
|
||||
*
|
||||
* message.content
|
||||
* message.length
|
||||
|
@ -101,6 +103,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
{"flags.whisper", m->flags.has(MessageFlag::Whisper)},
|
||||
{"flags.reply", m->flags.has(MessageFlag::ReplyMessage)},
|
||||
{"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.length", m->messageText.length()},
|
||||
|
|
|
@ -44,6 +44,8 @@ static const QMap<QString, Type> MESSAGE_TYPING_CONTEXT = {
|
|||
{"flags.whisper", Type::Bool},
|
||||
{"flags.reply", Type::Bool},
|
||||
{"flags.automod", Type::Bool},
|
||||
{"flags.restricted", Type::Bool},
|
||||
{"flags.monitored", Type::Bool},
|
||||
{"message.content", Type::String},
|
||||
{"message.length", Type::Int},
|
||||
};
|
||||
|
|
|
@ -32,6 +32,8 @@ static const QMap<QString, QString> validIdentifiersMap = {
|
|||
{"flags.whisper", "whisper message?"},
|
||||
{"flags.reply", "reply message?"},
|
||||
{"flags.automod", "automod message?"},
|
||||
{"flags.restricted", "restricted message?"},
|
||||
{"flags.monitored", "monitored message?"},
|
||||
{"message.content", "message text"},
|
||||
{"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
|
||||
AutoModOffendingMessage = (1LL << 31),
|
||||
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>;
|
||||
|
||||
|
|
|
@ -52,6 +52,14 @@ MessageFlagsPredicate::MessageFlagsPredicate(const QString &flags, bool negate)
|
|||
{
|
||||
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
|
||||
|
||||
TwitchMessageBuilder::TwitchMessageBuilder(
|
||||
|
@ -1113,24 +1226,6 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
|||
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(
|
||||
const QVariantMap &tags)
|
||||
{
|
||||
|
@ -1189,88 +1284,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
}
|
||||
|
||||
auto badgeInfos = TwitchMessageBuilder::parseBadgeInfoTag(this->tags);
|
||||
auto badges = this->parseBadgeTag(this->tags);
|
||||
|
||||
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;
|
||||
auto badges = TwitchMessageBuilder::parseBadgeTag(this->tags);
|
||||
appendBadges(this, badges, badgeInfos, this->twitchChannel);
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendChatterinoBadges()
|
||||
|
@ -1936,6 +1951,12 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeAutomodMessage(
|
|||
MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action)
|
||||
{
|
||||
/**
|
||||
* Known issues:
|
||||
* - Non-Twitch badges are not shown
|
||||
* - Non-Twitch emotes are not shown
|
||||
*/
|
||||
|
||||
MessageBuilder builder;
|
||||
builder.emplace<TimestampElement>();
|
||||
builder.message().flags.set(MessageFlag::System);
|
||||
|
@ -2006,7 +2027,8 @@ MessagePtr TwitchMessageBuilder::makeLowTrustUpdateMessage(
|
|||
}
|
||||
|
||||
std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName)
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||
const TwitchChannel *twitchChannel)
|
||||
{
|
||||
MessageBuilder builder, builder2;
|
||||
|
||||
|
@ -2029,10 +2051,12 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
|||
if (action.treatment == PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||
{
|
||||
headerMessage = "Restricted";
|
||||
builder2.message().flags.set(MessageFlag::RestrictedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
headerMessage = "Monitored";
|
||||
builder2.message().flags.set(MessageFlag::MonitoredMessage);
|
||||
}
|
||||
|
||||
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::LowTrustUsers);
|
||||
|
||||
// sender badges
|
||||
appendBadges(&builder2, action.senderBadges, {}, twitchChannel);
|
||||
|
||||
// sender username
|
||||
builder2
|
||||
.emplace<TextElement>(action.suspiciousUserDisplayName + ":",
|
||||
|
@ -2103,8 +2130,23 @@ std::pair<MessagePtr, MessagePtr> TwitchMessageBuilder::makeLowTrustUserMessage(
|
|||
->setLink({Link::UserInfo, action.suspiciousUserLogin});
|
||||
|
||||
// sender's message caught by AutoMod
|
||||
builder2.emplace<TextElement>(action.text, MessageElementFlag::Text,
|
||||
MessageColor::Text);
|
||||
for (const auto &fragment : action.fragments)
|
||||
{
|
||||
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 =
|
||||
QString("%1: %2").arg(action.suspiciousUserDisplayName, action.text);
|
||||
builder2.message().messageText = text;
|
||||
|
|
|
@ -95,7 +95,8 @@ public:
|
|||
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
||||
|
||||
static std::pair<MessagePtr, MessagePtr> makeLowTrustUserMessage(
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName);
|
||||
const PubSubLowTrustUsersMessage &action, const QString &channelName,
|
||||
const TwitchChannel *twitchChannel);
|
||||
static MessagePtr makeLowTrustUpdateMessage(
|
||||
const PubSubLowTrustUsersMessage &action);
|
||||
|
||||
|
@ -119,7 +120,6 @@ private:
|
|||
|
||||
void runIgnoreReplaces(std::vector<TwitchEmoteOccurrence> &twitchEmotes);
|
||||
|
||||
std::optional<EmotePtr> getTwitchBadge(const Badge &badge) const;
|
||||
Outcome tryAppendEmote(const EmoteName &name) override;
|
||||
|
||||
void addWords(const QStringList &words,
|
||||
|
|
|
@ -21,8 +21,12 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||
{
|
||||
this->msgID = data.value("message_id").toString();
|
||||
this->sentAt = data.value("sent_at").toString();
|
||||
this->text =
|
||||
data.value("message_content").toObject().value("text").toString();
|
||||
const auto content = data.value("message_content").toObject();
|
||||
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
|
||||
data = data.value("low_trust_user").toObject();
|
||||
|
@ -35,23 +39,22 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||
this->suspiciousUserColor =
|
||||
QColor(sender.value("chat_color").toString());
|
||||
|
||||
std::vector<LowTrustUserChatBadge> badges;
|
||||
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");
|
||||
std::vector<QString> sharedIDs;
|
||||
if (!sharedValue.isNull())
|
||||
{
|
||||
for (const auto &id : sharedValue.toArray())
|
||||
{
|
||||
sharedIDs.emplace_back(id.toString());
|
||||
this->sharedBanChannelIDs.emplace_back(id.toString());
|
||||
}
|
||||
}
|
||||
this->sharedBanChannelIDs = sharedIDs;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -88,17 +91,15 @@ PubSubLowTrustUsersMessage::PubSubLowTrustUsersMessage(const QJsonObject &root)
|
|||
this->evasionEvaluation = oEvaluation.value();
|
||||
}
|
||||
|
||||
FlagsEnum<RestrictionType> restrictions;
|
||||
for (const auto &rType : data.value("types").toArray())
|
||||
{
|
||||
if (const auto oRestriction = magic_enum::enum_cast<RestrictionType>(
|
||||
rType.toString().toStdString());
|
||||
oRestriction.has_value())
|
||||
{
|
||||
restrictions.set(oRestriction.value());
|
||||
this->restrictionTypes.set(oRestriction.value());
|
||||
}
|
||||
}
|
||||
this->restrictionTypes = restrictions;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "providers/twitch/TwitchBadge.hpp"
|
||||
|
||||
#include <common/FlagsEnum.hpp>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QColor>
|
||||
|
@ -8,18 +10,21 @@
|
|||
|
||||
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 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
|
||||
*/
|
||||
|
@ -102,6 +107,12 @@ struct PubSubLowTrustUsersMessage {
|
|||
*/
|
||||
QString text;
|
||||
|
||||
/**
|
||||
* Pre-parsed components of the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<Fragment> fragments;
|
||||
|
||||
/**
|
||||
* ID of the message.
|
||||
* Only used for the UserMessage type.
|
||||
|
@ -130,7 +141,7 @@ struct PubSubLowTrustUsersMessage {
|
|||
* A list of badges of the user who sent the message.
|
||||
* Only used for the UserMessage type.
|
||||
*/
|
||||
std::vector<LowTrustUserChatBadge> senderBadges;
|
||||
std::vector<Badge> senderBadges;
|
||||
|
||||
/**
|
||||
* Stores the string value of `type`
|
||||
|
|
Loading…
Reference in a new issue