Squashed commit of the following:

commit ea07bbef0b
Merge: 0b36f436 5cfcf114
Author: hemirt <hemirt@email.cz>
Date:   Sun Sep 23 20:05:14 2018 +0200

    Merge branch 'blacklist' into blacklistnew

commit 5cfcf114b6
Author: hemirt <hemirt@email.cz>
Date:   Sun Sep 23 20:00:16 2018 +0200

    rename second pattern to replacement

commit f08cc4cf88
Author: hemirt <hemirt@email.cz>
Date:   Sun Sep 23 19:52:30 2018 +0200

    delete out commented code

commit 1acb1278aa
Author: hemirt <hemirt@email.cz>
Date:   Sun Sep 23 19:52:03 2018 +0200

    fix replacement with emotes issues

commit 646268ab18
Author: hemirt <hemirt@email.cz>
Date:   Sun Aug 19 01:06:36 2018 +0200

    fix build

commit ad711b4c15
Merge: e8e059f8 8bcc9c48
Author: hemirt <hemirt@email.cz>
Date:   Sun Aug 19 00:52:38 2018 +0200

    Merge branch 'master' of https://github.com/fourtf/chatterino2 into blacklist

commit e8e059f847
Author: hemirt <hemirt@email.cz>
Date:   Sun Aug 19 00:25:58 2018 +0200

    add replaced emotes into twitchEmotes

commit a63454f00d
Merge: e7f2f397 63eaf3b9
Author: hemirt <hemirt@email.cz>
Date:   Sat Aug 11 22:38:16 2018 +0200

    Merge branch 'master' of https://github.com/fourtf/chatterino2 into blacklist

commit e7f2f39737
Author: hemirt <hemirt@email.cz>
Date:   Sat Aug 11 21:54:01 2018 +0200

    emotedata

commit f00d3da537
Author: hemirt <hemirt@email.cz>
Date:   Sat Jul 28 19:53:55 2018 +0200

    rename variables to fit better, emotes in capture groups from regex work

commit 00c9fa080a
Author: hemirt <hemirt@email.cz>
Date:   Mon Jul 9 19:53:53 2018 +0200

    add case sensitivity checkbox and fix validity issues due to isValid
    that checked regex

commit 4385fcd13f
Author: hemirt <hemirt@email.cz>
Date:   Sun Jul 8 21:09:14 2018 +0200

    remove commented code

commit 1834342f74
Author: hemirt <hemirt@email.cz>
Date:   Sun Jul 8 21:03:13 2018 +0200

    IgnorePhrase replacement

    also removes twitch emotes info about the matched and changed parts and
    shifts positions of other emotes from emote infos to the corresponding new
    position

commit d3b6e294ed
Author: hemirt <hemirt@email.cz>
Date:   Sun Jul 8 16:21:33 2018 +0200

    ignore phrases
This commit is contained in:
hemirt 2018-09-23 20:21:50 +02:00 committed by pajlada
parent 86024ade24
commit 3184234c19
8 changed files with 414 additions and 211 deletions

@ -1 +1 @@
Subproject commit bcbd29b793a2895ddf1772dca0105f51a0380798
Subproject commit 7f0db95f245fb726e756ecde15a800c0928b054b

@ -1 +1 @@
Subproject commit a1b61606c144c34a0f0f9173768cb85dd58e7f29
Subproject commit e03c868ec922027a0e672b64388808beb1297816

View file

