mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
feat: add sound and flash alert for automod caught messages (#5026)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
parent
1006bf955a
commit
eb12cfa50b
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -626,7 +628,8 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p = makeAutomodInfoMessage(action);
|
||||
const auto p =
|
||||
TwitchMessageBuilder::makeAutomodInfoMessage(action);
|
||||
chan->addMessage(p);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
FirstMessageRow = 4,
|
||||
ElevatedMessageRow = 5,
|
||||
ThreadMessageRow = 6,
|
||||
AutomodRow = 7,
|
||||
};
|
||||
|
||||
enum UserHighlightRowIndexes {
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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>())
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
@ -170,18 +172,10 @@ void SharedMessageBuilder::parseHighlights()
|
|||
this->highlightAlert_ = highlightResult.alert;
|
||||
|
||||
this->highlightSound_ = highlightResult.playSound;
|
||||
this->highlightSoundCustomUrl_ = highlightResult.customSoundUrl;
|
||||
|
||||
this->message().highlightColor = highlightResult.color;
|
||||
|
||||
if (highlightResult.customSoundUrl)
|
||||
{
|
||||
this->highlightSoundUrl_ = *highlightResult.customSoundUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||
}
|
||||
|
||||
if (highlightResult.showInMentions)
|
||||
{
|
||||
this->message().flags.set(MessageFlag::ShowInMentions);
|
||||
|
@ -199,6 +193,15 @@ void SharedMessageBuilder::appendChannelName()
|
|||
}
|
||||
|
||||
void SharedMessageBuilder::triggerHighlights()
|
||||
{
|
||||
SharedMessageBuilder::triggerHighlights(
|
||||
this->channel->getName(), this->highlightSound_,
|
||||
this->highlightSoundCustomUrl_, this->highlightAlert_);
|
||||
}
|
||||
|
||||
void SharedMessageBuilder::triggerHighlights(
|
||||
const QString &channelName, bool playSound,
|
||||
const std::optional<QUrl> &customSoundUrl, bool windowAlert)
|
||||
{
|
||||
if (isInStreamerMode() && getSettings()->streamerModeMuteMentions)
|
||||
{
|
||||
|
@ -206,21 +209,32 @@ void SharedMessageBuilder::triggerHighlights()
|
|||
return;
|
||||
}
|
||||
|
||||
if (getSettings()->isMutedChannel(this->channel->getName()))
|
||||
if (getSettings()->isMutedChannel(channelName))
|
||||
{
|
||||
// Do nothing. Pings are muted in this channel.
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasFocus = (QApplication::focusWidget() != nullptr);
|
||||
bool resolveFocus = !hasFocus || getSettings()->highlightAlwaysPlaySound;
|
||||
const bool hasFocus = (QApplication::focusWidget() != nullptr);
|
||||
const bool resolveFocus =
|
||||
!hasFocus || getSettings()->highlightAlwaysPlaySound;
|
||||
|
||||
if (this->highlightSound_ && resolveFocus)
|
||||
if (playSound && resolveFocus)
|
||||
{
|
||||
getIApp()->getSound()->play(this->highlightSoundUrl_);
|
||||
// TODO(C++23): optional or_else
|
||||
QUrl soundUrl;
|
||||
if (customSoundUrl)
|
||||
{
|
||||
soundUrl = *customSoundUrl;
|
||||
}
|
||||
else
|
||||
{
|
||||
soundUrl = getFallbackHighlightSound();
|
||||
}
|
||||
getIApp()->getSound()->play(soundUrl);
|
||||
}
|
||||
|
||||
if (this->highlightAlert_)
|
||||
if (windowAlert)
|
||||
{
|
||||
getApp()->windows->sendAlert();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <QColor>
|
||||
#include <QUrl>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Badge;
|
||||
|
@ -57,6 +59,9 @@ protected:
|
|||
|
||||
// parseHighlights only updates the visual state of the message, but leaves the playing of alerts and sounds to the triggerHighlights function
|
||||
virtual void parseHighlights();
|
||||
static void triggerHighlights(const QString &channelName, bool playSound,
|
||||
const std::optional<QUrl> &customSoundUrl,
|
||||
bool windowAlert);
|
||||
|
||||
void appendChannelName();
|
||||
|
||||
|
@ -72,8 +77,7 @@ protected:
|
|||
|
||||
bool highlightAlert_ = false;
|
||||
bool highlightSound_ = false;
|
||||
|
||||
QUrl highlightSoundUrl_;
|
||||
std::optional<QUrl> highlightSoundCustomUrl_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -342,9 +342,13 @@ void MessageLayout::updateBuffer(QPixmap *buffer,
|
|||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
||||
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
|
||||
{
|
||||
// Blend highlight color with usual background color
|
||||
backgroundColor =
|
||||
blendColors(backgroundColor, *this->message_->highlightColor);
|
||||
assert(this->message_->highlightColor);
|
||||
if (this->message_->highlightColor)
|
||||
{
|
||||
// Blend highlight color with usual background color
|
||||
backgroundColor =
|
||||
blendColors(backgroundColor, *this->message_->highlightColor);
|
||||
}
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::Subscription) &&
|
||||
ctx.preferences.enableSubHighlight)
|
||||
|
|
|
@ -47,6 +47,12 @@ void MessagePreferences::connectSettings(Settings *settings,
|
|||
},
|
||||
holder);
|
||||
|
||||
settings->enableAutomodHighlight.connect(
|
||||
[this](const auto &newValue) {
|
||||
this->enableAutomodHighlight = newValue;
|
||||
},
|
||||
holder);
|
||||
|
||||
settings->alternateMessages.connect(
|
||||
[this](const auto &newValue) {
|
||||
this->alternateMessages = newValue;
|
||||
|
|
|
@ -39,6 +39,7 @@ struct MessagePreferences {
|
|||
bool enableElevatedMessageHighlight{};
|
||||
bool enableFirstMessageHighlight{};
|
||||
bool enableSubHighlight{};
|
||||
bool enableAutomodHighlight{};
|
||||
|
||||
bool alternateMessages{};
|
||||
bool separateMessages{};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "common/Literals.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||
#include "controllers/userdata/UserDataController.hpp"
|
||||
|
@ -1765,6 +1766,173 @@ MessagePtr TwitchMessageBuilder::buildHypeChatMessage(
|
|||
return builder.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 TwitchMessageBuilder::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> TwitchMessageBuilder::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);
|
||||
builder2.message().flags.set(MessageFlag::AutoModOffendingMessage);
|
||||
|
||||
// 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();
|
||||
|
||||
// Normally highlights would be checked & triggered during the builder parse steps
|
||||
// and when the message is added to the channel
|
||||
// We do this a bit weird since the message comes in from PubSub and not the normal message route
|
||||
auto [highlighted, highlightResult] = getIApp()->getHighlights()->check(
|
||||
{}, {}, action.target.login, action.message, message2->flags);
|
||||
if (highlighted)
|
||||
{
|
||||
SharedMessageBuilder::triggerHighlights(
|
||||
channelName, highlightResult.playSound,
|
||||
highlightResult.customSoundUrl, highlightResult.alert);
|
||||
}
|
||||
|
||||
return std::make_pair(message1, message2);
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::setThread(std::shared_ptr<MessageThread> thread)
|
||||
{
|
||||
this->thread_ = std::move(thread);
|
||||
|
|
|
@ -89,6 +89,10 @@ public:
|
|||
|
||||
static MessagePtr buildHypeChatMessage(Communi::IrcPrivateMessage *message);
|
||||
|
||||
static std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||
const AutomodAction &action, const QString &channelName);
|
||||
static MessagePtr makeAutomodInfoMessage(const AutomodInfoAction &action);
|
||||
|
||||
// Shares some common logic from SharedMessageBuilder::parseBadgeTag
|
||||
static std::unordered_map<QString, QString> parseBadgeInfoTag(
|
||||
const QVariantMap &tags);
|
||||
|
|
|
@ -375,6 +375,23 @@ public:
|
|||
""};
|
||||
QStringSetting subHighlightColor = {"/highlighting/subHighlightColor", ""};
|
||||
|
||||
BoolSetting enableAutomodHighlight = {
|
||||
"/highlighting/automod/enabled",
|
||||
true,
|
||||
};
|
||||
BoolSetting enableAutomodHighlightSound = {
|
||||
"/highlighting/automod/enableSound",
|
||||
false,
|
||||
};
|
||||
BoolSetting enableAutomodHighlightTaskbar = {
|
||||
"/highlighting/automod/enableTaskbarFlashing",
|
||||
false,
|
||||
};
|
||||
QStringSetting automodHighlightSoundUrl = {
|
||||
"/highlighting/automod/soundUrl",
|
||||
"",
|
||||
};
|
||||
|
||||
BoolSetting enableThreadHighlight = {
|
||||
"/highlighting/thread/nameIsHighlightKeyword", true};
|
||||
BoolSetting showThreadHighlightInMentions = {
|
||||
|
|
|
@ -1093,12 +1093,13 @@ void ChannelView::messageAppended(MessagePtr &message,
|
|||
|
||||
if (!messageFlags->has(MessageFlag::DoNotTriggerNotification))
|
||||
{
|
||||
if (messageFlags->has(MessageFlag::Highlighted) &&
|
||||
messageFlags->has(MessageFlag::ShowInMentions) &&
|
||||
!messageFlags->has(MessageFlag::Subscription) &&
|
||||
(getSettings()->highlightMentions ||
|
||||
this->channel_->getType() != Channel::Type::TwitchMentions))
|
||||
|
||||
if ((messageFlags->has(MessageFlag::Highlighted) &&
|
||||
messageFlags->has(MessageFlag::ShowInMentions) &&
|
||||
!messageFlags->has(MessageFlag::Subscription) &&
|
||||
(getSettings()->highlightMentions ||
|
||||
this->channel_->getType() != Channel::Type::TwitchMentions)) ||
|
||||
(this->channel_->getType() == Channel::Type::TwitchAutomod &&
|
||||
getSettings()->enableAutomodHighlight))
|
||||
{
|
||||
this->tabHighlightRequested.invoke(HighlightState::Highlighted);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
|||
|
||||
QColor ScrollbarHighlight::getColor() const
|
||||
{
|
||||
assert(this->color_);
|
||||
return *this->color_;
|
||||
}
|
||||
|
||||
|
|
|
@ -370,21 +370,22 @@ void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
|||
EditableModelView *view,
|
||||
HighlightTab tab)
|
||||
{
|
||||
if (!clicked.flags().testFlag(Qt::ItemIsEnabled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab)
|
||||
{
|
||||
case HighlightTab::Messages:
|
||||
case HighlightTab::Users: {
|
||||
using Column = HighlightModel::Column;
|
||||
bool restrictColorRow =
|
||||
(tab == HighlightTab::Messages &&
|
||||
clicked.row() ==
|
||||
HighlightModel::HighlightRowIndexes::WhisperRow);
|
||||
if (clicked.column() == Column::SoundPath &&
|
||||
clicked.flags().testFlag(Qt::ItemIsEnabled))
|
||||
|
||||
if (clicked.column() == Column::SoundPath)
|
||||
{
|
||||
this->openSoundDialog(clicked, view, Column::SoundPath);
|
||||
}
|
||||
else if (clicked.column() == Column::Color && !restrictColorRow)
|
||||
else if (clicked.column() == Column::Color)
|
||||
{
|
||||
this->openColorDialog(clicked, view, tab);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue