Skip to content

Commit

Permalink
feat: add sound and flash alert for automod caught messages (#5026)
Browse files Browse the repository at this point in the history
Co-authored-by: Rasmus Karlsson <[email protected]>
  • Loading branch information
iProdigy and pajlada committed Dec 25, 2023
1 parent 1006bf9 commit eb12cfa
Show file tree
Hide file tree
Showing 19 changed files with 348 additions and 193 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Unversioned

- 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)
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026)
- 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)
Expand Down
9 changes: 6 additions & 3 deletions src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ void Application::initPubSub()
senderDisplayName, senderColor};
postToThread([chan, action] {
const auto p =
makeAutomodMessage(action, chan->getName());
TwitchMessageBuilder::makeAutomodMessage(
action, chan->getName());
chan->addMessage(p.first);
chan->addMessage(p.second);

Expand Down Expand Up @@ -584,7 +585,8 @@ void Application::initPubSub()
}

postToThread([chan, action] {
const auto p = makeAutomodMessage(action, chan->getName());
const auto p = TwitchMessageBuilder::makeAutomodMessage(
action, chan->getName());
chan->addMessage(p.first);
chan->addMessage(p.second);
});
Expand Down Expand Up @@ -626,7 +628,8 @@ void Application::initPubSub()
}

postToThread([chan, action] {
const auto p = makeAutomodInfoMessage(action);
const auto p =
TwitchMessageBuilder::makeAutomodInfoMessage(action);
chan->addMessage(p);
});
});
Expand Down
40 changes: 40 additions & 0 deletions src/controllers/highlights/HighlightController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,41 @@ void rebuildMessageHighlights(Settings &settings,
{
checks.emplace_back(highlightPhraseCheck(highlight));
}

if (settings.enableAutomodHighlight)
{
const auto highlightSound =
settings.enableAutomodHighlightSound.getValue();
const auto highlightAlert =
settings.enableAutomodHighlightTaskbar.getValue();
const auto highlightSoundUrlValue =
settings.automodHighlightSoundUrl.getValue();

checks.emplace_back(HighlightCheck{
[=](const auto & /*args*/, const auto & /*badges*/,
const auto & /*senderName*/, const auto & /*originalMessage*/,
const auto &flags,
const auto /*self*/) -> std::optional<HighlightResult> {
if (!flags.has(MessageFlag::AutoModOffendingMessage))
{
return std::nullopt;
}

std::optional<QUrl> highlightSoundUrl;
if (!highlightSoundUrlValue.isEmpty())
{
highlightSoundUrl = highlightSoundUrlValue;
}

return HighlightResult{
highlightAlert, // alert
highlightSound, // playSound
highlightSoundUrl, // customSoundUrl
nullptr, // color
false, // showInMentions
};
}});
}
}

void rebuildUserHighlights(Settings &settings,
Expand Down Expand Up @@ -434,6 +469,11 @@ void HighlightController::initialize(Settings &settings, Paths & /*paths*/)
this->rebuildListener_.addSetting(settings.threadHighlightSoundUrl);
this->rebuildListener_.addSetting(settings.showThreadHighlightInMentions);

this->rebuildListener_.addSetting(settings.enableAutomodHighlight);
this->rebuildListener_.addSetting(settings.enableAutomodHighlightSound);
this->rebuildListener_.addSetting(settings.enableAutomodHighlightTaskbar);
this->rebuildListener_.addSetting(settings.automodHighlightSoundUrl);

this->rebuildListener_.setCB([this, &settings] {
qCDebug(chatterinoHighlights)
<< "Rebuild checks because a setting changed";
Expand Down
44 changes: 44 additions & 0 deletions src/controllers/highlights/HighlightModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,30 @@ void HighlightModel::afterInit()

this->insertCustomRow(threadMessageRow,
HighlightRowIndexes::ThreadMessageRow);

// Highlight settings for automod caught messages
const std::vector<QStandardItem *> automodRow = this->createRow();
setBoolItem(automodRow[Column::Pattern],
getSettings()->enableAutomodHighlight.getValue(), true, false);
automodRow[Column::Pattern]->setData("AutoMod Caught Messages",
Qt::DisplayRole);
automodRow[Column::ShowInMentions]->setFlags({});
setBoolItem(automodRow[Column::FlashTaskbar],
getSettings()->enableAutomodHighlightTaskbar.getValue(), true,
false);
setBoolItem(automodRow[Column::PlaySound],
getSettings()->enableAutomodHighlightSound.getValue(), true,
false);
automodRow[Column::UseRegex]->setFlags({});
automodRow[Column::CaseSensitive]->setFlags({});

const auto automodSound =
QUrl(getSettings()->automodHighlightSoundUrl.getValue());
setFilePathItem(automodRow[Column::SoundPath], automodSound, false);

automodRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags);

this->insertCustomRow(automodRow, HighlightRowIndexes::AutomodRow);
}

void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
Expand Down Expand Up @@ -278,6 +302,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableThreadHighlight.setValue(
value.toBool());
}
else if (rowIndex == HighlightRowIndexes::AutomodRow)
{
getSettings()->enableAutomodHighlight.setValue(
value.toBool());
}
}
}
break;
Expand Down Expand Up @@ -336,6 +365,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableThreadHighlightTaskbar.setValue(
value.toBool());
}
else if (rowIndex == HighlightRowIndexes::AutomodRow)
{
getSettings()->enableAutomodHighlightTaskbar.setValue(
value.toBool());
}
}
}
break;
Expand Down Expand Up @@ -377,6 +411,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->enableThreadHighlightSound.setValue(
value.toBool());
}
else if (rowIndex == HighlightRowIndexes::AutomodRow)
{
getSettings()->enableAutomodHighlightSound.setValue(
value.toBool());
}
}
}
break;
Expand Down Expand Up @@ -412,6 +451,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
getSettings()->threadHighlightSoundUrl.setValue(
value.toString());
}
else if (rowIndex == HighlightRowIndexes::AutomodRow)
{
getSettings()->automodHighlightSoundUrl.setValue(
value.toString());
}
}
}
break;
Expand Down
1 change: 1 addition & 0 deletions src/controllers/highlights/HighlightModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
FirstMessageRow = 4,
ElevatedMessageRow = 5,
ThreadMessageRow = 6,
AutomodRow = 7,
};

enum UserHighlightRowIndexes {
Expand Down
2 changes: 2 additions & 0 deletions src/messages/Message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ enum class MessageFlag : int64_t {
LiveUpdatesAdd = (1LL << 28),
LiveUpdatesRemove = (1LL << 29),
LiveUpdatesUpdate = (1LL << 30),
/// The message caught by AutoMod containing the user who sent the message & its contents
AutoModOffendingMessage = (1LL << 31),
};
using MessageFlags = FlagsEnum<MessageFlag>;

Expand Down
153 changes: 0 additions & 153 deletions src/messages/MessageBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,159 +78,6 @@ MessagePtr makeSystemMessage(const QString &text, const QTime &time)
return MessageBuilder(systemMessage, text, time).release();
}

EmotePtr makeAutoModBadge()
{
return std::make_shared<Emote>(Emote{
EmoteName{},
ImageSet{Image::fromResourcePixmap(getResources().twitch.automod)},
Tooltip{"AutoMod"},
Url{"https://dashboard.twitch.tv/settings/moderation/automod"}});
}

MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action)
{
auto builder = MessageBuilder();
QString text("AutoMod: ");

builder.emplace<TimestampElement>();
builder.message().flags.set(MessageFlag::PubSub);

// AutoMod shield badge
builder.emplace<BadgeElement>(makeAutoModBadge(),
MessageElementFlag::BadgeChannelAuthority);
// AutoMod "username"
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
MessageColor(QColor("blue")),
FontStyle::ChatMediumBold);
builder.emplace<TextElement>(
"AutoMod:", MessageElementFlag::NonBoldUsername,
MessageColor(QColor("blue")));
switch (action.type)
{
case AutomodInfoAction::OnHold: {
QString info("Hey! Your message is being checked "
"by mods and has not been sent.");
text += info;
builder.emplace<TextElement>(info, MessageElementFlag::Text,
MessageColor::Text);
}
break;
case AutomodInfoAction::Denied: {
QString info("Mods have removed your message.");
text += info;
builder.emplace<TextElement>(info, MessageElementFlag::Text,
MessageColor::Text);
}
break;
case AutomodInfoAction::Approved: {
QString info("Mods have accepted your message.");
text += info;
builder.emplace<TextElement>(info, MessageElementFlag::Text,
MessageColor::Text);
}
break;
}