@ -8,7 +8,7 @@ namespace chatterino {
// commandmodel
IgnoreModel::IgnoreModel(QObject *parent)
: SignalVectorModel<IgnorePhrase>(2, parent)
: SignalVectorModel<IgnorePhrase>(5, parent)
{
}
@ -18,8 +18,10 @@ IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
{
// key, regex
return IgnorePhrase{row[0]->data(Qt::DisplayRole).toString(),
row[1]->data(Qt::CheckStateRole).toBool()};
return IgnorePhrase{
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
row[3]->data(Qt::CheckStateRole).toBool(), row[4]->data(Qt::DisplayRole).toString(),
row[2]->data(Qt::CheckStateRole).toBool()};
}
// turns a row in the model into a vector item
@ -28,6 +30,9 @@ void IgnoreModel::getRowFromItem(const IgnorePhrase &item,
{
setStringItem(row[0], item.getPattern());
setBoolItem(row[1], item.isRegex());
setBoolItem(row[2], item.isCaseSensitive());
setBoolItem(row[3], item.isBlock());
setStringItem(row[4], item.getReplace());
}
} // namespace chatterino

View file

@ -1,5 +1,9 @@
#pragma once
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "singletons/Settings.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
@ -16,80 +20,154 @@ class IgnorePhrase
public:
bool operator==(const IgnorePhrase &other) const
{
return std::tie(this->pattern_, this->isRegex_) ==
std::tie(other.pattern_, other.isRegex_);
return std::tie(this->pattern_, this->isRegex_, this->isBlock_, this->replace_,
this->isCaseSensitive_) == std::tie(other.pattern_, other.isRegex_,
other.isBlock_, other.replace_,
other.isCaseSensitive_);
}
IgnorePhrase(const QString &pattern, bool isRegex)
IgnorePhrase(const QString &pattern, bool isRegex, bool isBlock, const QString &replace,
bool isCaseSensitive)
: pattern_(pattern)
, isRegex_(isRegex)
, regex_(isRegex_ ? pattern
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption)
, regex_(pattern)
, isBlock_(isBlock)
, replace_(replace)
, isCaseSensitive_(isCaseSensitive)
{
if (this->isCaseSensitive_) {
regex_.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
} else {
regex_.setPatternOptions(QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption);
}
}
const QString &getPattern() const
{
return this->pattern_;
}
bool isRegex() const
{
return this->isRegex_;
}
bool isValid() const
bool isRegexValid() const
{
return !this->pattern_.isEmpty() && this->regex_.isValid();
return this->regex_.isValid();
}
bool isMatch(const QString &subject) const
{
return this->isValid() && this->regex_.match(subject).hasMatch();
return !this->pattern_.isEmpty() &&
(this->isRegex() ? (this->regex_.isValid() && this->regex_.match(subject).hasMatch())
: subject.contains(this->pattern_, this->caseSensitivity()));
}
const QRegularExpression &getRegex() const
{
return this->regex_;
}
bool isBlock() const
{
return this->isBlock_;
}
const QString &getReplace() const
{
return this->replace_;
}
bool isCaseSensitive() const
{
return this->isCaseSensitive_;
}
Qt::CaseSensitivity caseSensitivity() const
{
return this->isCaseSensitive_ ? Qt::CaseSensitive : Qt::CaseInsensitive;
}
const std::unordered_map<EmoteName, EmotePtr> &getEmotes() const
{
return this->emotes_;
}
bool containsEmote() const
{
if (!this->emotesChecked_) {
const auto &accvec = getApp()->accounts->twitch.accounts.getVector();
for (const auto &acc : accvec) {
const auto &accemotes = *acc->accessEmotes();
for (const auto &emote : accemotes.emotes) {
if (this->replace_.contains(emote.first.string, Qt::CaseSensitive)) {
this->emotes_.emplace(emote.first, emote.second);
}
}
}
this->emotesChecked_ = true;
}
return !this->emotes_.empty();
}
private:
QString pattern_;
bool isRegex_;
QRegularExpression regex_;
bool isBlock_;
QString replace_;
bool isCaseSensitive_;
mutable std::unordered_map<EmoteName, EmotePtr> emotes_;
mutable bool emotesChecked_{false};
};
} // namespace chatterino
namespace pajlada {
namespace Settings {
template <>
struct Serialize<chatterino::IgnorePhrase> {
static rapidjson::Value get(const chatterino::IgnorePhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
template <>
struct Serialize<chatterino::IgnorePhrase> {
static rapidjson::Value get(const chatterino::IgnorePhrase &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
AddMember(ret, "pattern", value.getPattern(), a);
AddMember(ret, "regex", value.isRegex(), a);
AddMember(ret, "isBlock", value.isBlock(), a);
AddMember(ret, "replaceWith", value.getReplace(), a);
AddMember(ret, "caseSensitive", value.isCaseSensitive(), a);
return ret;
return ret;
}
};
template <>
struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(
QString(), false, false,
::chatterino::getSettings()->ignoredPhraseReplace.getValue(), true);
}
};
template <>
struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::IgnorePhrase(QString(), false);
}
QString _pattern;
bool _isRegex = false;
bool _isBlock = false;
QString _replace;
bool _caseSens = true;
QString _pattern;
bool _isRegex = false;
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "regex", _isRegex);
return chatterino::IgnorePhrase(_pattern, _isRegex);
}
};
chatterino::rj::getSafe(value, "pattern", _pattern);
chatterino::rj::getSafe(value, "regex", _isRegex);
chatterino::rj::getSafe(value, "isBlock", _isBlock);
chatterino::rj::getSafe(value, "replaceWith", _replace);
chatterino::rj::getSafe(value, "caseSensitive", _caseSens);
return chatterino::IgnorePhrase(_pattern, _isRegex, _isBlock, _replace, _caseSens);
}
};
} // namespace Settings
} // namespace pajlada

