Add ability to highlight messages based on user badges (#1704)

Co-authored-by: Paweł <zneix@zneix.eu>
Co-authored-by: 23rd <23rd@vivaldi.net>
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Daniel
2021-05-02 18:08:08 -04:00
committed by GitHub
parent 6ab5b13017
commit f6d9fb2aac
34 changed files with 956 additions and 115 deletions
@@ -0,0 +1,56 @@
#include "BadgeHighlightModel.hpp"
#include "Application.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp"
#include "util/StandardItemHelper.hpp"
namespace chatterino {
// commandmodel
BadgeHighlightModel::BadgeHighlightModel(QObject *parent)
: SignalVectorModel<HighlightBadge>(5, parent)
{
}
// turn vector item into model row
HighlightBadge BadgeHighlightModel::getItemFromRow(
std::vector<QStandardItem *> &row, const HighlightBadge &original)
{
using Column = BadgeHighlightModel::Column;
// In order for old messages to update their highlight color, we need to
// update the highlight color here.
auto highlightColor = original.getColor();
*highlightColor =
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
return HighlightBadge{
original.badgeName(),
row[Column::Badge]->data(Qt::DisplayRole).toString(),
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
row[Column::SoundPath]->data(Qt::UserRole).toString(),
highlightColor};
}
// row into vector item
void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item,
std::vector<QStandardItem *> &row)
{
using QIconPtr = std::shared_ptr<QIcon>;
using Column = BadgeHighlightModel::Column;
setStringItem(row[Column::Badge], item.displayName(), false, true);
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
setBoolItem(row[Column::PlaySound], item.hasSound());
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
setColorItem(row[Column::Color], *item.getColor());
TwitchBadges::instance()->getBadgeIcon(
item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) {
row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole);
});
}
} // namespace chatterino
@@ -0,0 +1,36 @@
#pragma once
#include <QObject>
#include "common/SignalVectorModel.hpp"
#include "controllers/highlights/HighlightBadge.hpp"
#include "providers/twitch/TwitchBadges.hpp"
namespace chatterino {
class HighlightController;
class BadgeHighlightModel : public SignalVectorModel<HighlightBadge>
{
public:
explicit BadgeHighlightModel(QObject *parent);
enum Column {
Badge = 0,
FlashTaskbar = 1,
PlaySound = 2,
SoundPath = 3,
Color = 4
};
protected:
// vector into model row
virtual HighlightBadge getItemFromRow(
std::vector<QStandardItem *> &row,
const HighlightBadge &original) override;
virtual void getRowFromItem(const HighlightBadge &item,
std::vector<QStandardItem *> &row) override;
};
} // namespace chatterino
@@ -0,0 +1,121 @@
#include "HighlightBadge.hpp"
#include "singletons/Resources.hpp"
namespace chatterino {
QColor HighlightBadge::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127);
bool HighlightBadge::operator==(const HighlightBadge &other) const
{
return std::tie(this->badgeName_, this->displayName_, this->hasSound_,
this->hasAlert_, this->soundUrl_, this->color_) ==
std::tie(other.badgeName_, other.displayName_, other.hasSound_,
other.hasAlert_, other.soundUrl_, other.color_);
}
HighlightBadge::HighlightBadge(const QString &badgeName,
const QString &displayName, bool hasAlert,
bool hasSound, const QString &soundUrl,
QColor color)
: HighlightBadge(badgeName, displayName, hasAlert, hasSound, soundUrl,
std::make_shared<QColor>(color))
{
}
HighlightBadge::HighlightBadge(const QString &badgeName,
const QString &displayName, bool hasAlert,
bool hasSound, const QString &soundUrl,
std::shared_ptr<QColor> color)
: badgeName_(badgeName)
, displayName_(displayName)
, hasAlert_(hasAlert)
, hasSound_(hasSound)
, soundUrl_(soundUrl)
, color_(color)
{
// check badgeName at initialization to reduce cost per isMatch call
this->hasVersions_ = badgeName.contains("/");
this->isMulti_ = badgeName.contains(",");
if (this->isMulti_)
{
this->badges_ = badgeName.split(",");
}
}
const QString &HighlightBadge::badgeName() const
{
return this->badgeName_;
}
const QString &HighlightBadge::displayName() const
{
return this->displayName_;
}
bool HighlightBadge::hasAlert() const
{
return this->hasAlert_;
}
bool HighlightBadge::hasSound() const
{
return this->hasSound_;
}
bool HighlightBadge::isMatch(const Badge &badge) const
{
if (this->isMulti_)
{
for (const auto &id : this->badges_)
{
if (this->compare(id, badge))
{
return true;
}
}
return false;
}
else
{
return this->compare(this->badgeName_, badge);
}
}
bool HighlightBadge::compare(const QString &id, const Badge &badge) const
{
if (this->hasVersions_)
{
auto parts = id.split("/");
if (parts.size() == 2)
{
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0 &&
parts.at(1).compare(badge.value_, Qt::CaseInsensitive) == 0;
}
else
{
return parts.at(0).compare(badge.key_, Qt::CaseInsensitive) == 0;
}
}
else
{
return id.compare(badge.key_, Qt::CaseInsensitive) == 0;
}
}
bool HighlightBadge::hasCustomSound() const
{
return !this->soundUrl_.isEmpty();
}
const QUrl &HighlightBadge::getSoundUrl() const
{
return this->soundUrl_;
}
const std::shared_ptr<QColor> HighlightBadge::getColor() const
{
return this->color_;
}
} // namespace chatterino
@@ -0,0 +1,123 @@
#pragma once
#include "providers/twitch/TwitchBadge.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QString>
#include <QUrl>
#include <pajlada/serialize.hpp>
namespace chatterino {
class HighlightBadge
{
public:
bool operator==(const HighlightBadge &other) const;
HighlightBadge(const QString &badgeName, const QString &displayName,
bool hasAlert, bool hasSound, const QString &soundUrl,
QColor color);
HighlightBadge(const QString &badgeName, const QString &displayName,
bool hasAlert, bool hasSound, const QString &soundUrl,
std::shared_ptr<QColor> color);
const QString &badgeName() const;
const QString &displayName() const;
bool hasAlert() const;
bool hasSound() const;
bool isMatch(const Badge &badge) const;
/**
* @brief Check if this highlight phrase has a custom sound set.
*
* Note that this method only checks whether the path to the custom sound
* is not empty. It does not check whether the file still exists, is a
* sound file, or anything else.
*
* @return true, if the custom sound file path is not empty, false otherwise
*/
bool hasCustomSound() const;
const QUrl &getSoundUrl() const;
const std::shared_ptr<QColor> getColor() const;
/*
* XXX: Use the constexpr constructor here once we are building with
* Qt>=5.13.
*/
static QColor FALLBACK_HIGHLIGHT_COLOR;
private:
bool compare(const QString &id, const Badge &badge) const;
QString badgeName_;
QString displayName_;
bool hasAlert_;
bool hasSound_;
QUrl soundUrl_;
std::shared_ptr<QColor> color_;
bool isMulti_;
bool hasVersions_;
QStringList badges_;
};
}; // namespace chatterino
namespace pajlada {
template <>
struct Serialize<chatterino::HighlightBadge> {
static rapidjson::Value get(const chatterino::HighlightBadge &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
chatterino::rj::set(ret, "name", value.badgeName(), a);
chatterino::rj::set(ret, "displayName", value.displayName(), a);
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
chatterino::rj::set(ret, "sound", value.hasSound(), a);
chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a);
chatterino::rj::set(ret, "color",
value.getColor()->name(QColor::HexArgb), a);
return ret;
}
};
template <>
struct Deserialize<chatterino::HighlightBadge> {
static chatterino::HighlightBadge get(const rapidjson::Value &value,
bool *error)
{
if (!value.IsObject())
{
PAJLADA_REPORT_ERROR(error);
return chatterino::HighlightBadge(QString(), QString(), false,
false, "", QColor());
}
QString _name;
QString _displayName;
bool _hasAlert = true;
bool _hasSound = false;
QString _soundUrl;
QString encodedColor;
chatterino::rj::getSafe(value, "name", _name);
chatterino::rj::getSafe(value, "displayName", _displayName);
chatterino::rj::getSafe(value, "alert", _hasAlert);
chatterino::rj::getSafe(value, "sound", _hasSound);
chatterino::rj::getSafe(value, "soundUrl", _soundUrl);
chatterino::rj::getSafe(value, "color", encodedColor);
auto _color = QColor(encodedColor);
if (!_color.isValid())
_color = chatterino::HighlightBadge::FALLBACK_HIGHLIGHT_COLOR;
return chatterino::HighlightBadge(_name, _displayName, _hasAlert,
_hasSound, _soundUrl, _color);
}
};
} // namespace pajlada