builder.message().flags.set(MessageFlag::AutoMod);
builder.message().messageText = text;
builder.message().searchText = text;

auto message = builder.release();

return message;
}

std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
const AutomodAction &action, const QString &channelName)
{
MessageBuilder builder, builder2;

//
// Builder for AutoMod message with explanation
builder.message().loginName = "automod";
builder.message().channelName = channelName;
builder.message().flags.set(MessageFlag::PubSub);
builder.message().flags.set(MessageFlag::Timeout);
builder.message().flags.set(MessageFlag::AutoMod);

// AutoMod shield badge
builder.emplace<BadgeElement>(makeAutoModBadge(),
MessageElementFlag::BadgeChannelAuthority);
// AutoMod "username"
builder.emplace<TextElement>("AutoMod:", MessageElementFlag::BoldUsername,
MessageColor(QColor("blue")),
FontStyle::ChatMediumBold);
builder.emplace<TextElement>(
"AutoMod:", MessageElementFlag::NonBoldUsername,
MessageColor(QColor("blue")));
// AutoMod header message
builder.emplace<TextElement>(
("Held a message for reason: " + action.reason +
". Allow will post it in chat. "),
MessageElementFlag::Text, MessageColor::Text);
// Allow link button
builder
.emplace<TextElement>("Allow", MessageElementFlag::Text,
MessageColor(QColor("green")),
FontStyle::ChatMediumBold)
->setLink({Link::AutoModAllow, action.msgID});
// Deny link button
builder
.emplace<TextElement>(" Deny", MessageElementFlag::Text,
MessageColor(QColor("red")),
FontStyle::ChatMediumBold)
->setLink({Link::AutoModDeny, action.msgID});
// ID of message caught by AutoMod
// builder.emplace<TextElement>(action.msgID, MessageElementFlag::Text,
// MessageColor::Text);
auto text1 =
QString("AutoMod: Held a message for reason: %1. Allow will post "
"it in chat. Allow Deny")
.arg(action.reason);
builder.message().messageText = text1;
builder.message().searchText = text1;

auto message1 = builder.release();

//
// Builder for offender's message
builder2.message().channelName = channelName;
builder2
.emplace<TextElement>("#" + channelName,
MessageElementFlag::ChannelName,
MessageColor::System)
->setLink({Link::JumpToChannel, channelName});
builder2.emplace<TimestampElement>();
builder2.emplace<TwitchModerationElement>();
builder2.message().loginName = action.target.login;
builder2.message().flags.set(MessageFlag::PubSub);
builder2.message().flags.set(MessageFlag::Timeout);
builder2.message().flags.set(MessageFlag::AutoMod);

// sender username
builder2
.emplace<TextElement>(
action.target.displayName + ":", MessageElementFlag::BoldUsername,
MessageColor(action.target.color), FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, action.target.login});
builder2
.emplace<TextElement>(action.target.displayName + ":",
MessageElementFlag::NonBoldUsername,
MessageColor(action.target.color))
->setLink({Link::UserInfo, action.target.login});
// sender's message caught by AutoMod
builder2.emplace<TextElement>(action.message, MessageElementFlag::Text,
MessageColor::Text);
auto text2 =
QString("%1: %2").arg(action.target.displayName, action.message);
builder2.message().messageText = text2;
builder2.message().searchText = text2;

auto message2 = builder2.release();

return std::make_pair(message1, message2);
}

MessageBuilder::MessageBuilder()
: message_(std::make_shared<Message>())
{
Expand Down
3 changes: 0 additions & 3 deletions src/messages/MessageBuilder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ const ImageUploaderResultTag imageUploaderResultMessage{};

MessagePtr makeSystemMessage(const QString &text);
MessagePtr makeSystemMessage(const QString &text, const QTime &time);
std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
const AutomodAction &action, const QString &channelName);
MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);

struct MessageParseArgs {
bool disablePingSounds = false;
Expand Down
Loading

0 comments on commit eb12cfa

Please sign in to comment.