View file

@ -21,13 +21,14 @@
#include <QApplication>
#include <QDebug>
#include <QMediaPlayer>
#include <QStringRef>
#include <boost/variant.hpp>
namespace chatterino {
TwitchMessageBuilder::TwitchMessageBuilder(
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args)
TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel,
const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args)
: channel(_channel)
, twitchChannel(dynamic_cast<TwitchChannel *>(_channel))
, ircMessage(_ircMessage)
@ -39,9 +40,10 @@ TwitchMessageBuilder::TwitchMessageBuilder(
this->usernameColor_ = getApp()->themes->messages.textColors.system;
}
TwitchMessageBuilder::TwitchMessageBuilder(
Channel *_channel, const Communi::IrcMessage *_ircMessage,
const MessageParseArgs &_args, QString content, bool isAction)
TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel,
const Communi::IrcMessage *_ircMessage,
const MessageParseArgs &_args, QString content,
bool isAction)
: channel(_channel)
, twitchChannel(dynamic_cast<TwitchChannel *>(_channel))
, ircMessage(_ircMessage)
@ -59,22 +61,18 @@ bool TwitchMessageBuilder::isIgnored() const
// TODO(pajlada): Do we need to check if the phrase is valid first?
for (const auto &phrase : app->ignores->phrases.getVector()) {
if (phrase.isMatch(this->originalMessage_)) {
log("Blocking message because it contains ignored phrase {}",
phrase.getPattern());
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) {
log("Blocking message because it contains ignored phrase {}", phrase.getPattern());
return true;
}
}
if (getSettings()->enableTwitchIgnoredUsers &&
this->tags.contains("user-id")) {
if (getSettings()->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user :
app->accounts->twitch.getCurrent()->getIgnores()) {
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
log("Blocking message because it's from blocked user {}",
user.name);
log("Blocking message because it's from blocked user {}", user.name);
return true;
}
}
@ -149,7 +147,7 @@ MessagePtr TwitchMessageBuilder::build()
}
// twitch emotes
std::vector<std::pair<int, EmotePtr>> twitchEmotes;
std::vector<std::tuple<int, EmotePtr, EmoteName>> twitchEmotes;
iterator = this->tags.find("emotes");
if (iterator != this->tags.end()) {
@ -158,11 +156,176 @@ MessagePtr TwitchMessageBuilder::build()
for (QString emote : emoteString) {
this->appendTwitchEmote(emote, twitchEmotes);
}
std::sort(
twitchEmotes.begin(), twitchEmotes.end(),
[](const auto &a, const auto &b) { return a.first < b.first; });
}
auto app = getApp();
const auto &phrases = app->ignores->phrases.getVector();
auto removeEmotesInRange =
[](int pos, int len,
std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes) mutable {
auto it = std::partition(
twitchEmotes.begin(), twitchEmotes.end(), [pos, len](const auto &item) {
return !((std::get<0>(item) >= pos) && std::get<0>(item) < (pos + len));
});
for (auto copy = it; copy != twitchEmotes.end(); ++copy) {
if (std::get<1>(*copy) == nullptr) {
log("remem nullptr {}", std::get<2>(*copy).string);
}
}
std::vector<std::tuple<int, EmotePtr, EmoteName>> v(it, twitchEmotes.end());
twitchEmotes.erase(it, twitchEmotes.end());
return v;
};
auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable {
for (auto &item : twitchEmotes) {
auto &index = std::get<0>(item);
if (index >= pos) {
index += by;
}
}
};
auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase, const QStringRef &midrepl,
int startIndex) mutable {
if (!phrase.containsEmote()) {
return;
}
QVector<QStringRef> words = midrepl.split(' ');
int pos = 0;
for (const auto &word : words) {
for (const auto &emote : phrase.getEmotes()) {
if (word == emote.first.string) {
if (emote.second == nullptr) {
log("emote null {}", emote.first.string);
}
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
startIndex + pos, emote.second, emote.first});
}
}
pos += word.length() + 1;
}
};
for (const auto &phrase : phrases) {
if (phrase.isBlock()) {
continue;
}
if (phrase.isRegex()) {
const auto &regex = phrase.getRegex();
if (!regex.isValid()) {
continue;
}
QRegularExpressionMatch match;
int from = 0;
while ((from = this->originalMessage_.indexOf(regex, from, &match)) != -1) {
int len = match.capturedLength();
auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto mid = this->originalMessage_.mid(from, len);
mid.replace(regex, phrase.getReplace());
int midsize = mid.size();
this->originalMessage_.replace(from, len, mid);
int pos1 = from;
while (pos1 > 0) {
if (this->originalMessage_[pos1 - 1] == ' ') {
break;
}
--pos1;
}
int pos2 = from + midsize;
while (pos2 < this->originalMessage_.length()) {
if (this->originalMessage_[pos2] == ' ') {
break;
}
++pos2;
}
shiftIndicesAfter(from + len, midsize - len);
auto midExtendedRef = this->originalMessage_.midRef(pos1, pos2 - pos1);
for (auto &tup : vret) {
if (std::get<1>(tup) == nullptr) {
log("v nullptr {}", std::get<2>(tup).string);
continue;
}
int index = 0;
QString emote = " " + std::get<2>(tup).string + " ";
while ((index = midExtendedRef.indexOf(emote, index)) != -1) {
std::get<0>(tup) = from + index + 1;
index += emote.size() - 1;
twitchEmotes.push_back(tup);
}
}
addReplEmotes(phrase, midExtendedRef, pos1);
from += midsize;
}
} else {
const auto &pattern = phrase.getPattern();
if (pattern.isEmpty()) {
continue;
}
int from = 0;
while ((from = this->originalMessage_.indexOf(pattern, from,
phrase.caseSensitivity())) != -1) {
int len = pattern.size();
auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto replace = phrase.getReplace();
int replacesize = replace.size();
this->originalMessage_.replace(from, len, replace);
int pos1 = from;
while (pos1 > 0) {
if (this->originalMessage_[pos1 - 1] == ' ') {
break;
}
--pos1;
}
int pos2 = from + replacesize;
while (pos2 < this->originalMessage_.length()) {
if (this->originalMessage_[pos2] == ' ') {
break;
}
++pos2;
}
shiftIndicesAfter(from + len, replacesize - len);
auto midExtendedRef = this->originalMessage_.midRef(pos1, pos2 - pos1);
for (auto &tup : vret) {
if (std::get<1>(tup) == nullptr) {
log("v nullptr {}", std::get<2>(tup).string);
continue;
}
int index = 0;
QString emote = " " + std::get<2>(tup).string + " ";
while ((index = midExtendedRef.indexOf(emote, index)) != -1) {
std::get<0>(tup) = from + index + 1;
index += emote.size() - 1;
twitchEmotes.push_back(tup);
}
}
addReplEmotes(phrase, midExtendedRef, pos1);
from += replacesize;
}
}
}
std::sort(twitchEmotes.begin(), twitchEmotes.end(),
[](const auto &a, const auto &b) { return std::get<0>(a) < std::get<0>(b); });
twitchEmotes.erase(std::unique(twitchEmotes.begin(), twitchEmotes.end(),
[](const auto &first, const auto &second) {
return std::get<0>(first) == std::get<0>(second);
}),
twitchEmotes.end());
auto currentTwitchEmote = twitchEmotes.begin();
// words
QStringList splits = this->originalMessage_.split(' ');
@ -175,19 +338,22 @@ MessagePtr TwitchMessageBuilder::build()
}
void TwitchMessageBuilder::addWords(
const QStringList &words,
const std::vector<std::pair<int, EmotePtr>> &twitchEmotes)
const QStringList &words, const std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes)
{
auto i = int();
auto currentTwitchEmote = twitchEmotes.begin();
for (const auto &word : words) {
// check if it's a twitch emote twitch emote
if (currentTwitchEmote != twitchEmotes.end() &&
currentTwitchEmote->first == i) {
auto emoteImage = currentTwitchEmote->second;
this->emplace<EmoteElement>(emoteImage,
MessageElementFlag::TwitchEmote);
while (currentTwitchEmote != twitchEmotes.end() && std::get<0>(*currentTwitchEmote) < i) {
++currentTwitchEmote;
}
if (currentTwitchEmote != twitchEmotes.end() && std::get<0>(*currentTwitchEmote) == i) {
auto emoteImage = std::get<1>(*currentTwitchEmote);
if (emoteImage == nullptr) {
log("emoteImage nullptr {}", std::get<2>(*currentTwitchEmote).string);
}
this->emplace<EmoteElement>(emoteImage, MessageElementFlag::TwitchEmote);
i += word.length() + 1;
currentTwitchEmote++;
@ -197,8 +363,7 @@ void TwitchMessageBuilder::addWords(
// split words
for (auto &variant : getApp()->emotes->emojis.parse(word)) {
boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); },
variant);
boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); }, variant);
}
for (int j = 0; j < word.size(); j++) {
@ -240,18 +405,16 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
// Actually just text
auto linkString = this->matchLink(string);
auto link = Link();
auto textColor = this->action_ ? MessageColor(this->usernameColor_)
: MessageColor(MessageColor::Text);
auto textColor =
this->action_ ? MessageColor(this->usernameColor_) : MessageColor(MessageColor::Text);
if (linkString.isEmpty()) {
if (string.startsWith('@')) {
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
textColor, FontStyle::ChatMediumBold);
this->emplace<TextElement>(
string, MessageElementFlag::NonBoldUsername, textColor);
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername, textColor,
FontStyle::ChatMediumBold);
this->emplace<TextElement>(string, MessageElementFlag::NonBoldUsername, textColor);
} else {
this->emplace<TextElement>(string, MessageElementFlag::Text,
textColor);
this->emplace<TextElement>(string, MessageElementFlag::Text, textColor);
}
} else {
static QRegularExpression domainRegex(
@ -262,8 +425,7 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
auto match = domainRegex.match(string);
if (match.isValid()) {
lowercaseLinkString = string.mid(0, match.capturedStart(1)) +
match.captured(1).toLower() +
string.mid(match.capturedEnd(1));
match.captured(1).toLower() + string.mid(match.capturedEnd(1));
} else {
lowercaseLinkString = string;
}
@ -271,28 +433,24 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
textColor = MessageColor(MessageColor::Link);
auto linkMELowercase =
this->emplace<TextElement>(lowercaseLinkString,
MessageElementFlag::LowercaseLink,
this->emplace<TextElement>(lowercaseLinkString, MessageElementFlag::LowercaseLink,
textColor)
->setLink(link);
auto linkMEOriginal =
this->emplace<TextElement>(string, MessageElementFlag::OriginalLink,
textColor)
this->emplace<TextElement>(string, MessageElementFlag::OriginalLink, textColor)
->setLink(link);
LinkResolver::getLinkInfo(
linkString, [linkMELowercase, linkMEOriginal, linkString]
(QString tooltipText, Link originalLink) {
if (!tooltipText.isEmpty()) {
linkMELowercase->setTooltip(tooltipText);
linkMEOriginal->setTooltip(tooltipText);
}
if (originalLink.value != linkString &&
!originalLink.value.isEmpty()) {
linkMELowercase->setLink(originalLink)->updateLink();
linkMEOriginal->setLink(originalLink)->updateLink();
}
});
LinkResolver::getLinkInfo(linkString, [linkMELowercase, linkMEOriginal, linkString](
QString tooltipText, Link originalLink) {
if (!tooltipText.isEmpty()) {
linkMELowercase->setTooltip(tooltipText);
linkMEOriginal->setTooltip(tooltipText);
}
if (originalLink.value != linkString && !originalLink.value.isEmpty()) {
linkMELowercase->setLink(originalLink)->updateLink();
linkMEOriginal->setLink(originalLink)->updateLink();
}
});
}
// if (!linkString.isEmpty()) {
@ -400,11 +558,9 @@ void TwitchMessageBuilder::appendUsername()
auto iterator = this->tags.find("display-name");
if (iterator != this->tags.end()) {
QString displayName =
parseTagString(iterator.value().toString()).trimmed();
QString displayName = parseTagString(iterator.value().toString()).trimmed();
if (QString::compare(displayName, this->userName,
Qt::CaseInsensitive) == 0) {
if (QString::compare(displayName, this->userName, Qt::CaseInsensitive) == 0) {
username = displayName;
this->message().displayName = displayName;
@ -422,8 +578,7 @@ void TwitchMessageBuilder::appendUsername()
QString usernameText;
pajlada::Settings::Setting<int> usernameDisplayMode(
"/appearance/messages/usernameDisplayMode",
UsernameDisplayMode::UsernameAndLocalizedName);
"/appearance/messages/usernameDisplayMode", UsernameDisplayMode::UsernameAndLocalizedName);
switch (usernameDisplayMode.getValue()) {
case UsernameDisplayMode::Username: {
@ -454,8 +609,7 @@ void TwitchMessageBuilder::appendUsername()
// IrcManager::getInstance().getUser().getUserName();
} else if (this->args.isReceivedWhisper) {
// Sender username
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
this->usernameColor_,
this->emplace<TextElement>(usernameText, MessageElementFlag::Username, this->usernameColor_,
FontStyle::ChatMediumBold)
->setLink({Link::UserWhisper, this->message().displayName});
@ -463,8 +617,7 @@ void TwitchMessageBuilder::appendUsername()
// Separator
this->emplace<TextElement>("->", MessageElementFlag::Username,
app->themes->messages.textColors.system,
FontStyle::ChatMedium);
app->themes->messages.textColors.system, FontStyle::ChatMedium);
QColor selfColor = currentUser->color();
if (!selfColor.isValid()) {
@ -472,16 +625,14 @@ void TwitchMessageBuilder::appendUsername()
}
// Your own username
this->emplace<TextElement>(currentUser->getUserName() + ":",
MessageElementFlag::Username, selfColor,
FontStyle::ChatMediumBold);
this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElementFlag::Username,
selfColor, FontStyle::ChatMediumBold);
} else {
if (!this->action_) {
usernameText += ":";
}
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
this->usernameColor_,
this->emplace<TextElement>(usernameText, MessageElementFlag::Username, this->usernameColor_,
FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->message().displayName});
}
@ -507,8 +658,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
// update the media player url if necessary
QUrl highlightSoundUrl;
if (getSettings()->customHighlightSound) {
highlightSoundUrl =
QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
highlightSoundUrl = QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
} else {
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
}
@ -521,15 +671,12 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
// TODO: This vector should only be rebuilt upon highlights being changed
// fourtf: should be implemented in the HighlightsController
std::vector<HighlightPhrase> activeHighlights =
app->highlights->phrases.getVector();
std::vector<HighlightPhrase> userHighlights =
app->highlights->highlightedUsers.getVector();
std::vector<HighlightPhrase> activeHighlights = app->highlights->phrases.getVector();
std::vector<HighlightPhrase> userHighlights = app->highlights->highlightedUsers.getVector();
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0) {
HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false);
HighlightPhrase selfHighlight(currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false);
activeHighlights.emplace_back(std::move(selfHighlight));
}
@ -564,8 +711,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
}
for (const HighlightPhrase &userHighlight : userHighlights) {
if (userHighlight.isMatch(this->ircMessage->nick())) {
log("Highlight because user {} sent a message",
this->ircMessage->nick());
log("Highlight because user {} sent a message", this->ircMessage->nick());
doHighlight = true;
if (userHighlight.getAlert()) {
@ -583,8 +729,7 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
}
}
}
if (this->args.isReceivedWhisper &&
getSettings()->enableWhisperHighlight) {
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight) {
if (getSettings()->enableWhisperHighlightTaskbar) {
doAlert = true;
}
@ -596,22 +741,19 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
this->message().flags.set(MessageFlag::Highlighted, doHighlight);
if (!isPastMsg) {
if (playSound &&
(!hasFocus || getSettings()->highlightAlwaysPlaySound)) {
if (playSound && (!hasFocus || getSettings()->highlightAlwaysPlaySound)) {
player->play();
}
if (doAlert) {
QApplication::alert(getApp()->windows->getMainWindow().window(),
2500);
QApplication::alert(getApp()->windows->getMainWindow().window(), 2500);
}
}
}
}
void TwitchMessageBuilder::appendTwitchEmote(
const QString &emote,
std::vector<std::pair<int, EmotePtr>> &vec)
void TwitchMessageBuilder::appendTwitchEmote(const QString &emote,
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec)
{
auto app = getApp();
if (!emote.contains(':')) {
@ -638,16 +780,17 @@ void TwitchMessageBuilder::appendTwitchEmote(
auto start = coords.at(0).toInt();
auto end = coords.at(1).toInt();
if (start >= end || start < 0 ||
end > this->originalMessage_.length()) {
if (start >= end || start < 0 || end > this->originalMessage_.length()) {
return;
}
auto name =
EmoteName{this->originalMessage_.mid(start, end - start + 1)};
vec.push_back(std::make_pair(
start, app->emotes->twitch.getOrCreateEmote(id, name)));
auto name = EmoteName{this->originalMessage_.mid(start, end - start + 1)};
auto tup = std::tuple<int, EmotePtr, EmoteName>{
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
if (std::get<1>(tup) == nullptr) {
log("nullptr {}", std::get<2>(tup).string);
}
vec.push_back(std::move(tup));
}
}
@ -686,7 +829,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
auto app = getApp();
auto iterator = this->tags.find("badges");
if (iterator == this->tags.end()) return;
if (iterator == this->tags.end())
return;
for (QString badge : iterator.value().toString().split(',')) {
if (badge.startsWith("bits/")) {
@ -696,10 +840,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
// Try to fetch channel-specific bit badge
try {
if (twitchChannel)
if (const auto &badge = this->twitchChannel->twitchBadge(
"bits", cheerAmount)) {
this->emplace<EmoteElement>(
badge.get(), MessageElementFlag::BadgeVanity)
if (const auto &badge = this->twitchChannel->twitchBadge("bits", cheerAmount)) {
this->emplace<EmoteElement>(badge.get(), MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
continue;
}
@ -708,55 +850,45 @@ void TwitchMessageBuilder::appendTwitchBadges()
}
// Use default bit badge
if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
"bits", cheerAmount)) {
this->emplace<EmoteElement>(badge.get(),
MessageElementFlag::BadgeVanity)
if (auto badge = this->twitchChannel->globalTwitchBadges().badge("bits", cheerAmount)) {
this->emplace<EmoteElement>(badge.get(), MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
}
} else if (badge == "staff/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.staff),
MessageElementFlag::BadgeGlobalAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.staff),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Staff");
} else if (badge == "admin/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.admin),
MessageElementFlag::BadgeGlobalAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.admin),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Admin");
} else if (badge == "global_mod/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.globalmod),
MessageElementFlag::BadgeGlobalAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.globalmod),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Global Moderator");
} else if (badge == "moderator/1") {
// TODO: Implement custom FFZ moderator badge
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.moderator),
MessageElementFlag::BadgeChannelAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.moderator),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Channel Moderator");
} else if (badge == "turbo/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.turbo),
MessageElementFlag::BadgeGlobalAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.turbo),
MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Turbo Subscriber");
} else if (badge == "broadcaster/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.broadcaster),
MessageElementFlag::BadgeChannelAuthority)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.broadcaster),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Broadcaster");
} else if (badge == "premium/1") {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.prime),
MessageElementFlag::BadgeVanity)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.prime),
MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Prime Subscriber");
} else if (badge.startsWith("partner/")) {
int index = badge.midRef(8).toInt();
switch (index) {
case 1: {
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.verified,
0.25),
Image::fromPixmap(app->resources->twitch.verified, 0.25),
MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Verified");
} break;
@ -767,27 +899,23 @@ void TwitchMessageBuilder::appendTwitchBadges()
} break;
}
} else if (badge.startsWith("subscriber/")) {
if (auto badgeEmote = this->twitchChannel->twitchBadge(
"subscriber", badge.mid(11))) {
this->emplace<EmoteElement>(
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
if (auto badgeEmote = this->twitchChannel->twitchBadge("subscriber", badge.mid(11))) {
this->emplace<EmoteElement>(badgeEmote.get(), MessageElementFlag::BadgeSubscription)
->setTooltip((*badgeEmote)->tooltip.string);
continue;
}
// use default subscriber badge if custom one not found
this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.subscriber, 0.25),
MessageElementFlag::BadgeSubscription)
this->emplace<ImageElement>(Image::fromPixmap(app->resources->twitch.subscriber, 0.25),
MessageElementFlag::BadgeSubscription)
->setTooltip("Twitch Subscriber");
} else {
auto splits = badge.split('/');
if (splits.size() != 2) continue;
if (splits.size() != 2)
continue;
if (auto badgeEmote =
this->twitchChannel->twitchBadge(splits[0], splits[1])) {
this->emplace<EmoteElement>(badgeEmote.get(),
MessageElementFlag::BadgeVanity)
if (auto badgeEmote = this->twitchChannel->twitchBadge(splits[0], splits[1])) {
this->emplace<EmoteElement>(badgeEmote.get(), MessageElementFlag::BadgeVanity)
->setTooltip((*badgeEmote)->tooltip.string);
continue;
}
@ -797,11 +925,9 @@ void TwitchMessageBuilder::appendTwitchBadges()
void TwitchMessageBuilder::appendChatterinoBadges()
{
auto chatterinoBadgePtr =
getApp()->chatterinoBadges->getBadge({this->userName});
auto chatterinoBadgePtr = getApp()->chatterinoBadges->getBadge({this->userName});
if (chatterinoBadgePtr) {
this->emplace<EmoteElement>(*chatterinoBadgePtr,
MessageElementFlag::BadgeChatterino);
this->emplace<EmoteElement>(*chatterinoBadgePtr, MessageElementFlag::BadgeChatterino);
}
}

View file

@ -27,13 +27,10 @@ public:
TwitchMessageBuilder() = delete;
explicit TwitchMessageBuilder(Channel *_channel,
const Communi::IrcPrivateMessage *_ircMessage,
explicit TwitchMessageBuilder(Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args);
explicit TwitchMessageBuilder(Channel *_channel,
const Communi::IrcMessage *_ircMessage,
const MessageParseArgs &_args,
QString content, bool isAction);
explicit TwitchMessageBuilder(Channel *_channel, const Communi::IrcMessage *_ircMessage,
const MessageParseArgs &_args, QString content, bool isAction);
Channel *channel;
TwitchChannel *twitchChannel;
@ -56,11 +53,11 @@ private:
void parseHighlights(bool isPastMsg);
void appendTwitchEmote(const QString &emote,
std::vector<std::pair<int, EmotePtr>> &vec);
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec);
Outcome tryAppendEmote(const EmoteName &name);
void addWords(const QStringList &words,
const std::vector<std::pair<int, EmotePtr>> &twitchEmotes);
const std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes);
void addTextOrEmoji(EmotePtr emote);
void addTextOrEmoji(const QString &value);
@ -72,7 +69,7 @@ private:
bool hasBits_ = false;
QColor usernameColor_;
const QString originalMessage_;
QString originalMessage_;
bool senderIsBroadcaster{};
const bool action_ = false;

View file

@ -116,6 +116,9 @@ public:
BoolSetting enableUnshortLinks = {"/links/unshortLinks", false};
BoolSetting enableLowercaseLink = {"/links/linkLowercase", true};
/// Ignored phrases
QStringSetting ignoredPhraseReplace = {"/ignore/ignoredPhraseReplace", "***"};
/// Ingored Users
BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers",
true};

