Overhaul highlight system (#3399)
Checks have been moved into a Controller allowing for easier tests.
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
- Bugfix: Fixed automod queue pubsub topic persisting after user change. (#3718)
|
- Bugfix: Fixed automod queue pubsub topic persisting after user change. (#3718)
|
||||||
- Bugfix: Fixed viewer list not closing after pressing escape key. (#3734)
|
- Bugfix: Fixed viewer list not closing after pressing escape key. (#3734)
|
||||||
- Bugfix: Fixed links with no thumbnail having previous link's thumbnail. (#3720)
|
- Bugfix: Fixed links with no thumbnail having previous link's thumbnail. (#3720)
|
||||||
|
- Dev: Overhaul highlight system by moving all checks into a Controller allowing for easier tests. (#3399)
|
||||||
- Dev: Use Game Name returned by Get Streams instead of querying it from the Get Games API. (#3662)
|
- Dev: Use Game Name returned by Get Streams instead of querying it from the Get Games API. (#3662)
|
||||||
- Dev: Batch checking live status for all channels after startup. (#3757, #3762, #3767)
|
- Dev: Batch checking live status for all channels after startup. (#3757, #3762, #3767)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ project(chatterino-benchmark)
|
|||||||
set(benchmark_SOURCES
|
set(benchmark_SOURCES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/Highlights.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
|
||||||
# Add your new file above this line!
|
# Add your new file above this line!
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#include "Application.hpp"
|
||||||
|
#include "BaseSettings.hpp"
|
||||||
|
#include "common/Channel.hpp"
|
||||||
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/SharedMessageBuilder.hpp"
|
||||||
|
#include "util/Helpers.hpp"
|
||||||
|
|
||||||
|
#include <benchmark/benchmark.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
class BenchmarkMessageBuilder : public SharedMessageBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BenchmarkMessageBuilder(
|
||||||
|
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
|
||||||
|
const MessageParseArgs &_args)
|
||||||
|
: SharedMessageBuilder(_channel, _ircMessage, _args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual MessagePtr build()
|
||||||
|
{
|
||||||
|
// PARSE
|
||||||
|
this->parse();
|
||||||
|
this->usernameColor_ = getRandomColor(this->ircMessage->nick());
|
||||||
|
|
||||||
|
// words
|
||||||
|
// this->addWords(this->originalMessage_.split(' '));
|
||||||
|
|
||||||
|
this->message().messageText = this->originalMessage_;
|
||||||
|
this->message().searchText = this->message().localizedName + " " +
|
||||||
|
this->userName + ": " +
|
||||||
|
this->originalMessage_;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bench()
|
||||||
|
{
|
||||||
|
this->parseHighlights();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockApplication : BaseApplication
|
||||||
|
{
|
||||||
|
AccountController *const getAccounts() override
|
||||||
|
{
|
||||||
|
return &this->accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountController accounts;
|
||||||
|
// TODO: Figure this out
|
||||||
|
};
|
||||||
|
|
||||||
|
static void BM_HighlightTest(benchmark::State &state)
|
||||||
|
{
|
||||||
|
MockApplication mockApplication;
|
||||||
|
Settings settings("/tmp/c2-mock");
|
||||||
|
|
||||||
|
std::string message =
|
||||||
|
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))";
|
||||||
|
auto ircMessage = Communi::IrcMessage::fromData(message.c_str(), nullptr);
|
||||||
|
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(ircMessage);
|
||||||
|
assert(privMsg != nullptr);
|
||||||
|
MessageParseArgs args;
|
||||||
|
auto emptyChannel = Channel::getEmpty();
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
BenchmarkMessageBuilder b(emptyChannel.get(), privMsg, args);
|
||||||
|
|
||||||
|
b.build();
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
b.bench();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK(BM_HighlightTest);
|
||||||
@@ -157,6 +157,7 @@ SOURCES += \
|
|||||||
src/controllers/highlights/BadgeHighlightModel.cpp \
|
src/controllers/highlights/BadgeHighlightModel.cpp \
|
||||||
src/controllers/highlights/HighlightBadge.cpp \
|
src/controllers/highlights/HighlightBadge.cpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||||
|
src/controllers/highlights/HighlightController.cpp \
|
||||||
src/controllers/highlights/HighlightModel.cpp \
|
src/controllers/highlights/HighlightModel.cpp \
|
||||||
src/controllers/highlights/HighlightPhrase.cpp \
|
src/controllers/highlights/HighlightPhrase.cpp \
|
||||||
src/controllers/highlights/UserHighlightModel.cpp \
|
src/controllers/highlights/UserHighlightModel.cpp \
|
||||||
@@ -401,6 +402,7 @@ HEADERS += \
|
|||||||
src/controllers/highlights/HighlightBadge.hpp \
|
src/controllers/highlights/HighlightBadge.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
||||||
|
src/controllers/highlights/HighlightController.hpp \
|
||||||
src/controllers/highlights/HighlightModel.hpp \
|
src/controllers/highlights/HighlightModel.hpp \
|
||||||
src/controllers/highlights/HighlightPhrase.hpp \
|
src/controllers/highlights/HighlightPhrase.hpp \
|
||||||
src/controllers/highlights/UserHighlightModel.hpp \
|
src/controllers/highlights/UserHighlightModel.hpp \
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
@@ -67,6 +68,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||||||
|
|
||||||
, commands(&this->emplace<CommandController>())
|
, commands(&this->emplace<CommandController>())
|
||||||
, notifications(&this->emplace<NotificationController>())
|
, notifications(&this->emplace<NotificationController>())
|
||||||
|
, highlights(&this->emplace<HighlightController>())
|
||||||
, twitch(&this->emplace<TwitchIrcServer>())
|
, twitch(&this->emplace<TwitchIrcServer>())
|
||||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||||
, ffzBadges(&this->emplace<FfzBadges>())
|
, ffzBadges(&this->emplace<FfzBadges>())
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class PubSub;
|
|||||||
class CommandController;
|
class CommandController;
|
||||||
class AccountController;
|
class AccountController;
|
||||||
class NotificationController;
|
class NotificationController;
|
||||||
|
class HighlightController;
|
||||||
class HotkeyController;
|
class HotkeyController;
|
||||||
|
|
||||||
class Theme;
|
class Theme;
|
||||||
@@ -80,6 +81,7 @@ public:
|
|||||||
|
|
||||||
CommandController *const commands{};
|
CommandController *const commands{};
|
||||||
NotificationController *const notifications{};
|
NotificationController *const notifications{};
|
||||||
|
HighlightController *const highlights{};
|
||||||
TwitchIrcServer *const twitch{};
|
TwitchIrcServer *const twitch{};
|
||||||
ChatterinoBadges *const chatterinoBadges{};
|
ChatterinoBadges *const chatterinoBadges{};
|
||||||
FfzBadges *const ffzBadges{};
|
FfzBadges *const ffzBadges{};
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ Settings *getSettings()
|
|||||||
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
|
static_assert(std::is_same_v<AB_SETTINGS_CLASS, Settings>,
|
||||||
"`AB_SETTINGS_CLASS` must be the same as `Settings`");
|
"`AB_SETTINGS_CLASS` must be the same as `Settings`");
|
||||||
|
|
||||||
assert(AB_SETTINGS_CLASS::instance);
|
assert(AB_SETTINGS_CLASS::instance != nullptr);
|
||||||
|
|
||||||
return AB_SETTINGS_CLASS::instance;
|
return AB_SETTINGS_CLASS::instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#ifndef AB_SETTINGS_H
|
#ifndef AB_SETTINGS_H
|
||||||
#define AB_SETTINGS_H
|
#define AB_SETTINGS_H
|
||||||
|
|
||||||
|
#include "common/ChatterinoSetting.hpp"
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <memory>
|
|
||||||
#include <pajlada/settings/settingdata.hpp>
|
#include <pajlada/settings/settingdata.hpp>
|
||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
#include <memory>
|
||||||
|
|
||||||
#ifdef AB_CUSTOM_SETTINGS
|
#ifdef AB_CUSTOM_SETTINGS
|
||||||
# define AB_SETTINGS_CLASS ABSettings
|
# define AB_SETTINGS_CLASS ABSettings
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ set(SOURCE_FILES
|
|||||||
controllers/highlights/HighlightBadge.hpp
|
controllers/highlights/HighlightBadge.hpp
|
||||||
controllers/highlights/HighlightBlacklistModel.cpp
|
controllers/highlights/HighlightBlacklistModel.cpp
|
||||||
controllers/highlights/HighlightBlacklistModel.hpp
|
controllers/highlights/HighlightBlacklistModel.hpp
|
||||||
|
controllers/highlights/HighlightController.cpp
|
||||||
|
controllers/highlights/HighlightController.hpp
|
||||||
controllers/highlights/HighlightModel.cpp
|
controllers/highlights/HighlightModel.cpp
|
||||||
controllers/highlights/HighlightModel.hpp
|
controllers/highlights/HighlightModel.hpp
|
||||||
controllers/highlights/HighlightPhrase.cpp
|
controllers/highlights/HighlightPhrase.cpp
|
||||||
|
|||||||
@@ -39,3 +39,4 @@ Q_LOGGING_CATEGORY(chatterinoWebsocket, "chatterino.websocket", logThreshold);
|
|||||||
Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoWindowmanager, "chatterino.windowmanager",
|
Q_LOGGING_CATEGORY(chatterinoWindowmanager, "chatterino.windowmanager",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
|
Q_LOGGING_CATEGORY(chatterinoHighlights, "chatterino.highlights", logThreshold);
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoUpdate);
|
|||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWindowmanager);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWindowmanager);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoHighlights);
|
||||||
|
|||||||
@@ -0,0 +1,358 @@
|
|||||||
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
|
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
auto highlightPhraseCheck(HighlightPhrase highlight) -> HighlightCheck
|
||||||
|
{
|
||||||
|
return HighlightCheck{
|
||||||
|
[highlight](
|
||||||
|
const auto &args, const auto &badges, const auto &senderName,
|
||||||
|
const auto &originalMessage) -> boost::optional<HighlightResult> {
|
||||||
|
(void)args; // unused
|
||||||
|
(void)badges; // unused
|
||||||
|
(void)originalMessage; // unused
|
||||||
|
|
||||||
|
if (!highlight.isMatch(originalMessage))
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<QUrl> highlightSoundUrl;
|
||||||
|
if (highlight.hasCustomSound())
|
||||||
|
{
|
||||||
|
highlightSoundUrl = highlight.getSoundUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HighlightResult{
|
||||||
|
highlight.hasAlert(), highlight.hasSound(),
|
||||||
|
highlightSoundUrl, highlight.getColor(),
|
||||||
|
highlight.showInMentions(),
|
||||||
|
};
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildSubscriptionHighlights(Settings &settings,
|
||||||
|
std::vector<HighlightCheck> &checks)
|
||||||
|
{
|
||||||
|
if (settings.enableSubHighlight)
|
||||||
|
{
|
||||||
|
auto highlightSound = settings.enableSubHighlightSound.getValue();
|
||||||
|
auto highlightAlert = settings.enableSubHighlightTaskbar.getValue();
|
||||||
|
auto highlightSoundUrlValue =
|
||||||
|
settings.whisperHighlightSoundUrl.getValue();
|
||||||
|
boost::optional<QUrl> highlightSoundUrl;
|
||||||
|
if (!highlightSoundUrlValue.isEmpty())
|
||||||
|
{
|
||||||
|
highlightSoundUrl = highlightSoundUrlValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The custom sub highlight color is handled in ColorProvider
|
||||||
|
|
||||||
|
checks.emplace_back(HighlightCheck{
|
||||||
|
[=](const auto &args, const auto &badges, const auto &senderName,
|
||||||
|
const auto &originalMessage)
|
||||||
|
-> boost::optional<HighlightResult> {
|
||||||
|
(void)badges; // unused
|
||||||
|
(void)senderName; // unused
|
||||||
|
(void)originalMessage; // unused
|
||||||
|
if (!args.isSubscriptionMessage)
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto highlightColor =
|
||||||
|
ColorProvider::instance().color(ColorType::Subscription);
|
||||||
|
|
||||||
|
return HighlightResult{
|
||||||
|
highlightAlert, // alert
|
||||||
|
highlightSound, // playSound
|
||||||
|
highlightSoundUrl, // customSoundUrl
|
||||||
|
highlightColor, // color
|
||||||
|
false, // showInMentions
|
||||||
|
};
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildWhisperHighlights(Settings &settings,
|
||||||
|
std::vector<HighlightCheck> &checks)
|
||||||
|
{
|
||||||
|
if (settings.enableWhisperHighlight)
|
||||||
|
{
|
||||||
|
auto highlightSound = settings.enableWhisperHighlightSound.getValue();
|
||||||
|
auto highlightAlert = settings.enableWhisperHighlightTaskbar.getValue();
|
||||||
|
auto highlightSoundUrlValue =
|
||||||
|
settings.whisperHighlightSoundUrl.getValue();
|
||||||
|
boost::optional<QUrl> highlightSoundUrl;
|
||||||
|
if (!highlightSoundUrlValue.isEmpty())
|
||||||
|
{
|
||||||
|
highlightSoundUrl = highlightSoundUrlValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The custom whisper highlight color is handled in ColorProvider
|
||||||
|
|
||||||
|
checks.emplace_back(HighlightCheck{
|
||||||
|
[=](const auto &args, const auto &badges, const auto &senderName,
|
||||||
|
const auto &originalMessage)
|
||||||
|
-> boost::optional<HighlightResult> {
|
||||||
|
(void)badges; // unused
|
||||||
|
(void)senderName; // unused
|
||||||
|
(void)originalMessage; // unused
|
||||||
|
if (!args.isReceivedWhisper)
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HighlightResult{
|
||||||
|
highlightAlert,
|
||||||
|
highlightSound,
|
||||||
|
highlightSoundUrl,
|
||||||
|
ColorProvider::instance().color(ColorType::Whisper),
|
||||||
|
false,
|
||||||
|
};
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildMessageHighlights(Settings &settings,
|
||||||
|
std::vector<HighlightCheck> &checks)
|
||||||
|
{
|
||||||
|
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
|
||||||
|
QString currentUsername = currentUser->getUserName();
|
||||||
|
|
||||||
|
if (settings.enableSelfHighlight && !currentUsername.isEmpty())
|
||||||
|
{
|
||||||
|
HighlightPhrase highlight(
|
||||||
|
currentUsername, settings.showSelfHighlightInMentions,
|
||||||
|
settings.enableSelfHighlightTaskbar,
|
||||||
|
settings.enableSelfHighlightSound, false, false,
|
||||||
|
settings.selfHighlightSoundUrl.getValue(),
|
||||||
|
ColorProvider::instance().color(ColorType::SelfHighlight));
|
||||||
|
|
||||||
|
checks.emplace_back(highlightPhraseCheck(highlight));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto messageHighlights = settings.highlightedMessages.readOnly();
|
||||||
|
for (const auto &highlight : *messageHighlights)
|
||||||
|
{
|
||||||
|
checks.emplace_back(highlightPhraseCheck(highlight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildUserHighlights(Settings &settings,
|
||||||
|
std::vector<HighlightCheck> &checks)
|
||||||
|
{
|
||||||
|
auto userHighlights = settings.highlightedUsers.readOnly();
|
||||||
|
|
||||||
|
for (const auto &highlight : *userHighlights)
|
||||||
|
{
|
||||||
|
checks.emplace_back(HighlightCheck{
|
||||||
|
[highlight](const auto &args, const auto &badges,
|
||||||
|
const auto &senderName, const auto &originalMessage)
|
||||||
|
-> boost::optional<HighlightResult> {
|
||||||
|
(void)args; // unused
|
||||||
|
(void)badges; // unused
|
||||||
|
(void)originalMessage; // unused
|
||||||
|
|
||||||
|
if (!highlight.isMatch(senderName))
|
||||||
|
{
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<QUrl> highlightSoundUrl;
|
||||||
|
if (highlight.hasCustomSound())
|
||||||
|
{
|
||||||
|
highlightSoundUrl = highlight.getSoundUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HighlightResult{
|
||||||
|
highlight.hasAlert(),
|
||||||
|
highlight.hasSound(),
|
||||||
|
highlightSoundUrl,
|
||||||
|
highlight.getColor(),
|
||||||
|
false, // showInMentions
|
||||||
|
};
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildBadgeHighlights(Settings &settings,
|
||||||
|
std::vector<HighlightCheck> &checks)
|
||||||
|
{
|
||||||
|
auto badgeHighlights = settings.highlightedBadges.readOnly();
|
||||||
|
|
||||||
|
for (const auto &highlight : *badgeHighlights)
|
||||||
|
{
|
||||||
|
checks.emplace_back(HighlightCheck{
|
||||||
|
[highlight](const auto &args, const auto &badges,
|
||||||
|
const auto &senderName, const auto &originalMessage)
|
||||||
|
-> boost::optional<HighlightResult> {
|
||||||
|
(void)args; // unused
|
||||||
|
(void)senderName; // unused
|
||||||
|
(void)originalMessage; // unused
|
||||||
|
for (const Badge &badge : badges)
|
||||||
|
{
|
||||||
|
if (highlight.isMatch(badge))
|
||||||
|
{
|
||||||
|
boost::optional<QUrl> highlightSoundUrl;
|
||||||
|
if (highlight.hasCustomSound())
|
||||||
|
{
|
||||||
|
highlightSoundUrl = highlight.getSoundUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
return HighlightResult{
|
||||||
|
highlight.hasAlert(),
|
||||||
|
highlight.hasSound(),
|
||||||
|
highlightSoundUrl,
|
||||||
|
highlight.getColor(),
|
||||||
|
false, // showInMentions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return boost::none;
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void HighlightController::initialize(Settings &settings, Paths & /*paths*/)
|
||||||
|
{
|
||||||
|
this->rebuildListener_.addSetting(settings.enableWhisperHighlight);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableWhisperHighlightSound);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableWhisperHighlightTaskbar);
|
||||||
|
this->rebuildListener_.addSetting(settings.whisperHighlightSoundUrl);
|
||||||
|
this->rebuildListener_.addSetting(settings.whisperHighlightColor);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableSelfHighlight);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableSubHighlight);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableSubHighlightSound);
|
||||||
|
this->rebuildListener_.addSetting(settings.enableSubHighlightTaskbar);
|
||||||
|
|
||||||
|
this->rebuildListener_.setCB([this, &settings] {
|
||||||
|
qCDebug(chatterinoHighlights)
|
||||||
|
<< "Rebuild checks because a setting changed";
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->signalHolder_.managedConnect(
|
||||||
|
getCSettings().highlightedBadges.delayedItemsChanged,
|
||||||
|
[this, &settings] {
|
||||||
|
qCDebug(chatterinoHighlights)
|
||||||
|
<< "Rebuild checks because highlight badges changed";
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->signalHolder_.managedConnect(
|
||||||
|
getCSettings().highlightedUsers.delayedItemsChanged, [this, &settings] {
|
||||||
|
qCDebug(chatterinoHighlights)
|
||||||
|
<< "Rebuild checks because highlight users changed";
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->signalHolder_.managedConnect(
|
||||||
|
getCSettings().highlightedMessages.delayedItemsChanged,
|
||||||
|
[this, &settings] {
|
||||||
|
qCDebug(chatterinoHighlights)
|
||||||
|
<< "Rebuild checks because highlight messages changed";
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
getIApp()->getAccounts()->twitch.currentUserChanged.connect(
|
||||||
|
[this, &settings] {
|
||||||
|
qCDebug(chatterinoHighlights)
|
||||||
|
<< "Rebuild checks because user swapped accounts";
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->rebuildChecks(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HighlightController::rebuildChecks(Settings &settings)
|
||||||
|
{
|
||||||
|
// Access checks for modification
|
||||||
|
auto checks = this->checks_.access();
|
||||||
|
checks->clear();
|
||||||
|
|
||||||
|
// CURRENT ORDER:
|
||||||
|
// Subscription -> Whisper -> User -> Message -> Badge
|
||||||
|
|
||||||
|
rebuildSubscriptionHighlights(settings, *checks);
|
||||||
|
|
||||||
|
rebuildWhisperHighlights(settings, *checks);
|
||||||
|
|
||||||
|
rebuildUserHighlights(settings, *checks);
|
||||||
|
|
||||||
|
rebuildMessageHighlights(settings, *checks);
|
||||||
|
|
||||||
|
rebuildBadgeHighlights(settings, *checks);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, HighlightResult> HighlightController::check(
|
||||||
|
const MessageParseArgs &args, const std::vector<Badge> &badges,
|
||||||
|
const QString &senderName, const QString &originalMessage) const
|
||||||
|
{
|
||||||
|
bool highlighted = false;
|
||||||
|
auto result = HighlightResult::emptyResult();
|
||||||
|
|
||||||
|
// Access for checking
|
||||||
|
const auto checks = this->checks_.accessConst();
|
||||||
|
|
||||||
|
for (const auto &check : *checks)
|
||||||
|
{
|
||||||
|
if (auto checkResult =
|
||||||
|
check.cb(args, badges, senderName, originalMessage);
|
||||||
|
checkResult)
|
||||||
|
{
|
||||||
|
highlighted = true;
|
||||||
|
|
||||||
|
if (checkResult->alert)
|
||||||
|
{
|
||||||
|
if (!result.alert)
|
||||||
|
{
|
||||||
|
result.alert = checkResult->alert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkResult->playSound)
|
||||||
|
{
|
||||||
|
if (!result.playSound)
|
||||||
|
{
|
||||||
|
result.playSound = checkResult->playSound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkResult->customSoundUrl)
|
||||||
|
{
|
||||||
|
if (!result.customSoundUrl)
|
||||||
|
{
|
||||||
|
result.customSoundUrl = checkResult->customSoundUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkResult->color)
|
||||||
|
{
|
||||||
|
if (!result.color)
|
||||||
|
{
|
||||||
|
result.color = checkResult->color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.full())
|
||||||
|
{
|
||||||
|
// The final highlight result does not have room to add any more parameters, early out
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {highlighted, result};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Singleton.hpp"
|
||||||
|
#include "common/UniqueAccess.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/twitch/TwitchBadge.hpp"
|
||||||
|
#include "singletons/Paths.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct HighlightResult {
|
||||||
|
HighlightResult(bool _alert, bool _playSound,
|
||||||
|
boost::optional<QUrl> _customSoundUrl,
|
||||||
|
std::shared_ptr<QColor> _color, bool _showInMentions)
|
||||||
|
: alert(_alert)
|
||||||
|
, playSound(_playSound)
|
||||||
|
, customSoundUrl(std::move(_customSoundUrl))
|
||||||
|
, color(std::move(_color))
|
||||||
|
, showInMentions(_showInMentions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct an empty HighlightResult with all side-effects disabled
|
||||||
|
**/
|
||||||
|
static HighlightResult emptyResult()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
false, false, boost::none, nullptr, false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief true if highlight should trigger the taskbar to flash
|
||||||
|
**/
|
||||||
|
bool alert{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief true if highlight should play a notification sound
|
||||||
|
**/
|
||||||
|
bool playSound{false};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Can be set to a different sound that should play when this highlight is activated
|
||||||
|
*
|
||||||
|
* May only be set if playSound is true
|
||||||
|
**/
|
||||||
|
boost::optional<QUrl> customSoundUrl{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief set if highlight should set a background color
|
||||||
|
**/
|
||||||
|
std::shared_ptr<QColor> color{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief true if highlight should show message in the /mentions split
|
||||||
|
**/
|
||||||
|
bool showInMentions{false};
|
||||||
|
|
||||||
|
bool operator==(const HighlightResult &other) const
|
||||||
|
{
|
||||||
|
if (this->alert != other.alert)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->playSound != other.playSound)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->customSoundUrl != other.customSoundUrl)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->color && other.color)
|
||||||
|
{
|
||||||
|
if (*this->color != *other.color)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->showInMentions != other.showInMentions)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const HighlightResult &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns true if no side-effect has been enabled
|
||||||
|
**/
|
||||||
|
[[nodiscard]] bool empty() const
|
||||||
|
{
|
||||||
|
return !this->alert && !this->playSound &&
|
||||||
|
!this->customSoundUrl.has_value() && !this->color &&
|
||||||
|
!this->showInMentions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns true if all side-effects have been enabled
|
||||||
|
**/
|
||||||
|
[[nodiscard]] bool full() const
|
||||||
|
{
|
||||||
|
return this->alert && this->playSound &&
|
||||||
|
this->customSoundUrl.has_value() && this->color &&
|
||||||
|
this->showInMentions;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HighlightCheck {
|
||||||
|
using Checker = std::function<boost::optional<HighlightResult>(
|
||||||
|
const MessageParseArgs &args, const std::vector<Badge> &badges,
|
||||||
|
const QString &senderName, const QString &originalMessage)>;
|
||||||
|
Checker cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HighlightController final : public Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void initialize(Settings &settings, Paths &paths) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks the given message parameters if it matches our internal checks, and returns a result
|
||||||
|
**/
|
||||||
|
[[nodiscard]] std::pair<bool, HighlightResult> check(
|
||||||
|
const MessageParseArgs &args, const std::vector<Badge> &badges,
|
||||||
|
const QString &senderName, const QString &originalMessage) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief rebuildChecks is called whenever some outside variable has been changed and our checks need to be updated
|
||||||
|
*
|
||||||
|
* rebuilds are always full, so if something changes we throw away all checks and build them all up from scratch
|
||||||
|
**/
|
||||||
|
void rebuildChecks(Settings &settings);
|
||||||
|
|
||||||
|
UniqueAccess<std::vector<HighlightCheck>> checks_;
|
||||||
|
|
||||||
|
pajlada::SettingListener rebuildListener_;
|
||||||
|
pajlada::Signals::SignalHolder signalHolder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -34,6 +34,7 @@ struct MessageParseArgs {
|
|||||||
bool isSentWhisper = false;
|
bool isSentWhisper = false;
|
||||||
bool trimSubscriberUsername = false;
|
bool trimSubscriberUsername = false;
|
||||||
bool isStaffOrBroadcaster = false;
|
bool isStaffOrBroadcaster = false;
|
||||||
|
bool isSubscriptionMessage = false;
|
||||||
QString channelPointRewardId = "";
|
QString channelPointRewardId = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
@@ -140,259 +141,50 @@ void SharedMessageBuilder::parseUsername()
|
|||||||
|
|
||||||
void SharedMessageBuilder::parseHighlights()
|
void SharedMessageBuilder::parseHighlights()
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
if (getCSettings().isBlacklistedUser(this->ircMessage->nick()))
|
if (getCSettings().isBlacklistedUser(this->ircMessage->nick()))
|
||||||
{
|
{
|
||||||
// Do nothing. We ignore highlights from this user.
|
// Do nothing. We ignore highlights from this user.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight because it's a whisper
|
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
|
||||||
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight)
|
if (this->ircMessage->nick() == currentUser->getUserName())
|
||||||
{
|
{
|
||||||
if (getSettings()->enableWhisperHighlightTaskbar)
|
// Do nothing. We ignore any potential highlights from the logged in user
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSettings()->enableWhisperHighlightSound)
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
|
|
||||||
// Use custom sound if set, otherwise use fallback
|
|
||||||
if (!getSettings()->whisperHighlightSoundUrl.getValue().isEmpty())
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ =
|
|
||||||
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->message().highlightColor =
|
|
||||||
ColorProvider::instance().color(ColorType::Whisper);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Do _NOT_ return yet, we might want to apply phrase/user name
|
|
||||||
* highlights (which override whisper color/sound).
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight because of sender
|
|
||||||
auto userHighlights = getCSettings().highlightedUsers.readOnly();
|
|
||||||
for (const HighlightPhrase &userHighlight : *userHighlights)
|
|
||||||
{
|
|
||||||
if (!userHighlight.isMatch(this->ircMessage->nick()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
qCDebug(chatterinoMessage)
|
|
||||||
<< "Highlight because user" << this->ircMessage->nick()
|
|
||||||
<< "sent a message";
|
|
||||||
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
if (!(this->message().flags.has(MessageFlag::Subscription) &&
|
|
||||||
getSettings()->enableSubHighlight))
|
|
||||||
{
|
|
||||||
this->message().highlightColor = userHighlight.getColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userHighlight.showInMentions())
|
|
||||||
{
|
|
||||||
this->message().flags.set(MessageFlag::ShowInMentions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userHighlight.hasAlert())
|
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userHighlight.hasSound())
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
// Use custom sound if set, otherwise use the fallback sound
|
|
||||||
if (userHighlight.hasCustomSound())
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = userHighlight.getSoundUrl();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->highlightAlert_ && this->highlightSound_)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* User name highlights "beat" highlight phrases: If a message has
|
|
||||||
* all attributes (color, taskbar flashing, sound) set, highlight
|
|
||||||
* phrases will not be checked.
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto currentUser = app->accounts->twitch.getCurrent();
|
|
||||||
QString currentUsername = currentUser->getUserName();
|
|
||||||
|
|
||||||
if (this->ircMessage->nick() == currentUsername)
|
|
||||||
{
|
|
||||||
// Do nothing. Highlights cannot be triggered by yourself
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight because it's a subscription
|
auto badges = SharedMessageBuilder::parseBadgeTag(this->tags);
|
||||||
if (this->message().flags.has(MessageFlag::Subscription) &&
|
auto [highlighted, highlightResult] = getApp()->highlights->check(
|
||||||
getSettings()->enableSubHighlight)
|
this->args, badges, this->ircMessage->nick(), this->originalMessage_);
|
||||||
|
|
||||||
|
if (!highlighted)
|
||||||
{
|
{
|
||||||
if (getSettings()->enableSubHighlightTaskbar)
|
return;
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSettings()->enableSubHighlightSound)
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
|
|
||||||
// Use custom sound if set, otherwise use fallback
|
|
||||||
if (!getSettings()->subHighlightSoundUrl.getValue().isEmpty())
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ =
|
|
||||||
QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
this->message().highlightColor =
|
|
||||||
ColorProvider::instance().color(ColorType::Subscription);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This vector should only be rebuilt upon highlights being changed
|
// This message triggered one or more highlights, act upon the highlight result
|
||||||
// fourtf: should be implemented in the HighlightsController
|
|
||||||
std::vector<HighlightPhrase> activeHighlights =
|
|
||||||
getSettings()->highlightedMessages.cloneVector();
|
|
||||||
|
|
||||||
if (!currentUser->isAnon() && getSettings()->enableSelfHighlight &&
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
currentUsername.size() > 0)
|
|
||||||
|
this->highlightAlert_ = highlightResult.alert;
|
||||||
|
|
||||||
|
this->highlightSound_ = highlightResult.playSound;
|
||||||
|
|
||||||
|
this->message().highlightColor = highlightResult.color;
|
||||||
|
|
||||||
|
if (highlightResult.customSoundUrl)
|
||||||
{
|
{
|
||||||
HighlightPhrase selfHighlight(
|
this->highlightSoundUrl_ = highlightResult.customSoundUrl.get();
|
||||||
currentUsername, getSettings()->showSelfHighlightInMentions,
|
}
|
||||||
getSettings()->enableSelfHighlightTaskbar,
|
else
|
||||||
getSettings()->enableSelfHighlightSound, false, false,
|
{
|
||||||
getSettings()->selfHighlightSoundUrl.getValue(),
|
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||||
ColorProvider::instance().color(ColorType::SelfHighlight));
|
|
||||||
activeHighlights.emplace_back(std::move(selfHighlight));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight because of message
|
if (highlightResult.showInMentions)
|
||||||
for (const HighlightPhrase &highlight : activeHighlights)
|
|
||||||
{
|
{
|
||||||
if (!highlight.isMatch(this->originalMessage_))
|
this->message().flags.set(MessageFlag::ShowInMentions);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
if (!(this->message().flags.has(MessageFlag::Subscription) &&
|
|
||||||
getSettings()->enableSubHighlight))
|
|
||||||
{
|
|
||||||
this->message().highlightColor = highlight.getColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlight.showInMentions())
|
|
||||||
{
|
|
||||||
this->message().flags.set(MessageFlag::ShowInMentions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlight.hasAlert())
|
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only set highlightSound_ if it hasn't been set by username
|
|
||||||
// highlights already.
|
|
||||||
if (highlight.hasSound() && !this->highlightSound_)
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
|
|
||||||
// Use custom sound if set, otherwise use fallback sound
|
|
||||||
if (highlight.hasCustomSound())
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = highlight.getSoundUrl();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->highlightAlert_ && this->highlightSound_)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Break once no further attributes (taskbar, sound) can be
|
|
||||||
* applied.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highlight because of badge
|
|
||||||
auto badges = this->parseBadgeTag(this->tags);
|
|
||||||
auto badgeHighlights = getCSettings().highlightedBadges.readOnly();
|
|
||||||
bool badgeHighlightSet = false;
|
|
||||||
for (const HighlightBadge &highlight : *badgeHighlights)
|
|
||||||
{
|
|
||||||
for (const Badge &badge : badges)
|
|
||||||
{
|
|
||||||
if (!highlight.isMatch(badge))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!badgeHighlightSet)
|
|
||||||
{
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
if (!(this->message().flags.has(MessageFlag::Subscription) &&
|
|
||||||
getSettings()->enableSubHighlight))
|
|
||||||
{
|
|
||||||
this->message().highlightColor = highlight.getColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeHighlightSet = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlight.hasAlert())
|
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only set highlightSound_ if it hasn't been set by badge
|
|
||||||
// highlights already.
|
|
||||||
if (highlight.hasSound() && !this->highlightSound_)
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
// Use custom sound if set, otherwise use fallback sound
|
|
||||||
this->highlightSoundUrl_ = highlight.hasCustomSound()
|
|
||||||
? highlight.getSoundUrl()
|
|
||||||
: getFallbackHighlightSound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->highlightAlert_ && this->highlightSound_)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Break once no further attributes (taskbar, sound) can be
|
|
||||||
* applied.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
|||||||
MessageParseArgs args;
|
MessageParseArgs args;
|
||||||
if (isSub)
|
if (isSub)
|
||||||
{
|
{
|
||||||
|
args.isSubscriptionMessage = true;
|
||||||
args.trimSubscriberUsername = true;
|
args.trimSubscriberUsername = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ bool TwitchAccount::isAnon() const
|
|||||||
void TwitchAccount::loadBlocks()
|
void TwitchAccount::loadBlocks()
|
||||||
{
|
{
|
||||||
getHelix()->loadBlocks(
|
getHelix()->loadBlocks(
|
||||||
getApp()->accounts->twitch.getCurrent()->userId_,
|
getIApp()->getAccounts()->twitch.getCurrent()->userId_,
|
||||||
[this](std::vector<HelixBlock> blocks) {
|
[this](std::vector<HelixBlock> blocks) {
|
||||||
auto ignores = this->ignores_.access();
|
auto ignores = this->ignores_.access();
|
||||||
auto userIds = this->ignoresUserIds_.access();
|
auto userIds = this->ignoresUserIds_.access();
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ bool ScrollbarHighlight::isFirstMessageHighlight() const
|
|||||||
|
|
||||||
bool ScrollbarHighlight::isNull() const
|
bool ScrollbarHighlight::isNull() const
|
||||||
{
|
{
|
||||||
return this->style_ == None;
|
return this->style_ == None || !this->color_;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ set(test_SOURCES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/src/IrcHelpers.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/IrcHelpers.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/TwitchPubSubClient.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/TwitchPubSubClient.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/TwitchMessageBuilder.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/TwitchMessageBuilder.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/HighlightController.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
|
||||||
# Add your new file above this line!
|
# Add your new file above this line!
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,464 @@
|
|||||||
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
|
#include "Application.hpp"
|
||||||
|
#include "BaseSettings.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp" // for MessageParseArgs
|
||||||
|
#include "providers/twitch/TwitchBadge.hpp" // for Badge
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
using ::testing::Exactly;
|
||||||
|
|
||||||
|
class MockApplication : IApplication
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Theme *getThemes() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Fonts *getFonts() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Emotes *getEmotes() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
AccountController *getAccounts() override
|
||||||
|
{
|
||||||
|
return &this->accounts;
|
||||||
|
}
|
||||||
|
HotkeyController *getHotkeys() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
WindowManager *getWindows() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Toasts *getToasts() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
CommandController *getCommands() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
NotificationController *getNotifications() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
TwitchIrcServer *getTwitch() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ChatterinoBadges *getChatterinoBadges() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
FfzBadges *getFfzBadges() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountController accounts;
|
||||||
|
// TODO: Figure this out
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockHelix : public IHelix
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(void, fetchUsers,
|
||||||
|
(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getUserByName,
|
||||||
|
(QString userName, ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
MOCK_METHOD(void, getUserById,
|
||||||
|
(QString userId, ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchUsersFollows,
|
||||||
|
(QString fromId, QString toId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getUserFollowers,
|
||||||
|
(QString userId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchStreams,
|
||||||
|
(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getStreamById,
|
||||||
|
(QString userId,
|
||||||
|
(ResultCallback<bool, HelixStream> successCallback),
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getStreamByName,
|
||||||
|
(QString userName,
|
||||||
|
(ResultCallback<bool, HelixStream> successCallback),
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchGames,
|
||||||
|
(QStringList gameIds, QStringList gameNames,
|
||||||
|
(ResultCallback<std::vector<HelixGame>> successCallback),
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, searchGames,
|
||||||
|
(QString gameName,
|
||||||
|
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getGameById,
|
||||||
|
(QString gameId, ResultCallback<HelixGame> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, createClip,
|
||||||
|
(QString channelId, ResultCallback<HelixClip> successCallback,
|
||||||
|
std::function<void(HelixClipError)> failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getChannel,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<HelixChannel> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, createStreamMarker,
|
||||||
|
(QString broadcasterId, QString description,
|
||||||
|
ResultCallback<HelixStreamMarker> successCallback,
|
||||||
|
std::function<void(HelixStreamMarkerError)> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, loadBlocks,
|
||||||
|
(QString userId,
|
||||||
|
ResultCallback<std::vector<HelixBlock>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, blockUser,
|
||||||
|
(QString targetUserId, std::function<void()> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, unblockUser,
|
||||||
|
(QString targetUserId, std::function<void()> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, updateChannel,
|
||||||
|
(QString broadcasterId, QString gameId, QString language,
|
||||||
|
QString title,
|
||||||
|
std::function<void(NetworkResult)> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, manageAutoModMessages,
|
||||||
|
(QString userID, QString msgID, QString action,
|
||||||
|
std::function<void()> successCallback,
|
||||||
|
std::function<void(HelixAutoModMessageError)> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getCheermotes,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixCheermoteSet>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getEmoteSetData,
|
||||||
|
(QString emoteSetId,
|
||||||
|
ResultCallback<HelixEmoteSetData> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getChannelEmotes,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixChannelEmote>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, update, (QString clientId, QString oauthToken),
|
||||||
|
(override));
|
||||||
|
};
|
||||||
|
|
||||||
|
static QString DEFAULT_SETTINGS = R"!(
|
||||||
|
{
|
||||||
|
"accounts": {
|
||||||
|
"uid117166826": {
|
||||||
|
"username": "testaccount_420",
|
||||||
|
"userID": "117166826",
|
||||||
|
"clientID": "abc",
|
||||||
|
"oauthToken": "def"
|
||||||
|
},
|
||||||
|
"current": "testaccount_420"
|
||||||
|
},
|
||||||
|
"highlighting": {
|
||||||
|
"selfHighlight": {
|
||||||
|
"enableSound": true
|
||||||
|
},
|
||||||
|
"blacklist": [
|
||||||
|
{
|
||||||
|
"pattern": "zenix",
|
||||||
|
"regex": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"pattern": "pajlada",
|
||||||
|
"showInMentions": false,
|
||||||
|
"alert": false,
|
||||||
|
"sound": false,
|
||||||
|
"regex": false,
|
||||||
|
"case": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7fffffff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "gempir",
|
||||||
|
"showInMentions": true,
|
||||||
|
"alert": true,
|
||||||
|
"sound": false,
|
||||||
|
"regex": false,
|
||||||
|
"case": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7ff19900"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"alwaysPlaySound": true,
|
||||||
|
"highlights": [
|
||||||
|
{
|
||||||
|
"pattern": "!testmanxd",
|
||||||
|
"showInMentions": true,
|
||||||
|
"alert": true,
|
||||||
|
"sound": true,
|
||||||
|
"regex": false,
|
||||||
|
"case": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7f7f3f49"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"badges": [
|
||||||
|
{
|
||||||
|
"name": "broadcaster",
|
||||||
|
"displayName": "Broadcaster",
|
||||||
|
"alert": false,
|
||||||
|
"sound": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7f427f00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "subscriber",
|
||||||
|
"displayName": "Subscriber",
|
||||||
|
"alert": false,
|
||||||
|
"sound": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7f7f3f49"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "founder",
|
||||||
|
"displayName": "Founder",
|
||||||
|
"alert": true,
|
||||||
|
"sound": false,
|
||||||
|
"soundUrl": "",
|
||||||
|
"color": "#7fe8b7eb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subHighlightColor": "#64ffd641"
|
||||||
|
}
|
||||||
|
})!";
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
// TODO: create one of these from a raw irc message? hmm xD
|
||||||
|
struct {
|
||||||
|
MessageParseArgs args;
|
||||||
|
std::vector<Badge> badges;
|
||||||
|
QString senderName;
|
||||||
|
QString originalMessage;
|
||||||
|
} input;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool state;
|
||||||
|
HighlightResult result;
|
||||||
|
} expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HighlightControllerTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// Write default settings to the mock settings json file
|
||||||
|
QDir().mkpath("/tmp/c2-tests");
|
||||||
|
QFile settingsFile("/tmp/c2-tests/settings.json");
|
||||||
|
assert(settingsFile.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||||
|
QTextStream out(&settingsFile);
|
||||||
|
out << DEFAULT_SETTINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mockHelix = new MockHelix;
|
||||||
|
|
||||||
|
initializeHelix(this->mockHelix);
|
||||||
|
|
||||||
|
EXPECT_CALL(*this->mockHelix, loadBlocks).Times(Exactly(1));
|
||||||
|
EXPECT_CALL(*this->mockHelix, update).Times(Exactly(1));
|
||||||
|
|
||||||
|
this->mockApplication = std::make_unique<MockApplication>();
|
||||||
|
this->settings = std::make_unique<Settings>("/tmp/c2-tests");
|
||||||
|
this->paths = std::make_unique<Paths>();
|
||||||
|
|
||||||
|
this->controller = std::make_unique<HighlightController>();
|
||||||
|
|
||||||
|
this->mockApplication->accounts.initialize(*this->settings,
|
||||||
|
*this->paths);
|
||||||
|
this->controller->initialize(*this->settings, *this->paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
QDir().rmdir("/tmp/c2-tests");
|
||||||
|
this->mockApplication.reset();
|
||||||
|
this->settings.reset();
|
||||||
|
this->paths.reset();
|
||||||
|
|
||||||
|
this->controller.reset();
|
||||||
|
|
||||||
|
delete this->mockHelix;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<MockApplication> mockApplication;
|
||||||
|
std::unique_ptr<Settings> settings;
|
||||||
|
std::unique_ptr<Paths> paths;
|
||||||
|
|
||||||
|
std::unique_ptr<HighlightController> controller;
|
||||||
|
|
||||||
|
MockHelix *mockHelix;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(HighlightControllerTest, A)
|
||||||
|
{
|
||||||
|
auto currentUser =
|
||||||
|
this->mockApplication->getAccounts()->twitch.getCurrent();
|
||||||
|
std::vector<TestCase> tests{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// input
|
||||||
|
MessageParseArgs{}, // no special args
|
||||||
|
{}, // no badges
|
||||||
|
"pajlada", // sender name
|
||||||
|
"hello!", // original message
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
true, // state
|
||||||
|
{
|
||||||
|
false, // alert
|
||||||
|
false, // playsound
|
||||||
|
boost::none, // custom sound url
|
||||||
|
std::make_shared<QColor>("#7fffffff"), // color
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// input
|
||||||
|
MessageParseArgs{}, // no special args
|
||||||
|
{}, // no badges
|
||||||
|
"pajlada2", // sender name
|
||||||
|
"hello!", // original message
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
false, // state
|
||||||
|
HighlightResult::emptyResult(), // result
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// input
|
||||||
|
MessageParseArgs{}, // no special args
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"founder",
|
||||||
|
"0",
|
||||||
|
}, // founder badge
|
||||||
|
},
|
||||||
|
"pajlada22", // sender name
|
||||||
|
"hello!", // original message
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
true, // state
|
||||||
|
{
|
||||||
|
true, // alert
|
||||||
|
false, // playsound
|
||||||
|
boost::none, // custom sound url
|
||||||
|
std::make_shared<QColor>("#7fe8b7eb"), // color
|
||||||
|
false, //showInMentions
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
// input
|
||||||
|
MessageParseArgs{}, // no special args
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"founder",
|
||||||
|
"0",
|
||||||
|
}, // founder badge
|
||||||
|
},
|
||||||
|
"pajlada", // sender name
|
||||||
|
"hello!", // original message
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
true, // state
|
||||||
|
{
|
||||||
|
true, // alert
|
||||||
|
false, // playsound
|
||||||
|
boost::none, // custom sound url
|
||||||
|
std::make_shared<QColor>("#7fffffff"), // color
|
||||||
|
false, //showInMentions
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &[input, expected] : tests)
|
||||||
|
{
|
||||||
|
auto [isMatch, matchResult] = this->controller->check(
|
||||||
|
input.args, input.badges, input.senderName, input.originalMessage);
|
||||||
|
|
||||||
|
EXPECT_EQ(isMatch, expected.state);
|
||||||
|
EXPECT_EQ(matchResult, expected.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user