View file

@ -25,8 +25,7 @@
namespace chatterino {
static void addPhrasesTab(LayoutCreator<QVBoxLayout> box);
static void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> box,
QStringListModel &model);
static void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> box, QStringListModel &model);
IgnoresPage::IgnoresPage()
: SettingsPage("Ignores", "")
@ -36,8 +35,7 @@ IgnoresPage::IgnoresPage()
auto tabs = layout.emplace<QTabWidget>();
addPhrasesTab(tabs.appendTab(new QVBoxLayout, "Phrases"));
addUsersTab(*this, tabs.appendTab(new QVBoxLayout, "Users"),
this->userListModel_);
addUsersTab(*this, tabs.appendTab(new QVBoxLayout, "Users"), this->userListModel_);
auto label = layout.emplace<QLabel>(INFO);
label->setWordWrap(true);
@ -47,14 +45,10 @@ IgnoresPage::IgnoresPage()
void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
{
EditableModelView *view =
layout
.emplace<EditableModelView>(getApp()->ignores->createModel(nullptr))
.getElement();
view->setTitles({"Pattern", "Regex"});
view->getTableView()->horizontalHeader()->setSectionResizeMode(
QHeaderView::Fixed);
view->getTableView()->horizontalHeader()->setSectionResizeMode(
0, QHeaderView::Stretch);
layout.emplace<EditableModelView>(getApp()->ignores->createModel(nullptr)).getElement();
view->setTitles({"Pattern", "Regex", "Case Sensitive", "Block", "Replacement"});
view->getTableView()->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
view->getTableView()->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
QTimer::singleShot(1, [view] {
view->getTableView()->resizeColumnsToContents();
@ -62,12 +56,12 @@ void addPhrasesTab(LayoutCreator<QVBoxLayout> layout)
});
view->addButtonPressed.connect([] {
getApp()->ignores->phrases.appendItem(IgnorePhrase{"my phrase", false});
getApp()->ignores->phrases.appendItem(IgnorePhrase{
"my phrase", false, false, getSettings()->ignoredPhraseReplace.getValue(), true});
});
}
void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users,
QStringListModel &userModel)
void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users, QStringListModel &userModel)
{
users.append(page.createCheckBox("Enable twitch ignored users",
getSettings()->enableTwitchIgnoredUsers));