added brace wrapping after if and for

This commit is contained in:
fourtf 2018-10-21 13:43:02 +02:00
parent c6e1ec3c71
commit e259b9e39f
138 changed files with 4738 additions and 2237 deletions

View file

@ -4,7 +4,7 @@ AccessModifierOffset: -1
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignEscapedNewlinesLeft: true AlignEscapedNewlinesLeft: true
AllowShortFunctionsOnASingleLine: false AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: true AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: false AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: false
@ -12,10 +12,10 @@ BasedOnStyle: Google
BraceWrapping: { BraceWrapping: {
AfterNamespace: 'false' AfterNamespace: 'false'
AfterClass: 'true' AfterClass: 'true'
BeforeElse: 'false' BeforeElse: 'true'
AfterControlStatement: 'false' AfterControlStatement: 'true'
AfterFunction: 'true' AfterFunction: 'true'
BeforeCatch: 'false' BeforeCatch: 'true'
} }
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BreakConstructorInitializersBeforeComma: true BreakConstructorInitializersBeforeComma: true

View file

@ -75,7 +75,8 @@ void Application::initialize(Settings &settings, Paths &paths)
assert(isAppInitialized == false); assert(isAppInitialized == false);
isAppInitialized = true; isAppInitialized = true;
for (auto &singleton : this->singletons_) { for (auto &singleton : this->singletons_)
{
singleton->initialize(settings, paths); singleton->initialize(settings, paths);
} }
@ -98,7 +99,8 @@ int Application::run(QApplication &qtApp)
void Application::save() void Application::save()
{ {
for (auto &singleton : this->singletons_) { for (auto &singleton : this->singletons_)
{
singleton->save(); singleton->save();
} }
} }
@ -132,7 +134,8 @@ void Application::initPubsub()
[this](const auto &action) { [this](const auto &action) {
auto chan = auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID); this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }
@ -147,7 +150,8 @@ void Application::initPubsub()
[this](const auto &action) { [this](const auto &action) {
auto chan = auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID); this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }
@ -158,7 +162,8 @@ void Application::initPubsub()
: "off") : "off")
.arg(action.getModeName()); .arg(action.getModeName());
if (action.duration > 0) { if (action.duration > 0)
{
text.append(" (" + QString::number(action.duration) + text.append(" (" + QString::number(action.duration) +
" seconds)"); " seconds)");
} }
@ -171,16 +176,20 @@ void Application::initPubsub()
[this](const auto &action) { [this](const auto &action) {
auto chan = auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID); this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }
QString text; QString text;
if (action.modded) { if (action.modded)
{
text = QString("%1 modded %2") text = QString("%1 modded %2")
.arg(action.source.name, action.target.name); .arg(action.source.name, action.target.name);
} else { }
else
{
text = QString("%1 unmodded %2") text = QString("%1 unmodded %2")
.arg(action.source.name, action.target.name); .arg(action.source.name, action.target.name);
} }
@ -194,7 +203,8 @@ void Application::initPubsub()
auto chan = auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID); this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }
@ -211,7 +221,8 @@ void Application::initPubsub()
auto chan = auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID); this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }

View file

@ -27,11 +27,13 @@ namespace {
void runLoop(NativeMessagingClient &client) void runLoop(NativeMessagingClient &client)
{ {
while (true) { while (true)
{
char size_c[4]; char size_c[4];
std::cin.read(size_c, 4); std::cin.read(size_c, 4);
if (std::cin.eof()) break; if (std::cin.eof())
break;
auto size = *reinterpret_cast<uint32_t *>(size_c); auto size = *reinterpret_cast<uint32_t *>(size_c);
@ -72,7 +74,8 @@ void runBrowserExtensionHost()
QTimer timer; QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&ping] { QObject::connect(&timer, &QTimer::timeout, [&ping] {
if (!ping.exchange(false)) { if (!ping.exchange(false))
{
_Exit(0); _Exit(0);
} }
}); });

View file

@ -80,10 +80,14 @@ namespace {
#ifndef C_DISABLE_CRASH_DIALOG #ifndef C_DISABLE_CRASH_DIALOG
LastRunCrashDialog dialog; LastRunCrashDialog dialog;
switch (dialog.exec()) { switch (dialog.exec())
case QDialog::Accepted: { {
}; break; case QDialog::Accepted:
default: { {
};
break;
default:
{
_exit(0); _exit(0);
} }
} }
@ -120,9 +124,12 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
auto runningPath = auto runningPath =
paths.miscDirectory + "/running_" + paths.applicationFilePathHash; paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
if (QFile::exists(runningPath)) { if (QFile::exists(runningPath))
{
showLastCrashDialog(); showLastCrashDialog();
} else { }
else
{
createRunningFile(runningPath); createRunningFile(runningPath);
} }
@ -138,7 +145,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
chatterino::NetworkManager::deinit(); chatterino::NetworkManager::deinit();
#ifdef USEWINSDK #ifdef USEWINSDK
// flushing windows clipboard to keep copied messages // flushing windows clipboard to keep copied messages
flushClipboard(); flushClipboard();
#endif #endif

View file

@ -3,7 +3,8 @@
namespace chatterino { namespace chatterino {
class Resources2 : public Singleton { class Resources2 : public Singleton
{
public: public:
Resources2(); Resources2();

View file

@ -65,17 +65,20 @@ void Channel::addMessage(MessagePtr message,
MessagePtr deleted; MessagePtr deleted;
const QString &username = message->loginName; const QString &username = message->loginName;
if (!username.isEmpty()) { if (!username.isEmpty())
{
// TODO: Add recent chatters display name // TODO: Add recent chatters display name
this->addRecentChatter(message); this->addRecentChatter(message);
} }
// FOURTF: change this when adding more providers // FOURTF: change this when adding more providers
if (this->isTwitchChannel()) { if (this->isTwitchChannel())
{
app->logging->addMessage(this->name_, message); app->logging->addMessage(this->name_, message);
} }
if (this->messages_.pushBack(message, deleted)) { if (this->messages_.pushBack(message, deleted))
{
this->messageRemovedFromStart.invoke(deleted); this->messageRemovedFromStart.invoke(deleted);
} }
@ -93,15 +96,18 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
QTime minimumTime = QTime::currentTime().addSecs(-5); QTime minimumTime = QTime::currentTime().addSecs(-5);
for (int i = snapshotLength - 1; i >= end; --i) { for (int i = snapshotLength - 1; i >= end; --i)
{
auto &s = snapshot[i]; auto &s = snapshot[i];
if (s->parseTime < minimumTime) { if (s->parseTime < minimumTime)
{
break; break;
} }
if (s->flags.has(MessageFlag::Untimeout) && if (s->flags.has(MessageFlag::Untimeout) &&
s->timeoutUser == message->timeoutUser) { s->timeoutUser == message->timeoutUser)
{
break; break;
} }
@ -139,17 +145,20 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
} }
// disable the messages from the user // disable the messages from the user
for (int i = 0; i < snapshotLength; i++) { for (int i = 0; i < snapshotLength; i++)
{
auto &s = snapshot[i]; auto &s = snapshot[i];
if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) && if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) &&
s->loginName == message->timeoutUser) { s->loginName == message->timeoutUser)
{
// FOURTF: disabled for now // FOURTF: disabled for now
// PAJLADA: Shitty solution described in Message.hpp // PAJLADA: Shitty solution described in Message.hpp
s->flags.set(MessageFlag::Disabled); s->flags.set(MessageFlag::Disabled);
} }
} }
if (addMessage) { if (addMessage)
{
this->addMessage(message); this->addMessage(message);
} }
@ -161,10 +170,11 @@ void Channel::disableAllMessages()
{ {
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot(); LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength(); int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) { for (int i = 0; i < snapshotLength; i++)
{
auto &message = snapshot[i]; auto &message = snapshot[i];
if (message->flags.hasAny( if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout}))
{MessageFlag::System, MessageFlag::Timeout})) { {
continue; continue;
} }
@ -178,7 +188,8 @@ void Channel::addMessagesAtStart(std::vector<MessagePtr> &_messages)
std::vector<MessagePtr> addedMessages = std::vector<MessagePtr> addedMessages =
this->messages_.pushFront(_messages); this->messages_.pushFront(_messages);
if (addedMessages.size() != 0) { if (addedMessages.size() != 0)
{
this->messagesAddedAtStart.invoke(addedMessages); this->messagesAddedAtStart.invoke(addedMessages);
} }
} }
@ -187,7 +198,8 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
{ {
int index = this->messages_.replaceItem(message, replacement); int index = this->messages_.replaceItem(message, replacement);
if (index >= 0) { if (index >= 0)
{
this->messageReplaced.invoke((size_t)index, replacement); this->messageReplaced.invoke((size_t)index, replacement);
} }
} }
@ -276,9 +288,12 @@ pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
Channel::Type IndirectChannel::getType() Channel::Type IndirectChannel::getType()
{ {
if (this->data_->type == Channel::Type::Direct) { if (this->data_->type == Channel::Type::Direct)
{
return this->get()->getType(); return this->get()->getType();
} else { }
else
{
return this->data_->type; return this->data_->type;
} }
} }

View file

@ -33,14 +33,16 @@ bool CompletionModel::TaggedString::isEmote() const
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
{ {
if (this->isEmote() != that.isEmote()) { if (this->isEmote() != that.isEmote())
{
return this->isEmote(); return this->isEmote();
} }
// try comparing insensitively, if they are the same then senstively // try comparing insensitively, if they are the same then senstively
// (fixes order of LuL and LUL) // (fixes order of LuL and LUL)
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive); int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) return this->string > that.string; if (k == 0)
return this->string > that.string;
return k < 0; return k < 0;
} }
@ -79,17 +81,21 @@ void CompletionModel::refresh(const QString &prefix)
std::lock_guard<std::mutex> guard(this->itemsMutex_); std::lock_guard<std::mutex> guard(this->itemsMutex_);
this->items_.clear(); this->items_.clear();
if (prefix.length() < 2) return; if (prefix.length() < 2)
return;
auto addString = [&](const QString &str, TaggedString::Type type) { auto addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive)) if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type); this->items_.emplace(str + " ", type);
}; };
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) { if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_))
{
// account emotes // account emotes
if (auto account = getApp()->accounts->twitch.getCurrent()) { if (auto account = getApp()->accounts->twitch.getCurrent())
for (const auto &emote : account->accessEmotes()->allEmoteNames) { {
for (const auto &emote : account->accessEmotes()->allEmoteNames)
{
// XXX: No way to discern between a twitch global emote and sub // XXX: No way to discern between a twitch global emote and sub
// emote right now // emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote); addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
@ -97,60 +103,73 @@ void CompletionModel::refresh(const QString &prefix)
} }
// Usernames // Usernames
if (prefix.length() >= UsernameSet::PrefixLength) { if (prefix.length() >= UsernameSet::PrefixLength)
{
auto usernames = channel->accessChatters(); auto usernames = channel->accessChatters();
QString usernamePrefix = prefix; QString usernamePrefix = prefix;
if (usernamePrefix.startsWith("@")) { if (usernamePrefix.startsWith("@"))
{
usernamePrefix.remove(0, 1); usernamePrefix.remove(0, 1);
for (const auto &name : for (const auto &name :
usernames->subrange(Prefix(usernamePrefix))) { usernames->subrange(Prefix(usernamePrefix)))
{
addString("@" + name, TaggedString::Type::Username); addString("@" + name, TaggedString::Type::Username);
} }
} else { }
else
{
for (const auto &name : for (const auto &name :
usernames->subrange(Prefix(usernamePrefix))) { usernames->subrange(Prefix(usernamePrefix)))
{
addString(name, TaggedString::Type::Username); addString(name, TaggedString::Type::Username);
} }
} }
} }
// Bttv Global // Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) { for (auto &emote : *channel->globalBttv().emotes())
{
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote); addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
} }
// Ffz Global // Ffz Global
for (auto &emote : *channel->globalFfz().emotes()) { for (auto &emote : *channel->globalFfz().emotes())
{
addString(emote.first.string, TaggedString::Type::FFZChannelEmote); addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
} }
// Bttv Channel // Bttv Channel
for (auto &emote : *channel->bttvEmotes()) { for (auto &emote : *channel->bttvEmotes())
{
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
} }
// Ffz Channel // Ffz Channel
for (auto &emote : *channel->ffzEmotes()) { for (auto &emote : *channel->ffzEmotes())
{
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote); addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
} }
// Emojis // Emojis
if (prefix.startsWith(":")) { if (prefix.startsWith(":"))
{
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes; const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) { for (auto &m : emojiShortCodes)
{
addString(":" + m + ":", TaggedString::Type::Emoji); addString(":" + m + ":", TaggedString::Type::Emoji);
} }
} }
// Commands // Commands
for (auto &command : getApp()->commands->items.getVector()) { for (auto &command : getApp()->commands->items.getVector())
{
addString(command.name, TaggedString::Command); addString(command.name, TaggedString::Command);
} }
for (auto &command : for (auto &command : getApp()->commands->getDefaultTwitchCommandList())
getApp()->commands->getDefaultTwitchCommandList()) { {
addString(command, TaggedString::Command); addString(command, TaggedString::Command);
} }
} }

View file

@ -21,7 +21,8 @@ public:
QMutexLocker lock(&this->mutex); QMutexLocker lock(&this->mutex);
auto a = this->data.find(name); auto a = this->data.find(name);
if (a == this->data.end()) { if (a == this->data.end())
{
return false; return false;
} }
@ -35,7 +36,8 @@ public:
QMutexLocker lock(&this->mutex); QMutexLocker lock(&this->mutex);
auto a = this->data.find(name); auto a = this->data.find(name);
if (a == this->data.end()) { if (a == this->data.end())
{
TValue value = addLambda(); TValue value = addLambda();
this->data.insert(name, value); this->data.insert(name, value);
return value; return value;
@ -72,7 +74,8 @@ public:
QMapIterator<TKey, TValue> it(this->data); QMapIterator<TKey, TValue> it(this->data);
while (it.hasNext()) { while (it.hasNext())
{
it.next(); it.next();
func(it.key(), it.value()); func(it.key(), it.value());
} }
@ -84,7 +87,8 @@ public:
QMutableMapIterator<TKey, TValue> it(this->data); QMutableMapIterator<TKey, TValue> it(this->data);
while (it.hasNext()) { while (it.hasNext())
{
it.next(); it.next();
func(it.key(), it.value()); func(it.key(), it.value());
} }

View file

@ -48,16 +48,21 @@ void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
void DownloadManager::onFinished(QNetworkReply *reply) void DownloadManager::onFinished(QNetworkReply *reply)
{ {
switch (reply->error()) { switch (reply->error())
case QNetworkReply::NoError: { {
case QNetworkReply::NoError:
{
qDebug("file is downloaded successfully."); qDebug("file is downloaded successfully.");
} break; }
default: { break;
default:
{
qDebug() << reply->errorString().toLatin1(); qDebug() << reply->errorString().toLatin1();
}; };
} }
if (file->isOpen()) { if (file->isOpen())
{
file->close(); file->close();
file->deleteLater(); file->deleteLater();
} }
@ -71,7 +76,8 @@ void DownloadManager::onReadyRead()
void DownloadManager::onReplyFinished() void DownloadManager::onReplyFinished()
{ {
if (file->isOpen()) { if (file->isOpen())
{
file->close(); file->close();
file->deleteLater(); file->deleteLater();
} }

View file

@ -20,7 +20,8 @@ public:
FlagsEnum(std::initializer_list<T> flags) FlagsEnum(std::initializer_list<T> flags)
{ {
for (auto flag : flags) { for (auto flag : flags)
{
this->set(flag); this->set(flag);
} }
} }

View file

@ -20,12 +20,14 @@ NetworkData::~NetworkData()
QString NetworkData::getHash() QString NetworkData::getHash()
{ {
if (this->hash_.isEmpty()) { if (this->hash_.isEmpty())
{
QByteArray bytes; QByteArray bytes;
bytes.append(this->request_.url().toString()); bytes.append(this->request_.url().toString());
for (const auto &header : this->request_.rawHeaderList()) { for (const auto &header : this->request_.rawHeaderList())
{
bytes.append(header); bytes.append(header);
} }
@ -40,10 +42,12 @@ QString NetworkData::getHash()
void NetworkData::writeToCache(const QByteArray &bytes) void NetworkData::writeToCache(const QByteArray &bytes)
{ {
if (this->useQuickLoadCache_) { if (this->useQuickLoadCache_)
{
QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash()); QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) { if (cachedFile.open(QIODevice::WriteOnly))
{
cachedFile.write(bytes); cachedFile.write(bytes);
cachedFile.close(); cachedFile.close();

View file

@ -93,7 +93,8 @@ void NetworkRequest::makeAuthorizedV5(const QString &clientID,
{ {
this->setRawHeader("Client-ID", clientID); this->setRawHeader("Client-ID", clientID);
this->setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); this->setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
if (!oauthToken.isEmpty()) { if (!oauthToken.isEmpty())
{
this->setRawHeader("Authorization", "OAuth " + oauthToken); this->setRawHeader("Authorization", "OAuth " + oauthToken);
} }
} }
@ -112,34 +113,45 @@ void NetworkRequest::execute()
{ {
this->executed_ = true; this->executed_ = true;
switch (this->data->requestType_) { switch (this->data->requestType_)
case NetworkRequestType::Get: { {
case NetworkRequestType::Get:
{
// Get requests try to load from cache, then perform the request // Get requests try to load from cache, then perform the request
if (this->data->useQuickLoadCache_) { if (this->data->useQuickLoadCache_)
if (this->tryLoadCachedFile()) { {
if (this->tryLoadCachedFile())
{
// Successfully loaded from cache // Successfully loaded from cache
return; return;
} }
} }
this->doRequest(); this->doRequest();
} break; }
break;
case NetworkRequestType::Put: { case NetworkRequestType::Put:
{
// Put requests cannot be cached, therefore the request is called // Put requests cannot be cached, therefore the request is called
// immediately // immediately
this->doRequest(); this->doRequest();
} break; }
break;
case NetworkRequestType::Delete: { case NetworkRequestType::Delete:
{
// Delete requests cannot be cached, therefore the request is called // Delete requests cannot be cached, therefore the request is called
// immediately // immediately
this->doRequest(); this->doRequest();
} break; }
break;
default: { default:
{
log("[Execute] Unhandled request type"); log("[Execute] Unhandled request type");
} break; }
break;
} }
} }
@ -153,12 +165,14 @@ Outcome NetworkRequest::tryLoadCachedFile()
QFile cachedFile(getPaths()->cacheDirectory() + "/" + QFile cachedFile(getPaths()->cacheDirectory() + "/" +
this->data->getHash()); this->data->getHash());
if (!cachedFile.exists()) { if (!cachedFile.exists())
{
// File didn't exist // File didn't exist
return Failure; return Failure;
} }
if (!cachedFile.open(QIODevice::ReadOnly)) { if (!cachedFile.open(QIODevice::ReadOnly))
{
// File could not be opened // File could not be opened
return Failure; return Failure;
} }
@ -190,7 +204,8 @@ void NetworkRequest::doRequest()
auto onUrlRequested = [data = this->data, timer = this->timer, auto onUrlRequested = [data = this->data, timer = this->timer,
worker]() mutable { worker]() mutable {
auto reply = [&]() -> QNetworkReply * { auto reply = [&]() -> QNetworkReply * {
switch (data->requestType_) { switch (data->requestType_)
{
case NetworkRequestType::Get: case NetworkRequestType::Get:
return NetworkManager::accessManager.get(data->request_); return NetworkManager::accessManager.get(data->request_);
@ -207,29 +222,35 @@ void NetworkRequest::doRequest()
} }
}(); }();
if (reply == nullptr) { if (reply == nullptr)
{
log("Unhandled request type"); log("Unhandled request type");
return; return;
} }
if (timer->isStarted()) { if (timer->isStarted())
{
timer->onTimeout(worker, [reply, data]() { timer->onTimeout(worker, [reply, data]() {
log("Aborted!"); log("Aborted!");
reply->abort(); reply->abort();
if (data->onError_) { if (data->onError_)
{
data->onError_(-2); data->onError_(-2);
} }
}); });
} }
if (data->onReplyCreated_) { if (data->onReplyCreated_)
{
data->onReplyCreated_(reply); data->onReplyCreated_(reply);
} }
auto handleReply = [data, timer, reply]() mutable { auto handleReply = [data, timer, reply]() mutable {
// TODO(pajlada): A reply was received, kill the timeout timer // TODO(pajlada): A reply was received, kill the timeout timer
if (reply->error() != QNetworkReply::NetworkError::NoError) { if (reply->error() != QNetworkReply::NetworkError::NoError)
if (data->onError_) { {
if (data->onError_)
{
data->onError_(reply->error()); data->onError_(reply->error());
} }
return; return;
@ -242,7 +263,8 @@ void NetworkRequest::doRequest()
DebugCount::increase("http request success"); DebugCount::increase("http request success");
// log("starting {}", data->request_.url().toString()); // log("starting {}", data->request_.url().toString());
if (data->onSuccess_) { if (data->onSuccess_)
{
if (data->executeConcurrently) if (data->executeConcurrently)
QtConcurrent::run( QtConcurrent::run(
[onSuccess = std::move(data->onSuccess_), [onSuccess = std::move(data->onSuccess_),
@ -255,7 +277,8 @@ void NetworkRequest::doRequest()
reply->deleteLater(); reply->deleteLater();
}; };
if (data->caller_ != nullptr) { if (data->caller_ != nullptr)
{
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_,
handleReply); handleReply);
QObject::connect(reply, &QNetworkReply::finished, worker, QObject::connect(reply, &QNetworkReply::finished, worker,
@ -264,7 +287,9 @@ void NetworkRequest::doRequest()
delete worker; delete worker;
}); });
} else { }
else
{
QObject::connect(reply, &QNetworkReply::finished, worker, QObject::connect(reply, &QNetworkReply::finished, worker,
[handleReply, worker]() mutable { [handleReply, worker]() mutable {
handleReply(); handleReply();

View file

@ -16,7 +16,8 @@ NetworkResult::NetworkResult(const QByteArray &data)
QJsonObject NetworkResult::parseJson() const QJsonObject NetworkResult::parseJson() const
{ {
QJsonDocument jsonDoc(QJsonDocument::fromJson(this->data_)); QJsonDocument jsonDoc(QJsonDocument::fromJson(this->data_));
if (jsonDoc.isNull()) { if (jsonDoc.isNull())
{
return QJsonObject{}; return QJsonObject{};
} }
@ -30,7 +31,8 @@ rapidjson::Document NetworkResult::parseRapidJson() const
rapidjson::ParseResult result = rapidjson::ParseResult result =
ret.Parse(this->data_.data(), this->data_.length()); ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone)
{
log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret; return ret;

View file

@ -10,7 +10,8 @@ namespace chatterino {
void NetworkTimer::start() void NetworkTimer::start()
{ {
if (this->timeoutMS_ <= 0) { if (this->timeoutMS_ <= 0)
{
return; return;
} }

View file

@ -45,7 +45,8 @@ public:
{ {
assertInGuiThread(); assertInGuiThread();
if (!this->itemsChangedTimer_.isActive()) { if (!this->itemsChangedTimer_.isActive())
{
this->itemsChangedTimer_.start(); this->itemsChangedTimer_.start();
} }
} }
@ -92,9 +93,12 @@ public:
void *caller = nullptr) override void *caller = nullptr) override
{ {
assertInGuiThread(); assertInGuiThread();
if (index == -1) { if (index == -1)
{
index = this->vector_.size(); index = this->vector_.size();
} else { }
else
{
assert(index >= 0 && index <= this->vector_.size()); assert(index >= 0 && index <= this->vector_.size());
} }

View file

@ -19,7 +19,8 @@ public:
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
, columnCount_(columnCount) , columnCount_(columnCount)
{ {
for (int i = 0; i < columnCount; i++) { for (int i = 0; i < columnCount; i++)
{
this->headerData_.emplace_back(); this->headerData_.emplace_back();
} }
} }
@ -29,7 +30,8 @@ public:
this->vector_ = vec; this->vector_ = vec;
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) { auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
if (args.caller == this) { if (args.caller == this)
{
return; return;
} }
// get row index // get row index
@ -50,7 +52,8 @@ public:
}; };
int i = 0; int i = 0;
for (const TVectorItem &item : vec->getVector()) { for (const TVectorItem &item : vec->getVector())
{
SignalVectorItemArgs<TVectorItem> args{item, i++, 0}; SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
insert(args); insert(args);
@ -59,7 +62,8 @@ public:
this->managedConnect(vec->itemInserted, insert); this->managedConnect(vec->itemInserted, insert);
this->managedConnect(vec->itemRemoved, [this](auto args) { this->managedConnect(vec->itemRemoved, [this](auto args) {
if (args.caller == this) { if (args.caller == this)
{
return; return;
} }
@ -76,7 +80,8 @@ public:
this->afterRemoved(args.item, items, row); this->afterRemoved(args.item, items, row);
for (QStandardItem *item : items) { for (QStandardItem *item : items)
{
delete item; delete item;
} }
}); });
@ -86,8 +91,10 @@ public:
virtual ~SignalVectorModel() virtual ~SignalVectorModel()
{ {
for (Row &row : this->rows_) { for (Row &row : this->rows_)
for (QStandardItem *item : row.items) { {
for (QStandardItem *item : row.items)
{
delete item; delete item;
} }
} }
@ -123,9 +130,12 @@ public:
rowItem.items[column]->setData(value, role); rowItem.items[column]->setData(value, role);
if (rowItem.isCustomRow) { if (rowItem.isCustomRow)
{
this->customRowSetData(rowItem.items, column, value, role, row); this->customRowSetData(rowItem.items, column, value, role, row);
} else { }
else
{
int vecRow = this->getVectorIndexFromModelIndex(row); int vecRow = this->getVectorIndexFromModelIndex(row);
this->vector_->removeItem(vecRow, this); this->vector_->removeItem(vecRow, this);
@ -141,14 +151,18 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, QVariant headerData(int section, Qt::Orientation orientation,
int role) const override int role) const override
{ {
if (orientation != Qt::Horizontal) { if (orientation != Qt::Horizontal)
{
return QVariant(); return QVariant();
} }
auto it = this->headerData_[section].find(role); auto it = this->headerData_[section].find(role);
if (it == this->headerData_[section].end()) { if (it == this->headerData_[section].end())
{
return QVariant(); return QVariant();
} else { }
else
{
return it.value(); return it.value();
} }
} }
@ -157,7 +171,8 @@ public:
const QVariant &value, const QVariant &value,
int role = Qt::DisplayRole) override int role = Qt::DisplayRole) override
{ {
if (orientation != Qt::Horizontal) { if (orientation != Qt::Horizontal)
{
return false; return false;
} }
@ -192,7 +207,8 @@ public:
bool removeRows(int row, int count, const QModelIndex &parent) override bool removeRows(int row, int count, const QModelIndex &parent) override
{ {
if (count != 1) { if (count != 1)
{
return false; return false;
} }
@ -258,7 +274,8 @@ protected:
std::vector<QStandardItem *> createRow() std::vector<QStandardItem *> createRow()
{ {
std::vector<QStandardItem *> row; std::vector<QStandardItem *> row;
for (int i = 0; i < this->columnCount_; i++) { for (int i = 0; i < this->columnCount_; i++)
{
row.push_back(new QStandardItem()); row.push_back(new QStandardItem());
} }
return row; return row;
@ -296,13 +313,16 @@ private:
{ {
int i = 0; int i = 0;
for (auto &row : this->rows_) { for (auto &row : this->rows_)
if (row.isCustomRow) { {
if (row.isCustomRow)
{
index--; index--;
continue; continue;
} }
if (i == index) { if (i == index)
{
return i; return i;
} }
i++; i++;
@ -316,12 +336,15 @@ private:
{ {
int i = 0; int i = 0;
for (auto &row : this->rows_) { for (auto &row : this->rows_)
if (row.isCustomRow) { {
if (row.isCustomRow)
{
index++; index++;
} }
if (i == index) { if (i == index)
{
return i; return i;
} }
i++; i++;

View file

@ -32,7 +32,8 @@ public:
~AccessGuard() ~AccessGuard()
{ {
if (this->isValid_) this->mutex_->unlock(); if (this->isValid_)
this->mutex_->unlock();
} }
T *operator->() const T *operator->() const

View file

@ -21,11 +21,13 @@ UsernameSet::ConstIterator UsernameSet::end() const
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
{ {
auto it = this->firstKeyForPrefix.find(prefix); auto it = this->firstKeyForPrefix.find(prefix);
if (it != this->firstKeyForPrefix.end()) { if (it != this->firstKeyForPrefix.end())
{
auto start = this->items.find(it->second); auto start = this->items.find(it->second);
auto end = start; auto end = start;
while (end != this->items.end() && prefix.isStartOf(*end)) { while (end != this->items.end() && prefix.isStartOf(*end))
{
end++; end++;
} }
return {start, end}; return {start, end};
@ -57,7 +59,8 @@ void UsernameSet::insertPrefix(const QString &value)
{ {
auto &string = this->firstKeyForPrefix[Prefix(value)]; auto &string = this->firstKeyForPrefix[Prefix(value)];
if (string.isNull() || value < string) string = value; if (string.isNull() || value < string)
string = value;
} }
// //
@ -98,12 +101,17 @@ bool Prefix::operator==(const Prefix &other) const
bool Prefix::isStartOf(const QString &string) const bool Prefix::isStartOf(const QString &string) const
{ {
if (string.size() == 0) { if (string.size() == 0)
{
return this->first == QChar('\0') && this->second == QChar('\0'); return this->first == QChar('\0') && this->second == QChar('\0');
} else if (string.size() == 1) { }
else if (string.size() == 1)
{
return this->first == string[0].toLower() && return this->first == string[0].toLower() &&
this->second == QChar('\0'); this->second == QChar('\0');
} else { }
else
{
return this->first == string[0].toLower() && return this->first == string[0].toLower() &&
this->second == string[1].toLower(); this->second == string[1].toLower();
} }

View file

@ -10,7 +10,8 @@ Account::Account(ProviderId providerId)
static QString twitch("Twitch"); static QString twitch("Twitch");
this->category_ = [&]() { this->category_ = [&]() {
switch (providerId) { switch (providerId)
{
case ProviderId::Twitch: case ProviderId::Twitch:
return twitch; return twitch;
} }

View file

@ -14,7 +14,8 @@ AccountController::AccountController()
}); });
this->twitch.accounts.itemRemoved.connect([this](const auto &args) { this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) { if (args.caller != this)
{
auto &accs = this->twitch.accounts.getVector(); auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());
@ -24,14 +25,17 @@ AccountController::AccountController()
}); });
this->accounts_.itemRemoved.connect([this](const auto &args) { this->accounts_.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) { switch (args.item->getProviderId())
case ProviderId::Twitch: { {
case ProviderId::Twitch:
{
auto &accs = this->twitch.accounts.getVector(); auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());
this->twitch.accounts.removeItem(it - accs.begin(), this); this->twitch.accounts.removeItem(it - accs.begin(), this);
} break; }
break;
} }
}); });
} }

View file

@ -29,7 +29,8 @@ int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, std::vector<QStandardItem *> &row,
int proposedIndex) int proposedIndex)
{ {
if (this->categoryCount_[item->getCategory()]++ == 0) { if (this->categoryCount_[item->getCategory()]++ == 0)
{
auto row = this->createRow(); auto row = this->createRow();
setStringItem(row[0], item->getCategory(), false, false); setStringItem(row[0], item->getCategory(), false, false);
@ -49,10 +50,13 @@ void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
auto it = this->categoryCount_.find(item->getCategory()); auto it = this->categoryCount_.find(item->getCategory());
assert(it != this->categoryCount_.end()); assert(it != this->categoryCount_.end());
if (it->second <= 1) { if (it->second <= 1)
{
this->categoryCount_.erase(it); this->categoryCount_.erase(it);
this->removeCustomRow(index - 1); this->removeCustomRow(index - 1);
} else { }
else
{
it->second--; it->second--;
} }
} }

View file

@ -7,7 +7,8 @@ Command::Command(const QString &_text)
{ {
int index = _text.indexOf(' '); int index = _text.indexOf(' ');
if (index == -1) { if (index == -1)
{
this->name = _text; this->name = _text;
return; return;
} }

View file

@ -37,8 +37,10 @@ CommandController::CommandController()
auto addFirstMatchToMap = [this](auto args) { auto addFirstMatchToMap = [this](auto args) {
this->commandsMap_.remove(args.item.name); this->commandsMap_.remove(args.item.name);
for (const Command &cmd : this->items.getVector()) { for (const Command &cmd : this->items.getVector())
if (cmd.name == args.item.name) { {
if (cmd.name == args.item.name)
{
this->commandsMap_[cmd.name] = cmd; this->commandsMap_[cmd.name] = cmd;
break; break;
} }
@ -59,15 +61,18 @@ void CommandController::load(Paths &paths)
this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt"); this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt");
QFile textFile(this->filePath_); QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::ReadOnly)) { if (!textFile.open(QIODevice::ReadOnly))
{
// No commands file created yet // No commands file created yet
return; return;
} }
QList<QByteArray> test = textFile.readAll().split('\n'); QList<QByteArray> test = textFile.readAll().split('\n');
for (const auto &command : test) { for (const auto &command : test)
if (command.isEmpty()) { {
if (command.isEmpty())
{
continue; continue;
} }
@ -80,13 +85,15 @@ void CommandController::load(Paths &paths)
void CommandController::save() void CommandController::save()
{ {
QFile textFile(this->filePath_); QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::WriteOnly)) { if (!textFile.open(QIODevice::WriteOnly))
{
log("[CommandController::saveCommands] Unable to open {} for writing", log("[CommandController::saveCommands] Unable to open {} for writing",
this->filePath_); this->filePath_);
return; return;
} }
for (const Command &cmd : this->items.getVector()) { for (const Command &cmd : this->items.getVector())
{
textFile.write((cmd.toString() + "\n").toUtf8()); textFile.write((cmd.toString() + "\n").toUtf8());
} }
@ -110,16 +117,20 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
{ {
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
if (words.length() == 0) { if (words.length() == 0)
{
return text; return text;
} }
QString commandName = words[0]; QString commandName = words[0];
// works in a valid twitch channel and /whispers, etc... // works in a valid twitch channel and /whispers, etc...
if (!dryRun && channel->isTwitchChannel()) { if (!dryRun && channel->isTwitchChannel())
if (commandName == "/w") { {
if (words.length() <= 2) { if (commandName == "/w")
{
if (words.length() <= 2)
{
return ""; return "";
} }
@ -139,7 +150,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
QString rest = ""; QString rest = "";
for (int i = 2; i < words.length(); i++) { for (int i = 2; i < words.length(); i++)
{
rest += words[i] + " "; rest += words[i] + " ";
} }
@ -151,7 +163,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
app->twitch.server->sendMessage("jtv", text); app->twitch.server->sendMessage("jtv", text);
if (getSettings()->inlineWhispers) { if (getSettings()->inlineWhispers)
{
app->twitch.server->forEachChannel( app->twitch.server->forEachChannel(
[&messagexD](ChannelPtr _channel) { [&messagexD](ChannelPtr _channel) {
_channel->addMessage(messagexD); _channel->addMessage(messagexD);
@ -166,14 +179,18 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()); auto *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
// works only in a valid twitch channel // works only in a valid twitch channel
if (!dryRun && twitchChannel != nullptr) { if (!dryRun && twitchChannel != nullptr)
if (commandName == "/debug-args") { {
if (commandName == "/debug-args")
{
QString msg = QApplication::instance()->arguments().join(' '); QString msg = QApplication::instance()->arguments().join(' ');
channel->addMessage(makeSystemMessage(msg)); channel->addMessage(makeSystemMessage(msg));
return ""; return "";
} else if (commandName == "/uptime") { }
else if (commandName == "/uptime")
{
const auto &streamStatus = twitchChannel->accessStreamStatus(); const auto &streamStatus = twitchChannel->accessStreamStatus();
QString messageText = streamStatus->live QString messageText = streamStatus->live
@ -183,8 +200,11 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
channel->addMessage(makeSystemMessage(messageText)); channel->addMessage(makeSystemMessage(messageText));
return ""; return "";
} else if (commandName == "/ignore") { }
if (words.size() < 2) { else if (commandName == "/ignore")
{
if (words.size() < 2)
{
channel->addMessage( channel->addMessage(
makeSystemMessage("Usage: /ignore [user]")); makeSystemMessage("Usage: /ignore [user]"));
return ""; return "";
@ -194,7 +214,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto user = app->accounts->twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"You must be logged in to ignore someone")); "You must be logged in to ignore someone"));
return ""; return "";
@ -206,8 +227,11 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
}); });
return ""; return "";
} else if (commandName == "/unignore") { }
if (words.size() < 2) { else if (commandName == "/unignore")
{
if (words.size() < 2)
{
channel->addMessage( channel->addMessage(
makeSystemMessage("Usage: /unignore [user]")); makeSystemMessage("Usage: /unignore [user]"));
return ""; return "";
@ -217,7 +241,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto user = app->accounts->twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"You must be logged in to ignore someone")); "You must be logged in to ignore someone"));
return ""; return "";
@ -229,8 +254,11 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
}); });
return ""; return "";
} else if (commandName == "/follow") { }
if (words.size() < 2) { else if (commandName == "/follow")
{
if (words.size() < 2)
{
channel->addMessage( channel->addMessage(
makeSystemMessage("Usage: /follow [user]")); makeSystemMessage("Usage: /follow [user]"));
return ""; return "";
@ -240,7 +268,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto user = app->accounts->twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"You must be logged in to follow someone")); "You must be logged in to follow someone"));
return ""; return "";
@ -248,7 +277,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
TwitchApi::findUserId( TwitchApi::findUserId(
target, [user, channel, target](QString userId) { target, [user, channel, target](QString userId) {
if (userId.isEmpty()) { if (userId.isEmpty())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"User " + target + " could not be followed!")); "User " + target + " could not be followed!"));
return; return;
@ -260,8 +290,11 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
}); });
return ""; return "";
} else if (commandName == "/unfollow") { }
if (words.size() < 2) { else if (commandName == "/unfollow")
{
if (words.size() < 2)
{
channel->addMessage( channel->addMessage(
makeSystemMessage("Usage: /unfollow [user]")); makeSystemMessage("Usage: /unfollow [user]"));
return ""; return "";
@ -271,7 +304,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto user = app->accounts->twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"You must be logged in to follow someone")); "You must be logged in to follow someone"));
return ""; return "";
@ -279,7 +313,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
TwitchApi::findUserId( TwitchApi::findUserId(
target, [user, channel, target](QString userId) { target, [user, channel, target](QString userId) {
if (userId.isEmpty()) { if (userId.isEmpty())
{
channel->addMessage(makeSystemMessage( channel->addMessage(makeSystemMessage(
"User " + target + " could not be followed!")); "User " + target + " could not be followed!"));
return; return;
@ -291,8 +326,11 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
}); });
return ""; return "";
} else if (commandName == "/logs") { }
if (words.size() < 2) { else if (commandName == "/logs")
{
if (words.size() < 2)
{
channel->addMessage( channel->addMessage(
makeSystemMessage("Usage: /logs [user] (channel)")); makeSystemMessage("Usage: /logs [user] (channel)"));
return ""; return "";
@ -302,24 +340,34 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
auto logs = new LogsPopup(); auto logs = new LogsPopup();
QString target; QString target;
if (words.at(1).at(0) == "@") { if (words.at(1).at(0) == "@")
{
target = words.at(1).mid(1); target = words.at(1).mid(1);
} else { }
else
{
target = words.at(1); target = words.at(1);
} }
if (words.size() == 3) { if (words.size() == 3)
{
QString channelName = words.at(2); QString channelName = words.at(2);
if (words.at(2).at(0) == "#") { if (words.at(2).at(0) == "#")
{
channelName = words.at(2).mid(1); channelName = words.at(2).mid(1);
} }
auto logsChannel = auto logsChannel =
app->twitch.server->getChannelOrEmpty(channelName); app->twitch.server->getChannelOrEmpty(channelName);
if (logsChannel == nullptr) { if (logsChannel == nullptr)
} else { {
}
else
{
logs->setInfo(logsChannel, target); logs->setInfo(logsChannel, target);
} }
} else { }
else
{
logs->setInfo(channel, target); logs->setInfo(channel, target);
} }
logs->setAttribute(Qt::WA_DeleteOnClose); logs->setAttribute(Qt::WA_DeleteOnClose);
@ -330,7 +378,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
// check if custom command exists // check if custom command exists
auto it = this->commandsMap_.find(commandName); auto it = this->commandsMap_.find(commandName);
if (it == this->commandsMap_.end()) { if (it == this->commandsMap_.end())
{
return text; return text;
} }
@ -352,11 +401,13 @@ QString CommandController::execCustomCommand(const QStringList &words,
auto globalMatch = parseCommand.globalMatch(command.func); auto globalMatch = parseCommand.globalMatch(command.func);
int matchOffset = 0; int matchOffset = 0;
while (true) { while (true)
{
QRegularExpressionMatch match = QRegularExpressionMatch match =
parseCommand.match(command.func, matchOffset); parseCommand.match(command.func, matchOffset);
if (!match.hasMatch()) { if (!match.hasMatch())
{
break; break;
} }
@ -373,32 +424,40 @@ QString CommandController::execCustomCommand(const QStringList &words,
bool ok; bool ok;
int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok); int wordIndex = wordIndexMatch.replace("=", "").toInt(&ok);
if (!ok || wordIndex == 0) { if (!ok || wordIndex == 0)
{
result += "{" + match.captured(3) + "}"; result += "{" + match.captured(3) + "}";
continue; continue;
} }
if (words.length() <= wordIndex) { if (words.length() <= wordIndex)
{
continue; continue;
} }
if (plus) { if (plus)
{
bool first = true; bool first = true;
for (int i = wordIndex; i < words.length(); i++) { for (int i = wordIndex; i < words.length(); i++)
if (!first) { {
if (!first)
{
result += " "; result += " ";
} }
result += words[i]; result += words[i];
first = false; first = false;
} }
} else { }
else
{
result += words[wordIndex]; result += words[wordIndex];
} }
} }
result += command.func.mid(lastCaptureEnd); result += command.func.mid(lastCaptureEnd);
if (result.size() > 0 && result.at(0) == '{') { if (result.size() > 0 && result.at(0) == '{')
{
result = result.mid(1); result = result.mid(1);
} }

View file

@ -46,8 +46,10 @@ public:
bool isMatch(const QString &subject) const bool isMatch(const QString &subject) const
{ {
if (this->isRegex()) { if (this->isRegex())
if (this->isValidRegex()) { {
if (this->isValidRegex())
{
return this->regex_.match(subject).hasMatch(); return this->regex_.match(subject).hasMatch();
} }
@ -91,7 +93,8 @@ namespace Settings {
QString pattern; QString pattern;
bool isRegex = false; bool isRegex = false;
if (!value.IsObject()) { if (!value.IsObject())
{
return chatterino::HighlightBlacklistUser(pattern, isRegex); return chatterino::HighlightBlacklistUser(pattern, isRegex);
} }

View file

@ -17,7 +17,8 @@ void HighlightController::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;
for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue()) { for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
{
this->phrases.appendItem(phrase); this->phrases.appendItem(phrase);
} }
@ -26,7 +27,8 @@ void HighlightController::initialize(Settings &settings, Paths &paths)
}); });
for (const HighlightBlacklistUser &blacklistedUser : for (const HighlightBlacklistUser &blacklistedUser :
this->blacklistSetting_.getValue()) { this->blacklistSetting_.getValue())
{
this->blacklistedUsers.appendItem(blacklistedUser); this->blacklistedUsers.appendItem(blacklistedUser);
} }
@ -54,8 +56,10 @@ UserHighlightModel *HighlightController::createUserModel(QObject *parent)
bool HighlightController::isHighlightedUser(const QString &username) bool HighlightController::isHighlightedUser(const QString &username)
{ {
const auto &userItems = this->highlightedUsers.getVector(); const auto &userItems = this->highlightedUsers.getVector();
for (const auto &highlightedUser : userItems) { for (const auto &highlightedUser : userItems)
if (highlightedUser.isMatch(username)) { {
if (highlightedUser.isMatch(username))
{
return true; return true;
} }
} }
@ -76,8 +80,10 @@ bool HighlightController::blacklistContains(const QString &username)
{ {
std::vector<HighlightBlacklistUser> blacklistItems = std::vector<HighlightBlacklistUser> blacklistItems =
this->blacklistedUsers.getVector(); this->blacklistedUsers.getVector();
for (const auto &blacklistedUser : blacklistItems) { for (const auto &blacklistedUser : blacklistItems)
if (blacklistedUser.isMatch(username)) { {
if (blacklistedUser.isMatch(username))
{
return true; return true;
} }
} }

View file

@ -66,42 +66,63 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
int column, const QVariant &value, int column, const QVariant &value,
int role, int rowIndex) int role, int rowIndex)
{ {
switch (column) { switch (column)
case 0: { {
if (role == Qt::CheckStateRole) { case 0:
if (rowIndex == 0) { {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
{
getSettings()->enableSelfHighlight.setValue(value.toBool()); getSettings()->enableSelfHighlight.setValue(value.toBool());
} else if (rowIndex == 1) { }
else if (rowIndex == 1)
{
getSettings()->enableWhisperHighlight.setValue( getSettings()->enableWhisperHighlight.setValue(
value.toBool()); value.toBool());
} }
} }
} break; }
case 1: { break;
if (role == Qt::CheckStateRole) { case 1:
if (rowIndex == 0) { {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
{
getSettings()->enableSelfHighlightTaskbar.setValue( getSettings()->enableSelfHighlightTaskbar.setValue(
value.toBool()); value.toBool());
} else if (rowIndex == 1) { }
else if (rowIndex == 1)
{
getSettings()->enableWhisperHighlightTaskbar.setValue( getSettings()->enableWhisperHighlightTaskbar.setValue(
value.toBool()); value.toBool());
} }
} }
} break; }
case 2: { break;
if (role == Qt::CheckStateRole) { case 2:
if (rowIndex == 0) { {
if (role == Qt::CheckStateRole)
{
if (rowIndex == 0)
{
getSettings()->enableSelfHighlightSound.setValue( getSettings()->enableSelfHighlightSound.setValue(
value.toBool()); value.toBool());
} else if (rowIndex == 1) { }
else if (rowIndex == 1)
{
getSettings()->enableWhisperHighlightSound.setValue( getSettings()->enableWhisperHighlightSound.setValue(
value.toBool()); value.toBool());
} }
} }
} break; }
case 3: { break;
case 3:
{
// empty element // empty element
} break; }
break;
} }
} }

View file

@ -92,7 +92,8 @@ namespace Settings {
struct Deserialize<chatterino::HighlightPhrase> { struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value) static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{ {
if (!value.IsObject()) { if (!value.IsObject())
{
return chatterino::HighlightPhrase(QString(), true, false, return chatterino::HighlightPhrase(QString(), true, false,
false); false);
} }

View file

@ -12,7 +12,8 @@ void IgnoreController::initialize(Settings &, Paths &)
assert(!this->initialized_); assert(!this->initialized_);
this->initialized_ = true; this->initialized_ = true;
for (const IgnorePhrase &phrase : this->ignoresSetting_.getValue()) { for (const IgnorePhrase &phrase : this->ignoresSetting_.getValue())
{
this->phrases.appendItem(phrase); this->phrases.appendItem(phrase);
} }

View file

@ -35,10 +35,13 @@ public:
, replace_(replace) , replace_(replace)
, isCaseSensitive_(isCaseSensitive) , isCaseSensitive_(isCaseSensitive)
{ {
if (this->isCaseSensitive_) { if (this->isCaseSensitive_)
{
regex_.setPatternOptions( regex_.setPatternOptions(
QRegularExpression::UseUnicodePropertiesOption); QRegularExpression::UseUnicodePropertiesOption);
} else { }
else
{
regex_.setPatternOptions( regex_.setPatternOptions(
QRegularExpression::CaseInsensitiveOption | QRegularExpression::CaseInsensitiveOption |
QRegularExpression::UseUnicodePropertiesOption); QRegularExpression::UseUnicodePropertiesOption);
@ -101,14 +104,18 @@ public:
bool containsEmote() const bool containsEmote() const
{ {
if (!this->emotesChecked_) { if (!this->emotesChecked_)
{
const auto &accvec = const auto &accvec =
getApp()->accounts->twitch.accounts.getVector(); getApp()->accounts->twitch.accounts.getVector();
for (const auto &acc : accvec) { for (const auto &acc : accvec)
{
const auto &accemotes = *acc->accessEmotes(); const auto &accemotes = *acc->accessEmotes();
for (const auto &emote : accemotes.emotes) { for (const auto &emote : accemotes.emotes)
{
if (this->replace_.contains(emote.first.string, if (this->replace_.contains(emote.first.string,
Qt::CaseSensitive)) { Qt::CaseSensitive))
{
this->emotes_.emplace(emote.first, emote.second); this->emotes_.emplace(emote.first, emote.second);
} }
} }
@ -154,7 +161,8 @@ namespace Settings {
struct Deserialize<chatterino::IgnorePhrase> { struct Deserialize<chatterino::IgnorePhrase> {
static chatterino::IgnorePhrase get(const rapidjson::Value &value) static chatterino::IgnorePhrase get(const rapidjson::Value &value)
{ {
if (!value.IsObject()) { if (!value.IsObject())
{
return chatterino::IgnorePhrase( return chatterino::IgnorePhrase(
QString(), false, false, QString(), false, false,
::chatterino::getSettings() ::chatterino::getSettings()

View file

@ -33,23 +33,31 @@ ModerationAction::ModerationAction(const QString &action)
auto timeoutMatch = timeoutRegex.match(action); auto timeoutMatch = timeoutRegex.match(action);
if (timeoutMatch.hasMatch()) { if (timeoutMatch.hasMatch())
{
// if (multipleTimeouts > 1) { // if (multipleTimeouts > 1) {
// QString line1; // QString line1;
// QString line2; // QString line2;
int amount = timeoutMatch.captured(1).toInt(); int amount = timeoutMatch.captured(1).toInt();
if (amount < 60) { if (amount < 60)
{
this->line1_ = QString::number(amount); this->line1_ = QString::number(amount);
this->line2_ = "s"; this->line2_ = "s";
} else if (amount < 60 * 60) { }
else if (amount < 60 * 60)
{
this->line1_ = QString::number(amount / 60); this->line1_ = QString::number(amount / 60);
this->line2_ = "m"; this->line2_ = "m";
} else if (amount < 60 * 60 * 24) { }
else if (amount < 60 * 60 * 24)
{
this->line1_ = QString::number(amount / 60 / 60); this->line1_ = QString::number(amount / 60 / 60);
this->line2_ = "h"; this->line2_ = "h";
} else { }
else
{
this->line1_ = QString::number(amount / 60 / 60 / 24); this->line1_ = QString::number(amount / 60 / 60 / 24);
this->line2_ = "d"; this->line2_ = "d";
} }
@ -60,9 +68,13 @@ ModerationAction::ModerationAction(const QString &action)
// this->_moderationActions.emplace_back(app->resources->buttonTimeout, // this->_moderationActions.emplace_back(app->resources->buttonTimeout,
// str); // str);
// } // }
} else if (action.startsWith("/ban ")) { }
else if (action.startsWith("/ban "))
{
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban); this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
} else { }
else
{
QString xD = action; QString xD = action;
xD.replace(replaceRegex, ""); xD.replace(replaceRegex, "");

View file

@ -53,7 +53,8 @@ namespace Settings {
struct Deserialize<chatterino::ModerationAction> { struct Deserialize<chatterino::ModerationAction> {
static chatterino::ModerationAction get(const rapidjson::Value &value) static chatterino::ModerationAction get(const rapidjson::Value &value)
{ {
if (!value.IsObject()) { if (!value.IsObject())
{
return chatterino::ModerationAction(QString()); return chatterino::ModerationAction(QString());
} }

View file

@ -21,7 +21,8 @@ void ModerationActions::initialize(Settings &settings, Paths &paths)
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>( std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
"/moderation/actions"); "/moderation/actions");
for (auto &val : this->setting_->getValue()) { for (auto &val : this->setting_->getValue())
{
this->items.insertItem(val); this->items.insertItem(val);
} }

View file

@ -25,7 +25,8 @@ namespace chatterino {
void NotificationController::initialize(Settings &settings, Paths &paths) void NotificationController::initialize(Settings &settings, Paths &paths)
{ {
this->initialized_ = true; this->initialized_ = true;
for (const QString &channelName : this->twitchSetting_.getValue()) { for (const QString &channelName : this->twitchSetting_.getValue())
{
this->channelMap[Platform::Twitch].appendItem(channelName); this->channelMap[Platform::Twitch].appendItem(channelName);
} }
@ -55,9 +56,12 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
void NotificationController::updateChannelNotification( void NotificationController::updateChannelNotification(
const QString &channelName, Platform p) const QString &channelName, Platform p)
{ {
if (isChannelNotified(channelName, p)) { if (isChannelNotified(channelName, p))
{
removeChannelNotification(channelName, p); removeChannelNotification(channelName, p);
} else { }
else
{
addChannelNotification(channelName, p); addChannelNotification(channelName, p);
} }
} }
@ -65,8 +69,10 @@ void NotificationController::updateChannelNotification(
bool NotificationController::isChannelNotified(const QString &channelName, bool NotificationController::isChannelNotified(const QString &channelName,
Platform p) Platform p)
{ {
for (const auto &channel : this->channelMap[p].getVector()) { for (const auto &channel : this->channelMap[p].getVector())
if (channelName.toLower() == channel.toLower()) { {
if (channelName.toLower() == channel.toLower())
{
return true; return true;
} }
} }
@ -83,8 +89,10 @@ void NotificationController::removeChannelNotification(
const QString &channelName, Platform p) const QString &channelName, Platform p)
{ {
for (std::vector<int>::size_type i = 0; for (std::vector<int>::size_type i = 0;
i != channelMap[p].getVector().size(); i++) { i != channelMap[p].getVector().size(); i++)
if (channelMap[p].getVector()[i].toLower() == channelName.toLower()) { {
if (channelMap[p].getVector()[i].toLower() == channelName.toLower())
{
channelMap[p].removeItem(i); channelMap[p].removeItem(i);
i--; i--;
} }
@ -96,13 +104,17 @@ void NotificationController::playSound()
static QUrl currentPlayerUrl; static QUrl currentPlayerUrl;
QUrl highlightSoundUrl; QUrl highlightSoundUrl;
if (getSettings()->notificationCustomSound) { if (getSettings()->notificationCustomSound)
{
highlightSoundUrl = QUrl::fromLocalFile( highlightSoundUrl = QUrl::fromLocalFile(
getSettings()->notificationPathSound.getValue()); getSettings()->notificationPathSound.getValue());
} else { }
else
{
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav"); highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
} }
if (currentPlayerUrl != highlightSoundUrl) { if (currentPlayerUrl != highlightSoundUrl)
{
player->setMedia(highlightSoundUrl); player->setMedia(highlightSoundUrl);
currentPlayerUrl = highlightSoundUrl; currentPlayerUrl = highlightSoundUrl;
@ -121,10 +133,12 @@ NotificationModel *NotificationController::createModel(QObject *parent,
void NotificationController::fetchFakeChannels() void NotificationController::fetchFakeChannels()
{ {
for (std::vector<int>::size_type i = 0; for (std::vector<int>::size_type i = 0;
i != channelMap[Platform::Twitch].getVector().size(); i++) { i != channelMap[Platform::Twitch].getVector().size(); i++)
{
auto chan = getApp()->twitch.server->getChannelOrEmpty( auto chan = getApp()->twitch.server->getChannelOrEmpty(
channelMap[Platform::Twitch].getVector()[i]); channelMap[Platform::Twitch].getVector()[i]);
if (chan->isEmpty()) { if (chan->isEmpty())
{
getFakeTwitchChannelLiveStatus( getFakeTwitchChannelLiveStatus(
channelMap[Platform::Twitch].getVector()[i]); channelMap[Platform::Twitch].getVector()[i]);
} }
@ -135,7 +149,8 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
const QString &channelName) const QString &channelName)
{ {
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) { TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
if (roomID.isEmpty()) { if (roomID.isEmpty())
{
log("[TwitchChannel:{}] Refreshing live status (Missing ID)", log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
channelName); channelName);
removeFakeChannel(channelName); removeFakeChannel(channelName);
@ -149,19 +164,22 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
request.onSuccess([this, channelName](auto result) -> Outcome { request.onSuccess([this, channelName](auto result) -> Outcome {
rapidjson::Document document = result.parseRapidJson(); rapidjson::Document document = result.parseRapidJson();
if (!document.IsObject()) { if (!document.IsObject())
{
log("[TwitchChannel:refreshLiveStatus]root is not an object"); log("[TwitchChannel:refreshLiveStatus]root is not an object");
return Failure; return Failure;
} }
if (!document.HasMember("stream")) { if (!document.HasMember("stream"))
{
log("[TwitchChannel:refreshLiveStatus] Missing stream in root"); log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
return Failure; return Failure;
} }
const auto &stream = document["stream"]; const auto &stream = document["stream"];
if (!stream.IsObject()) { if (!stream.IsObject())
{
// Stream is offline (stream is most likely null) // Stream is offline (stream is most likely null)
// removeFakeChannel(channelName); // removeFakeChannel(channelName);
return Failure; return Failure;
@ -170,16 +188,20 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
auto i = std::find(fakeTwitchChannels.begin(), auto i = std::find(fakeTwitchChannels.begin(),
fakeTwitchChannels.end(), channelName); fakeTwitchChannels.end(), channelName);
if (!(i != fakeTwitchChannels.end())) { if (!(i != fakeTwitchChannels.end()))
{
fakeTwitchChannels.push_back(channelName); fakeTwitchChannels.push_back(channelName);
if (Toasts::isEnabled()) { if (Toasts::isEnabled())
{
getApp()->toasts->sendChannelNotification(channelName, getApp()->toasts->sendChannelNotification(channelName,
Platform::Twitch); Platform::Twitch);
} }
if (getSettings()->notificationPlaySound) { if (getSettings()->notificationPlaySound)
{
getApp()->notifications->playSound(); getApp()->notifications->playSound();
} }
if (getSettings()->notificationFlashTaskbar) { if (getSettings()->notificationFlashTaskbar)
{
getApp()->windows->sendAlert(); getApp()->windows->sendAlert();
} }
} }
@ -194,7 +216,8 @@ void NotificationController::removeFakeChannel(const QString channelName)
{ {
auto i = std::find(fakeTwitchChannels.begin(), fakeTwitchChannels.end(), auto i = std::find(fakeTwitchChannels.begin(), fakeTwitchChannels.end(),
channelName); channelName);
if (i != fakeTwitchChannels.end()) { if (i != fakeTwitchChannels.end())
{
fakeTwitchChannels.erase(i); fakeTwitchChannels.erase(i);
} }
} }

View file

@ -20,9 +20,12 @@ int main(int argc, char **argv)
[&](auto s) { return s; }); [&](auto s) { return s; });
// run in gui mode or browser extension host mode // run in gui mode or browser extension host mode
if (shouldRunBrowserExtensionHost(args)) { if (shouldRunBrowserExtensionHost(args))
{
runBrowserExtensionHost(); runBrowserExtensionHost();
} else { }
else
{
Paths paths; Paths paths;
Settings settings(paths); Settings settings(paths);

View file

@ -19,7 +19,8 @@ EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
{ {
// reuse old shared_ptr if nothing changed // reuse old shared_ptr if nothing changed
auto it = cache.find(emote.name); auto it = cache.find(emote.name);
if (it != cache.end() && *it->second == emote) return it->second; if (it != cache.end() && *it->second == emote)
return it->second;
return std::make_shared<Emote>(std::move(emote)); return std::make_shared<Emote>(std::move(emote));
} }
@ -32,10 +33,13 @@ EmotePtr cachedOrMakeEmotePtr(
std::lock_guard<std::mutex> guard(mutex); std::lock_guard<std::mutex> guard(mutex);
auto shared = cache[id].lock(); auto shared = cache[id].lock();
if (shared && *shared == emote) { if (shared && *shared == emote)
{
// reuse old shared_ptr if nothing changed // reuse old shared_ptr if nothing changed
return shared; return shared;
} else { }
else
{
shared = std::make_shared<Emote>(std::move(emote)); shared = std::make_shared<Emote>(std::move(emote));
cache[id] = shared; cache[id] = shared;
return shared; return shared;

View file

@ -34,7 +34,8 @@ namespace detail {
assertInGuiThread(); assertInGuiThread();
DebugCount::increase("images"); DebugCount::increase("images");
if (this->animated()) { if (this->animated())
{
DebugCount::increase("animated images"); DebugCount::increase("animated images");
this->gifTimerConnection_ = this->gifTimerConnection_ =
@ -48,7 +49,8 @@ namespace detail {
assertInGuiThread(); assertInGuiThread();
DebugCount::decrease("images"); DebugCount::decrease("images");
if (this->animated()) { if (this->animated())
{
DebugCount::decrease("animated images"); DebugCount::decrease("animated images");
} }
@ -59,7 +61,8 @@ namespace detail {
{ {
this->durationOffset_ += GIF_FRAME_LENGTH; this->durationOffset_ += GIF_FRAME_LENGTH;
while (true) { while (true)
{
this->index_ %= this->items_.size(); this->index_ %= this->items_.size();
// TODO: Figure out what this was supposed to achieve // TODO: Figure out what this was supposed to achieve
@ -67,10 +70,13 @@ namespace detail {
// this->index_ = this->index_; // this->index_ = this->index_;
// } // }
if (this->durationOffset_ > this->items_[this->index_].duration) { if (this->durationOffset_ > this->items_[this->index_].duration)
{
this->durationOffset_ -= this->items_[this->index_].duration; this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size(); this->index_ = (this->index_ + 1) % this->items_.size();
} else { }
else
{
break; break;
} }
} }
@ -83,13 +89,15 @@ namespace detail {
boost::optional<QPixmap> Frames::current() const boost::optional<QPixmap> Frames::current() const
{ {
if (this->items_.size() == 0) return boost::none; if (this->items_.size() == 0)
return boost::none;
return this->items_[this->index_].image; return this->items_[this->index_].image;
} }
boost::optional<QPixmap> Frames::first() const boost::optional<QPixmap> Frames::first() const
{ {
if (this->items_.size() == 0) return boost::none; if (this->items_.size() == 0)
return boost::none;
return this->items_.front().image; return this->items_.front().image;
} }
@ -98,15 +106,18 @@ namespace detail {
{ {
QVector<Frame<QImage>> frames; QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0) { if (reader.imageCount() == 0)
{
log("Error while reading image {}: '{}'", url.string, log("Error while reading image {}: '{}'", url.string,
reader.errorString()); reader.errorString());
return frames; return frames;
} }
QImage image; QImage image;
for (int index = 0; index < reader.imageCount(); ++index) { for (int index = 0; index < reader.imageCount(); ++index)
if (reader.read(&image)) { {
if (reader.read(&image))
{
QPixmap::fromImage(image); QPixmap::fromImage(image);
int duration = std::max(20, reader.nextImageDelay()); int duration = std::max(20, reader.nextImageDelay());
@ -114,7 +125,8 @@ namespace detail {
} }
} }
if (frames.size() == 0) { if (frames.size() == 0)
{
log("Error while reading image {}: '{}'", url.string, log("Error while reading image {}: '{}'", url.string,
reader.errorString()); reader.errorString());
} }
@ -131,11 +143,13 @@ namespace detail {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
int i = 0; int i = 0;
while (!queued.empty()) { while (!queued.empty())
{
queued.front().first(queued.front().second); queued.front().first(queued.front().second);
queued.pop(); queued.pop();
if (++i > 50) { if (++i > 50)
{
QTimer::singleShot(3, [&] { QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued); assignDelayed(queued, mutex, loadedEventQueued);
}); });
@ -171,7 +185,8 @@ namespace detail {
static std::atomic_bool loadedEventQueued{false}; static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) { if (!loadedEventQueued)
{
loadedEventQueued = true; loadedEventQueued = true;
QTimer::singleShot(100, [=] { QTimer::singleShot(100, [=] {
@ -192,9 +207,12 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
auto shared = cache[url].lock(); auto shared = cache[url].lock();
if (!shared) { if (!shared)
{
cache[url] = shared = ImagePtr(new Image(url, scale)); cache[url] = shared = ImagePtr(new Image(url, scale));
} else { }
else
{
// Warn("same image loaded multiple times: {}", url.string); // Warn("same image loaded multiple times: {}", url.string);
} }
@ -241,7 +259,8 @@ boost::optional<QPixmap> Image::pixmap() const
{ {
assertInGuiThread(); assertInGuiThread();
if (this->shouldLoad_) { if (this->shouldLoad_)
{
const_cast<Image *>(this)->shouldLoad_ = false; const_cast<Image *>(this)->shouldLoad_ = false;
const_cast<Image *>(this)->load(); const_cast<Image *>(this)->load();
} }
@ -294,7 +313,8 @@ void Image::load()
req.setUseQuickLoadCache(true); req.setUseQuickLoadCache(true);
req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome { req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared)
return Failure;
auto data = result.getData(); auto data = result.getData();
@ -313,7 +333,8 @@ void Image::load()
}); });
req.onError([weak = weakOf(this)](auto result) -> bool { req.onError([weak = weakOf(this)](auto result) -> bool {
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return false; if (!shared)
return false;
shared->empty_ = true; shared->empty_ = true;
@ -325,9 +346,12 @@ void Image::load()
bool Image::operator==(const Image &other) const bool Image::operator==(const Image &other) const
{ {
if (this->isEmpty() && other.isEmpty()) return true; if (this->isEmpty() && other.isEmpty())
if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true; return true;
if (this->frames_->first() == other.frames_->first()) return true; if (!this->url_.string.isEmpty() && this->url_ == other.url_)
return true;
if (this->frames_->first() == other.frames_->first())
return true;
return false; return false;
} }

View file

@ -67,11 +67,13 @@ const ImagePtr &ImageSet::getImage(float scale) const
else if (scale > 1.5) else if (scale > 1.5)
quality = 2; quality = 2;
if (!this->imageX3_->isEmpty() && quality == 3) { if (!this->imageX3_->isEmpty() && quality == 3)
{
return this->imageX3_; return this->imageX3_;
} }
if (!this->imageX2_->isEmpty() && quality == 2) { if (!this->imageX2_->isEmpty() && quality == 2)
{
return this->imageX2_; return this->imageX2_;
} }

View file

@ -60,13 +60,15 @@ public:
Chunk lastChunk = this->chunks_->back(); Chunk lastChunk = this->chunks_->back();
// still space in the last chunk // still space in the last chunk
if (lastChunk->size() <= this->lastChunkEnd_) { if (lastChunk->size() <= this->lastChunkEnd_)
{
// create new chunk vector // create new chunk vector
ChunkVector newVector = std::make_shared< ChunkVector newVector = std::make_shared<
std::vector<std::shared_ptr<std::vector<T>>>>(); std::vector<std::shared_ptr<std::vector<T>>>>();
// copy chunks // copy chunks
for (Chunk &chunk : *this->chunks_) { for (Chunk &chunk : *this->chunks_)
{
newVector->push_back(chunk); newVector->push_back(chunk);
} }
@ -91,7 +93,8 @@ public:
{ {
std::vector<T> acceptedItems; std::vector<T> acceptedItems;
if (this->space() > 0) { if (this->space() > 0)
{
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
// create new vector to clone chunks into // create new vector to clone chunks into
@ -101,21 +104,25 @@ public:
newChunks->resize(this->chunks_->size()); newChunks->resize(this->chunks_->size());
// copy chunks except for first one // copy chunks except for first one
for (size_t i = 1; i < this->chunks_->size(); i++) { for (size_t i = 1; i < this->chunks_->size(); i++)
{
newChunks->at(i) = this->chunks_->at(i); newChunks->at(i) = this->chunks_->at(i);
} }
// create new chunk for the first one // create new chunk for the first one
size_t offset = std::min(this->space(), static_cast<qsizetype>(items.size())); size_t offset =
std::min(this->space(), static_cast<qsizetype>(items.size()));
Chunk newFirstChunk = std::make_shared<std::vector<T>>(); Chunk newFirstChunk = std::make_shared<std::vector<T>>();
newFirstChunk->resize(this->chunks_->front()->size() + offset); newFirstChunk->resize(this->chunks_->front()->size() + offset);
for (size_t i = 0; i < offset; i++) { for (size_t i = 0; i < offset; i++)
{
newFirstChunk->at(i) = items[items.size() - offset + i]; newFirstChunk->at(i) = items[items.size() - offset + i];
acceptedItems.push_back(items[items.size() - offset + i]); acceptedItems.push_back(items[items.size() - offset + i]);
} }
for (size_t i = 0; i < this->chunks_->at(0)->size(); i++) { for (size_t i = 0; i < this->chunks_->at(0)->size(); i++)
{
newFirstChunk->at(i + offset) = this->chunks_->at(0)->at(i); newFirstChunk->at(i + offset) = this->chunks_->at(0)->at(i);
} }
@ -125,7 +132,8 @@ public:
// qDebug() << acceptedItems.size(); // qDebug() << acceptedItems.size();
// qDebug() << this->chunks->at(0)->size(); // qDebug() << this->chunks->at(0)->size();
if (this->chunks_->size() == 1) { if (this->chunks_->size() == 1)
{
this->lastChunkEnd_ += offset; this->lastChunkEnd_ += offset;
} }
} }
@ -140,19 +148,23 @@ public:
int x = 0; int x = 0;
for (size_t i = 0; i < this->chunks_->size(); i++) { for (size_t i = 0; i < this->chunks_->size(); i++)
{
Chunk &chunk = this->chunks_->at(i); Chunk &chunk = this->chunks_->at(i);
size_t start = i == 0 ? this->firstChunkOffset_ : 0; size_t start = i == 0 ? this->firstChunkOffset_ : 0;
size_t end = size_t end =
i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size(); i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
for (size_t j = start; j < end; j++) { for (size_t j = start; j < end; j++)
if (chunk->at(j) == item) { {
if (chunk->at(j) == item)
{
Chunk newChunk = std::make_shared<std::vector<T>>(); Chunk newChunk = std::make_shared<std::vector<T>>();
newChunk->resize(chunk->size()); newChunk->resize(chunk->size());
for (size_t k = 0; k < chunk->size(); k++) { for (size_t k = 0; k < chunk->size(); k++)
{
newChunk->at(k) = chunk->at(k); newChunk->at(k) = chunk->at(k);
} }
@ -175,19 +187,23 @@ public:
size_t x = 0; size_t x = 0;
for (size_t i = 0; i < this->chunks_->size(); i++) { for (size_t i = 0; i < this->chunks_->size(); i++)
{
Chunk &chunk = this->chunks_->at(i); Chunk &chunk = this->chunks_->at(i);
size_t start = i == 0 ? this->firstChunkOffset_ : 0; size_t start = i == 0 ? this->firstChunkOffset_ : 0;
size_t end = size_t end =
i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size(); i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
for (size_t j = start; j < end; j++) { for (size_t j = start; j < end; j++)
if (x == index) { {
if (x == index)
{
Chunk newChunk = std::make_shared<std::vector<T>>(); Chunk newChunk = std::make_shared<std::vector<T>>();
newChunk->resize(chunk->size()); newChunk->resize(chunk->size());
for (size_t k = 0; k < chunk->size(); k++) { for (size_t k = 0; k < chunk->size(); k++)
{
newChunk->at(k) = chunk->at(k); newChunk->at(k) = chunk->at(k);
} }
@ -217,12 +233,14 @@ private:
qsizetype space() qsizetype space()
{ {
size_t totalSize = 0; size_t totalSize = 0;
for (Chunk &chunk : *this->chunks_) { for (Chunk &chunk : *this->chunks_)
{
totalSize += chunk->size(); totalSize += chunk->size();
} }
totalSize -= this->chunks_->back()->size() - this->lastChunkEnd_; totalSize -= this->chunks_->back()->size() - this->lastChunkEnd_;
if (this->chunks_->size() != 1) { if (this->chunks_->size() != 1)
{
totalSize -= this->firstChunkOffset_; totalSize -= this->firstChunkOffset_;
} }
@ -232,7 +250,8 @@ private:
bool deleteFirstItem(T &deleted) bool deleteFirstItem(T &deleted)
{ {
// determine if the first chunk should be deleted // determine if the first chunk should be deleted
if (space() > 0) { if (space() > 0)
{
return false; return false;
} }
@ -241,15 +260,18 @@ private:
this->firstChunkOffset_++; this->firstChunkOffset_++;
// need to delete the first chunk // need to delete the first chunk
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1) { if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1)
{
// copy the chunk vector // copy the chunk vector
ChunkVector newVector = std::make_shared< ChunkVector newVector = std::make_shared<
std::vector<std::shared_ptr<std::vector<T>>>>(); std::vector<std::shared_ptr<std::vector<T>>>>();
// delete first chunk // delete first chunk
bool first = true; bool first = true;
for (Chunk &chunk : *this->chunks_) { for (Chunk &chunk : *this->chunks_)
if (!first) { {
if (!first)
{
newVector->push_back(chunk); newVector->push_back(chunk);
} }
first = false; first = false;

View file

@ -33,10 +33,12 @@ public:
size_t x = 0; size_t x = 0;
for (size_t i = 0; i < this->chunks_->size(); i++) { for (size_t i = 0; i < this->chunks_->size(); i++)
{
auto &chunk = this->chunks_->at(i); auto &chunk = this->chunks_->at(i);
if (x <= index && x + chunk->size() > index) { if (x <= index && x + chunk->size() > index)
{
return chunk->at(index - x); return chunk->at(index - x);
} }
x += chunk->size(); x += chunk->size();

View file

@ -21,9 +21,12 @@ Message::~Message()
SBHighlight Message::getScrollBarHighlight() const SBHighlight Message::getScrollBarHighlight() const
{ {
if (this->flags.has(MessageFlag::Highlighted)) { if (this->flags.has(MessageFlag::Highlighted))
{
return SBHighlight(SBHighlight::Highlight); return SBHighlight(SBHighlight::Highlight);
} else if (this->flags.has(MessageFlag::Subscription)) { }
else if (this->flags.has(MessageFlag::Subscription))
{
return SBHighlight(SBHighlight::Subscription); return SBHighlight(SBHighlight::Subscription);
} }
return SBHighlight(); return SBHighlight();

View file

@ -52,7 +52,8 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
QString text; QString text;
text.append(username); text.append(username);
if (!durationInSeconds.isEmpty()) { if (!durationInSeconds.isEmpty())
{
text.append(" has been timed out"); text.append(" has been timed out");
// TODO: Implement who timed the user out // TODO: Implement who timed the user out
@ -60,21 +61,26 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
text.append(" for "); text.append(" for ");
bool ok = true; bool ok = true;
int timeoutSeconds = durationInSeconds.toInt(&ok); int timeoutSeconds = durationInSeconds.toInt(&ok);
if (ok) { if (ok)
{
text.append(formatTime(timeoutSeconds)); text.append(formatTime(timeoutSeconds));
} }
} else { }
else
{
text.append(" has been permanently banned"); text.append(" has been permanently banned");
} }
if (reason.length() > 0) { if (reason.length() > 0)
{
text.append(": \""); text.append(": \"");
text.append(parseTagString(reason)); text.append(parseTagString(reason));
text.append("\""); text.append("\"");
} }
text.append("."); text.append(".");
if (multipleTimes) { if (multipleTimes)
{
text.append(" (multiple times)"); text.append(" (multiple times)");
} }
@ -99,24 +105,33 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
QString text; QString text;
if (action.isBan()) { if (action.isBan())
if (action.reason.isEmpty()) { {
if (action.reason.isEmpty())
{
text = QString("%1 banned %2.") // text = QString("%1 banned %2.") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name); .arg(action.target.name);
} else { }
else
{
text = QString("%1 banned %2: \"%3\".") // text = QString("%1 banned %2: \"%3\".") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name) .arg(action.target.name)
.arg(action.reason); .arg(action.reason);
} }
} else { }
if (action.reason.isEmpty()) { else
{
if (action.reason.isEmpty())
{
text = QString("%1 timed out %2 for %3.") // text = QString("%1 timed out %2 for %3.") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name) .arg(action.target.name)
.arg(formatTime(action.duration)); .arg(formatTime(action.duration));
} else { }
else
{
text = QString("%1 timed out %2 for %3: \"%4\".") // text = QString("%1 timed out %2 for %3: \"%4\".") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name) .arg(action.target.name)
@ -124,7 +139,8 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
.arg(action.reason); .arg(action.reason);
} }
if (count > 1) { if (count > 1)
{
text.append(QString(" (%1 times)").arg(count)); text.append(QString(" (%1 times)").arg(count));
} }
} }
@ -145,11 +161,14 @@ MessageBuilder::MessageBuilder(const UnbanAction &action)
QString text; QString text;
if (action.wasBan()) { if (action.wasBan())
{
text = QString("%1 unbanned %2.") // text = QString("%1 unbanned %2.") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name); .arg(action.target.name);
} else { }
else
{
text = QString("%1 untimedout %2.") // text = QString("%1 untimedout %2.") //
.arg(action.source.name) .arg(action.source.name)
.arg(action.target.name); .arg(action.target.name);
@ -193,14 +212,16 @@ QString MessageBuilder::matchLink(const QString &string)
static QRegularExpression spotifyRegex( static QRegularExpression spotifyRegex(
"\\bspotify:", QRegularExpression::CaseInsensitiveOption); "\\bspotify:", QRegularExpression::CaseInsensitiveOption);
if (!linkParser.hasMatch()) { if (!linkParser.hasMatch())
{
return QString(); return QString();
} }
QString captured = linkParser.getCaptured(); QString captured = linkParser.getCaptured();
if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) && if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) &&
!captured.contains(spotifyRegex)) { !captured.contains(spotifyRegex))
{
captured.insert(0, "http://"); captured.insert(0, "http://");
} }

View file

@ -17,7 +17,8 @@ MessageColor::MessageColor(Type type)
const QColor &MessageColor::getColor(Theme &themeManager) const const QColor &MessageColor::getColor(Theme &themeManager) const
{ {
switch (this->type_) { switch (this->type_)
{
case Type::Custom: case Type::Custom:
return this->customColor_; return this->customColor_;
case Type::Text: case Type::Text:

View file

@ -84,7 +84,8 @@ ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container, void ImageElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) MessageElementFlags flags)
{ {
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags()))
{
auto size = QSize(this->image_->width() * container.getScale(), auto size = QSize(this->image_->width() * container.getScale(),
this->image_->height() * container.getScale()); this->image_->height() * container.getScale());
@ -112,10 +113,13 @@ EmotePtr EmoteElement::getEmote() const
void EmoteElement::addToContainer(MessageLayoutContainer &container, void EmoteElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) MessageElementFlags flags)
{ {
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags()))
if (flags.has(MessageElementFlag::EmoteImages)) { {
if (flags.has(MessageElementFlag::EmoteImages))
{
auto image = this->emote_->images.getImage(container.getScale()); auto image = this->emote_->images.getImage(container.getScale());
if (image->isEmpty()) return; if (image->isEmpty())
return;
auto emoteScale = getSettings()->emoteScale.getValue(); auto emoteScale = getSettings()->emoteScale.getValue();
@ -125,8 +129,11 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
container.addElement((new ImageLayoutElement(*this, image, size)) container.addElement((new ImageLayoutElement(*this, image, size))
->setLink(this->getLink())); ->setLink(this->getLink()));
} else { }
if (this->textElement_) { else
{
if (this->textElement_)
{
this->textElement_->addToContainer(container, this->textElement_->addToContainer(container,
MessageElementFlag::Misc); MessageElementFlag::Misc);
} }
@ -141,7 +148,8 @@ TextElement::TextElement(const QString &text, MessageElementFlags flags,
, color_(color) , color_(color)
, style_(style) , style_(style)
{ {
for (const auto &word : text.split(' ')) { for (const auto &word : text.split(' '))
{
this->words_.push_back({word, -1}); this->words_.push_back({word, -1});
// fourtf: add logic to store multiple spaces after message // fourtf: add logic to store multiple spaces after message
} }
@ -152,11 +160,13 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
{ {
auto app = getApp(); auto app = getApp();
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags()))
{
QFontMetrics metrics = QFontMetrics metrics =
app->fonts->getFontMetrics(this->style_, container.getScale()); app->fonts->getFontMetrics(this->style_, container.getScale());
for (Word &word : this->words_) { for (Word &word : this->words_)
{
auto getTextLayoutElement = [&](QString text, int width, auto getTextLayoutElement = [&](QString text, int width,
bool trailingSpace) { bool trailingSpace) {
auto color = this->color_.getColor(*app->themes); auto color = this->color_.getColor(*app->themes);
@ -171,7 +181,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
// If URL link was changed, // If URL link was changed,
// Should update it in MessageLayoutElement too! // Should update it in MessageLayoutElement too!
if (this->getLink().type == Link::Url) { if (this->getLink().type == Link::Url)
{
this->linkChanged.connect( this->linkChanged.connect(
[this, e]() { e->setLink(this->getLink()); }); [this, e]() { e->setLink(this->getLink()); });
} }
@ -184,17 +195,20 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
// } // }
// see if the text fits in the current line // see if the text fits in the current line
if (container.fitsInLine(word.width)) { if (container.fitsInLine(word.width))
{
container.addElementNoLineBreak(getTextLayoutElement( container.addElementNoLineBreak(getTextLayoutElement(
word.text, word.width, this->hasTrailingSpace())); word.text, word.width, this->hasTrailingSpace()));
continue; continue;
} }
// see if the text fits in the next line // see if the text fits in the next line
if (!container.atStartOfLine()) { if (!container.atStartOfLine())
{
container.breakLine(); container.breakLine();
if (container.fitsInLine(word.width)) { if (container.fitsInLine(word.width))
{
container.addElementNoLineBreak(getTextLayoutElement( container.addElementNoLineBreak(getTextLayoutElement(
word.text, word.width, this->hasTrailingSpace())); word.text, word.width, this->hasTrailingSpace()));
continue; continue;
@ -226,13 +240,15 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
wordStart = i; wordStart = i;
width = charWidth; width = charWidth;
if (isSurrogate) i++; if (isSurrogate)
i++;
continue; continue;
} }
width += charWidth; width += charWidth;
if (isSurrogate) i++; if (isSurrogate)
i++;
} }
container.addElement(getTextLayoutElement( container.addElement(getTextLayoutElement(
@ -254,9 +270,11 @@ TimestampElement::TimestampElement(QTime time)
void TimestampElement::addToContainer(MessageLayoutContainer &container, void TimestampElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) MessageElementFlags flags)
{ {
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags()))
{
auto app = getApp(); auto app = getApp();
if (getSettings()->timestampFormat != this->format_) { if (getSettings()->timestampFormat != this->format_)
{
this->format_ = getSettings()->timestampFormat.getValue(); this->format_ = getSettings()->timestampFormat.getValue();
this->element_.reset(this->formatTime(this->time_)); this->element_.reset(this->formatTime(this->time_));
} }
@ -284,17 +302,22 @@ TwitchModerationElement::TwitchModerationElement()
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
MessageElementFlags flags) MessageElementFlags flags)
{ {
if (flags.has(MessageElementFlag::ModeratorTools)) { if (flags.has(MessageElementFlag::ModeratorTools))
{
QSize size(int(container.getScale() * 16), QSize size(int(container.getScale() * 16),
int(container.getScale() * 16)); int(container.getScale() * 16));
for (const auto &action : for (const auto &action :
getApp()->moderationActions->items.getVector()) { getApp()->moderationActions->items.getVector())
if (auto image = action.getImage()) { {
if (auto image = action.getImage())
{
container.addElement( container.addElement(
(new ImageLayoutElement(*this, image.get(), size)) (new ImageLayoutElement(*this, image.get(), size))
->setLink(Link(Link::UserAction, action.getAction()))); ->setLink(Link(Link::UserAction, action.getAction())));
} else { }
else
{
container.addElement( container.addElement(
(new TextIconLayoutElement(*this, action.getLine1(), (new TextIconLayoutElement(*this, action.getLine1(),
action.getLine2(), action.getLine2(),

View file

@ -59,7 +59,8 @@ struct Selection {
, selectionMin(start) , selectionMin(start)
, selectionMax(end) , selectionMax(end)
{ {
if (selectionMin > selectionMax) { if (selectionMin > selectionMax)
{
std::swap(this->selectionMin, this->selectionMax); std::swap(this->selectionMin, this->selectionMax);
} }
} }

View file

@ -64,7 +64,8 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
this->currentLayoutWidth_ = width; this->currentLayoutWidth_ = width;
// check if layout state changed // check if layout state changed
if (this->layoutState_ != app->windows->getGeneration()) { if (this->layoutState_ != app->windows->getGeneration())
{
layoutRequired = true; layoutRequired = true;
this->flags.set(MessageLayoutFlag::RequiresBufferUpdate); this->flags.set(MessageLayoutFlag::RequiresBufferUpdate);
this->layoutState_ = app->windows->getGeneration(); this->layoutState_ = app->windows->getGeneration();
@ -82,13 +83,15 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
layoutRequired |= this->scale_ != scale; layoutRequired |= this->scale_ != scale;
this->scale_ = scale; this->scale_ = scale;
if (!layoutRequired) { if (!layoutRequired)
{
return false; return false;
} }
int oldHeight = this->container_->getHeight(); int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags); this->actuallyLayout(width, flags);
if (widthChanged || this->container_->getHeight() != oldHeight) { if (widthChanged || this->container_->getHeight() != oldHeight)
{
this->deleteBuffer(); this->deleteBuffer();
} }
this->invalidateBuffer(); this->invalidateBuffer();
@ -109,11 +112,13 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
this->container_->begin(width, this->scale_, messageFlags); this->container_->begin(width, this->scale_, messageFlags);
for (const auto &element : this->message_->elements) { for (const auto &element : this->message_->elements)
{
element->addToContainer(*this->container_, _flags); element->addToContainer(*this->container_, _flags);
} }
if (this->height_ != this->container_->getHeight()) { if (this->height_ != this->container_->getHeight())
{
this->deleteBuffer(); this->deleteBuffer();
} }
@ -122,7 +127,8 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
// collapsed state // collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed); this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_->isCollapsed()) { if (this->container_->isCollapsed())
{
this->flags.set(MessageLayoutFlag::Collapsed); this->flags.set(MessageLayoutFlag::Collapsed);
} }
} }
@ -136,7 +142,8 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
QPixmap *pixmap = this->buffer_.get(); QPixmap *pixmap = this->buffer_.get();
// create new buffer if required // create new buffer if required
if (!pixmap) { if (!pixmap)
{
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()), pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_->getHeight() * int(container_->getHeight() *
@ -152,7 +159,8 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
DebugCount::increase("message drawing buffers"); DebugCount::increase("message drawing buffers");
} }
if (!this->bufferValid_ || !selection.isEmpty()) { if (!this->bufferValid_ || !selection.isEmpty())
{
this->updateBuffer(pixmap, messageIndex, selection); this->updateBuffer(pixmap, messageIndex, selection);
} }
@ -165,28 +173,35 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
this->container_->paintAnimatedElements(painter, y); this->container_->paintAnimatedElements(painter, y);
// draw disabled // draw disabled
if (this->message_->flags.has(MessageFlag::Disabled)) { if (this->message_->flags.has(MessageFlag::Disabled))
{
painter.fillRect(0, y, pixmap->width(), pixmap->height(), painter.fillRect(0, y, pixmap->width(), pixmap->height(),
app->themes->messages.disabled); app->themes->messages.disabled);
} }
// draw selection // draw selection
if (!selection.isEmpty()) { if (!selection.isEmpty())
{
this->container_->paintSelection(painter, messageIndex, selection, y); this->container_->paintSelection(painter, messageIndex, selection, y);
} }
// draw message seperation line // draw message seperation line
if (getSettings()->separateMessages.getValue()) { if (getSettings()->separateMessages.getValue())
{
painter.fillRect(0, y, this->container_->getWidth(), 1, painter.fillRect(0, y, this->container_->getWidth(), 1,
app->themes->splits.messageSeperator); app->themes->splits.messageSeperator);
} }
// draw last read message line // draw last read message line
if (isLastReadMessage) { if (isLastReadMessage)
{
QColor color; QColor color;
if (getSettings()->lastMessageColor != "") { if (getSettings()->lastMessageColor != "")
{
color = QColor(getSettings()->lastMessageColor.getValue()); color = QColor(getSettings()->lastMessageColor.getValue());
} else { }
else
{
color = color =
isWindowFocused isWindowFocused
? app->themes->tabs.selected.backgrounds.regular.color() ? app->themes->tabs.selected.backgrounds.regular.color()
@ -214,12 +229,17 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
// draw background // draw background
QColor backgroundColor = app->themes->messages.backgrounds.regular; QColor backgroundColor = app->themes->messages.backgrounds.regular;
if (this->message_->flags.has(MessageFlag::Highlighted) && if (this->message_->flags.has(MessageFlag::Highlighted) &&
!this->flags.has(MessageLayoutFlag::IgnoreHighlights)) { !this->flags.has(MessageLayoutFlag::IgnoreHighlights))
{
backgroundColor = app->themes->messages.backgrounds.highlighted; backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags.has(MessageFlag::Subscription)) { }
else if (this->message_->flags.has(MessageFlag::Subscription))
{
backgroundColor = app->themes->messages.backgrounds.subscription; backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (getSettings()->alternateMessageBackground.getValue() && }
this->flags.has(MessageLayoutFlag::AlternateBackground)) { else if (getSettings()->alternateMessageBackground.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground))
{
backgroundColor = app->themes->messages.backgrounds.alternate; backgroundColor = app->themes->messages.backgrounds.alternate;
} }
@ -249,7 +269,8 @@ void MessageLayout::invalidateBuffer()
void MessageLayout::deleteBuffer() void MessageLayout::deleteBuffer()
{ {
if (this->buffer_ != nullptr) { if (this->buffer_ != nullptr)
{
DebugCount::decrease("message drawing buffers"); DebugCount::decrease("message drawing buffers");
this->buffer_ = nullptr; this->buffer_ = nullptr;

View file

@ -65,7 +65,8 @@ void MessageLayoutContainer::clear()
void MessageLayoutContainer::addElement(MessageLayoutElement *element) void MessageLayoutContainer::addElement(MessageLayoutElement *element)
{ {
if (!this->fitsInLine(element->getRect().width())) { if (!this->fitsInLine(element->getRect().width()))
{
this->breakLine(); this->breakLine();
} }
@ -86,13 +87,15 @@ bool MessageLayoutContainer::canAddElements()
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
bool forceAdd) bool forceAdd)
{ {
if (!this->canAddElements() && !forceAdd) { if (!this->canAddElements() && !forceAdd)
{
delete element; delete element;
return; return;
} }
// top margin // top margin
if (this->elements_.size() == 0) { if (this->elements_.size() == 0)
{
this->currentY_ = this->margin.top * this->scale_; this->currentY_ = this->margin.top * this->scale_;
} }
@ -103,7 +106,8 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
!this->flags_.has(MessageFlag::DisableCompactEmotes) && !this->flags_.has(MessageFlag::DisableCompactEmotes) &&
element->getCreator().getFlags().has(MessageElementFlag::EmoteImages); element->getCreator().getFlags().has(MessageElementFlag::EmoteImages);
if (isCompactEmote) { if (isCompactEmote)
{
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale_; newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale_;
} }
@ -120,7 +124,8 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
// set current x // set current x
this->currentX_ += element->getRect().width(); this->currentX_ += element->getRect().width();
if (element->hasTrailingSpace()) { if (element->hasTrailingSpace())
{
this->currentX_ += this->spaceWidth_; this->currentX_ += this->spaceWidth_;
} }
} }
@ -129,7 +134,8 @@ void MessageLayoutContainer::breakLine()
{ {
int xOffset = 0; int xOffset = 0;
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) { if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0)
{
xOffset = (width_ - this->elements_.at(0)->getRect().left() - xOffset = (width_ - this->elements_.at(0)->getRect().left() -
this->elements_.at(this->elements_.size() - 1) this->elements_.at(this->elements_.size() - 1)
->getRect() ->getRect()
@ -137,7 +143,8 @@ void MessageLayoutContainer::breakLine()
2; 2;
} }
for (size_t i = lineStart_; i < this->elements_.size(); i++) { for (size_t i = lineStart_; i < this->elements_.size(); i++)
{
MessageLayoutElement *element = this->elements_.at(i).get(); MessageLayoutElement *element = this->elements_.at(i).get();
bool isCompactEmote = bool isCompactEmote =
@ -146,14 +153,16 @@ void MessageLayoutContainer::breakLine()
MessageElementFlag::EmoteImages); MessageElementFlag::EmoteImages);
int yExtra = 0; int yExtra = 0;
if (isCompactEmote) { if (isCompactEmote)
{
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_; yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
} }
// if (element->getCreator().getFlags() & // if (element->getCreator().getFlags() &
// MessageElementFlag::Badges) // MessageElementFlag::Badges)
// { // {
if (element->getRect().height() < this->textLineHeight_) { if (element->getRect().height() < this->textLineHeight_)
{
yExtra -= (this->textLineHeight_ - element->getRect().height()) / 2; yExtra -= (this->textLineHeight_ - element->getRect().height()) / 2;
} }
@ -162,7 +171,8 @@ void MessageLayoutContainer::breakLine()
element->getRect().y() + this->lineHeight_ + yExtra)); element->getRect().y() + this->lineHeight_ + yExtra));
} }
if (this->lines_.size() != 0) { if (this->lines_.size() != 0)
{
this->lines_.back().endIndex = this->lineStart_; this->lines_.back().endIndex = this->lineStart_;
this->lines_.back().endCharIndex = this->charIndex_; this->lines_.back().endCharIndex = this->charIndex_;
} }
@ -170,14 +180,16 @@ void MessageLayoutContainer::breakLine()
{(int)lineStart_, 0, this->charIndex_, 0, {(int)lineStart_, 0, this->charIndex_, 0,
QRect(-100000, this->currentY_, 200000, lineHeight_)}); QRect(-100000, this->currentY_, 200000, lineHeight_)});
for (int i = this->lineStart_; i < this->elements_.size(); i++) { for (int i = this->lineStart_; i < this->elements_.size(); i++)
{
this->charIndex_ += this->elements_[i]->getSelectionIndexCount(); this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
} }
this->lineStart_ = this->elements_.size(); this->lineStart_ = this->elements_.size();
// this->currentX = (int)(this->scale * 8); // this->currentX = (int)(this->scale * 8);
if (this->canCollapse() && line_ + 1 >= MAX_UNCOLLAPSED_LINES) { if (this->canCollapse() && line_ + 1 >= MAX_UNCOLLAPSED_LINES)
{
this->canAddMessages_ = false; this->canAddMessages_ = false;
return; return;
} }
@ -204,7 +216,8 @@ bool MessageLayoutContainer::fitsInLine(int _width)
void MessageLayoutContainer::end() void MessageLayoutContainer::end()
{ {
if (!this->canAddElements()) { if (!this->canAddElements())
{
static TextElement dotdotdot("...", MessageElementFlag::Collapsed, static TextElement dotdotdot("...", MessageElementFlag::Collapsed,
MessageColor::Link); MessageColor::Link);
static QString dotdotdotText("..."); static QString dotdotdotText("...");
@ -219,13 +232,15 @@ void MessageLayoutContainer::end()
this->isCollapsed_ = true; this->isCollapsed_ = true;
} }
if (!this->atStartOfLine()) { if (!this->atStartOfLine())
{
this->breakLine(); this->breakLine();
} }
this->height_ += this->lineHeight_; this->height_ += this->lineHeight_;
if (this->lines_.size() != 0) { if (this->lines_.size() != 0)
{
this->lines_[0].rect.setTop(-100000); this->lines_[0].rect.setTop(-100000);
this->lines_.back().rect.setBottom(100000); this->lines_.back().rect.setBottom(100000);
this->lines_.back().endIndex = this->elements_.size(); this->lines_.back().endIndex = this->elements_.size();
@ -246,8 +261,10 @@ bool MessageLayoutContainer::isCollapsed()
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point) MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
{ {
for (std::unique_ptr<MessageLayoutElement> &element : this->elements_) { for (std::unique_ptr<MessageLayoutElement> &element : this->elements_)
if (element->getRect().contains(point)) { {
if (element->getRect().contains(point))
{
return element.get(); return element.get();
} }
} }
@ -258,8 +275,8 @@ MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
// painting // painting
void MessageLayoutContainer::paintElements(QPainter &painter) void MessageLayoutContainer::paintElements(QPainter &painter)
{ {
for (const std::unique_ptr<MessageLayoutElement> &element : for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_)
this->elements_) { {
#ifdef FOURTF #ifdef FOURTF
painter.setPen(QColor(0, 255, 0)); painter.setPen(QColor(0, 255, 0));
painter.drawRect(element->getRect()); painter.drawRect(element->getRect());
@ -272,8 +289,8 @@ void MessageLayoutContainer::paintElements(QPainter &painter)
void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, void MessageLayoutContainer::paintAnimatedElements(QPainter &painter,
int yOffset) int yOffset)
{ {
for (const std::unique_ptr<MessageLayoutElement> &element : for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_)
this->elements_) { {
element->paintAnimated(painter, yOffset); element->paintAnimated(painter, yOffset);
} }
} }
@ -286,14 +303,17 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
// don't draw anything // don't draw anything
if (selection.selectionMin.messageIndex > messageIndex || if (selection.selectionMin.messageIndex > messageIndex ||
selection.selectionMax.messageIndex < messageIndex) { selection.selectionMax.messageIndex < messageIndex)
{
return; return;
} }
// fully selected // fully selected
if (selection.selectionMin.messageIndex < messageIndex && if (selection.selectionMin.messageIndex < messageIndex &&
selection.selectionMax.messageIndex > messageIndex) { selection.selectionMax.messageIndex > messageIndex)
for (Line &line : this->lines_) { {
for (Line &line : this->lines_)
{
QRect rect = line.rect; QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()) + yOffset); rect.setTop(std::max(0, rect.top()) + yOffset);
@ -311,8 +331,10 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int index = 0; int index = 0;
// start in this message // start in this message
if (selection.selectionMin.messageIndex == messageIndex) { if (selection.selectionMin.messageIndex == messageIndex)
for (; lineIndex < this->lines_.size(); lineIndex++) { {
for (; lineIndex < this->lines_.size(); lineIndex++)
{
Line &line = this->lines_[lineIndex]; Line &line = this->lines_[lineIndex];
index = line.startCharIndex; index = line.startCharIndex;
@ -321,14 +343,17 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int x = this->elements_[line.startIndex]->getRect().left(); int x = this->elements_[line.startIndex]->getRect().left();
int r = this->elements_[line.endIndex - 1]->getRect().right(); int r = this->elements_[line.endIndex - 1]->getRect().right();
if (line.endCharIndex <= selection.selectionMin.charIndex) { if (line.endCharIndex <= selection.selectionMin.charIndex)
{
continue; continue;
} }
for (int i = line.startIndex; i < line.endIndex; i++) { for (int i = line.startIndex; i < line.endIndex; i++)
{
int c = this->elements_[i]->getSelectionIndexCount(); int c = this->elements_[i]->getSelectionIndexCount();
if (index + c > selection.selectionMin.charIndex) { if (index + c > selection.selectionMin.charIndex)
{
x = this->elements_[i]->getXFromIndex( x = this->elements_[i]->getXFromIndex(
selection.selectionMin.charIndex - index); selection.selectionMin.charIndex - index);
@ -339,11 +364,13 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
{ {
returnAfter = true; returnAfter = true;
index = line.startCharIndex; index = line.startCharIndex;
for (int i = line.startIndex; i < line.endIndex; i++) { for (int i = line.startIndex; i < line.endIndex; i++)
{
int c = int c =
this->elements_[i]->getSelectionIndexCount(); this->elements_[i]->getSelectionIndexCount();
if (index + c > selection.selectionMax.charIndex) { if (index + c > selection.selectionMax.charIndex)
{
r = this->elements_[i]->getXFromIndex( r = this->elements_[i]->getXFromIndex(
selection.selectionMax.charIndex - index); selection.selectionMax.charIndex - index);
break; break;
@ -353,9 +380,11 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
} }
// ends in same line end // ends in same line end
if (selection.selectionMax.messageIndex != messageIndex) { if (selection.selectionMax.messageIndex != messageIndex)
{
int lineIndex2 = lineIndex + 1; int lineIndex2 = lineIndex + 1;
for (; lineIndex2 < this->lines_.size(); lineIndex2++) { for (; lineIndex2 < this->lines_.size(); lineIndex2++)
{
Line &line = this->lines_[lineIndex2]; Line &line = this->lines_[lineIndex2];
QRect rect = line.rect; QRect rect = line.rect;
@ -373,7 +402,9 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
painter.fillRect(rect, selectionColor); painter.fillRect(rect, selectionColor);
} }
returnAfter = true; returnAfter = true;
} else { }
else
{
lineIndex++; lineIndex++;
breakAfter = true; breakAfter = true;
} }
@ -392,23 +423,27 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
painter.fillRect(rect, selectionColor); painter.fillRect(rect, selectionColor);
if (returnAfter) { if (returnAfter)
{
return; return;
} }
if (breakAfter) { if (breakAfter)
{
break; break;
} }
} }
} }
// start in this message // start in this message
for (; lineIndex < this->lines_.size(); lineIndex++) { for (; lineIndex < this->lines_.size(); lineIndex++)
{
Line &line = this->lines_[lineIndex]; Line &line = this->lines_[lineIndex];
index = line.startCharIndex; index = line.startCharIndex;
// just draw the garbage // just draw the garbage
if (line.endCharIndex < /*=*/selection.selectionMax.charIndex) { if (line.endCharIndex < /*=*/selection.selectionMax.charIndex)
{
QRect rect = line.rect; QRect rect = line.rect;
rect.setTop(std::max(0, rect.top()) + yOffset); rect.setTop(std::max(0, rect.top()) + yOffset);
@ -423,10 +458,12 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int r = this->elements_[line.endIndex - 1]->getRect().right(); int r = this->elements_[line.endIndex - 1]->getRect().right();
for (int i = line.startIndex; i < line.endIndex; i++) { for (int i = line.startIndex; i < line.endIndex; i++)
{
int c = this->elements_[i]->getSelectionIndexCount(); int c = this->elements_[i]->getSelectionIndexCount();
if (index + c > selection.selectionMax.charIndex) { if (index + c > selection.selectionMax.charIndex)
{
r = this->elements_[i]->getXFromIndex( r = this->elements_[i]->getXFromIndex(
selection.selectionMax.charIndex - index); selection.selectionMax.charIndex - index);
break; break;
@ -450,21 +487,25 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
// selection // selection
int MessageLayoutContainer::getSelectionIndex(QPoint point) int MessageLayoutContainer::getSelectionIndex(QPoint point)
{ {
if (this->elements_.size() == 0) { if (this->elements_.size() == 0)
{
return 0; return 0;
} }
auto line = this->lines_.begin(); auto line = this->lines_.begin();
for (; line != this->lines_.end(); line++) { for (; line != this->lines_.end(); line++)
if (line->rect.contains(point)) { {
if (line->rect.contains(point))
{
break; break;
} }
} }
int lineStart = line == this->lines_.end() ? this->lines_.back().startIndex int lineStart = line == this->lines_.end() ? this->lines_.back().startIndex
: line->startIndex; : line->startIndex;
if (line != this->lines_.end()) { if (line != this->lines_.end())
{
line++; line++;
} }
int lineEnd = int lineEnd =
@ -472,20 +513,24 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
int index = 0; int index = 0;
for (int i = 0; i < lineEnd; i++) { for (int i = 0; i < lineEnd; i++)
{
// end of line // end of line
if (i == lineEnd) { if (i == lineEnd)
{
break; break;
} }
// before line // before line
if (i < lineStart) { if (i < lineStart)
{
index += this->elements_[i]->getSelectionIndexCount(); index += this->elements_[i]->getSelectionIndexCount();
continue; continue;
} }
// this is the word // this is the word
if (point.x() <= this->elements_[i]->getRect().right()) { if (point.x() <= this->elements_[i]->getRect().right())
{
index += this->elements_[i]->getMouseOverIndex(point); index += this->elements_[i]->getMouseOverIndex(point);
break; break;
} }
@ -499,7 +544,8 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
// fourtf: no idea if this is acurate LOL // fourtf: no idea if this is acurate LOL
int MessageLayoutContainer::getLastCharacterIndex() const int MessageLayoutContainer::getLastCharacterIndex() const
{ {
if (this->lines_.size() == 0) { if (this->lines_.size() == 0)
{
return 0; return 0;
} }
return this->lines_.back().endCharIndex; return this->lines_.back().endCharIndex;
@ -515,10 +561,14 @@ int MessageLayoutContainer::getFirstMessageCharacterIndex() const
// Get the index of the first character of the real message // Get the index of the first character of the real message
// (no badges/timestamps/username) // (no badges/timestamps/username)
int index = 0; int index = 0;
for (auto &element : this->elements_) { for (auto &element : this->elements_)
if (element->getFlags().hasAny(flags)) { {
if (element->getFlags().hasAny(flags))
{
index += element->getSelectionIndexCount(); index += element->getSelectionIndexCount();
} else { }
else
{
break; break;
} }
} }
@ -531,8 +581,10 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to,
int index = 0; int index = 0;
bool first = true; bool first = true;
for (auto &element : this->elements_) { for (auto &element : this->elements_)
if (copymode == CopyMode::OnlyTextAndEmotes) { {
if (copymode == CopyMode::OnlyTextAndEmotes)
{
if (element->getCreator().getFlags().hasAny( if (element->getCreator().getFlags().hasAny(
{MessageElementFlag::Timestamp, {MessageElementFlag::Timestamp,
MessageElementFlag::Username, MessageElementFlag::Badges})) MessageElementFlag::Username, MessageElementFlag::Badges}))
@ -541,20 +593,28 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to,
auto indexCount = element->getSelectionIndexCount(); auto indexCount = element->getSelectionIndexCount();
if (first) { if (first)
if (index + indexCount > from) { {
if (index + indexCount > from)
{
element->addCopyTextToString(str, from - index, to - index); element->addCopyTextToString(str, from - index, to - index);
first = false; first = false;
if (index + indexCount > to) { if (index + indexCount > to)
{
break; break;
} }
} }
} else { }
if (index + indexCount > to) { else
{
if (index + indexCount > to)
{
element->addCopyTextToString(str, 0, to - index); element->addCopyTextToString(str, 0, to - index);
break; break;
} else { }
else
{
element->addCopyTextToString(str); element->addCopyTextToString(str);
} }
} }

View file

@ -96,9 +96,11 @@ void ImageLayoutElement::addCopyTextToString(QString &str, int from,
{ {
const auto *emoteElement = const auto *emoteElement =
dynamic_cast<EmoteElement *>(&this->getCreator()); dynamic_cast<EmoteElement *>(&this->getCreator());
if (emoteElement) { if (emoteElement)
{
str += emoteElement->getEmote()->getCopyString(); str += emoteElement->getEmote()->getCopyString();
if (this->hasTrailingSpace()) { if (this->hasTrailingSpace())
{
str += " "; str += " ";
} }
} }
@ -111,12 +113,14 @@ int ImageLayoutElement::getSelectionIndexCount() const
void ImageLayoutElement::paint(QPainter &painter) void ImageLayoutElement::paint(QPainter &painter)
{ {
if (this->image_ == nullptr) { if (this->image_ == nullptr)
{
return; return;
} }
auto pixmap = this->image_->pixmap(); auto pixmap = this->image_->pixmap();
if (pixmap && !this->image_->animated()) { if (pixmap && !this->image_->animated())
{
// fourtf: make it use qreal values // fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF()); painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
} }
@ -124,12 +128,15 @@ void ImageLayoutElement::paint(QPainter &painter)
void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset) void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{ {
if (this->image_ == nullptr) { if (this->image_ == nullptr)
{
return; return;
} }
if (this->image_->animated()) { if (this->image_->animated())
if (auto pixmap = this->image_->pixmap()) { {
if (auto pixmap = this->image_->pixmap())
{
auto rect = this->getRect(); auto rect = this->getRect();
rect.moveTop(rect.y() + yOffset); rect.moveTop(rect.y() + yOffset);
painter.drawPixmap(QRectF(rect), *pixmap, QRectF()); painter.drawPixmap(QRectF(rect), *pixmap, QRectF());
@ -144,12 +151,17 @@ int ImageLayoutElement::getMouseOverIndex(const QPoint &abs) const
int ImageLayoutElement::getXFromIndex(int index) int ImageLayoutElement::getXFromIndex(int index)
{ {
if (index <= 0) { if (index <= 0)
{
return this->getRect().left(); return this->getRect().left();
} else if (index == 1) { }
else if (index == 1)
{
// fourtf: remove space width // fourtf: remove space width
return this->getRect().right(); return this->getRect().right();
} else { }
else
{
return this->getRect().right(); return this->getRect().right();
} }
} }
@ -174,7 +186,8 @@ void TextLayoutElement::addCopyTextToString(QString &str, int from,
{ {
str += this->getText().mid(from, to - from); str += this->getText().mid(from, to - from);
if (this->hasTrailingSpace()) { if (this->hasTrailingSpace())
{
str += " "; str += " ";
} }
} }
@ -203,7 +216,8 @@ void TextLayoutElement::paintAnimated(QPainter &, int)
int TextLayoutElement::getMouseOverIndex(const QPoint &abs) const int TextLayoutElement::getMouseOverIndex(const QPoint &abs) const
{ {
if (abs.x() < this->getRect().left()) { if (abs.x() < this->getRect().left())
{
return 0; return 0;
} }
@ -213,11 +227,13 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs) const
int x = this->getRect().left(); int x = this->getRect().left();
for (int i = 0; i < this->getText().size(); i++) { for (int i = 0; i < this->getText().size(); i++)
{
auto &text = this->getText(); auto &text = this->getText();
auto width = metrics.width(this->getText()[i]); auto width = metrics.width(this->getText()[i]);
if (x + width > abs.x()) { if (x + width > abs.x())
{
if (text.size() > i + 1 && if (text.size() > i + 1 &&
QChar::isLowSurrogate(text[i].unicode())) // QChar::isLowSurrogate(text[i].unicode())) //
{ {
@ -239,15 +255,21 @@ int TextLayoutElement::getXFromIndex(int index)
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale); QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
if (index <= 0) { if (index <= 0)
{
return this->getRect().left(); return this->getRect().left();
} else if (index < this->getText().size()) { }
else if (index < this->getText().size())
{
int x = 0; int x = 0;
for (int i = 0; i < index; i++) { for (int i = 0; i < index; i++)
{
x += metrics.width(this->getText()[i]); x += metrics.width(this->getText()[i]);
} }
return x + this->getRect().left(); return x + this->getRect().left();
} else { }
else
{
return this->getRect().right(); return this->getRect().right();
} }
} }
@ -286,10 +308,13 @@ void TextIconLayoutElement::paint(QPainter &painter)
QTextOption option; QTextOption option;
option.setAlignment(Qt::AlignHCenter); option.setAlignment(Qt::AlignHCenter);
if (this->line2.isEmpty()) { if (this->line2.isEmpty())
{
QRect _rect(this->getRect()); QRect _rect(this->getRect());
painter.drawText(_rect, this->line1, option); painter.drawText(_rect, this->line1, option);
} else { }
else
{
painter.drawText( painter.drawText(
QPoint(this->getRect().x(), QPoint(this->getRect().x(),
this->getRect().y() + this->getRect().height() / 2), this->getRect().y() + this->getRect().height() / 2),
@ -311,12 +336,17 @@ int TextIconLayoutElement::getMouseOverIndex(const QPoint &abs) const
int TextIconLayoutElement::getXFromIndex(int index) int TextIconLayoutElement::getXFromIndex(int index)
{ {
if (index <= 0) { if (index <= 0)
{
return this->getRect().left(); return this->getRect().left();
} else if (index == 1) { }
else if (index == 1)
{
// fourtf: remove space width // fourtf: remove space width
return this->getRect().right(); return this->getRect().right();
} else { }
else
{
return this->getRect().right(); return this->getRect().right();
} }
} }

View file

@ -23,12 +23,16 @@ void LinkResolver::getLinkInfo(
auto statusCode = root.value("status").toInt(); auto statusCode = root.value("status").toInt();
QString response = QString(); QString response = QString();
QString linkString = url; QString linkString = url;
if (statusCode == 200) { if (statusCode == 200)
{
response = root.value("tooltip").toString(); response = root.value("tooltip").toString();
if (getSettings()->enableUnshortLinks) { if (getSettings()->enableUnshortLinks)
{
linkString = root.value("link").toString(); linkString = root.value("link").toString();
} }
} else { }
else
{
response = root.value("message").toString(); response = root.value("message").toString();
} }
successCallback(QUrl::fromPercentEncoding(response.toUtf8()), successCallback(QUrl::fromPercentEncoding(response.toUtf8()),

View file

@ -11,7 +11,7 @@ class LinkResolver
{ {
public: public:
static void getLinkInfo(const QString url, static void getLinkInfo(const QString url,
std::function<void(QString, Link)> callback); std::function<void(QString, Link)> callback);
private: private:
}; };

View file

@ -29,7 +29,8 @@ namespace {
auto urlTemplate = auto urlTemplate =
qS("https:") + jsonRoot.value("urlTemplate").toString(); qS("https:") + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote : jsonEmotes) { for (auto jsonEmote : jsonEmotes)
{
auto id = EmoteId{jsonEmote.toObject().value("id").toString()}; auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = auto name =
EmoteName{jsonEmote.toObject().value("code").toString()}; EmoteName{jsonEmote.toObject().value("code").toString()};
@ -62,7 +63,8 @@ namespace {
auto jsonEmotes = jsonRoot.value("emotes").toArray(); auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString(); auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote_ : jsonEmotes) { for (auto jsonEmote_ : jsonEmotes)
{
auto jsonEmote = jsonEmote_.toObject(); auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()}; auto id = EmoteId{jsonEmote.value("id").toString()};
@ -103,7 +105,8 @@ boost::optional<EmotePtr> BttvEmotes::emote(const EmoteName &name) const
auto emotes = this->global_.get(); auto emotes = this->global_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it == emotes->end()) return boost::none; if (it == emotes->end())
return boost::none;
return it->second; return it->second;
} }
@ -137,7 +140,8 @@ void BttvEmotes::loadChannel(const QString &channelName,
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome { request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson()); auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second)); if (pair.first)
callback(std::move(pair.second));
return pair.first; return pair.first;
}); });

View file

@ -25,7 +25,8 @@ ChatterinoBadges::ChatterinoBadges()
boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username) boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username)
{ {
auto it = badgeMap.find(username.string); auto it = badgeMap.find(username.string);
if (it != badgeMap.end()) { if (it != badgeMap.end())
{
return emotes[it->second]; return emotes[it->second];
} }
return boost::none; return boost::none;
@ -41,7 +42,8 @@ void ChatterinoBadges::loadChatterinoBadges()
req.onSuccess([this](auto result) -> Outcome { req.onSuccess([this](auto result) -> Outcome {
auto jsonRoot = result.parseJson(); auto jsonRoot = result.parseJson();
int index = 0; int index = 0;
for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) { for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray())
{
auto jsonBadge = jsonBadge_.toObject(); auto jsonBadge = jsonBadge_.toObject();
auto emote = Emote{ auto emote = Emote{
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}}, EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
@ -49,7 +51,8 @@ void ChatterinoBadges::loadChatterinoBadges()
emotes.push_back(std::make_shared<const Emote>(std::move(emote))); emotes.push_back(std::make_shared<const Emote>(std::move(emote)));
for (const auto &user : jsonBadge.value("users").toArray()) { for (const auto &user : jsonBadge.value("users").toArray())
{
badgeMap[user.toString()] = index; badgeMap[user.toString()] = index;
} }
++index; ++index;

View file

@ -29,11 +29,15 @@ namespace {
bool messenger; bool messenger;
} capabilities; } capabilities;
if (!shortCode.isEmpty()) { if (!shortCode.isEmpty())
{
emojiData->shortCodes.push_back(shortCode); emojiData->shortCodes.push_back(shortCode);
} else { }
else
{
const auto &shortCodes = unparsedEmoji["short_names"]; const auto &shortCodes = unparsedEmoji["short_names"];
for (const auto &shortCode : shortCodes.GetArray()) { for (const auto &shortCode : shortCodes.GetArray())
{
emojiData->shortCodes.emplace_back(shortCode.GetString()); emojiData->shortCodes.emplace_back(shortCode.GetString());
} }
} }
@ -49,39 +53,50 @@ namespace {
rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook); rj::getSafe(unparsedEmoji, "has_img_facebook", capabilities.facebook);
rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger); rj::getSafe(unparsedEmoji, "has_img_messenger", capabilities.messenger);
if (capabilities.apple) { if (capabilities.apple)
{
emojiData->capabilities.insert("Apple"); emojiData->capabilities.insert("Apple");
} }
if (capabilities.google) { if (capabilities.google)
{
emojiData->capabilities.insert("Google"); emojiData->capabilities.insert("Google");
} }
if (capabilities.twitter) { if (capabilities.twitter)
{
emojiData->capabilities.insert("Twitter"); emojiData->capabilities.insert("Twitter");
} }
if (capabilities.emojione) { if (capabilities.emojione)
{
emojiData->capabilities.insert("EmojiOne 3"); emojiData->capabilities.insert("EmojiOne 3");
} }
if (capabilities.facebook) { if (capabilities.facebook)
{
emojiData->capabilities.insert("Facebook"); emojiData->capabilities.insert("Facebook");
} }
if (capabilities.messenger) { if (capabilities.messenger)
{
emojiData->capabilities.insert("Messenger"); emojiData->capabilities.insert("Messenger");
} }
QStringList unicodeCharacters; QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) { if (!emojiData->nonQualifiedCode.isEmpty())
{
unicodeCharacters = unicodeCharacters =
emojiData->nonQualifiedCode.toLower().split('-'); emojiData->nonQualifiedCode.toLower().split('-');
} else { }
else
{
unicodeCharacters = emojiData->unifiedCode.toLower().split('-'); unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
} }
if (unicodeCharacters.length() < 1) { if (unicodeCharacters.length() < 1)
{
return; return;
} }
int numUnicodeBytes = 0; int numUnicodeBytes = 0;
for (const QString &unicodeCharacter : unicodeCharacters) { for (const QString &unicodeCharacter : unicodeCharacters)
{
unicodeBytes[numUnicodeBytes++] = unicodeBytes[numUnicodeBytes++] =
QString(unicodeCharacter).toUInt(nullptr, 16); QString(unicodeCharacter).toUInt(nullptr, 16);
} }
@ -115,17 +130,20 @@ void Emojis::loadEmojis()
rapidjson::Document root; rapidjson::Document root;
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length()); rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) { if (result.Code() != rapidjson::kParseErrorNone)
{
log("JSON parse error: {} ({})", log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset()); rapidjson::GetParseError_En(result.Code()), result.Offset());
return; return;
} }
for (const auto &unparsedEmoji : root.GetArray()) { for (const auto &unparsedEmoji : root.GetArray())
{
auto emojiData = std::make_shared<EmojiData>(); auto emojiData = std::make_shared<EmojiData>();
parseEmoji(emojiData, unparsedEmoji); parseEmoji(emojiData, unparsedEmoji);
for (const auto &shortCode : emojiData->shortCodes) { for (const auto &shortCode : emojiData->shortCodes)
{
this->emojiShortCodeToEmoji_.insert(shortCode, emojiData); this->emojiShortCodeToEmoji_.insert(shortCode, emojiData);
this->shortCodes.emplace_back(shortCode); this->shortCodes.emplace_back(shortCode);
} }
@ -134,16 +152,19 @@ void Emojis::loadEmojis()
this->emojis.insert(emojiData->unifiedCode, emojiData); this->emojis.insert(emojiData->unifiedCode, emojiData);
if (unparsedEmoji.HasMember("skin_variations")) { if (unparsedEmoji.HasMember("skin_variations"))
{
for (const auto &skinVariation : for (const auto &skinVariation :
unparsedEmoji["skin_variations"].GetObject()) { unparsedEmoji["skin_variations"].GetObject())
{
std::string tone = skinVariation.name.GetString(); std::string tone = skinVariation.name.GetString();
const auto &variation = skinVariation.value; const auto &variation = skinVariation.value;
auto variationEmojiData = std::make_shared<EmojiData>(); auto variationEmojiData = std::make_shared<EmojiData>();
auto toneNameIt = toneNames.find(tone); auto toneNameIt = toneNames.find(tone);
if (toneNameIt == toneNames.end()) { if (toneNameIt == toneNames.end())
{
log("Tone with key {} does not exist in tone names map", log("Tone with key {} does not exist in tone names map",
tone); tone);
continue; continue;
@ -172,24 +193,28 @@ void Emojis::loadEmojiOne2Capabilities()
file.open(QFile::ReadOnly); file.open(QFile::ReadOnly);
QTextStream in(&file); QTextStream in(&file);
while (!in.atEnd()) { while (!in.atEnd())
{
// Line example: sunglasses 1f60e // Line example: sunglasses 1f60e
QString line = in.readLine(); QString line = in.readLine();
if (line.at(0) == '#') { if (line.at(0) == '#')
{
// Ignore lines starting with # (comments) // Ignore lines starting with # (comments)
continue; continue;
} }
QStringList parts = line.split(' '); QStringList parts = line.split(' ');
if (parts.length() < 2) { if (parts.length() < 2)
{
continue; continue;
} }
QString shortCode = parts[0]; QString shortCode = parts[0];
auto emojiIt = this->emojiShortCodeToEmoji_.find(shortCode); auto emojiIt = this->emojiShortCodeToEmoji_.find(shortCode);
if (emojiIt != this->emojiShortCodeToEmoji_.end()) { if (emojiIt != this->emojiShortCodeToEmoji_.end())
{
std::shared_ptr<EmojiData> emoji = *emojiIt; std::shared_ptr<EmojiData> emoji = *emojiIt;
emoji->capabilities.insert("EmojiOne 2"); emoji->capabilities.insert("EmojiOne 2");
continue; continue;
@ -199,7 +224,8 @@ void Emojis::loadEmojiOne2Capabilities()
void Emojis::sortEmojis() void Emojis::sortEmojis()
{ {
for (auto &p : this->emojiFirstByte_) { for (auto &p : this->emojiFirstByte_)
{
std::stable_sort(p.begin(), p.end(), std::stable_sort(p.begin(), p.end(),
[](const auto &lhs, const auto &rhs) { [](const auto &lhs, const auto &rhs) {
return lhs->value.length() > rhs->value.length(); return lhs->value.length() > rhs->value.length();
@ -239,13 +265,16 @@ void Emojis::loadEmojiSet()
}; };
// clang-format on // clang-format on
if (emoji->capabilities.count(emojiSetToUse) == 0) { if (emoji->capabilities.count(emojiSetToUse) == 0)
{
emojiSetToUse = "EmojiOne 3"; emojiSetToUse = "EmojiOne 3";
} }
QString code = emoji->unifiedCode; QString code = emoji->unifiedCode;
if (emojiSetToUse == "EmojiOne 2") { if (emojiSetToUse == "EmojiOne 2")
if (!emoji->nonQualifiedCode.isEmpty()) { {
if (!emoji->nonQualifiedCode.isEmpty())
{
code = emoji->nonQualifiedCode; code = emoji->nonQualifiedCode;
} }
} }
@ -253,7 +282,8 @@ void Emojis::loadEmojiSet()
QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/" QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/"; "emojione/2.2.6/assets/png/";
auto it = emojiSets.find(emojiSetToUse); auto it = emojiSets.find(emojiSetToUse);
if (it != emojiSets.end()) { if (it != emojiSets.end())
{
urlPrefix = it->second; urlPrefix = it->second;
} }
QString url = urlPrefix + code + ".png"; QString url = urlPrefix + code + ".png";
@ -270,15 +300,18 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
auto result = std::vector<boost::variant<EmotePtr, QString>>(); auto result = std::vector<boost::variant<EmotePtr, QString>>();
int lastParsedEmojiEndIndex = 0; int lastParsedEmojiEndIndex = 0;
for (auto i = 0; i < text.length(); ++i) { for (auto i = 0; i < text.length(); ++i)
{
const QChar character = text.at(i); const QChar character = text.at(i);
if (character.isLowSurrogate()) { if (character.isLowSurrogate())
{
continue; continue;
} }
auto it = this->emojiFirstByte_.find(character); auto it = this->emojiFirstByte_.find(character);
if (it == this->emojiFirstByte_.end()) { if (it == this->emojiFirstByte_.end())
{
// No emoji starts with this character // No emoji starts with this character
continue; continue;
} }
@ -291,24 +324,29 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
int matchedEmojiLength = 0; int matchedEmojiLength = 0;
for (const std::shared_ptr<EmojiData> &emoji : possibleEmojis) { for (const std::shared_ptr<EmojiData> &emoji : possibleEmojis)
{
int emojiExtraCharacters = emoji->value.length() - 1; int emojiExtraCharacters = emoji->value.length() - 1;
if (emojiExtraCharacters > remainingCharacters) { if (emojiExtraCharacters > remainingCharacters)
{
// It cannot be this emoji, there's not enough space for it // It cannot be this emoji, there's not enough space for it
continue; continue;
} }
bool match = true; bool match = true;
for (int j = 1; j < emoji->value.length(); ++j) { for (int j = 1; j < emoji->value.length(); ++j)
if (text.at(i + j) != emoji->value.at(j)) { {
if (text.at(i + j) != emoji->value.at(j))
{
match = false; match = false;
break; break;
} }
} }
if (match) { if (match)
{
matchedEmoji = emoji; matchedEmoji = emoji;
matchedEmojiLength = emoji->value.length(); matchedEmojiLength = emoji->value.length();
@ -316,7 +354,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
} }
} }
if (matchedEmojiLength == 0) { if (matchedEmojiLength == 0)
{
continue; continue;
} }
@ -326,7 +365,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
int charactersFromLastParsedEmoji = int charactersFromLastParsedEmoji =
currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex; currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
if (charactersFromLastParsedEmoji > 0) { if (charactersFromLastParsedEmoji > 0)
{
// Add characters inbetween emojis // Add characters inbetween emojis
result.emplace_back(text.mid(lastParsedEmojiEndIndex, result.emplace_back(text.mid(lastParsedEmojiEndIndex,
charactersFromLastParsedEmoji)); charactersFromLastParsedEmoji));
@ -340,7 +380,8 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
i += matchedEmojiLength - 1; i += matchedEmojiLength - 1;
} }
if (lastParsedEmojiEndIndex < text.length()) { if (lastParsedEmojiEndIndex < text.length())
{
// Add remaining characters // Add remaining characters
result.emplace_back(text.mid(lastParsedEmojiEndIndex)); result.emplace_back(text.mid(lastParsedEmojiEndIndex));
} }
@ -355,7 +396,8 @@ QString Emojis::replaceShortCodes(const QString &text)
int32_t offset = 0; int32_t offset = 0;
while (it.hasNext()) { while (it.hasNext())
{
auto match = it.next(); auto match = it.next();
auto capturedString = match.captured(); auto capturedString = match.captured();
@ -365,7 +407,8 @@ QString Emojis::replaceShortCodes(const QString &text)
auto emojiIt = this->emojiShortCodeToEmoji_.constFind(matchString); auto emojiIt = this->emojiShortCodeToEmoji_.constFind(matchString);
if (emojiIt == this->emojiShortCodeToEmoji_.constEnd()) { if (emojiIt == this->emojiShortCodeToEmoji_.constEnd())
{
continue; continue;
} }

View file

@ -13,7 +13,8 @@ namespace {
Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale) Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
{ {
auto emote = urls.value(emoteScale); auto emote = urls.value(emoteScale);
if (emote.isUndefined()) { if (emote.isUndefined())
{
return {""}; return {""};
} }
@ -48,10 +49,12 @@ namespace {
auto jsonSets = jsonRoot.value("sets").toObject(); auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap(); auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) { for (auto jsonSet : jsonSets)
{
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto jsonEmoteValue : jsonEmotes) { for (auto jsonEmoteValue : jsonEmotes)
{
auto jsonEmote = jsonEmoteValue.toObject(); auto jsonEmote = jsonEmoteValue.toObject();
auto name = EmoteName{jsonEmote.value("name").toString()}; auto name = EmoteName{jsonEmote.value("name").toString()};
@ -78,10 +81,12 @@ namespace {
auto jsonSets = jsonRoot.value("sets").toObject(); auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap(); auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) { for (auto jsonSet : jsonSets)
{
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray(); auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto _jsonEmote : jsonEmotes) { for (auto _jsonEmote : jsonEmotes)
{
auto jsonEmote = _jsonEmote.toObject(); auto jsonEmote = _jsonEmote.toObject();
// margins // margins
@ -120,7 +125,8 @@ boost::optional<EmotePtr> FfzEmotes::emote(const EmoteName &name) const
{ {
auto emotes = this->global_.get(); auto emotes = this->global_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it != emotes->end()) return it->second; if (it != emotes->end())
return it->second;
return boost::none; return boost::none;
} }
@ -156,17 +162,20 @@ void FfzEmotes::loadChannel(const QString &channelName,
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome { request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson()); auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second)); if (pair.first)
callback(std::move(pair.second));
return pair.first; return pair.first;
}); });
request.onError([channelName](int result) { request.onError([channelName](int result) {
if (result == 203) { if (result == 203)
{
// User does not have any FFZ emotes // User does not have any FFZ emotes
return true; return true;
} }
if (result == -2) { if (result == -2)
{
// TODO: Auto retry in case of a timeout, with a delay // TODO: Auto retry in case of a timeout, with a delay
log("Fetching FFZ emotes for channel {} failed due to timeout", log("Fetching FFZ emotes for channel {} failed due to timeout",
channelName); channelName);

View file

@ -60,7 +60,8 @@ AbstractIrcServer::AbstractIrcServer()
this->falloffCounter_ = this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1); std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected()) { if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_); log("Trying to reconnect... {}", this->falloffCounter_);
this->connect(); this->connect();
} }
@ -73,10 +74,13 @@ void AbstractIrcServer::connect()
bool separateWriteConnection = this->hasSeparateWriteConnection(); bool separateWriteConnection = this->hasSeparateWriteConnection();
if (separateWriteConnection) { if (separateWriteConnection)
{
this->initializeConnection(this->writeConnection_.get(), false, true); this->initializeConnection(this->writeConnection_.get(), false, true);
this->initializeConnection(this->readConnection_.get(), true, false); this->initializeConnection(this->readConnection_.get(), true, false);
} else { }
else
{
this->initializeConnection(this->readConnection_.get(), true, true); this->initializeConnection(this->readConnection_.get(), true, true);
} }
@ -85,8 +89,10 @@ void AbstractIrcServer::connect()
std::lock_guard<std::mutex> lock1(this->connectionMutex_); std::lock_guard<std::mutex> lock1(this->connectionMutex_);
std::lock_guard<std::mutex> lock2(this->channelMutex); std::lock_guard<std::mutex> lock2(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values()) { for (std::weak_ptr<Channel> &weak : this->channels.values())
if (auto channel = std::shared_ptr<Channel>(weak.lock())) { {
if (auto channel = std::shared_ptr<Channel>(weak.lock()))
{
this->readConnection_->sendRaw("JOIN #" + channel->getName()); this->readConnection_->sendRaw("JOIN #" + channel->getName());
} }
} }
@ -117,9 +123,12 @@ void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
{ {
std::lock_guard<std::mutex> locker(this->connectionMutex_); std::lock_guard<std::mutex> locker(this->connectionMutex_);
if (this->hasSeparateWriteConnection()) { if (this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw(rawMessage); this->writeConnection_->sendRaw(rawMessage);
} else { }
else
{
this->readConnection_->sendRaw(rawMessage); this->readConnection_->sendRaw(rawMessage);
} }
} }
@ -136,7 +145,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
// try get channel // try get channel
ChannelPtr chan = this->getChannelOrEmpty(channelName); ChannelPtr chan = this->getChannelOrEmpty(channelName);
if (chan != Channel::getEmpty()) { if (chan != Channel::getEmpty())
{
return chan; return chan;
} }
@ -144,7 +154,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
// value doesn't exist // value doesn't exist
chan = this->createChannel(channelName); chan = this->createChannel(channelName);
if (!chan) { if (!chan)
{
return Channel::getEmpty(); return Channel::getEmpty();
} }
@ -158,11 +169,13 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
clojuresInCppAreShit); clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit); this->channels.remove(clojuresInCppAreShit);
if (this->readConnection_) { if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit); this->readConnection_->sendRaw("PART #" + clojuresInCppAreShit);
} }
if (this->writeConnection_) { if (this->writeConnection_)
{
this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit); this->writeConnection_->sendRaw("PART #" + clojuresInCppAreShit);
} }
}); });
@ -171,11 +184,13 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
{ {
std::lock_guard<std::mutex> lock2(this->connectionMutex_); std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_) { if (this->readConnection_)
{
this->readConnection_->sendRaw("JOIN #" + channelName); this->readConnection_->sendRaw("JOIN #" + channelName);
} }
if (this->writeConnection_) { if (this->writeConnection_)
{
this->writeConnection_->sendRaw("JOIN #" + channelName); this->writeConnection_->sendRaw("JOIN #" + channelName);
} }
} }
@ -192,16 +207,19 @@ std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
// try get special channel // try get special channel
ChannelPtr chan = this->getCustomChannel(channelName); ChannelPtr chan = this->getCustomChannel(channelName);
if (chan) { if (chan)
{
return chan; return chan;
} }
// value exists // value exists
auto it = this->channels.find(channelName); auto it = this->channels.find(channelName);
if (it != this->channels.end()) { if (it != this->channels.end())
{
chan = it.value().lock(); chan = it.value().lock();
if (chan) { if (chan)
{
return chan; return chan;
} }
} }
@ -216,9 +234,11 @@ void AbstractIrcServer::onConnected()
auto connected = makeSystemMessage("connected to chat"); auto connected = makeSystemMessage("connected to chat");
auto reconnected = makeSystemMessage("reconnected to chat"); auto reconnected = makeSystemMessage("reconnected to chat");
for (std::weak_ptr<Channel> &weak : this->channels.values()) { for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) { if (!chan)
{
continue; continue;
} }
@ -228,7 +248,8 @@ void AbstractIrcServer::onConnected()
snapshot[snapshot.getLength() - 1]->flags.has( snapshot[snapshot.getLength() - 1]->flags.has(
MessageFlag::DisconnectedMessage); MessageFlag::DisconnectedMessage);
if (replaceMessage) { if (replaceMessage)
{
chan->replaceMessage(snapshot[snapshot.getLength() - 1], chan->replaceMessage(snapshot[snapshot.getLength() - 1],
reconnected); reconnected);
continue; continue;
@ -248,9 +269,11 @@ void AbstractIrcServer::onDisconnected()
b->flags.set(MessageFlag::DisconnectedMessage); b->flags.set(MessageFlag::DisconnectedMessage);
auto disconnected = b.release(); auto disconnected = b.release();
for (std::weak_ptr<Channel> &weak : this->channels.values()) { for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) { if (!chan)
{
continue; continue;
} }
@ -279,10 +302,13 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
auto fakeMessage = Communi::IrcMessage::fromData( auto fakeMessage = Communi::IrcMessage::fromData(
data.toUtf8(), this->readConnection_.get()); data.toUtf8(), this->readConnection_.get());
if (fakeMessage->command() == "PRIVMSG") { if (fakeMessage->command() == "PRIVMSG")
{
this->privateMessageReceived( this->privateMessageReceived(
static_cast<Communi::IrcPrivateMessage *>(fakeMessage)); static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
} else { }
else
{
this->messageReceived(fakeMessage); this->messageReceived(fakeMessage);
} }
} }
@ -300,9 +326,11 @@ void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values()) { for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock(); std::shared_ptr<Channel> chan = weak.lock();
if (!chan) { if (!chan)
{
continue; continue;
} }

View file

@ -9,8 +9,10 @@ IrcConnection::IrcConnection(QObject *parent)
this->pingTimer_.setInterval(5000); this->pingTimer_.setInterval(5000);
this->pingTimer_.start(); this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] { QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected()) { if (this->isConnected())
if (!this->recentlyReceivedMessage_.load()) { {
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING"); this->sendRaw("PING");
this->reconnectTimer_.start(); this->reconnectTimer_.start();
} }
@ -22,7 +24,8 @@ IrcConnection::IrcConnection(QObject *parent)
this->reconnectTimer_.setInterval(5000); this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true); this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] { QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected()) { if (this->isConnected())
{
reconnectRequested.invoke(); reconnectRequested.invoke();
} }
}); });
@ -31,7 +34,8 @@ IrcConnection::IrcConnection(QObject *parent)
[this](Communi::IrcMessage *) { [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true; this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) { if (this->reconnectTimer_.isActive())
{
this->reconnectTimer_.stop(); this->reconnectTimer_.stop();
} }
}); });

View file

@ -39,29 +39,35 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
bool isSub, bool isAction) bool isSub, bool isAction)
{ {
QString channelName; QString channelName;
if (!trimChannelName(target, channelName)) { if (!trimChannelName(target, channelName))
{
return; return;
} }
auto chan = server.getChannelOrEmpty(channelName); auto chan = server.getChannelOrEmpty(channelName);
if (chan->isEmpty()) { if (chan->isEmpty())
{
return; return;
} }
MessageParseArgs args; MessageParseArgs args;
if (isSub) { if (isSub)
{
args.trimSubscriberUsername = true; args.trimSubscriberUsername = true;
} }
if (chan->isBroadcaster()) { if (chan->isBroadcaster())
{
args.isStaffOrBroadcaster = true; args.isStaffOrBroadcaster = true;
} }
TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction); TwitchMessageBuilder builder(chan.get(), _message, args, content, isAction);
if (isSub || !builder.isIgnored()) { if (isSub || !builder.isIgnored())
if (isSub) { {
if (isSub)
{
builder->flags.set(MessageFlag::Subscription); builder->flags.set(MessageFlag::Subscription);
builder->flags.unset(MessageFlag::Highlighted); builder->flags.unset(MessageFlag::Highlighted);
} }
@ -69,8 +75,10 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
auto msg = builder.build(); auto msg = builder.build();
auto highlighted = msg->flags.has(MessageFlag::Highlighted); auto highlighted = msg->flags.has(MessageFlag::Highlighted);
if (!isSub) { if (!isSub)
if (highlighted) { {
if (highlighted)
{
server.mentionsChannel->addMessage(msg); server.mentionsChannel->addMessage(msg);
getApp()->highlights->addHighlight(msg); getApp()->highlights->addHighlight(msg);
} }
@ -87,16 +95,19 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
// get twitch channel // get twitch channel
QString chanName; QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) { if (!trimChannelName(message->parameter(0), chanName))
{
return; return;
} }
auto chan = app->twitch.server->getChannelOrEmpty(chanName); auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get())) { if (auto *twitchChannel = dynamic_cast<TwitchChannel *>(chan.get()))
{
// room-id // room-id
decltype(tags.find("xD")) it; decltype(tags.find("xD")) it;
if ((it = tags.find("room-id")) != tags.end()) { if ((it = tags.find("room-id")) != tags.end())
{
auto roomId = it.value().toString(); auto roomId = it.value().toString();
twitchChannel->setRoomId(roomId); twitchChannel->setRoomId(roomId);
@ -106,19 +117,24 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{ {
auto roomModes = *twitchChannel->accessRoomModes(); auto roomModes = *twitchChannel->accessRoomModes();
if ((it = tags.find("emote-only")) != tags.end()) { if ((it = tags.find("emote-only")) != tags.end())
{
roomModes.emoteOnly = it.value() == "1"; roomModes.emoteOnly = it.value() == "1";
} }
if ((it = tags.find("subs-only")) != tags.end()) { if ((it = tags.find("subs-only")) != tags.end())
{
roomModes.submode = it.value() == "1"; roomModes.submode = it.value() == "1";
} }
if ((it = tags.find("slow")) != tags.end()) { if ((it = tags.find("slow")) != tags.end())
{
roomModes.slowMode = it.value().toInt(); roomModes.slowMode = it.value().toInt();
} }
if ((it = tags.find("r9k")) != tags.end()) { if ((it = tags.find("r9k")) != tags.end())
{
roomModes.r9k = it.value() == "1"; roomModes.r9k = it.value() == "1";
} }
if ((it = tags.find("broadcaster-lang")) != tags.end()) { if ((it = tags.find("broadcaster-lang")) != tags.end())
{
roomModes.broadcasterLang = it.value().toString(); roomModes.broadcasterLang = it.value().toString();
} }
twitchChannel->setRoomModes(roomModes); twitchChannel->setRoomModes(roomModes);
@ -131,12 +147,14 @@ void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{ {
// check parameter count // check parameter count
if (message->parameters().length() < 1) { if (message->parameters().length() < 1)
{
return; return;
} }
QString chanName; QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) { if (!trimChannelName(message->parameter(0), chanName))
{
return; return;
} }
@ -145,7 +163,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
// get channel // get channel
auto chan = app->twitch.server->getChannelOrEmpty(chanName); auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) { if (chan->isEmpty())
{
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not " log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found", "found",
chanName); chanName);
@ -153,7 +172,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
} }
// check if the chat has been cleared by a moderator // check if the chat has been cleared by a moderator
if (message->parameters().length() == 1) { if (message->parameters().length() == 1)
{
chan->disableAllMessages(); chan->disableAllMessages();
chan->addMessage( chan->addMessage(
makeSystemMessage("Chat has been cleared by a moderator.")); makeSystemMessage("Chat has been cleared by a moderator."));
@ -165,12 +185,14 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
QString username = message->parameter(1); QString username = message->parameter(1);
QString durationInSeconds, reason; QString durationInSeconds, reason;
QVariant v = message->tag("ban-duration"); QVariant v = message->tag("ban-duration");
if (v.isValid()) { if (v.isValid())
{
durationInSeconds = v.toString(); durationInSeconds = v.toString();
} }
v = message->tag("ban-reason"); v = message->tag("ban-reason");
if (v.isValid()) { if (v.isValid())
{
reason = v.toString(); reason = v.toString();
} }
@ -187,21 +209,25 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
{ {
QVariant _mod = message->tag("mod"); QVariant _mod = message->tag("mod");
if (_mod.isValid()) { if (_mod.isValid())
{
auto app = getApp(); auto app = getApp();
QString channelName; QString channelName;
if (!trimChannelName(message->parameter(0), channelName)) { if (!trimChannelName(message->parameter(0), channelName))
{
return; return;
} }
auto c = app->twitch.server->getChannelOrEmpty(channelName); auto c = app->twitch.server->getChannelOrEmpty(channelName);
if (c->isEmpty()) { if (c->isEmpty())
{
return; return;
} }
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get()); TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get());
if (tc != nullptr) { if (tc != nullptr)
{
tc->setMod(_mod == "1"); tc->setMod(_mod == "1");
} }
} }
@ -220,12 +246,14 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
TwitchMessageBuilder builder(c, message, args, message->parameter(1), TwitchMessageBuilder builder(c, message, args, message->parameter(1),
false); false);
if (!builder.isIgnored()) { if (!builder.isIgnored())
{
MessagePtr _message = builder.build(); MessagePtr _message = builder.build();
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName); app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
if (_message->flags.has(MessageFlag::Highlighted)) { if (_message->flags.has(MessageFlag::Highlighted))
{
app->twitch.server->mentionsChannel->addMessage(_message); app->twitch.server->mentionsChannel->addMessage(_message);
} }
@ -234,7 +262,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
auto overrideFlags = boost::optional<MessageFlags>(_message->flags); auto overrideFlags = boost::optional<MessageFlags>(_message->flags);
overrideFlags->set(MessageFlag::DoNotTriggerNotification); overrideFlags->set(MessageFlag::DoNotTriggerNotification);
if (getSettings()->inlineWhispers) { if (getSettings()->inlineWhispers)
{
app->twitch.server->forEachChannel( app->twitch.server->forEachChannel(
[_message, overrideFlags](ChannelPtr channel) { [_message, overrideFlags](ChannelPtr channel) {
channel->addMessage(_message, overrideFlags); // channel->addMessage(_message, overrideFlags); //
@ -254,21 +283,25 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
auto target = parameters[0]; auto target = parameters[0];
QString msgType = tags.value("msg-id", "").toString(); QString msgType = tags.value("msg-id", "").toString();
QString content; QString content;
if (parameters.size() >= 2) { if (parameters.size() >= 2)
{
content = parameters[1]; content = parameters[1];
} }
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") { if (msgType == "sub" || msgType == "resub" || msgType == "subgift")
{
// Sub-specific message. I think it's only allowed for "resub" messages // Sub-specific message. I think it's only allowed for "resub" messages
// atm // atm
if (!content.isEmpty()) { if (!content.isEmpty())
{
this->addMessage(message, target, content, server, true, false); this->addMessage(message, target, content, server, true, false);
} }
} }
auto it = tags.find("system-msg"); auto it = tags.find("system-msg");
if (it != tags.end()) { if (it != tags.end())
{
auto b = MessageBuilder(systemMessage, auto b = MessageBuilder(systemMessage,
parseTagString(it.value().toString())); parseTagString(it.value().toString()));
@ -277,17 +310,20 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
QString channelName; QString channelName;
if (message->parameters().size() < 1) { if (message->parameters().size() < 1)
{
return; return;
} }
if (!trimChannelName(message->parameter(0), channelName)) { if (!trimChannelName(message->parameter(0), channelName))
{
return; return;
} }
auto chan = server.getChannelOrEmpty(channelName); auto chan = server.getChannelOrEmpty(channelName);
if (!chan->isEmpty()) { if (!chan->isEmpty())
{
chan->addMessage(newMessage); chan->addMessage(newMessage);
} }
} }
@ -300,13 +336,17 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty( auto channel = app->twitch.server->getChannelOrEmpty(
message->parameter(0).remove(0, 1)); message->parameter(0).remove(0, 1));
if (channel->isEmpty()) { if (channel->isEmpty())
{
return; return;
} }
if (message->parameter(1) == "+o") { if (message->parameter(1) == "+o")
{
channel->modList.append(message->parameter(2)); channel->modList.append(message->parameter(2));
} else if (message->parameter(1) == "-o") { }
else if (message->parameter(1) == "-o")
{
channel->modList.append(message->parameter(2)); channel->modList.append(message->parameter(2));
} }
} }
@ -317,7 +357,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
MessagePtr msg = makeSystemMessage(message->content()); MessagePtr msg = makeSystemMessage(message->content());
QString channelName; QString channelName;
if (!trimChannelName(message->target(), channelName)) { if (!trimChannelName(message->target(), channelName))
{
// Notice wasn't targeted at a single channel, send to all twitch // Notice wasn't targeted at a single channel, send to all twitch
// channels // channels
app->twitch.server->forEachChannelAndSpecialChannels( app->twitch.server->forEachChannelAndSpecialChannels(
@ -330,7 +371,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName); auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) { if (channel->isEmpty())
{
log("[IrcManager:handleNoticeMessage] Channel {} not found in channel " log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ", "manager ",
channelName); channelName);
@ -367,10 +409,12 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
}; };
QVariant v = message->tag("msg-id"); QVariant v = message->tag("msg-id");
if (v.isValid()) { if (v.isValid())
{
std::string msgID = v.toString().toStdString(); std::string msgID = v.toString().toStdString();
if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end()) { if (readConnectionOnlyIDs.find(msgID) != readConnectionOnlyIDs.end())
{
return; return;
} }
@ -388,7 +432,8 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
message->parameter(0).remove(0, 1)); message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = if (TwitchChannel *twitchChannel =
dynamic_cast<TwitchChannel *>(channel.get())) { dynamic_cast<TwitchChannel *>(channel.get()))
{
twitchChannel->addJoinedUser(message->nick()); twitchChannel->addJoinedUser(message->nick());
} }
} }
@ -400,7 +445,8 @@ void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
message->parameter(0).remove(0, 1)); message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = if (TwitchChannel *twitchChannel =
dynamic_cast<TwitchChannel *>(channel.get())) { dynamic_cast<TwitchChannel *>(channel.get()))
{
twitchChannel->addPartedUser(message->nick()); twitchChannel->addPartedUser(message->nick());
} }
} }

View file

@ -31,7 +31,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
{ {
assert(!this->username_.isEmpty()); assert(!this->username_.isEmpty());
if (caller == nullptr) { if (caller == nullptr)
{
caller = QThread::currentThread(); caller = QThread::currentThread();
} }
@ -42,23 +43,27 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome { request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray())
{
log("API Error while getting user id, users is not an array"); log("API Error while getting user id, users is not an array");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1)
{
log("API Error while getting user id, users array size is not 1"); log("API Error while getting user id, users array size is not 1");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject())
{
log("API Error while getting user id, first user is not an object"); log("API Error while getting user id, first user is not an object");
return Failure; return Failure;
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString())
{
log("API Error: while getting user id, first user object `_id` key " log("API Error: while getting user id, first user object `_id` key "
"is not a " "is not a "
"string"); "string");

View file

@ -44,7 +44,8 @@ struct ModeChangedAction : PubSubAction {
const char *getModeName() const const char *getModeName() const
{ {
switch (this->mode) { switch (this->mode)
{
case Mode::Slow: case Mode::Slow:
return "slow"; return "slow";
case Mode::R9K: case Mode::R9K:

View file

@ -51,14 +51,16 @@ namespace detail {
{ {
int numRequestedListens = message["data"]["topics"].Size(); int numRequestedListens = message["data"]["topics"].Size();
if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS) { if (this->numListens_ + numRequestedListens > MAX_PUBSUB_LISTENS)
{
// This PubSubClient is already at its peak listens // This PubSubClient is already at its peak listens
return false; return false;
} }
this->numListens_ += numRequestedListens; this->numListens_ += numRequestedListens;
for (const auto &topic : message["data"]["topics"].GetArray()) { for (const auto &topic : message["data"]["topics"].GetArray())
{
this->listeners_.emplace_back( this->listeners_.emplace_back(
Listener{topic.GetString(), false, false, false}); Listener{topic.GetString(), false, false, false});
} }
@ -79,18 +81,22 @@ namespace detail {
{ {
std::vector<std::string> topics; std::vector<std::string> topics;
for (auto it = this->listeners_.begin(); for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
it != this->listeners_.end();) { {
const auto &listener = *it; const auto &listener = *it;
if (listener.topic.find(prefix) == 0) { if (listener.topic.find(prefix) == 0)
{
topics.push_back(listener.topic); topics.push_back(listener.topic);
it = this->listeners_.erase(it); it = this->listeners_.erase(it);
} else { }
else
{
++it; ++it;
} }
} }
if (topics.empty()) { if (topics.empty())
{
return; return;
} }
@ -117,8 +123,10 @@ namespace detail {
bool PubSubClient::isListeningToTopic(const std::string &payload) bool PubSubClient::isListeningToTopic(const std::string &payload)
{ {
for (const auto &listener : this->listeners_) { for (const auto &listener : this->listeners_)
if (listener.topic == payload) { {
if (listener.topic == payload)
{
return true; return true;
} }
} }
@ -130,7 +138,8 @@ namespace detail {
{ {
assert(this->started_); assert(this->started_);
if (!this->send(pingPayload)) { if (!this->send(pingPayload))
{
return; return;
} }
@ -140,11 +149,13 @@ namespace detail {
runAfter(this->websocketClient_.get_io_service(), runAfter(this->websocketClient_.get_io_service(),
std::chrono::seconds(15), [self](auto timer) { std::chrono::seconds(15), [self](auto timer) {
if (!self->started_) { if (!self->started_)
{
return; return;
} }
if (self->awaitingPong_) { if (self->awaitingPong_)
{
log("No pong respnose, disconnect!"); log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect // TODO(pajlada): Label this connection as "disconnect
// me" // me"
@ -153,7 +164,8 @@ namespace detail {
runAfter(this->websocketClient_.get_io_service(), runAfter(this->websocketClient_.get_io_service(),
std::chrono::minutes(5), [self](auto timer) { std::chrono::minutes(5), [self](auto timer) {
if (!self->started_) { if (!self->started_)
{
return; return;
} }
@ -167,7 +179,8 @@ namespace detail {
this->websocketClient_.send(this->handle_, payload, this->websocketClient_.send(this->handle_, payload,
websocketpp::frame::opcode::text, ec); websocketpp::frame::opcode::text, ec);
if (ec) { if (ec)
{
log("Error sending message {}: {}", payload, ec.message()); log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe // TODO(pajlada): Check which error code happened and maybe
// gracefully handle it // gracefully handle it
@ -208,26 +221,30 @@ PubSub::PubSub()
action.mode = ModeChangedAction::Mode::Slow; action.mode = ModeChangedAction::Mode::Slow;
action.state = ModeChangedAction::State::On; action.state = ModeChangedAction::State::On;
if (!data.HasMember("args")) { if (!data.HasMember("args"))
{
log("Missing required args member"); log("Missing required args member");
return; return;
} }
const auto &args = data["args"]; const auto &args = data["args"];
if (!args.IsArray()) { if (!args.IsArray())
{
log("args member must be an array"); log("args member must be an array");
return; return;
} }
if (args.Size() == 0) { if (args.Size() == 0)
{
log("Missing duration argument in slowmode on"); log("Missing duration argument in slowmode on");
return; return;
} }
const auto &durationArg = args[0]; const auto &durationArg = args[0];
if (!durationArg.IsString()) { if (!durationArg.IsString())
{
log("Duration arg must be a string"); log("Duration arg must be a string");
return; return;
} }
@ -305,17 +322,22 @@ PubSub::PubSub()
getTargetUser(data, action.target); getTargetUser(data, action.target);
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 1) { if (args.Size() < 1)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
@ -330,17 +352,22 @@ PubSub::PubSub()
getTargetUser(data, action.target); getTargetUser(data, action.target);
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 1) { if (args.Size() < 1)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
@ -356,32 +383,40 @@ PubSub::PubSub()
getCreatedByUser(data, action.source); getCreatedByUser(data, action.source);
getTargetUser(data, action.target); getTargetUser(data, action.target);
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 2) { if (args.Size() < 2)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
QString durationString; QString durationString;
if (!rj::getSafe(args[1], durationString)) { if (!rj::getSafe(args[1], durationString))
{
return; return;
} }
bool ok; bool ok;
action.duration = durationString.toUInt(&ok, 10); action.duration = durationString.toUInt(&ok, 10);
if (args.Size() >= 3) { if (args.Size() >= 3)
if (!rj::getSafe(args[2], action.reason)) { {
if (!rj::getSafe(args[2], action.reason))
{
return; return;
} }
} }
this->signals_.moderation.userBanned.invoke(action); this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -393,25 +428,32 @@ PubSub::PubSub()
getCreatedByUser(data, action.source); getCreatedByUser(data, action.source);
getTargetUser(data, action.target); getTargetUser(data, action.target);
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 1) { if (args.Size() < 1)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
if (args.Size() >= 2) { if (args.Size() >= 2)
if (!rj::getSafe(args[1], action.reason)) { {
if (!rj::getSafe(args[1], action.reason))
{
return; return;
} }
} }
this->signals_.moderation.userBanned.invoke(action); this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -425,19 +467,24 @@ PubSub::PubSub()
action.previousState = UnbanAction::Banned; action.previousState = UnbanAction::Banned;
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 1) { if (args.Size() < 1)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
this->signals_.moderation.userUnbanned.invoke(action); this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -451,19 +498,24 @@ PubSub::PubSub()
action.previousState = UnbanAction::TimedOut; action.previousState = UnbanAction::TimedOut;
try { try
{
const auto &args = getArgs(data); const auto &args = getArgs(data);
if (args.Size() < 1) { if (args.Size() < 1)
{
return; return;
} }
if (!rj::getSafe(args[0], action.target.name)) { if (!rj::getSafe(args[0], action.target.name))
{
return; return;
} }
this->signals_.moderation.userUnbanned.invoke(action); this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) { }
catch (const std::runtime_error &ex)
{
log("Error parsing moderation action: {}", ex.what()); log("Error parsing moderation action: {}", ex.what());
} }
}; };
@ -494,7 +546,8 @@ void PubSub::addClient()
websocketpp::lib::error_code ec; websocketpp::lib::error_code ec;
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec); auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
if (ec) { if (ec)
{
log("Unable to establish connection: {}", ec.message()); log("Unable to establish connection: {}", ec.message());
return; return;
} }
@ -521,7 +574,8 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
this->listen(createListenMessage(topics, account)); this->listen(createListenMessage(topics, account));
if (ec) { if (ec)
{
log("Unable to send message to websocket server: {}", ec.message()); log("Unable to send message to websocket server: {}", ec.message());
return; return;
} }
@ -529,7 +583,8 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
void PubSub::unlistenAllModerationActions() void PubSub::unlistenAllModerationActions()
{ {
for (const auto &p : this->clients) { for (const auto &p : this->clients)
{
const auto &client = p.second; const auto &client = p.second;
client->unlistenPrefix("chat_moderator_actions."); client->unlistenPrefix("chat_moderator_actions.");
} }
@ -541,11 +596,13 @@ void PubSub::listenToChannelModerationActions(
assert(!channelID.isEmpty()); assert(!channelID.isEmpty());
assert(account != nullptr); assert(account != nullptr);
QString userID = account->getUserId(); QString userID = account->getUserId();
if (userID.isEmpty()) return; if (userID.isEmpty())
return;
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID)); std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
if (this->isListeningToTopic(topic)) { if (this->isListeningToTopic(topic))
{
log("We are already listening to topic {}", topic); log("We are already listening to topic {}", topic);
return; return;
} }
@ -565,7 +622,8 @@ void PubSub::listenToTopic(const std::string &topic,
void PubSub::listen(rapidjson::Document &&msg) void PubSub::listen(rapidjson::Document &&msg)
{ {
if (this->tryListen(msg)) { if (this->tryListen(msg))
{
log("Successfully listened!"); log("Successfully listened!");
return; return;
} }
@ -578,9 +636,11 @@ void PubSub::listen(rapidjson::Document &&msg)
bool PubSub::tryListen(rapidjson::Document &msg) bool PubSub::tryListen(rapidjson::Document &msg)
{ {
log("tryListen with {} clients", this->clients.size()); log("tryListen with {} clients", this->clients.size());
for (const auto &p : this->clients) { for (const auto &p : this->clients)
{
const auto &client = p.second; const auto &client = p.second;
if (client->listen(msg)) { if (client->listen(msg))
{
return true; return true;
} }
} }
@ -590,9 +650,11 @@ bool PubSub::tryListen(rapidjson::Document &msg)
bool PubSub::isListeningToTopic(const std::string &topic) bool PubSub::isListeningToTopic(const std::string &topic)
{ {
for (const auto &p : this->clients) { for (const auto &p : this->clients)
{
const auto &client = p.second; const auto &client = p.second;
if (client->isListeningToTopic(topic)) { if (client->isListeningToTopic(topic))
{
return true; return true;
} }
} }
@ -609,13 +671,15 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
rapidjson::ParseResult res = msg.Parse(payload.c_str()); rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) { if (!res)
{
log("Error parsing message '{}' from PubSub: {}", payload, log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code())); rapidjson::GetParseError_En(res.Code()));
return; return;
} }
if (!msg.IsObject()) { if (!msg.IsObject())
{
log("Error parsing message '{}' from PubSub. Root object is not an " log("Error parsing message '{}' from PubSub. Root object is not an "
"object", "object",
payload); payload);
@ -624,28 +688,36 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
std::string type; std::string type;
if (!rj::getSafe(msg, "type", type)) { if (!rj::getSafe(msg, "type", type))
{
log("Missing required string member `type` in message root"); log("Missing required string member `type` in message root");
return; return;
} }
if (type == "RESPONSE") { if (type == "RESPONSE")
{
this->handleListenResponse(msg); this->handleListenResponse(msg);
} else if (type == "MESSAGE") { }
if (!msg.HasMember("data")) { else if (type == "MESSAGE")
{
if (!msg.HasMember("data"))
{
log("Missing required object member `data` in message root"); log("Missing required object member `data` in message root");
return; return;
} }
const auto &data = msg["data"]; const auto &data = msg["data"];
if (!data.IsObject()) { if (!data.IsObject())
{
log("Member `data` must be an object"); log("Member `data` must be an object");
return; return;
} }
this->handleMessageResponse(data); this->handleMessageResponse(data);
} else if (type == "PONG") { }
else if (type == "PONG")
{
auto clientIt = this->clients.find(hdl); auto clientIt = this->clients.find(hdl);
// If this assert goes off, there's something wrong with the connection // If this assert goes off, there's something wrong with the connection
@ -655,7 +727,9 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
auto &client = *clientIt; auto &client = *clientIt;
client.second->handlePong(); client.second->handlePong();
} else { }
else
{
log("Unknown message type: {}", type); log("Unknown message type: {}", type);
} }
} }
@ -696,11 +770,14 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
WebsocketContextPtr ctx( WebsocketContextPtr ctx(
new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1)); new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
try { try
{
ctx->set_options(boost::asio::ssl::context::default_workarounds | ctx->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use); boost::asio::ssl::context::single_dh_use);
} catch (const std::exception &e) { }
catch (const std::exception &e)
{
log("Exception caught in OnTLSInit: {}", e.what()); log("Exception caught in OnTLSInit: {}", e.what());
} }
@ -711,11 +788,13 @@ void PubSub::handleListenResponse(const rapidjson::Document &msg)
{ {
std::string error; std::string error;
if (rj::getSafe(msg, "error", error)) { if (rj::getSafe(msg, "error", error))
{
std::string nonce; std::string nonce;
rj::getSafe(msg, "nonce", nonce); rj::getSafe(msg, "nonce", nonce);
if (error.empty()) { if (error.empty())
{
log("Successfully listened to nonce {}", nonce); log("Successfully listened to nonce {}", nonce);
// Nothing went wrong // Nothing went wrong
return; return;
@ -730,14 +809,16 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
{ {
QString topic; QString topic;
if (!rj::getSafe(outerData, "topic", topic)) { if (!rj::getSafe(outerData, "topic", topic))
{
log("Missing required string member `topic` in outerData"); log("Missing required string member `topic` in outerData");
return; return;
} }
std::string payload; std::string payload;
if (!rj::getSafe(outerData, "message", payload)) { if (!rj::getSafe(outerData, "message", payload))
{
log("Expected string message in outerData"); log("Expected string message in outerData");
return; return;
} }
@ -746,53 +827,69 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
rapidjson::ParseResult res = msg.Parse(payload.c_str()); rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) { if (!res)
{
log("Error parsing message '{}' from PubSub: {}", payload, log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code())); rapidjson::GetParseError_En(res.Code()));
return; return;
} }
if (topic.startsWith("whispers.")) { if (topic.startsWith("whispers."))
{
std::string whisperType; std::string whisperType;
if (!rj::getSafe(msg, "type", whisperType)) { if (!rj::getSafe(msg, "type", whisperType))
{
log("Bad whisper data"); log("Bad whisper data");
return; return;
} }
if (whisperType == "whisper_received") { if (whisperType == "whisper_received")
{
this->signals_.whisper.received.invoke(msg); this->signals_.whisper.received.invoke(msg);
} else if (whisperType == "whisper_sent") { }
else if (whisperType == "whisper_sent")
{
this->signals_.whisper.sent.invoke(msg); this->signals_.whisper.sent.invoke(msg);
} else if (whisperType == "thread") { }
else if (whisperType == "thread")
{
// Handle thread? // Handle thread?
} else { }
else
{
log("Invalid whisper type: {}", whisperType); log("Invalid whisper type: {}", whisperType);
assert(false); assert(false);
return; return;
} }
} else if (topic.startsWith("chat_moderator_actions.")) { }
else if (topic.startsWith("chat_moderator_actions."))
{
auto topicParts = topic.split("."); auto topicParts = topic.split(".");
assert(topicParts.length() == 3); assert(topicParts.length() == 3);
const auto &data = msg["data"]; const auto &data = msg["data"];
std::string moderationAction; std::string moderationAction;
if (!rj::getSafe(data, "moderation_action", moderationAction)) { if (!rj::getSafe(data, "moderation_action", moderationAction))
{
log("Missing moderation action in data: {}", rj::stringify(data)); log("Missing moderation action in data: {}", rj::stringify(data));
return; return;
} }
auto handlerIt = this->moderationActionHandlers.find(moderationAction); auto handlerIt = this->moderationActionHandlers.find(moderationAction);
if (handlerIt == this->moderationActionHandlers.end()) { if (handlerIt == this->moderationActionHandlers.end())
{
log("No handler found for moderation action {}", moderationAction); log("No handler found for moderation action {}", moderationAction);
return; return;
} }
// Invoke handler function // Invoke handler function
handlerIt->second(data, topicParts[2]); handlerIt->second(data, topicParts[2]);
} else { }
else
{
log("Unknown topic: {}", topic); log("Unknown topic: {}", topic);
return; return;
} }

View file

@ -8,13 +8,15 @@ namespace chatterino {
const rapidjson::Value &getArgs(const rapidjson::Value &data) const rapidjson::Value &getArgs(const rapidjson::Value &data)
{ {
if (!data.HasMember("args")) { if (!data.HasMember("args"))
{
throw std::runtime_error("Missing member args"); throw std::runtime_error("Missing member args");
} }
const auto &args = data["args"]; const auto &args = data["args"];
if (!args.IsArray()) { if (!args.IsArray())
{
throw std::runtime_error("args must be an array"); throw std::runtime_error("args must be an array");
} }
@ -43,12 +45,14 @@ rapidjson::Document createListenMessage(
rapidjson::Value data(rapidjson::kObjectType); rapidjson::Value data(rapidjson::kObjectType);
if (account) { if (account)
{
rj::set(data, "auth_token", account->getOAuthToken(), a); rj::set(data, "auth_token", account->getOAuthToken(), a);
} }
rapidjson::Value topics(rapidjson::kArrayType); rapidjson::Value topics(rapidjson::kArrayType);
for (const auto &topic : topicsVec) { for (const auto &topic : topicsVec)
{
rj::add(topics, topic, a); rj::add(topics, topic, a);
} }
@ -70,7 +74,8 @@ rapidjson::Document createUnlistenMessage(
rapidjson::Value data(rapidjson::kObjectType); rapidjson::Value data(rapidjson::kObjectType);
rapidjson::Value topics(rapidjson::kArrayType); rapidjson::Value topics(rapidjson::kArrayType);
for (const auto &topic : topicsVec) { for (const auto &topic : topicsVec)
{
rj::add(topics, topic, a); rj::add(topics, topic, a);
} }

View file

@ -32,7 +32,8 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
timer->expires_from_now(duration); timer->expires_from_now(duration);
timer->async_wait([timer, cb](const boost::system::error_code &ec) { timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) { if (ec)
{
log("Error in runAfter: {}", ec.message()); log("Error in runAfter: {}", ec.message());
return; return;
} }
@ -49,7 +50,8 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
timer->expires_from_now(duration); timer->expires_from_now(duration);
timer->async_wait([timer, cb](const boost::system::error_code &ec) { timer->async_wait([timer, cb](const boost::system::error_code &ec) {
if (ec) { if (ec)
{
log("Error in runAfter: {}", ec.message()); log("Error in runAfter: {}", ec.message());
return; return;
} }

View file

@ -31,7 +31,8 @@ namespace {
}; };
auto it = emoteNameReplacements.find(dirtyEmoteCode.string); auto it = emoteNameReplacements.find(dirtyEmoteCode.string);
if (it != emoteNameReplacements.end()) { if (it != emoteNameReplacements.end())
{
cleanCode = it.value(); cleanCode = it.value();
} }
@ -91,7 +92,8 @@ void TwitchAccount::setColor(QColor color)
bool TwitchAccount::setOAuthClient(const QString &newClientID) bool TwitchAccount::setOAuthClient(const QString &newClientID)
{ {
if (this->oauthClient_.compare(newClientID) == 0) { if (this->oauthClient_.compare(newClientID) == 0)
{
return false; return false;
} }
@ -102,7 +104,8 @@ bool TwitchAccount::setOAuthClient(const QString &newClientID)
bool TwitchAccount::setOAuthToken(const QString &newOAuthToken) bool TwitchAccount::setOAuthToken(const QString &newOAuthToken)
{ {
if (this->oauthToken_.compare(newOAuthToken) == 0) { if (this->oauthToken_.compare(newOAuthToken) == 0)
{
return false; return false;
} }
@ -126,17 +129,20 @@ void TwitchAccount::loadIgnores()
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken()); req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
req.onSuccess([=](auto result) -> Outcome { req.onSuccess([=](auto result) -> Outcome {
auto document = result.parseRapidJson(); auto document = result.parseRapidJson();
if (!document.IsObject()) { if (!document.IsObject())
{
return Failure; return Failure;
} }
auto blocksIt = document.FindMember("blocks"); auto blocksIt = document.FindMember("blocks");
if (blocksIt == document.MemberEnd()) { if (blocksIt == document.MemberEnd())
{
return Failure; return Failure;
} }
const auto &blocks = blocksIt->value; const auto &blocks = blocksIt->value;
if (!blocks.IsArray()) { if (!blocks.IsArray())
{
return Failure; return Failure;
} }
@ -144,16 +150,20 @@ void TwitchAccount::loadIgnores()
std::lock_guard<std::mutex> lock(this->ignoresMutex_); std::lock_guard<std::mutex> lock(this->ignoresMutex_);
this->ignores_.clear(); this->ignores_.clear();
for (const auto &block : blocks.GetArray()) { for (const auto &block : blocks.GetArray())
if (!block.IsObject()) { {
if (!block.IsObject())
{
continue; continue;
} }
auto userIt = block.FindMember("user"); auto userIt = block.FindMember("user");
if (userIt == block.MemberEnd()) { if (userIt == block.MemberEnd())
{
continue; continue;
} }
TwitchUser ignoredUser; TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser)) { if (!rj::getSafe(userIt->value, ignoredUser))
{
log("Error parsing twitch user JSON {}", log("Error parsing twitch user JSON {}",
rj::stringify(userIt->value)); rj::stringify(userIt->value));
continue; continue;
@ -201,14 +211,16 @@ void TwitchAccount::ignoreByID(
req.onSuccess([=](auto result) -> Outcome { req.onSuccess([=](auto result) -> Outcome {
auto document = result.parseRapidJson(); auto document = result.parseRapidJson();
if (!document.IsObject()) { if (!document.IsObject())
{
onFinished(IgnoreResult_Failed, onFinished(IgnoreResult_Failed,
"Bad JSON data while ignoring user " + targetName); "Bad JSON data while ignoring user " + targetName);
return Failure; return Failure;
} }
auto userIt = document.FindMember("user"); auto userIt = document.FindMember("user");
if (userIt == document.MemberEnd()) { if (userIt == document.MemberEnd())
{
onFinished(IgnoreResult_Failed, onFinished(IgnoreResult_Failed,
"Bad JSON data while ignoring user (missing user) " + "Bad JSON data while ignoring user (missing user) " +
targetName); targetName);
@ -216,7 +228,8 @@ void TwitchAccount::ignoreByID(
} }
TwitchUser ignoredUser; TwitchUser ignoredUser;
if (!rj::getSafe(userIt->value, ignoredUser)) { if (!rj::getSafe(userIt->value, ignoredUser))
{
onFinished(IgnoreResult_Failed, onFinished(IgnoreResult_Failed,
"Bad JSON data while ignoring user (invalid user) " + "Bad JSON data while ignoring user (invalid user) " +
targetName); targetName);
@ -226,7 +239,8 @@ void TwitchAccount::ignoreByID(
std::lock_guard<std::mutex> lock(this->ignoresMutex_); std::lock_guard<std::mutex> lock(this->ignoresMutex_);
auto res = this->ignores_.insert(ignoredUser); auto res = this->ignores_.insert(ignoredUser);
if (!res.second) { if (!res.second)
{
const TwitchUser &existingUser = *(res.first); const TwitchUser &existingUser = *(res.first);
existingUser.update(ignoredUser); existingUser.update(ignoredUser);
onFinished(IgnoreResult_AlreadyIgnored, onFinished(IgnoreResult_AlreadyIgnored,
@ -303,9 +317,12 @@ void TwitchAccount::checkFollow(const QString targetUserID,
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken()); req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
req.onError([=](int errorCode) { req.onError([=](int errorCode) {
if (errorCode == 203) { if (errorCode == 203)
{
onFinished(FollowResult_NotFollowing); onFinished(FollowResult_NotFollowing);
} else { }
else
{
onFinished(FollowResult_Failed); onFinished(FollowResult_Failed);
} }
@ -354,7 +371,8 @@ void TwitchAccount::unfollowUser(const QString userID,
request.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken()); request.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
request.onError([successCallback](int code) { request.onError([successCallback](int code) {
if (code >= 200 && code <= 299) { if (code >= 200 && code <= 299)
{
successCallback(); successCallback();
} }
@ -384,7 +402,8 @@ void TwitchAccount::loadEmotes()
const auto &clientID = this->getOAuthClient(); const auto &clientID = this->getOAuthClient();
const auto &oauthToken = this->getOAuthToken(); const auto &oauthToken = this->getOAuthToken();
if (clientID.isEmpty() || oauthToken.isEmpty()) { if (clientID.isEmpty() || oauthToken.isEmpty())
{
log("Missing Client ID or OAuth token"); log("Missing Client ID or OAuth token");
return; return;
} }
@ -398,9 +417,12 @@ void TwitchAccount::loadEmotes()
req.onError([=](int errorCode) { req.onError([=](int errorCode) {
log("[TwitchAccount::loadEmotes] Error {}", errorCode); log("[TwitchAccount::loadEmotes] Error {}", errorCode);
if (errorCode == 203) { if (errorCode == 203)
{
// onFinished(FollowResult_NotFollowing); // onFinished(FollowResult_NotFollowing);
} else { }
else
{
// onFinished(FollowResult_Failed); // onFinished(FollowResult_Failed);
} }
@ -430,33 +452,38 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
emoteData->allEmoteNames.clear(); emoteData->allEmoteNames.clear();
auto emoticonSets = root.FindMember("emoticon_sets"); auto emoticonSets = root.FindMember("emoticon_sets");
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject()) { if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
{
log("No emoticon_sets in load emotes response"); log("No emoticon_sets in load emotes response");
return; return;
} }
for (const auto &emoteSetJSON : emoticonSets->value.GetObject()) { for (const auto &emoteSetJSON : emoticonSets->value.GetObject())
{
auto emoteSet = std::make_shared<EmoteSet>(); auto emoteSet = std::make_shared<EmoteSet>();
emoteSet->key = emoteSetJSON.name.GetString(); emoteSet->key = emoteSetJSON.name.GetString();
this->loadEmoteSetData(emoteSet); this->loadEmoteSetData(emoteSet);
for (const rapidjson::Value &emoteJSON : for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray())
emoteSetJSON.value.GetArray()) { {
if (!emoteJSON.IsObject()) { if (!emoteJSON.IsObject())
{
log("Emote value was invalid"); log("Emote value was invalid");
return; return;
} }
uint64_t idNumber; uint64_t idNumber;
if (!rj::getSafe(emoteJSON, "id", idNumber)) { if (!rj::getSafe(emoteJSON, "id", idNumber))
{
log("No ID key found in Emote value"); log("No ID key found in Emote value");
return; return;
} }
QString _code; QString _code;
if (!rj::getSafe(emoteJSON, "code", _code)) { if (!rj::getSafe(emoteJSON, "code", _code))
{
log("No code key found in Emote value"); log("No code key found in Emote value");
return; return;
} }
@ -478,13 +505,15 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet) void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
{ {
if (!emoteSet) { if (!emoteSet)
{
log("null emote set sent"); log("null emote set sent");
return; return;
} }
auto staticSetIt = this->staticEmoteSets.find(emoteSet->key); auto staticSetIt = this->staticEmoteSets.find(emoteSet->key);
if (staticSetIt != this->staticEmoteSets.end()) { if (staticSetIt != this->staticEmoteSets.end())
{
const auto &staticSet = staticSetIt->second; const auto &staticSet = staticSetIt->second;
emoteSet->channelName = staticSet.channelName; emoteSet->channelName = staticSet.channelName;
emoteSet->text = staticSet.text; emoteSet->text = staticSet.text;
@ -503,18 +532,21 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
req.onSuccess([emoteSet](auto result) -> Outcome { req.onSuccess([emoteSet](auto result) -> Outcome {
auto root = result.parseRapidJson(); auto root = result.parseRapidJson();
if (!root.IsObject()) { if (!root.IsObject())
{
return Failure; return Failure;
} }
std::string emoteSetID; std::string emoteSetID;
QString channelName; QString channelName;
QString type; QString type;
if (!rj::getSafe(root, "channel_name", channelName)) { if (!rj::getSafe(root, "channel_name", channelName))
{
return Failure; return Failure;
} }
if (!rj::getSafe(root, "type", type)) { if (!rj::getSafe(root, "type", type))
{
return Failure; return Failure;
} }

View file

@ -21,7 +21,8 @@ TwitchAccountManager::TwitchAccountManager()
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent() std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
{ {
if (!this->currentUser_) { if (!this->currentUser_)
{
return this->anonymousUser_; return this->anonymousUser_;
} }
@ -34,7 +35,8 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts.getVector()) { for (const auto &user : this->accounts.getVector())
{
userNames.push_back(user->getUserName()); userNames.push_back(user->getUserName());
} }
@ -46,8 +48,10 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{ {
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts.getVector()) { for (const auto &user : this->accounts.getVector())
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) { {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{
return user; return user;
} }
} }
@ -68,8 +72,10 @@ void TwitchAccountManager::reloadUsers()
bool listUpdated = false; bool listUpdated = false;
for (const auto &uid : keys) { for (const auto &uid : keys)
if (uid == "current") { {
if (uid == "current")
{
continue; continue;
} }
@ -83,7 +89,8 @@ void TwitchAccountManager::reloadUsers()
"/accounts/" + uid + "/oauthToken"); "/accounts/" + uid + "/oauthToken");
if (username.empty() || userID.empty() || clientID.empty() || if (username.empty() || userID.empty() || clientID.empty() ||
oauthToken.empty()) { oauthToken.empty())
{
continue; continue;
} }
@ -92,28 +99,37 @@ void TwitchAccountManager::reloadUsers()
userData.clientID = qS(clientID).trimmed(); userData.clientID = qS(clientID).trimmed();
userData.oauthToken = qS(oauthToken).trimmed(); userData.oauthToken = qS(oauthToken).trimmed();
switch (this->addUser(userData)) { switch (this->addUser(userData))
case AddUserResponse::UserAlreadyExists: { {
case AddUserResponse::UserAlreadyExists:
{
log("User {} already exists", userData.username); log("User {} already exists", userData.username);
// Do nothing // Do nothing
} break; }
case AddUserResponse::UserValuesUpdated: { break;
case AddUserResponse::UserValuesUpdated:
{
log("User {} already exists, and values updated!", log("User {} already exists, and values updated!",
userData.username); userData.username);
if (userData.username == this->getCurrent()->getUserName()) { if (userData.username == this->getCurrent()->getUserName())
{
log("It was the current user, so we need to reconnect " log("It was the current user, so we need to reconnect "
"stuff!"); "stuff!");
this->currentUserChanged.invoke(); this->currentUserChanged.invoke();
} }
} break; }
case AddUserResponse::UserAdded: { break;
case AddUserResponse::UserAdded:
{
log("Added user {}", userData.username); log("Added user {}", userData.username);
listUpdated = true; listUpdated = true;
} break; }
break;
} }
} }
if (listUpdated) { if (listUpdated)
{
this->userListUpdated.invoke(); this->userListUpdated.invoke();
} }
} }
@ -125,12 +141,15 @@ void TwitchAccountManager::load()
this->currentUsername.connect([this](const auto &newValue, auto) { this->currentUsername.connect([this](const auto &newValue, auto) {
QString newUsername(QString::fromStdString(newValue)); QString newUsername(QString::fromStdString(newValue));
auto user = this->findUserByUsername(newUsername); auto user = this->findUserByUsername(newUsername);
if (user) { if (user)
{
log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to {}", "updated to {}",
newUsername); newUsername);
this->currentUser_ = user; this->currentUser_ = user;
} else { }
else
{
log("[AccountManager:currentUsernameChanged] User successfully " log("[AccountManager:currentUsernameChanged] User successfully "
"updated to anonymous"); "updated to anonymous");
this->currentUser_ = this->anonymousUser_; this->currentUser_ = this->anonymousUser_;
@ -142,7 +161,8 @@ void TwitchAccountManager::load()
bool TwitchAccountManager::isLoggedIn() const bool TwitchAccountManager::isLoggedIn() const
{ {
if (!this->currentUser_) { if (!this->currentUser_)
{
return false; return false;
} }
@ -156,12 +176,14 @@ bool TwitchAccountManager::removeUser(TwitchAccount *account)
const auto &accs = this->accounts.getVector(); const auto &accs = this->accounts.getVector();
std::string userID(account->getUserId().toStdString()); std::string userID(account->getUserId().toStdString());
if (!userID.empty()) { if (!userID.empty())
{
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + pajlada::Settings::SettingManager::removeSetting("/accounts/uid" +
userID); userID);
} }
if (account->getUserName() == qS(this->currentUsername.getValue())) { if (account->getUserName() == qS(this->currentUsername.getValue()))
{
// The user that was removed is the current user, log into the anonymous // The user that was removed is the current user, log into the anonymous
// user // user
this->currentUsername = ""; this->currentUsername = "";
@ -176,20 +198,26 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
const TwitchAccountManager::UserData &userData) const TwitchAccountManager::UserData &userData)
{ {
auto previousUser = this->findUserByUsername(userData.username); auto previousUser = this->findUserByUsername(userData.username);
if (previousUser) { if (previousUser)
{
bool userUpdated = false; bool userUpdated = false;
if (previousUser->setOAuthClient(userData.clientID)) { if (previousUser->setOAuthClient(userData.clientID))
{
userUpdated = true; userUpdated = true;
} }
if (previousUser->setOAuthToken(userData.oauthToken)) { if (previousUser->setOAuthToken(userData.oauthToken))
{
userUpdated = true; userUpdated = true;
} }
if (userUpdated) { if (userUpdated)
{
return AddUserResponse::UserValuesUpdated; return AddUserResponse::UserValuesUpdated;
} else { }
else
{
return AddUserResponse::UserAlreadyExists; return AddUserResponse::UserAlreadyExists;
} }
} }

View file

@ -21,25 +21,29 @@ void TwitchApi::findUserId(const QString user,
request.setTimeout(30000); request.setTimeout(30000);
request.onSuccess([successCallback](auto result) mutable -> Outcome { request.onSuccess([successCallback](auto result) mutable -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray())
{
log("API Error while getting user id, users is not an array"); log("API Error while getting user id, users is not an array");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1)
{
log("API Error while getting user id, users array size is not 1"); log("API Error while getting user id, users array size is not 1");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject())
{
log("API Error while getting user id, first user is not an object"); log("API Error while getting user id, first user is not an object");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString())
{
log("API Error: while getting user id, first user object `_id` key " log("API Error: while getting user id, first user object `_id` key "
"is not a " "is not a "
"string"); "string");

View file

@ -24,11 +24,13 @@ void TwitchBadges::loadTwitchBadges()
auto badgeSets = this->badgeSets_.access(); auto badgeSets = this->badgeSets_.access();
auto jsonSets = root.value("badge_sets").toObject(); auto jsonSets = root.value("badge_sets").toObject();
for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt) { for (auto sIt = jsonSets.begin(); sIt != jsonSets.end(); ++sIt)
{
auto key = sIt.key(); auto key = sIt.key();
auto versions = sIt.value().toObject().value("versions").toObject(); auto versions = sIt.value().toObject().value("versions").toObject();
for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt) { for (auto vIt = versions.begin(); vIt != versions.end(); ++vIt)
{
auto versionObj = vIt.value().toObject(); auto versionObj = vIt.value().toObject();
auto emote = Emote{ auto emote = Emote{
@ -61,9 +63,11 @@ boost::optional<EmotePtr> TwitchBadges::badge(const QString &set,
{ {
auto badgeSets = this->badgeSets_.access(); auto badgeSets = this->badgeSets_.access();
auto it = badgeSets->find(set); auto it = badgeSets->find(set);
if (it != badgeSets->end()) { if (it != badgeSets->end())
{
auto it2 = it->second.find(version); auto it2 = it->second.find(version);
if (it2 != it->second.end()) { if (it2 != it->second.end())
{
return it2->second; return it2->second;
} }
} }

View file

@ -34,9 +34,11 @@ namespace {
QJsonArray jsonMessages = jsonRoot.value("messages").toArray(); QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
std::vector<MessagePtr> messages; std::vector<MessagePtr> messages;
if (jsonMessages.empty()) return messages; if (jsonMessages.empty())
return messages;
for (const auto jsonMessage : jsonMessages) { for (const auto jsonMessage : jsonMessages)
{
auto content = jsonMessage.toString().toUtf8(); auto content = jsonMessage.toString().toUtf8();
// passing nullptr as the channel makes the message invalid but we // passing nullptr as the channel makes the message invalid but we
// don't check for that anyways // don't check for that anyways
@ -46,7 +48,8 @@ namespace {
MessageParseArgs args; MessageParseArgs args;
TwitchMessageBuilder builder(channel.get(), privMsg, args); TwitchMessageBuilder builder(channel.get(), privMsg, args);
if (!builder.isIgnored()) { if (!builder.isIgnored())
{
messages.push_back(builder.build()); messages.push_back(builder.build());
} }
} }
@ -63,8 +66,10 @@ namespace {
// parse json // parse json
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject(); QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
for (const auto &category : categories) { for (const auto &category : categories)
for (auto jsonCategory : jsonCategories.value(category).toArray()) { {
for (auto jsonCategory : jsonCategories.value(category).toArray())
{
usernames.insert(jsonCategory.toString()); usernames.insert(jsonCategory.toString());
} }
} }
@ -90,7 +95,8 @@ TwitchChannel::TwitchChannel(const QString &name,
log("[TwitchChannel:{}] Opened", name); log("[TwitchChannel:{}] Opened", name);
this->liveStatusChanged.connect([this]() { this->liveStatusChanged.connect([this]() {
if (this->isLive() == 1) { if (this->isLive() == 1)
{
} }
}); });
@ -168,7 +174,8 @@ void TwitchChannel::sendMessage(const QString &message)
{ {
auto app = getApp(); auto app = getApp();
if (!app->accounts->twitch.isLoggedIn()) { if (!app->accounts->twitch.isLoggedIn())
{
// XXX: It would be nice if we could add a link here somehow that opened // XXX: It would be nice if we could add a link here somehow that opened
// the "account manager" dialog // the "account manager" dialog
this->addMessage( this->addMessage(
@ -184,13 +191,17 @@ void TwitchChannel::sendMessage(const QString &message)
parsedMessage = parsedMessage.trimmed(); parsedMessage = parsedMessage.trimmed();
if (parsedMessage.isEmpty()) { if (parsedMessage.isEmpty())
{
return; return;
} }
if (!this->hasModRights()) { if (!this->hasModRights())
if (getSettings()->allowDuplicateMessages) { {
if (parsedMessage == this->lastSentMessage_) { if (getSettings()->allowDuplicateMessages)
{
if (parsedMessage == this->lastSentMessage_)
{
parsedMessage.append(this->messageSuffix_); parsedMessage.append(this->messageSuffix_);
} }
} }
@ -199,7 +210,8 @@ void TwitchChannel::sendMessage(const QString &message)
bool messageSent = false; bool messageSent = false;
this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent); this->sendMessageSignal.invoke(this->getName(), parsedMessage, messageSent);
if (messageSent) { if (messageSent)
{
qDebug() << "sent"; qDebug() << "sent";
this->lastSentMessage_ = parsedMessage; this->lastSentMessage_ = parsedMessage;
} }
@ -212,7 +224,8 @@ bool TwitchChannel::isMod() const
void TwitchChannel::setMod(bool value) void TwitchChannel::setMod(bool value)
{ {
if (this->mod_ != value) { if (this->mod_ != value)
{
this->mod_ = value; this->mod_ = value;
this->userStateChanged.invoke(); this->userStateChanged.invoke();
@ -235,14 +248,16 @@ void TwitchChannel::addJoinedUser(const QString &user)
{ {
auto app = getApp(); auto app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() || if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!getSettings()->showJoins.getValue()) { !getSettings()->showJoins.getValue())
{
return; return;
} }
auto joinedUsers = this->joinedUsers_.access(); auto joinedUsers = this->joinedUsers_.access();
joinedUsers->append(user); joinedUsers->append(user);
if (!this->joinedUsersMergeQueued_) { if (!this->joinedUsersMergeQueued_)
{
this->joinedUsersMergeQueued_ = true; this->joinedUsersMergeQueued_ = true;
QTimer::singleShot(500, &this->lifetimeGuard_, [this] { QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
@ -263,14 +278,16 @@ void TwitchChannel::addPartedUser(const QString &user)
auto app = getApp(); auto app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() || if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!getSettings()->showJoins.getValue()) { !getSettings()->showJoins.getValue())
{
return; return;
} }
auto partedUsers = this->partedUsers_.access(); auto partedUsers = this->partedUsers_.access();
partedUsers->append(user); partedUsers->append(user);
if (!this->partedUsersMergeQueued_) { if (!this->partedUsersMergeQueued_)
{
this->partedUsersMergeQueued_ = true; this->partedUsersMergeQueued_ = true;
QTimer::singleShot(500, &this->lifetimeGuard_, [this] { QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
@ -294,7 +311,8 @@ QString TwitchChannel::roomId() const
void TwitchChannel::setRoomId(const QString &id) void TwitchChannel::setRoomId(const QString &id)
{ {
if (*this->roomID_.accessConst() != id) { if (*this->roomID_.accessConst() != id)
{
*this->roomID_.access() = id; *this->roomID_.access() = id;
this->roomIdChanged.invoke(); this->roomIdChanged.invoke();
this->loadRecentMessages(); this->loadRecentMessages();
@ -350,7 +368,8 @@ boost::optional<EmotePtr> TwitchChannel::bttvEmote(const EmoteName &name) const
auto emotes = this->bttvEmotes_.get(); auto emotes = this->bttvEmotes_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it == emotes->end()) return boost::none; if (it == emotes->end())
return boost::none;
return it->second; return it->second;
} }
@ -359,7 +378,8 @@ boost::optional<EmotePtr> TwitchChannel::ffzEmote(const EmoteName &name) const
auto emotes = this->ffzEmotes_.get(); auto emotes = this->ffzEmotes_.get();
auto it = emotes->find(name); auto it = emotes->find(name);
if (it == emotes->end()) return boost::none; if (it == emotes->end())
return boost::none;
return it->second; return it->second;
} }
@ -393,25 +413,33 @@ void TwitchChannel::setLive(bool newLiveStatus)
bool gotNewLiveStatus = false; bool gotNewLiveStatus = false;
{ {
auto guard = this->streamStatus_.access(); auto guard = this->streamStatus_.access();
if (guard->live != newLiveStatus) { if (guard->live != newLiveStatus)
{
gotNewLiveStatus = true; gotNewLiveStatus = true;
if (newLiveStatus) { if (newLiveStatus)
{
if (getApp()->notifications->isChannelNotified( if (getApp()->notifications->isChannelNotified(
this->getName(), Platform::Twitch)) { this->getName(), Platform::Twitch))
if (Toasts::isEnabled()) { {
if (Toasts::isEnabled())
{
getApp()->toasts->sendChannelNotification( getApp()->toasts->sendChannelNotification(
this->getName(), Platform::Twitch); this->getName(), Platform::Twitch);
} }
if (getSettings()->notificationPlaySound) { if (getSettings()->notificationPlaySound)
{
getApp()->notifications->playSound(); getApp()->notifications->playSound();
} }
if (getSettings()->notificationFlashTaskbar) { if (getSettings()->notificationFlashTaskbar)
{
getApp()->windows->sendAlert(); getApp()->windows->sendAlert();
} }
} }
auto live = makeSystemMessage(this->getName() + " is live"); auto live = makeSystemMessage(this->getName() + " is live");
this->addMessage(live); this->addMessage(live);
} else { }
else
{
auto offline = auto offline =
makeSystemMessage(this->getName() + " is offline"); makeSystemMessage(this->getName() + " is offline");
this->addMessage(offline); this->addMessage(offline);
@ -420,7 +448,8 @@ void TwitchChannel::setLive(bool newLiveStatus)
} }
} }
if (gotNewLiveStatus) { if (gotNewLiveStatus)
{
this->liveStatusChanged.invoke(); this->liveStatusChanged.invoke();
} }
} }
@ -429,7 +458,8 @@ void TwitchChannel::refreshLiveStatus()
{ {
auto roomID = this->roomId(); auto roomID = this->roomId();
if (roomID.isEmpty()) { if (roomID.isEmpty())
{
log("[TwitchChannel:{}] Refreshing live status (Missing ID)", log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
this->getName()); this->getName());
this->setLive(false); this->setLive(false);
@ -450,7 +480,8 @@ void TwitchChannel::refreshLiveStatus()
request.onSuccess( request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome { [this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
ChannelPtr shared = weak.lock(); ChannelPtr shared = weak.lock();
if (!shared) return Failure; if (!shared)
return Failure;
return this->parseLiveStatus(result.parseRapidJson()); return this->parseLiveStatus(result.parseRapidJson());
}); });
@ -460,26 +491,30 @@ void TwitchChannel::refreshLiveStatus()
Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document) Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
{ {
if (!document.IsObject()) { if (!document.IsObject())
{
log("[TwitchChannel:refreshLiveStatus] root is not an object"); log("[TwitchChannel:refreshLiveStatus] root is not an object");
return Failure; return Failure;
} }
if (!document.HasMember("stream")) { if (!document.HasMember("stream"))
{
log("[TwitchChannel:refreshLiveStatus] Missing stream in root"); log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
return Failure; return Failure;
} }
const auto &stream = document["stream"]; const auto &stream = document["stream"];
if (!stream.IsObject()) { if (!stream.IsObject())
{
// Stream is offline (stream is most likely null) // Stream is offline (stream is most likely null)
this->setLive(false); this->setLive(false);
return Failure; return Failure;
} }
if (!stream.HasMember("viewers") || !stream.HasMember("game") || if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
!stream.HasMember("channel") || !stream.HasMember("created_at")) { !stream.HasMember("channel") || !stream.HasMember("created_at"))
{
log("[TwitchChannel:refreshLiveStatus] Missing members in stream"); log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
this->setLive(false); this->setLive(false);
return Failure; return Failure;
@ -487,7 +522,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
const rapidjson::Value &streamChannel = stream["channel"]; const rapidjson::Value &streamChannel = stream["channel"];
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) { if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
{
log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in " log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
"channel"); "channel");
return Failure; return Failure;
@ -507,19 +543,25 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
QString::number(diff % 3600 / 60) + "m"; QString::number(diff % 3600 / 60) + "m";
status->rerun = false; status->rerun = false;
if (stream.HasMember("stream_type")) { if (stream.HasMember("stream_type"))
{
status->streamType = stream["stream_type"].GetString(); status->streamType = stream["stream_type"].GetString();
} else { }
else
{
status->streamType = QString(); status->streamType = QString();
} }
if (stream.HasMember("broadcast_platform")) { if (stream.HasMember("broadcast_platform"))
{
const auto &broadcastPlatformValue = stream["broadcast_platform"]; const auto &broadcastPlatformValue = stream["broadcast_platform"];
if (broadcastPlatformValue.IsString()) { if (broadcastPlatformValue.IsString())
{
const char *broadcastPlatform = const char *broadcastPlatform =
stream["broadcast_platform"].GetString(); stream["broadcast_platform"].GetString();
if (strcmp(broadcastPlatform, "rerun") == 0) { if (strcmp(broadcastPlatform, "rerun") == 0)
{
status->rerun = true; status->rerun = true;
} }
} }
@ -546,7 +588,8 @@ void TwitchChannel::loadRecentMessages()
request.onSuccess([weak = weakOf<Channel>(this)](auto result) -> Outcome { request.onSuccess([weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared)
return Failure;
auto messages = parseRecentMessages(result.parseJson(), shared); auto messages = parseRecentMessages(result.parseJson(), shared);
@ -561,9 +604,11 @@ void TwitchChannel::loadRecentMessages()
void TwitchChannel::refreshPubsub() void TwitchChannel::refreshPubsub()
{ {
// listen to moderation actions // listen to moderation actions
if (!this->hasModRights()) return; if (!this->hasModRights())
return;
auto roomId = this->roomId(); auto roomId = this->roomId();
if (roomId.isEmpty()) return; if (roomId.isEmpty())
return;
auto account = getApp()->accounts->twitch.getCurrent(); auto account = getApp()->accounts->twitch.getCurrent();
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId, getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId,
@ -575,9 +620,11 @@ void TwitchChannel::refreshChatters()
// setting? // setting?
const auto streamStatus = this->accessStreamStatus(); const auto streamStatus = this->accessStreamStatus();
if (getSettings()->onlyFetchChattersForSmallerStreamers) { if (getSettings()->onlyFetchChattersForSmallerStreamers)
{
if (streamStatus->live && if (streamStatus->live &&
streamStatus->viewerCount > getSettings()->smallStreamerLimit) { streamStatus->viewerCount > getSettings()->smallStreamerLimit)
{
return; return;
} }
} }
@ -591,10 +638,12 @@ void TwitchChannel::refreshChatters()
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome { [this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
// channel still exists? // channel still exists?
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared)
return Failure;
auto pair = parseChatters(result.parseJson()); auto pair = parseChatters(result.parseJson());
if (pair.first) { if (pair.first)
{
*this->chatters_.access() = std::move(pair.second); *this->chatters_.access() = std::move(pair.second);
} }
@ -613,7 +662,8 @@ void TwitchChannel::refreshBadges()
req.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome { req.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared)
return Failure;
auto badgeSets = this->badgeSets_.access(); auto badgeSets = this->badgeSets_.access();
@ -621,12 +671,14 @@ void TwitchChannel::refreshBadges()
auto _ = jsonRoot["badge_sets"].toObject(); auto _ = jsonRoot["badge_sets"].toObject();
for (auto jsonBadgeSet = _.begin(); jsonBadgeSet != _.end(); for (auto jsonBadgeSet = _.begin(); jsonBadgeSet != _.end();
jsonBadgeSet++) { jsonBadgeSet++)
{
auto &versions = (*badgeSets)[jsonBadgeSet.key()]; auto &versions = (*badgeSets)[jsonBadgeSet.key()];
auto _ = jsonBadgeSet->toObject()["versions"].toObject(); auto _ = jsonBadgeSet->toObject()["versions"].toObject();
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end(); for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
jsonVersion_++) { jsonVersion_++)
{
auto jsonVersion = jsonVersion_->toObject(); auto jsonVersion = jsonVersion_->toObject();
auto emote = std::make_shared<Emote>(Emote{ auto emote = std::make_shared<Emote>(Emote{
EmoteName{}, EmoteName{},
@ -719,9 +771,11 @@ boost::optional<EmotePtr> TwitchChannel::twitchBadge(
{ {
auto badgeSets = this->badgeSets_.access(); auto badgeSets = this->badgeSets_.access();
auto it = badgeSets->find(set); auto it = badgeSets->find(set);
if (it != badgeSets->end()) { if (it != badgeSets->end())
{
auto it2 = it->second.find(version); auto it2 = it->second.find(version);
if (it2 != it->second.end()) { if (it2 != it->second.end())
{
return it2->second; return it2->second;
} }
} }

View file

@ -37,7 +37,8 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
// replace regexes // replace regexes
auto it = replacements.find(name); auto it = replacements.find(name);
if (it != replacements.end()) { if (it != replacements.end())
{
name = it.value(); name = it.value();
} }
@ -45,7 +46,8 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
auto cache = this->twitchEmotesCache_.access(); auto cache = this->twitchEmotesCache_.access();
auto shared = (*cache)[id].lock(); auto shared = (*cache)[id].lock();
if (!shared) { if (!shared)
{
(*cache)[id] = shared = std::make_shared<Emote>( (*cache)[id] = shared = std::make_shared<Emote>(
Emote{EmoteName{name}, Emote{EmoteName{name},
ImageSet{ ImageSet{

View file

@ -5,7 +5,8 @@ namespace chatterino {
bool trimChannelName(const QString &channelName, QString &outChannelName) bool trimChannelName(const QString &channelName, QString &outChannelName)
{ {
if (channelName.length() < 2) { if (channelName.length() < 2)
{
log("channel name length below 2"); log("channel name length below 2");
return false; return false;
} }

View file

@ -59,8 +59,10 @@ bool TwitchMessageBuilder::isIgnored() const
auto app = getApp(); auto app = getApp();
// TODO(pajlada): Do we need to check if the phrase is valid first? // TODO(pajlada): Do we need to check if the phrase is valid first?
for (const auto &phrase : app->ignores->phrases.getVector()) { for (const auto &phrase : app->ignores->phrases.getVector())
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) { {
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
{
log("Blocking message because it contains ignored phrase {}", log("Blocking message because it contains ignored phrase {}",
phrase.getPattern()); phrase.getPattern());
return true; return true;
@ -68,12 +70,15 @@ bool TwitchMessageBuilder::isIgnored() const
} }
if (getSettings()->enableTwitchIgnoredUsers && if (getSettings()->enableTwitchIgnoredUsers &&
this->tags.contains("user-id")) { this->tags.contains("user-id"))
{
auto sourceUserID = this->tags.value("user-id").toString(); auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : for (const auto &user :
app->accounts->twitch.getCurrent()->getIgnores()) { app->accounts->twitch.getCurrent()->getIgnores())
if (sourceUserID == user.id) { {
if (sourceUserID == user.id)
{
log("Blocking message because it's from blocked user {}", log("Blocking message because it's from blocked user {}",
user.name); user.name);
return true; return true;
@ -89,7 +94,8 @@ MessagePtr TwitchMessageBuilder::build()
// PARSING // PARSING
this->parseUsername(); this->parseUsername();
if (this->userName == this->channel->getName()) { if (this->userName == this->channel->getName())
{
this->senderIsBroadcaster = true; this->senderIsBroadcaster = true;
} }
@ -104,32 +110,42 @@ MessagePtr TwitchMessageBuilder::build()
// timestamp // timestamp
bool isPastMsg = this->tags.contains("historical"); bool isPastMsg = this->tags.contains("historical");
if (isPastMsg) { if (isPastMsg)
{
// This may be architecture dependent(datatype) // This may be architecture dependent(datatype)
qint64 ts = this->tags.value("tmi-sent-ts").toLongLong(); qint64 ts = this->tags.value("tmi-sent-ts").toLongLong();
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts); QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
this->emplace<TimestampElement>(dateTime.time()); this->emplace<TimestampElement>(dateTime.time());
} else { }
else
{
this->emplace<TimestampElement>(); this->emplace<TimestampElement>();
} }
bool addModerationElement = true; bool addModerationElement = true;
if (this->senderIsBroadcaster) { if (this->senderIsBroadcaster)
{
addModerationElement = false; addModerationElement = false;
} else { }
else
{
bool hasUserType = this->tags.contains("user-type"); bool hasUserType = this->tags.contains("user-type");
if (hasUserType) { if (hasUserType)
{
QString userType = this->tags.value("user-type").toString(); QString userType = this->tags.value("user-type").toString();
if (userType == "mod") { if (userType == "mod")
if (!args.isStaffOrBroadcaster) { {
if (!args.isStaffOrBroadcaster)
{
addModerationElement = false; addModerationElement = false;
} }
} }
} }
} }
if (addModerationElement) { if (addModerationElement)
{
this->emplace<TwitchModerationElement>(); this->emplace<TwitchModerationElement>();
} }
@ -144,7 +160,8 @@ MessagePtr TwitchMessageBuilder::build()
// QString bits; // QString bits;
auto iterator = this->tags.find("bits"); auto iterator = this->tags.find("bits");
if (iterator != this->tags.end()) { if (iterator != this->tags.end())
{
this->hasBits_ = true; this->hasBits_ = true;
// bits = iterator.value().toString(); // bits = iterator.value().toString();
} }
@ -153,10 +170,12 @@ MessagePtr TwitchMessageBuilder::build()
std::vector<std::tuple<int, EmotePtr, EmoteName>> twitchEmotes; std::vector<std::tuple<int, EmotePtr, EmoteName>> twitchEmotes;
iterator = this->tags.find("emotes"); iterator = this->tags.find("emotes");
if (iterator != this->tags.end()) { if (iterator != this->tags.end())
{
QStringList emoteString = iterator.value().toString().split('/'); QStringList emoteString = iterator.value().toString().split('/');
for (QString emote : emoteString) { for (QString emote : emoteString)
{
this->appendTwitchEmote(emote, twitchEmotes); this->appendTwitchEmote(emote, twitchEmotes);
} }
} }
@ -172,8 +191,10 @@ MessagePtr TwitchMessageBuilder::build()
return !((std::get<0>(item) >= pos) && return !((std::get<0>(item) >= pos) &&
std::get<0>(item) < (pos + len)); std::get<0>(item) < (pos + len));
}); });
for (auto copy = it; copy != twitchEmotes.end(); ++copy) { for (auto copy = it; copy != twitchEmotes.end(); ++copy)
if (std::get<1>(*copy) == nullptr) { {
if (std::get<1>(*copy) == nullptr)
{
log("remem nullptr {}", std::get<2>(*copy).string); log("remem nullptr {}", std::get<2>(*copy).string);
} }
} }
@ -184,9 +205,11 @@ MessagePtr TwitchMessageBuilder::build()
}; };
auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable { auto shiftIndicesAfter = [&twitchEmotes](int pos, int by) mutable {
for (auto &item : twitchEmotes) { for (auto &item : twitchEmotes)
{
auto &index = std::get<0>(item); auto &index = std::get<0>(item);
if (index >= pos) { if (index >= pos)
{
index += by; index += by;
} }
} }
@ -195,16 +218,21 @@ MessagePtr TwitchMessageBuilder::build()
auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase, auto addReplEmotes = [&twitchEmotes](const IgnorePhrase &phrase,
const QStringRef &midrepl, const QStringRef &midrepl,
int startIndex) mutable { int startIndex) mutable {
if (!phrase.containsEmote()) { if (!phrase.containsEmote())
{
return; return;
} }
QVector<QStringRef> words = midrepl.split(' '); QVector<QStringRef> words = midrepl.split(' ');
int pos = 0; int pos = 0;
for (const auto &word : words) { for (const auto &word : words)
for (const auto &emote : phrase.getEmotes()) { {
if (word == emote.first.string) { for (const auto &emote : phrase.getEmotes())
if (emote.second == nullptr) { {
if (word == emote.first.string)
{
if (emote.second == nullptr)
{
log("emote null {}", emote.first.string); log("emote null {}", emote.first.string);
} }
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{ twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
@ -215,19 +243,24 @@ MessagePtr TwitchMessageBuilder::build()
} }
}; };
for (const auto &phrase : phrases) { for (const auto &phrase : phrases)
if (phrase.isBlock()) { {
if (phrase.isBlock())
{
continue; continue;
} }
if (phrase.isRegex()) { if (phrase.isRegex())
{
const auto &regex = phrase.getRegex(); const auto &regex = phrase.getRegex();
if (!regex.isValid()) { if (!regex.isValid())
{
continue; continue;
} }
QRegularExpressionMatch match; QRegularExpressionMatch match;
int from = 0; int from = 0;
while ((from = this->originalMessage_.indexOf(regex, from, while ((from = this->originalMessage_.indexOf(regex, from,
&match)) != -1) { &match)) != -1)
{
int len = match.capturedLength(); int len = match.capturedLength();
auto vret = removeEmotesInRange(from, len, twitchEmotes); auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto mid = this->originalMessage_.mid(from, len); auto mid = this->originalMessage_.mid(from, len);
@ -236,15 +269,19 @@ MessagePtr TwitchMessageBuilder::build()
int midsize = mid.size(); int midsize = mid.size();
this->originalMessage_.replace(from, len, mid); this->originalMessage_.replace(from, len, mid);
int pos1 = from; int pos1 = from;
while (pos1 > 0) { while (pos1 > 0)
if (this->originalMessage_[pos1 - 1] == ' ') { {
if (this->originalMessage_[pos1 - 1] == ' ')
{
break; break;
} }
--pos1; --pos1;
} }
int pos2 = from + midsize; int pos2 = from + midsize;
while (pos2 < this->originalMessage_.length()) { while (pos2 < this->originalMessage_.length())
if (this->originalMessage_[pos2] == ' ') { {
if (this->originalMessage_[pos2] == ' ')
{
break; break;
} }
++pos2; ++pos2;
@ -255,8 +292,10 @@ MessagePtr TwitchMessageBuilder::build()
auto midExtendedRef = auto midExtendedRef =
this->originalMessage_.midRef(pos1, pos2 - pos1); this->originalMessage_.midRef(pos1, pos2 - pos1);
for (auto &tup : vret) { for (auto &tup : vret)
if (std::get<1>(tup) == nullptr) { {
if (std::get<1>(tup) == nullptr)
{
log("v nullptr {}", std::get<2>(tup).string); log("v nullptr {}", std::get<2>(tup).string);
continue; continue;
} }
@ -264,9 +303,11 @@ MessagePtr TwitchMessageBuilder::build()
"\\b" + std::get<2>(tup).string + "\\b", "\\b" + std::get<2>(tup).string + "\\b",
QRegularExpression::UseUnicodePropertiesOption); QRegularExpression::UseUnicodePropertiesOption);
auto match = emoteregex.match(midExtendedRef); auto match = emoteregex.match(midExtendedRef);
if (match.hasMatch()) { if (match.hasMatch())
{
int last = match.lastCapturedIndex(); int last = match.lastCapturedIndex();
for (int i = 0; i <= last; ++i) { for (int i = 0; i <= last; ++i)
{
std::get<0>(tup) = from + match.capturedStart(); std::get<0>(tup) = from + match.capturedStart();
twitchEmotes.push_back(std::move(tup)); twitchEmotes.push_back(std::move(tup));
} }
@ -277,14 +318,18 @@ MessagePtr TwitchMessageBuilder::build()
from += midsize; from += midsize;
} }
} else { }
else
{
const auto &pattern = phrase.getPattern(); const auto &pattern = phrase.getPattern();
if (pattern.isEmpty()) { if (pattern.isEmpty())
{
continue; continue;
} }
int from = 0; int from = 0;
while ((from = this->originalMessage_.indexOf( while ((from = this->originalMessage_.indexOf(
pattern, from, phrase.caseSensitivity())) != -1) { pattern, from, phrase.caseSensitivity())) != -1)
{
int len = pattern.size(); int len = pattern.size();
auto vret = removeEmotesInRange(from, len, twitchEmotes); auto vret = removeEmotesInRange(from, len, twitchEmotes);
auto replace = phrase.getReplace(); auto replace = phrase.getReplace();
@ -293,15 +338,19 @@ MessagePtr TwitchMessageBuilder::build()
this->originalMessage_.replace(from, len, replace); this->originalMessage_.replace(from, len, replace);
int pos1 = from; int pos1 = from;
while (pos1 > 0) { while (pos1 > 0)
if (this->originalMessage_[pos1 - 1] == ' ') { {
if (this->originalMessage_[pos1 - 1] == ' ')
{
break; break;
} }
--pos1; --pos1;
} }
int pos2 = from + replacesize; int pos2 = from + replacesize;
while (pos2 < this->originalMessage_.length()) { while (pos2 < this->originalMessage_.length())
if (this->originalMessage_[pos2] == ' ') { {
if (this->originalMessage_[pos2] == ' ')
{
break; break;
} }
++pos2; ++pos2;
@ -312,8 +361,10 @@ MessagePtr TwitchMessageBuilder::build()
auto midExtendedRef = auto midExtendedRef =
this->originalMessage_.midRef(pos1, pos2 - pos1); this->originalMessage_.midRef(pos1, pos2 - pos1);
for (auto &tup : vret) { for (auto &tup : vret)
if (std::get<1>(tup) == nullptr) { {
if (std::get<1>(tup) == nullptr)
{
log("v nullptr {}", std::get<2>(tup).string); log("v nullptr {}", std::get<2>(tup).string);
continue; continue;
} }
@ -321,9 +372,11 @@ MessagePtr TwitchMessageBuilder::build()
"\\b" + std::get<2>(tup).string + "\\b", "\\b" + std::get<2>(tup).string + "\\b",
QRegularExpression::UseUnicodePropertiesOption); QRegularExpression::UseUnicodePropertiesOption);
auto match = emoteregex.match(midExtendedRef); auto match = emoteregex.match(midExtendedRef);
if (match.hasMatch()) { if (match.hasMatch())
{
int last = match.lastCapturedIndex(); int last = match.lastCapturedIndex();
for (int i = 0; i <= last; ++i) { for (int i = 0; i <= last; ++i)
{
std::get<0>(tup) = from + match.capturedStart(); std::get<0>(tup) = from + match.capturedStart();
twitchEmotes.push_back(std::move(tup)); twitchEmotes.push_back(std::move(tup));
} }
@ -365,16 +418,20 @@ void TwitchMessageBuilder::addWords(
auto i = int(); auto i = int();
auto currentTwitchEmote = twitchEmotes.begin(); auto currentTwitchEmote = twitchEmotes.begin();
for (const auto &word : words) { for (const auto &word : words)
{
// check if it's a twitch emote twitch emote // check if it's a twitch emote twitch emote
while (currentTwitchEmote != twitchEmotes.end() && while (currentTwitchEmote != twitchEmotes.end() &&
std::get<0>(*currentTwitchEmote) < i) { std::get<0>(*currentTwitchEmote) < i)
{
++currentTwitchEmote; ++currentTwitchEmote;
} }
if (currentTwitchEmote != twitchEmotes.end() && if (currentTwitchEmote != twitchEmotes.end() &&
std::get<0>(*currentTwitchEmote) == i) { std::get<0>(*currentTwitchEmote) == i)
{
auto emoteImage = std::get<1>(*currentTwitchEmote); auto emoteImage = std::get<1>(*currentTwitchEmote);
if (emoteImage == nullptr) { if (emoteImage == nullptr)
{
log("emoteImage nullptr {}", log("emoteImage nullptr {}",
std::get<2>(*currentTwitchEmote).string); std::get<2>(*currentTwitchEmote).string);
} }
@ -388,15 +445,18 @@ void TwitchMessageBuilder::addWords(
} }
// split words // split words
for (auto &variant : getApp()->emotes->emojis.parse(word)) { for (auto &variant : getApp()->emotes->emojis.parse(word))
{
boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); }, boost::apply_visitor([&](auto &&arg) { this->addTextOrEmoji(arg); },
variant); variant);
} }
for (int j = 0; j < word.size(); j++) { for (int j = 0; j < word.size(); j++)
{
i++; i++;
if (word.at(j).isHighSurrogate()) { if (word.at(j).isHighSurrogate())
{
j++; j++;
} }
} }
@ -414,7 +474,8 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
{ {
auto string = QString(string_); auto string = QString(string_);
if (this->hasBits_ && this->tryParseCheermote(string)) { if (this->hasBits_ && this->tryParseCheermote(string))
{
// This string was parsed as a cheermote // This string was parsed as a cheermote
return; return;
} }
@ -424,7 +485,8 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
// Emote name: "forsenPuke" - if string in ignoredEmotes // Emote name: "forsenPuke" - if string in ignoredEmotes
// Will match emote regardless of source (i.e. bttv, ffz) // Will match emote regardless of source (i.e. bttv, ffz)
// Emote source + name: "bttv:nyanPls" // Emote source + name: "bttv:nyanPls"
if (this->tryAppendEmote({string})) { if (this->tryAppendEmote({string}))
{
// Successfully appended an emote // Successfully appended an emote
return; return;
} }
@ -435,28 +497,37 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
auto textColor = this->action_ ? MessageColor(this->usernameColor_) auto textColor = this->action_ ? MessageColor(this->usernameColor_)
: MessageColor(MessageColor::Text); : MessageColor(MessageColor::Text);
if (linkString.isEmpty()) { if (linkString.isEmpty())
if (string.startsWith('@')) { {
if (string.startsWith('@'))
{
this->emplace<TextElement>(string, MessageElementFlag::BoldUsername, this->emplace<TextElement>(string, MessageElementFlag::BoldUsername,
textColor, FontStyle::ChatMediumBold); textColor, FontStyle::ChatMediumBold);
this->emplace<TextElement>( this->emplace<TextElement>(
string, MessageElementFlag::NonBoldUsername, textColor); string, MessageElementFlag::NonBoldUsername, textColor);
} else { }
else
{
this->emplace<TextElement>(string, MessageElementFlag::Text, this->emplace<TextElement>(string, MessageElementFlag::Text,
textColor); textColor);
} }
} else { }
else
{
static QRegularExpression domainRegex( static QRegularExpression domainRegex(
R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)", R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
QRegularExpression::CaseInsensitiveOption); QRegularExpression::CaseInsensitiveOption);
QString lowercaseLinkString; QString lowercaseLinkString;
auto match = domainRegex.match(string); auto match = domainRegex.match(string);
if (match.isValid()) { if (match.isValid())
{
lowercaseLinkString = string.mid(0, match.capturedStart(1)) + lowercaseLinkString = string.mid(0, match.capturedStart(1)) +
match.captured(1).toLower() + match.captured(1).toLower() +
string.mid(match.capturedEnd(1)); string.mid(match.capturedEnd(1));
} else { }
else
{
lowercaseLinkString = string; lowercaseLinkString = string;
} }
link = Link(Link::Url, linkString); link = Link(Link::Url, linkString);
@ -475,12 +546,14 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
LinkResolver::getLinkInfo( LinkResolver::getLinkInfo(
linkString, [linkMELowercase, linkMEOriginal, linkString]( linkString, [linkMELowercase, linkMEOriginal, linkString](
QString tooltipText, Link originalLink) { QString tooltipText, Link originalLink) {
if (!tooltipText.isEmpty()) { if (!tooltipText.isEmpty())
{
linkMELowercase->setTooltip(tooltipText); linkMELowercase->setTooltip(tooltipText);
linkMEOriginal->setTooltip(tooltipText); linkMEOriginal->setTooltip(tooltipText);
} }
if (originalLink.value != linkString && if (originalLink.value != linkString &&
!originalLink.value.isEmpty()) { !originalLink.value.isEmpty())
{
linkMELowercase->setLink(originalLink)->updateLink(); linkMELowercase->setLink(originalLink)->updateLink();
linkMEOriginal->setLink(originalLink)->updateLink(); linkMEOriginal->setLink(originalLink)->updateLink();
} }
@ -526,23 +599,27 @@ void TwitchMessageBuilder::parseMessageID()
{ {
auto iterator = this->tags.find("id"); auto iterator = this->tags.find("id");
if (iterator != this->tags.end()) { if (iterator != this->tags.end())
{
this->messageID = iterator.value().toString(); this->messageID = iterator.value().toString();
} }
} }
void TwitchMessageBuilder::parseRoomID() void TwitchMessageBuilder::parseRoomID()
{ {
if (this->twitchChannel == nullptr) { if (this->twitchChannel == nullptr)
{
return; return;
} }
auto iterator = this->tags.find("room-id"); auto iterator = this->tags.find("room-id");
if (iterator != std::end(this->tags)) { if (iterator != std::end(this->tags))
{
this->roomID_ = iterator.value().toString(); this->roomID_ = iterator.value().toString();
if (this->twitchChannel->roomId().isEmpty()) { if (this->twitchChannel->roomId().isEmpty())
{
this->twitchChannel->setRoomId(this->roomID_); this->twitchChannel->setRoomId(this->roomID_);
} }
} }
@ -561,14 +638,16 @@ void TwitchMessageBuilder::appendChannelName()
void TwitchMessageBuilder::parseUsername() void TwitchMessageBuilder::parseUsername()
{ {
auto iterator = this->tags.find("color"); auto iterator = this->tags.find("color");
if (iterator != this->tags.end()) { if (iterator != this->tags.end())
{
this->usernameColor_ = QColor(iterator.value().toString()); this->usernameColor_ = QColor(iterator.value().toString());
} }
// username // username
this->userName = this->ircMessage->nick(); this->userName = this->ircMessage->nick();
if (this->userName.isEmpty() || this->args.trimSubscriberUsername) { if (this->userName.isEmpty() || this->args.trimSubscriberUsername)
{
this->userName = this->tags.value(QLatin1String("login")).toString(); this->userName = this->tags.value(QLatin1String("login")).toString();
} }
@ -591,16 +670,20 @@ void TwitchMessageBuilder::appendUsername()
QString localizedName; QString localizedName;
auto iterator = this->tags.find("display-name"); auto iterator = this->tags.find("display-name");
if (iterator != this->tags.end()) { if (iterator != this->tags.end())
{
QString displayName = QString displayName =
parseTagString(iterator.value().toString()).trimmed(); parseTagString(iterator.value().toString()).trimmed();
if (QString::compare(displayName, this->userName, if (QString::compare(displayName, this->userName,
Qt::CaseInsensitive) == 0) { Qt::CaseInsensitive) == 0)
{
username = displayName; username = displayName;
this->message().displayName = displayName; this->message().displayName = displayName;
} else { }
else
{
localizedName = displayName; localizedName = displayName;
this->message().displayName = username; this->message().displayName = username;
@ -617,34 +700,50 @@ void TwitchMessageBuilder::appendUsername()
"/appearance/messages/usernameDisplayMode", "/appearance/messages/usernameDisplayMode",
UsernameDisplayMode::UsernameAndLocalizedName); UsernameDisplayMode::UsernameAndLocalizedName);
switch (usernameDisplayMode.getValue()) { switch (usernameDisplayMode.getValue())
case UsernameDisplayMode::Username: { {
case UsernameDisplayMode::Username:
{
usernameText = username; usernameText = username;
} break; }
break;
case UsernameDisplayMode::LocalizedName: { case UsernameDisplayMode::LocalizedName:
if (hasLocalizedName) { {
if (hasLocalizedName)
{
usernameText = localizedName; usernameText = localizedName;
} else { }
else
{
usernameText = username; usernameText = username;
} }
} break; }
break;
default: default:
case UsernameDisplayMode::UsernameAndLocalizedName: { case UsernameDisplayMode::UsernameAndLocalizedName:
if (hasLocalizedName) { {
if (hasLocalizedName)
{
usernameText = username + "(" + localizedName + ")"; usernameText = username + "(" + localizedName + ")";
} else { }
else
{
usernameText = username; usernameText = username;
} }
} break; }
break;
} }
if (this->args.isSentWhisper) { if (this->args.isSentWhisper)
{
// TODO(pajlada): Re-implement // TODO(pajlada): Re-implement
// userDisplayString += // userDisplayString +=
// IrcManager::getInstance().getUser().getUserName(); // IrcManager::getInstance().getUser().getUserName();
} else if (this->args.isReceivedWhisper) { }
else if (this->args.isReceivedWhisper)
{
// Sender username // Sender username
this->emplace<TextElement>(usernameText, MessageElementFlag::Username, this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
this->usernameColor_, this->usernameColor_,
@ -659,7 +758,8 @@ void TwitchMessageBuilder::appendUsername()
FontStyle::ChatMedium); FontStyle::ChatMedium);
QColor selfColor = currentUser->color(); QColor selfColor = currentUser->color();
if (!selfColor.isValid()) { if (!selfColor.isValid())
{
selfColor = app->themes->messages.textColors.system; selfColor = app->themes->messages.textColors.system;
} }
@ -667,8 +767,11 @@ void TwitchMessageBuilder::appendUsername()
this->emplace<TextElement>(currentUser->getUserName() + ":", this->emplace<TextElement>(currentUser->getUserName() + ":",
MessageElementFlag::Username, selfColor, MessageElementFlag::Username, selfColor,
FontStyle::ChatMediumBold); FontStyle::ChatMediumBold);
} else { }
if (!this->action_) { else
{
if (!this->action_)
{
usernameText += ":"; usernameText += ":";
} }
@ -690,7 +793,8 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
QString currentUsername = currentUser->getUserName(); QString currentUsername = currentUser->getUserName();
if (this->ircMessage->nick() == currentUsername) { if (this->ircMessage->nick() == currentUsername)
{
currentUser->setColor(this->usernameColor_); currentUser->setColor(this->usernameColor_);
// Do nothing. Highlights cannot be triggered by yourself // Do nothing. Highlights cannot be triggered by yourself
return; return;
@ -698,14 +802,18 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
// update the media player url if necessary // update the media player url if necessary
QUrl highlightSoundUrl; QUrl highlightSoundUrl;
if (getSettings()->customHighlightSound) { if (getSettings()->customHighlightSound)
{
highlightSoundUrl = highlightSoundUrl =
QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue()); QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
} else { }
else
{
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav"); highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
} }
if (currentPlayerUrl != highlightSoundUrl) { if (currentPlayerUrl != highlightSoundUrl)
{
player->setMedia(highlightSoundUrl); player->setMedia(highlightSoundUrl);
currentPlayerUrl = highlightSoundUrl; currentPlayerUrl = highlightSoundUrl;
@ -718,7 +826,8 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
std::vector<HighlightPhrase> userHighlights = std::vector<HighlightPhrase> userHighlights =
app->highlights->highlightedUsers.getVector(); app->highlights->highlightedUsers.getVector();
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0) { if (getSettings()->enableSelfHighlight && currentUsername.size() > 0)
{
HighlightPhrase selfHighlight( HighlightPhrase selfHighlight(
currentUsername, getSettings()->enableSelfHighlightTaskbar, currentUsername, getSettings()->enableSelfHighlightTaskbar,
getSettings()->enableSelfHighlightSound, false); getSettings()->enableSelfHighlightSound, false);
@ -731,22 +840,28 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
bool hasFocus = (QApplication::focusWidget() != nullptr); bool hasFocus = (QApplication::focusWidget() != nullptr);
if (!app->highlights->blacklistContains(this->ircMessage->nick())) { if (!app->highlights->blacklistContains(this->ircMessage->nick()))
for (const HighlightPhrase &highlight : activeHighlights) { {
if (highlight.isMatch(this->originalMessage_)) { for (const HighlightPhrase &highlight : activeHighlights)
{
if (highlight.isMatch(this->originalMessage_))
{
log("Highlight because {} matches {}", this->originalMessage_, log("Highlight because {} matches {}", this->originalMessage_,
highlight.getPattern()); highlight.getPattern());
doHighlight = true; doHighlight = true;
if (highlight.getAlert()) { if (highlight.getAlert())
{
doAlert = true; doAlert = true;
} }
if (highlight.getSound()) { if (highlight.getSound())
{
playSound = true; playSound = true;
} }
if (playSound && doAlert) { if (playSound && doAlert)
{
// Break if no further action can be taken from other // Break if no further action can be taken from other
// highlights This might change if highlights can have // highlights This might change if highlights can have
// custom colors/sounds/actions // custom colors/sounds/actions
@ -754,21 +869,26 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
} }
} }
} }
for (const HighlightPhrase &userHighlight : userHighlights) { for (const HighlightPhrase &userHighlight : userHighlights)
if (userHighlight.isMatch(this->ircMessage->nick())) { {
if (userHighlight.isMatch(this->ircMessage->nick()))
{
log("Highlight because user {} sent a message", log("Highlight because user {} sent a message",
this->ircMessage->nick()); this->ircMessage->nick());
doHighlight = true; doHighlight = true;
if (userHighlight.getAlert()) { if (userHighlight.getAlert())
{
doAlert = true; doAlert = true;
} }
if (userHighlight.getSound()) { if (userHighlight.getSound())
{
playSound = true; playSound = true;
} }
if (playSound && doAlert) { if (playSound && doAlert)
{
// Break if no further action can be taken from other // Break if no further action can be taken from other
// usernames Mostly used for regex stuff // usernames Mostly used for regex stuff
break; break;
@ -776,24 +896,30 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
} }
} }
if (this->args.isReceivedWhisper && if (this->args.isReceivedWhisper &&
getSettings()->enableWhisperHighlight) { getSettings()->enableWhisperHighlight)
if (getSettings()->enableWhisperHighlightTaskbar) { {
if (getSettings()->enableWhisperHighlightTaskbar)
{
doAlert = true; doAlert = true;
} }
if (getSettings()->enableWhisperHighlightSound) { if (getSettings()->enableWhisperHighlightSound)
{
playSound = true; playSound = true;
} }
} }
this->message().flags.set(MessageFlag::Highlighted, doHighlight); this->message().flags.set(MessageFlag::Highlighted, doHighlight);
if (!isPastMsg) { if (!isPastMsg)
{
if (playSound && if (playSound &&
(!hasFocus || getSettings()->highlightAlwaysPlaySound)) { (!hasFocus || getSettings()->highlightAlwaysPlaySound))
{
player->play(); player->play();
} }
if (doAlert) { if (doAlert)
{
getApp()->windows->sendAlert(); getApp()->windows->sendAlert();
} }
} }
@ -805,13 +931,15 @@ void TwitchMessageBuilder::appendTwitchEmote(
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec) std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec)
{ {
auto app = getApp(); auto app = getApp();
if (!emote.contains(':')) { if (!emote.contains(':'))
{
return; return;
} }
auto parameters = emote.split(':'); auto parameters = emote.split(':');
if (parameters.length() < 2) { if (parameters.length() < 2)
{
return; return;
} }
@ -819,18 +947,20 @@ void TwitchMessageBuilder::appendTwitchEmote(
auto occurences = parameters.at(1).split(','); auto occurences = parameters.at(1).split(',');
for (QString occurence : occurences) { for (QString occurence : occurences)
{
auto coords = occurence.split('-'); auto coords = occurence.split('-');
if (coords.length() < 2) { if (coords.length() < 2)
{
return; return;
} }
auto start = coords.at(0).toInt(); auto start = coords.at(0).toInt();
auto end = coords.at(1).toInt(); auto end = coords.at(1).toInt();
if (start >= end || start < 0 || if (start >= end || start < 0 || end > this->originalMessage_.length())
end > this->originalMessage_.length()) { {
return; return;
} }
@ -838,7 +968,8 @@ void TwitchMessageBuilder::appendTwitchEmote(
EmoteName{this->originalMessage_.mid(start, end - start + 1)}; EmoteName{this->originalMessage_.mid(start, end - start + 1)};
auto tup = std::tuple<int, EmotePtr, EmoteName>{ auto tup = std::tuple<int, EmotePtr, EmoteName>{
start, app->emotes->twitch.getOrCreateEmote(id, name), name}; start, app->emotes->twitch.getOrCreateEmote(id, name), name};
if (std::get<1>(tup) == nullptr) { if (std::get<1>(tup) == nullptr)
{
log("nullptr {}", std::get<2>(tup).string); log("nullptr {}", std::get<2>(tup).string);
} }
vec.push_back(std::move(tup)); vec.push_back(std::move(tup));
@ -849,24 +980,33 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
{ {
// Special channels, like /whispers and /channels return here // Special channels, like /whispers and /channels return here
// This means they will not render any BTTV or FFZ emotes // This means they will not render any BTTV or FFZ emotes
if (this->twitchChannel == nullptr) { if (this->twitchChannel == nullptr)
{
return Failure; return Failure;
} }
auto flags = MessageElementFlags(); auto flags = MessageElementFlags();
auto emote = boost::optional<EmotePtr>{}; auto emote = boost::optional<EmotePtr>{};
if ((emote = this->twitchChannel->globalBttv().emote(name))) { if ((emote = this->twitchChannel->globalBttv().emote(name)))
{
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if ((emote = this->twitchChannel->bttvEmote(name))) { }
else if ((emote = this->twitchChannel->bttvEmote(name)))
{
flags = MessageElementFlag::BttvEmote; flags = MessageElementFlag::BttvEmote;
} else if ((emote = this->twitchChannel->globalFfz().emote(name))) { }
else if ((emote = this->twitchChannel->globalFfz().emote(name)))
{
flags = MessageElementFlag::FfzEmote; flags = MessageElementFlag::FfzEmote;
} else if ((emote = this->twitchChannel->ffzEmote(name))) { }
else if ((emote = this->twitchChannel->ffzEmote(name)))
{
flags = MessageElementFlag::FfzEmote; flags = MessageElementFlag::FfzEmote;
} }
if (emote) { if (emote)
{
this->emplace<EmoteElement>(emote.get(), flags); this->emplace<EmoteElement>(emote.get(), flags);
return Success; return Success;
} }
@ -877,96 +1017,129 @@ Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
// fourtf: this is ugly // fourtf: this is ugly
void TwitchMessageBuilder::appendTwitchBadges() void TwitchMessageBuilder::appendTwitchBadges()
{ {
if (this->twitchChannel == nullptr) { if (this->twitchChannel == nullptr)
{
return; return;
} }
auto app = getApp(); auto app = getApp();
auto iterator = this->tags.find("badges"); 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(',')) { for (QString badge : iterator.value().toString().split(','))
if (badge.startsWith("bits/")) { {
if (badge.startsWith("bits/"))
{
QString cheerAmount = badge.mid(5); QString cheerAmount = badge.mid(5);
QString tooltip = QString("Twitch cheer ") + cheerAmount; QString tooltip = QString("Twitch cheer ") + cheerAmount;
// Try to fetch channel-specific bit badge // Try to fetch channel-specific bit badge
try { try
{
if (twitchChannel) if (twitchChannel)
if (const auto &badge = this->twitchChannel->twitchBadge( if (const auto &badge = this->twitchChannel->twitchBadge(
"bits", cheerAmount)) { "bits", cheerAmount))
{
this->emplace<EmoteElement>( this->emplace<EmoteElement>(
badge.get(), MessageElementFlag::BadgeVanity) badge.get(), MessageElementFlag::BadgeVanity)
->setTooltip(tooltip); ->setTooltip(tooltip);
continue; continue;
} }
} catch (const std::out_of_range &) { }
catch (const std::out_of_range &)
{
// Channel does not contain a special bit badge for this version // Channel does not contain a special bit badge for this version
} }
// Use default bit badge // Use default bit badge
if (auto badge = this->twitchChannel->globalTwitchBadges().badge( if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
"bits", cheerAmount)) { "bits", cheerAmount))
{
this->emplace<EmoteElement>(badge.get(), this->emplace<EmoteElement>(badge.get(),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip(tooltip); ->setTooltip(tooltip);
} }
} else if (badge == "staff/1") { }
else if (badge == "staff/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.staff), Image::fromPixmap(app->resources->twitch.staff),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Staff"); ->setTooltip("Twitch Staff");
} else if (badge == "admin/1") { }
else if (badge == "admin/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.admin), Image::fromPixmap(app->resources->twitch.admin),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Admin"); ->setTooltip("Twitch Admin");
} else if (badge == "global_mod/1") { }
else if (badge == "global_mod/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.globalmod), Image::fromPixmap(app->resources->twitch.globalmod),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Global Moderator"); ->setTooltip("Twitch Global Moderator");
} else if (badge == "moderator/1") { }
else if (badge == "moderator/1")
{
// TODO: Implement custom FFZ moderator badge // TODO: Implement custom FFZ moderator badge
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.moderator), Image::fromPixmap(app->resources->twitch.moderator),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Channel Moderator"); ->setTooltip("Twitch Channel Moderator");
} else if (badge == "turbo/1") { }
else if (badge == "turbo/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.turbo), Image::fromPixmap(app->resources->twitch.turbo),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Turbo Subscriber"); ->setTooltip("Twitch Turbo Subscriber");
} else if (badge == "broadcaster/1") { }
else if (badge == "broadcaster/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.broadcaster), Image::fromPixmap(app->resources->twitch.broadcaster),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Broadcaster"); ->setTooltip("Twitch Broadcaster");
} else if (badge == "premium/1") { }
else if (badge == "premium/1")
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.prime), Image::fromPixmap(app->resources->twitch.prime),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Prime Subscriber"); ->setTooltip("Twitch Prime Subscriber");
} else if (badge.startsWith("partner/")) { }
else if (badge.startsWith("partner/"))
{
int index = badge.midRef(8).toInt(); int index = badge.midRef(8).toInt();
switch (index) { switch (index)
case 1: { {
case 1:
{
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromPixmap(app->resources->twitch.verified, Image::fromPixmap(app->resources->twitch.verified,
0.25), 0.25),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Verified"); ->setTooltip("Twitch Verified");
} break; }
default: { break;
default:
{
printf("[TwitchMessageBuilder] Unhandled partner badge " printf("[TwitchMessageBuilder] Unhandled partner badge "
"index: %d\n", "index: %d\n",
index); index);
} break; }
break;
} }
} else if (badge.startsWith("subscriber/")) { }
else if (badge.startsWith("subscriber/"))
{
if (auto badgeEmote = this->twitchChannel->twitchBadge( if (auto badgeEmote = this->twitchChannel->twitchBadge(
"subscriber", badge.mid(11))) { "subscriber", badge.mid(11)))
{
this->emplace<EmoteElement>( this->emplace<EmoteElement>(
badgeEmote.get(), MessageElementFlag::BadgeSubscription) badgeEmote.get(), MessageElementFlag::BadgeSubscription)
->setTooltip((*badgeEmote)->tooltip.string); ->setTooltip((*badgeEmote)->tooltip.string);
@ -978,12 +1151,16 @@ void TwitchMessageBuilder::appendTwitchBadges()
Image::fromPixmap(app->resources->twitch.subscriber, 0.25), Image::fromPixmap(app->resources->twitch.subscriber, 0.25),
MessageElementFlag::BadgeSubscription) MessageElementFlag::BadgeSubscription)
->setTooltip("Twitch Subscriber"); ->setTooltip("Twitch Subscriber");
} else { }
else
{
auto splits = badge.split('/'); auto splits = badge.split('/');
if (splits.size() != 2) continue; if (splits.size() != 2)
continue;
if (auto badgeEmote = if (auto badgeEmote =
this->twitchChannel->twitchBadge(splits[0], splits[1])) { this->twitchChannel->twitchBadge(splits[0], splits[1]))
{
this->emplace<EmoteElement>(badgeEmote.get(), this->emplace<EmoteElement>(badgeEmote.get(),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip((*badgeEmote)->tooltip.string); ->setTooltip((*badgeEmote)->tooltip.string);
@ -997,7 +1174,8 @@ void TwitchMessageBuilder::appendChatterinoBadges()
{ {
auto chatterinoBadgePtr = auto chatterinoBadgePtr =
getApp()->chatterinoBadges->getBadge({this->userName}); getApp()->chatterinoBadges->getBadge({this->userName});
if (chatterinoBadgePtr) { if (chatterinoBadgePtr)
{
this->emplace<EmoteElement>(*chatterinoBadgePtr, this->emplace<EmoteElement>(*chatterinoBadgePtr,
MessageElementFlag::BadgeChatterino); MessageElementFlag::BadgeChatterino);
} }

View file

@ -12,13 +12,15 @@ namespace {
inline bool ReadValue(const rapidjson::Value &object, const char *key, inline bool ReadValue(const rapidjson::Value &object, const char *key,
Type &out) Type &out)
{ {
if (!object.HasMember(key)) { if (!object.HasMember(key))
{
return false; return false;
} }
const auto &value = object[key]; const auto &value = object[key];
if (!value.Is<Type>()) { if (!value.Is<Type>())
{
return false; return false;
} }
@ -31,13 +33,15 @@ namespace {
inline bool ReadValue<QString>(const rapidjson::Value &object, inline bool ReadValue<QString>(const rapidjson::Value &object,
const char *key, QString &out) const char *key, QString &out)
{ {
if (!object.HasMember(key)) { if (!object.HasMember(key))
{
return false; return false;
} }
const auto &value = object[key]; const auto &value = object[key];
if (!value.IsString()) { if (!value.IsString())
{
return false; return false;
} }
@ -51,18 +55,22 @@ namespace {
const char *key, const char *key,
std::vector<QString> &out) std::vector<QString> &out)
{ {
if (!object.HasMember(key)) { if (!object.HasMember(key))
{
return false; return false;
} }
const auto &value = object[key]; const auto &value = object[key];
if (!value.IsArray()) { if (!value.IsArray())
{
return false; return false;
} }
for (const rapidjson::Value &innerValue : value.GetArray()) { for (const rapidjson::Value &innerValue : value.GetArray())
if (!innerValue.IsString()) { {
if (!innerValue.IsString())
{
return false; return false;
} }
@ -76,141 +84,173 @@ namespace {
inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set, inline bool ParseSingleCheermoteSet(JSONCheermoteSet &set,
const rapidjson::Value &action) const rapidjson::Value &action)
{ {
if (!action.IsObject()) { if (!action.IsObject())
{
return false; return false;
} }
if (!ReadValue(action, "prefix", set.prefix)) { if (!ReadValue(action, "prefix", set.prefix))
{
return false; return false;
} }
if (!ReadValue(action, "scales", set.scales)) { if (!ReadValue(action, "scales", set.scales))
{
return false; return false;
} }
if (!ReadValue(action, "backgrounds", set.backgrounds)) { if (!ReadValue(action, "backgrounds", set.backgrounds))
{
return false; return false;
} }
if (!ReadValue(action, "states", set.states)) { if (!ReadValue(action, "states", set.states))
{
return false; return false;
} }
if (!ReadValue(action, "type", set.type)) { if (!ReadValue(action, "type", set.type))
{
return false; return false;
} }
if (!ReadValue(action, "updated_at", set.updatedAt)) { if (!ReadValue(action, "updated_at", set.updatedAt))
{
return false; return false;
} }
if (!ReadValue(action, "priority", set.priority)) { if (!ReadValue(action, "priority", set.priority))
{
return false; return false;
} }
// Tiers // Tiers
if (!action.HasMember("tiers")) { if (!action.HasMember("tiers"))
{
return false; return false;
} }
const auto &tiersValue = action["tiers"]; const auto &tiersValue = action["tiers"];
if (!tiersValue.IsArray()) { if (!tiersValue.IsArray())
{
return false; return false;
} }
for (const rapidjson::Value &tierValue : tiersValue.GetArray()) { for (const rapidjson::Value &tierValue : tiersValue.GetArray())
{
JSONCheermoteSet::CheermoteTier tier; JSONCheermoteSet::CheermoteTier tier;
if (!tierValue.IsObject()) { if (!tierValue.IsObject())
{
return false; return false;
} }
if (!ReadValue(tierValue, "min_bits", tier.minBits)) { if (!ReadValue(tierValue, "min_bits", tier.minBits))
{
return false; return false;
} }
if (!ReadValue(tierValue, "id", tier.id)) { if (!ReadValue(tierValue, "id", tier.id))
{
return false; return false;
} }
if (!ReadValue(tierValue, "color", tier.color)) { if (!ReadValue(tierValue, "color", tier.color))
{
return false; return false;
} }
// Images // Images
if (!tierValue.HasMember("images")) { if (!tierValue.HasMember("images"))
{
return false; return false;
} }
const auto &imagesValue = tierValue["images"]; const auto &imagesValue = tierValue["images"];
if (!imagesValue.IsObject()) { if (!imagesValue.IsObject())
{
return false; return false;
} }
// Read images object // Read images object
for (const auto &imageBackgroundValue : imagesValue.GetObject()) { for (const auto &imageBackgroundValue : imagesValue.GetObject())
{
QString background = imageBackgroundValue.name.GetString(); QString background = imageBackgroundValue.name.GetString();
bool backgroundExists = false; bool backgroundExists = false;
for (const auto &bg : set.backgrounds) { for (const auto &bg : set.backgrounds)
if (background == bg) { {
if (background == bg)
{
backgroundExists = true; backgroundExists = true;
break; break;
} }
} }
if (!backgroundExists) { if (!backgroundExists)
{
continue; continue;
} }
const rapidjson::Value &imageBackgroundStates = const rapidjson::Value &imageBackgroundStates =
imageBackgroundValue.value; imageBackgroundValue.value;
if (!imageBackgroundStates.IsObject()) { if (!imageBackgroundStates.IsObject())
{
continue; continue;
} }
// Read each key which represents a background // Read each key which represents a background
for (const auto &imageBackgroundState : for (const auto &imageBackgroundState :
imageBackgroundStates.GetObject()) { imageBackgroundStates.GetObject())
{
QString state = imageBackgroundState.name.GetString(); QString state = imageBackgroundState.name.GetString();
bool stateExists = false; bool stateExists = false;
for (const auto &_state : set.states) { for (const auto &_state : set.states)
if (state == _state) { {
if (state == _state)
{
stateExists = true; stateExists = true;
break; break;
} }
} }
if (!stateExists) { if (!stateExists)
{
continue; continue;
} }
const rapidjson::Value &imageScalesValue = const rapidjson::Value &imageScalesValue =
imageBackgroundState.value; imageBackgroundState.value;
if (!imageScalesValue.IsObject()) { if (!imageScalesValue.IsObject())
{
continue; continue;
} }
// Read each key which represents a scale // Read each key which represents a scale
for (const auto &imageScaleValue : for (const auto &imageScaleValue :
imageScalesValue.GetObject()) { imageScalesValue.GetObject())
{
QString scale = imageScaleValue.name.GetString(); QString scale = imageScaleValue.name.GetString();
bool scaleExists = false; bool scaleExists = false;
for (const auto &_scale : set.scales) { for (const auto &_scale : set.scales)
if (scale == _scale) { {
if (scale == _scale)
{
scaleExists = true; scaleExists = true;
break; break;
} }
} }
if (!scaleExists) { if (!scaleExists)
{
continue; continue;
} }
const rapidjson::Value &imageScaleURLValue = const rapidjson::Value &imageScaleURLValue =
imageScaleValue.value; imageScaleValue.value;
if (!imageScaleURLValue.IsString()) { if (!imageScaleURLValue.IsString())
{
continue; continue;
} }
@ -218,7 +258,8 @@ namespace {
bool ok = false; bool ok = false;
qreal scaleNumber = scale.toFloat(&ok); qreal scaleNumber = scale.toFloat(&ok);
if (!ok) { if (!ok)
{
continue; continue;
} }
@ -246,25 +287,30 @@ std::vector<JSONCheermoteSet> ParseCheermoteSets(const rapidjson::Document &d)
{ {
std::vector<JSONCheermoteSet> sets; std::vector<JSONCheermoteSet> sets;
if (!d.IsObject()) { if (!d.IsObject())
{
return sets; return sets;
} }
if (!d.HasMember("actions")) { if (!d.HasMember("actions"))
{
return sets; return sets;
} }
const auto &actionsValue = d["actions"]; const auto &actionsValue = d["actions"];
if (!actionsValue.IsArray()) { if (!actionsValue.IsArray())
{
return sets; return sets;
} }
for (const auto &action : actionsValue.GetArray()) { for (const auto &action : actionsValue.GetArray())
{
JSONCheermoteSet set; JSONCheermoteSet set;
bool res = ParseSingleCheermoteSet(set, action); bool res = ParseSingleCheermoteSet(set, action);
if (res) { if (res)
{
sets.emplace_back(set); sets.emplace_back(set);
} }
} }

View file

@ -58,7 +58,8 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
QString username = account->getUserName(); QString username = account->getUserName();
QString oauthToken = account->getOAuthToken(); QString oauthToken = account->getOAuthToken();
if (!oauthToken.startsWith("oauth:")) { if (!oauthToken.startsWith("oauth:"))
{
oauthToken.prepend("oauth:"); oauthToken.prepend("oauth:");
} }
@ -66,7 +67,8 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
connection->setNickName(username); connection->setNickName(username);
connection->setRealName(username); connection->setRealName(username);
if (!account->isAnon()) { if (!account->isAnon())
{
connection->setPassword(oauthToken); connection->setPassword(oauthToken);
} }
@ -103,7 +105,8 @@ void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
void TwitchServer::messageReceived(Communi::IrcMessage *message) void TwitchServer::messageReceived(Communi::IrcMessage *message)
{ {
// this->readConnection // this->readConnection
if (message->type() == Communi::IrcMessage::Type::Private) { if (message->type() == Communi::IrcMessage::Type::Private)
{
// We already have a handler for private messages // We already have a handler for private messages
return; return;
} }
@ -112,35 +115,55 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
auto &handler = IrcMessageHandler::getInstance(); auto &handler = IrcMessageHandler::getInstance();
if (command == "ROOMSTATE") { if (command == "ROOMSTATE")
{
handler.handleRoomStateMessage(message); handler.handleRoomStateMessage(message);
} else if (command == "CLEARCHAT") { }
else if (command == "CLEARCHAT")
{
handler.handleClearChatMessage(message); handler.handleClearChatMessage(message);
} else if (command == "USERSTATE") { }
else if (command == "USERSTATE")
{
handler.handleUserStateMessage(message); handler.handleUserStateMessage(message);
} else if (command == "WHISPER") { }
else if (command == "WHISPER")
{
handler.handleWhisperMessage(message); handler.handleWhisperMessage(message);
} else if (command == "USERNOTICE") { }
else if (command == "USERNOTICE")
{
handler.handleUserNoticeMessage(message, *this); handler.handleUserNoticeMessage(message, *this);
} else if (command == "MODE") { }
else if (command == "MODE")
{
handler.handleModeMessage(message); handler.handleModeMessage(message);
} else if (command == "NOTICE") { }
else if (command == "NOTICE")
{
handler.handleNoticeMessage( handler.handleNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message)); static_cast<Communi::IrcNoticeMessage *>(message));
} else if (command == "JOIN") { }
else if (command == "JOIN")
{
handler.handleJoinMessage(message); handler.handleJoinMessage(message);
} else if (command == "PART") { }
else if (command == "PART")
{
handler.handlePartMessage(message); handler.handlePartMessage(message);
} }
} }
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message) void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
{ {
switch (message->type()) { switch (message->type())
case Communi::IrcMessage::Type::Notice: { {
case Communi::IrcMessage::Type::Notice:
{
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage( IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
static_cast<Communi::IrcNoticeMessage *>(message)); static_cast<Communi::IrcNoticeMessage *>(message));
} break; }
break;
default:; default:;
} }
@ -149,11 +172,13 @@ void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
std::shared_ptr<Channel> TwitchServer::getCustomChannel( std::shared_ptr<Channel> TwitchServer::getCustomChannel(
const QString &channelName) const QString &channelName)
{ {
if (channelName == "/whispers") { if (channelName == "/whispers")
{
return this->whispersChannel; return this->whispersChannel;
} }
if (channelName == "/mentions") { if (channelName == "/mentions")
{
return this->mentionsChannel; return this->mentionsChannel;
} }
@ -174,14 +199,18 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
{ {
std::lock_guard<std::mutex> lock(this->channelMutex); std::lock_guard<std::mutex> lock(this->channelMutex);
for (const auto &weakChannel : this->channels) { for (const auto &weakChannel : this->channels)
{
auto channel = weakChannel.lock(); auto channel = weakChannel.lock();
if (!channel) continue; if (!channel)
continue;
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel); auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
if (!twitchChannel) continue; if (!twitchChannel)
continue;
if (twitchChannel->roomId() == channelId) { if (twitchChannel->roomId() == channelId)
{
return twitchChannel; return twitchChannel;
} }
} }
@ -217,9 +246,10 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
// check if you are sending messages too fast // check if you are sending messages too fast
if (!lastMessage.empty() && if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
lastMessage.back() + minMessageOffset > now) { {
if (this->lastErrorTimeSpeed_ + 30s < now) { if (this->lastErrorTimeSpeed_ + 30s < now)
{
auto errorMessage = auto errorMessage =
makeSystemMessage("sending messages too fast"); makeSystemMessage("sending messages too fast");
@ -231,13 +261,16 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
} }
// remove messages older than 30 seconds // remove messages older than 30 seconds
while (!lastMessage.empty() && lastMessage.front() + 32s < now) { while (!lastMessage.empty() && lastMessage.front() + 32s < now)
{
lastMessage.pop(); lastMessage.pop();
} }
// check if you are sending too many messages // check if you are sending too many messages
if (lastMessage.size() >= maxMessageCount) { if (lastMessage.size() >= maxMessageCount)
if (this->lastErrorTimeAmount_ + 30s < now) { {
if (this->lastErrorTimeAmount_ + 30s < now)
{
auto errorMessage = auto errorMessage =
makeSystemMessage("sending too many messages"); makeSystemMessage("sending too many messages");

View file

@ -43,26 +43,30 @@ namespace Settings {
TwitchUser user; TwitchUser user;
if (!value.IsObject()) { if (!value.IsObject())
{
PAJLADA_REPORT_ERROR(error) PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION( PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is wrong type"); "Deserialized rapidjson::Value is wrong type");
return user; return user;
} }
if (!rj::getSafe(value, "_id", user.id)) { if (!rj::getSafe(value, "_id", user.id))
{
PAJLADA_REPORT_ERROR(error) PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing ID key"); PAJLADA_THROW_EXCEPTION("Missing ID key");
return user; return user;
} }
if (!rj::getSafe(value, "name", user.name)) { if (!rj::getSafe(value, "name", user.name))
{
PAJLADA_REPORT_ERROR(error) PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing name key"); PAJLADA_THROW_EXCEPTION("Missing name key");
return user; return user;
} }
if (!rj::getSafe(value, "display_name", user.displayName)) { if (!rj::getSafe(value, "display_name", user.displayName))
{
PAJLADA_REPORT_ERROR(error) PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION("Missing display name key"); PAJLADA_THROW_EXCEPTION("Missing display name key");
return user; return user;

View file

@ -35,7 +35,8 @@ void Fonts::initialize(Settings &, Paths &)
this->chatFontFamily.connect([this](const std::string &, auto) { this->chatFontFamily.connect([this](const std::string &, auto) {
assertInGuiThread(); assertInGuiThread();
for (auto &map : this->fontsByType_) { for (auto &map : this->fontsByType_)
{
map.clear(); map.clear();
} }
this->fontChanged.invoke(); this->fontChanged.invoke();
@ -44,7 +45,8 @@ void Fonts::initialize(Settings &, Paths &)
this->chatFontSize.connect([this](const int &, auto) { this->chatFontSize.connect([this](const int &, auto) {
assertInGuiThread(); assertInGuiThread();
for (auto &map : this->fontsByType_) { for (auto &map : this->fontsByType_)
{
map.clear(); map.clear();
} }
this->fontChanged.invoke(); this->fontChanged.invoke();
@ -55,7 +57,8 @@ void Fonts::initialize(Settings &, Paths &)
getApp()->windows->incGeneration(); getApp()->windows->incGeneration();
for (auto &map : this->fontsByType_) { for (auto &map : this->fontsByType_)
{
map.clear(); map.clear();
} }
this->fontChanged.invoke(); this->fontChanged.invoke();
@ -82,7 +85,8 @@ Fonts::FontData &Fonts::getOrCreateFontData(FontStyle type, float scale)
// find element // find element
auto it = map.find(scale); auto it = map.find(scale);
if (it != map.end()) { if (it != map.end())
{
// return if found // return if found
return it->second; return it->second;
@ -98,7 +102,8 @@ Fonts::FontData &Fonts::getOrCreateFontData(FontStyle type, float scale)
Fonts::FontData Fonts::createFontData(FontStyle type, float scale) Fonts::FontData Fonts::createFontData(FontStyle type, float scale)
{ {
// check if it's a chat (scale the setting) // check if it's a chat (scale the setting)
if (type >= FontStyle::ChatStart && type <= FontStyle::ChatEnd) { if (type >= FontStyle::ChatStart && type <= FontStyle::ChatEnd)
{
static std::unordered_map<FontStyle, ChatFontData> sizeScale{ static std::unordered_map<FontStyle, ChatFontData> sizeScale{
{FontStyle::ChatSmall, {0.6f, false, QFont::Normal}}, {FontStyle::ChatSmall, {0.6f, false, QFont::Normal}},
{FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}}, {FontStyle::ChatMediumSmall, {0.8f, false, QFont::Normal}},

View file

@ -18,17 +18,21 @@ void Logging::initialize(Settings &settings, Paths &paths)
void Logging::addMessage(const QString &channelName, MessagePtr message) void Logging::addMessage(const QString &channelName, MessagePtr message)
{ {
if (!getSettings()->enableLogging) { if (!getSettings()->enableLogging)
{
return; return;
} }
auto it = this->loggingChannels_.find(channelName); auto it = this->loggingChannels_.find(channelName);
if (it == this->loggingChannels_.end()) { if (it == this->loggingChannels_.end())
{
auto channel = new LoggingChannel(channelName); auto channel = new LoggingChannel(channelName);
channel->addMessage(message); channel->addMessage(message);
this->loggingChannels_.emplace( this->loggingChannels_.emplace(
channelName, std::unique_ptr<LoggingChannel>(std::move(channel))); channelName, std::unique_ptr<LoggingChannel>(std::move(channel)));
} else { }
else
{
it->second->addMessage(message); it->second->addMessage(message);
} }
} }

View file

@ -37,7 +37,8 @@ void registerNmManifest(Paths &paths, const QString &manifestFilename,
void registerNmHost(Paths &paths) void registerNmHost(Paths &paths)
{ {
if (paths.isPortable()) return; if (paths.isPortable())
return;
auto getBaseDocument = [&] { auto getBaseDocument = [&] {
QJsonObject obj; QJsonObject obj;
@ -112,11 +113,14 @@ std::string &getNmQueueName(Paths &paths)
void NativeMessagingClient::sendMessage(const QByteArray &array) void NativeMessagingClient::sendMessage(const QByteArray &array)
{ {
try { try
{
ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui"); ipc::message_queue messageQueue(ipc::open_only, "chatterino_gui");
messageQueue.try_send(array.data(), array.size(), 1); messageQueue.try_send(array.data(), array.size(), 1);
} catch (ipc::interprocess_exception &ex) { }
catch (ipc::interprocess_exception &ex)
{
qDebug() << "send to gui process:" << ex.what(); qDebug() << "send to gui process:" << ex.what();
} }
} }
@ -144,8 +148,10 @@ void NativeMessagingServer::ReceiverThread::run()
ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100, ipc::message_queue messageQueue(ipc::open_or_create, "chatterino_gui", 100,
MESSAGE_SIZE); MESSAGE_SIZE);
while (true) { while (true)
try { {
try
{
auto buf = std::make_unique<char[]>(MESSAGE_SIZE); auto buf = std::make_unique<char[]>(MESSAGE_SIZE);
auto retSize = ipc::message_queue::size_type(); auto retSize = ipc::message_queue::size_type();
auto priority = static_cast<unsigned int>(0); auto priority = static_cast<unsigned int>(0);
@ -156,7 +162,9 @@ void NativeMessagingServer::ReceiverThread::run()
QByteArray::fromRawData(buf.get(), retSize)); QByteArray::fromRawData(buf.get(), retSize));
this->handleMessage(document.object()); this->handleMessage(document.object());
} catch (ipc::interprocess_exception &ex) { }
catch (ipc::interprocess_exception &ex)
{
qDebug() << "received from gui process:" << ex.what(); qDebug() << "received from gui process:" << ex.what();
} }
} }
@ -169,12 +177,14 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
QString action = root.value("action").toString(); QString action = root.value("action").toString();
if (action.isNull()) { if (action.isNull())
{
qDebug() << "NM action was null"; qDebug() << "NM action was null";
return; return;
} }
if (action == "select") { if (action == "select")
{
QString _type = root.value("type").toString(); QString _type = root.value("type").toString();
bool attach = root.value("attach").toBool(); bool attach = root.value("attach").toBool();
QString name = root.value("name").toString(); QString name = root.value("name").toString();
@ -188,26 +198,31 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
args.width = root.value("size").toObject().value("width").toInt(-1); args.width = root.value("size").toObject().value("width").toInt(-1);
args.height = root.value("size").toObject().value("height").toInt(-1); args.height = root.value("size").toObject().value("height").toInt(-1);
if (_type.isNull() || args.winId.isNull()) { if (_type.isNull() || args.winId.isNull())
{
qDebug() << "NM type, name or winId missing"; qDebug() << "NM type, name or winId missing";
attach = false; attach = false;
return; return;
} }
#endif #endif
if (_type == "twitch") { if (_type == "twitch")
{
postToThread([=] { postToThread([=] {
if (!name.isEmpty()) { if (!name.isEmpty())
{
app->twitch.server->watchingChannel.reset( app->twitch.server->watchingChannel.reset(
app->twitch.server->getOrAddChannel(name)); app->twitch.server->getOrAddChannel(name));
} }
if (attach) { if (attach)
{
#ifdef USEWINSDK #ifdef USEWINSDK
// if (args.height != -1) { // if (args.height != -1) {
auto *window = auto *window =
AttachedWindow::get(::GetForegroundWindow(), args); AttachedWindow::get(::GetForegroundWindow(), args);
if (!name.isEmpty()) { if (!name.isEmpty())
{
window->setChannel( window->setChannel(
app->twitch.server->getOrAddChannel(name)); app->twitch.server->getOrAddChannel(name));
} }
@ -216,14 +231,18 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
#endif #endif
} }
}); });
}
} else { else
{
qDebug() << "NM unknown channel type"; qDebug() << "NM unknown channel type";
} }
} else if (action == "detach") { }
else if (action == "detach")
{
QString winId = root.value("winId").toString(); QString winId = root.value("winId").toString();
if (winId.isNull()) { if (winId.isNull())
{
qDebug() << "NM winId missing"; qDebug() << "NM winId missing";
return; return;
} }
@ -234,7 +253,9 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
AttachedWindow::detach(winId); AttachedWindow::detach(winId);
}); });
#endif #endif
} else { }
else
{
qDebug() << "NM unknown action " + action; qDebug() << "NM unknown action " + action;
} }
} }

View file

@ -49,7 +49,8 @@ QString Paths::cacheDirectory()
auto path = cachePathSetting.getValue(); auto path = cachePathSetting.getValue();
if (path == "") { if (path == "")
{
return this->cacheDirectory_; return this->cacheDirectory_;
} }
@ -83,14 +84,16 @@ void Paths::initAppDataDirectory()
this->rootAppDataDirectory = [&]() -> QString { this->rootAppDataDirectory = [&]() -> QString {
// portable // portable
if (this->isPortable()) { if (this->isPortable())
{
return QCoreApplication::applicationDirPath(); return QCoreApplication::applicationDirPath();
} }
// permanent installation // permanent installation
QString path = QString path =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (path.isEmpty()) { if (path.isEmpty())
{
throw std::runtime_error( throw std::runtime_error(
"Error finding writable location for settings"); "Error finding writable location for settings");
} }
@ -117,7 +120,8 @@ void Paths::initSubDirectories()
auto path = combinePath(this->rootAppDataDirectory, auto path = combinePath(this->rootAppDataDirectory,
QString::fromStdString(name)); QString::fromStdString(name));
if (!QDir().mkpath(path)) { if (!QDir().mkpath(path))
{
throw std::runtime_error( throw std::runtime_error(
"Error creating appdata path %appdata%/chatterino/" + name); "Error creating appdata path %appdata%/chatterino/" + name);
} }

View file

@ -37,9 +37,11 @@ void Settings::saveSnapshot()
rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType); rapidjson::Document *d = new rapidjson::Document(rapidjson::kObjectType);
rapidjson::Document::AllocatorType &a = d->GetAllocator(); rapidjson::Document::AllocatorType &a = d->GetAllocator();
for (const auto &weakSetting : _settings) { for (const auto &weakSetting : _settings)
{
auto setting = weakSetting.lock(); auto setting = weakSetting.lock();
if (!setting) { if (!setting)
{
continue; continue;
} }
@ -55,22 +57,26 @@ void Settings::saveSnapshot()
void Settings::restoreSnapshot() void Settings::restoreSnapshot()
{ {
if (!this->snapshot_) { if (!this->snapshot_)
{
return; return;
} }
const auto &snapshotObject = this->snapshot_->GetObject(); const auto &snapshotObject = this->snapshot_->GetObject();
for (const auto &weakSetting : _settings) { for (const auto &weakSetting : _settings)
{
auto setting = weakSetting.lock(); auto setting = weakSetting.lock();
if (!setting) { if (!setting)
{
log("Error stage 1 of loading"); log("Error stage 1 of loading");
continue; continue;
} }
const char *path = setting->getPath().c_str(); const char *path = setting->getPath().c_str();
if (!snapshotObject.HasMember(path)) { if (!snapshotObject.HasMember(path))
{
log("Error stage 2 of loading"); log("Error stage 2 of loading");
continue; continue;
} }

View file

@ -12,13 +12,20 @@ namespace detail {
double getMultiplierByTheme(const QString &themeName) double getMultiplierByTheme(const QString &themeName)
{ {
if (themeName == "Light") { if (themeName == "Light")
{
return 0.8; return 0.8;
} else if (themeName == "White") { }
else if (themeName == "White")
{
return 1.0; return 1.0;
} else if (themeName == "Black") { }
else if (themeName == "Black")
{
return -1.0; return -1.0;
} else if (themeName == "Dark") { }
else if (themeName == "Dark")
{
return -0.8; return -0.8;
} }
@ -88,7 +95,8 @@ void Theme::actuallyUpdate(double hue, double multiplier)
QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166"); QColor highlighted = lightWin ? QColor("#ff0000") : QColor("#ee6166");
/// TABS /// TABS
if (lightWin) { if (lightWin)
{
this->tabs.regular = { this->tabs.regular = {
QColor("#444"), QColor("#444"),
{QColor("#fff"), QColor("#eee"), QColor("#fff")}, {QColor("#fff"), QColor("#eee"), QColor("#fff")},
@ -105,7 +113,9 @@ void Theme::actuallyUpdate(double hue, double multiplier)
QColor("#000"), QColor("#000"),
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")}, {QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}}; {QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} else { }
else
{
this->tabs.regular = { this->tabs.regular = {
QColor("#aaa"), QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")}, {QColor("#252525"), QColor("#252525"), QColor("#252525")},
@ -161,13 +171,16 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->splits.dropPreview = QColor(0, 148, 255, 0x30); this->splits.dropPreview = QColor(0, 148, 255, 0x30);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff); this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
if (isLight_) { if (isLight_)
{
this->splits.dropTargetRect = QColor(255, 255, 255, 0x00); this->splits.dropTargetRect = QColor(255, 255, 255, 0x00);
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00); this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
this->splits.resizeHandle = QColor(0, 148, 255, 0xff); this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50); this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
} else { }
else
{
this->splits.dropTargetRect = QColor(0, 148, 255, 0x00); this->splits.dropTargetRect = QColor(0, 148, 255, 0x00);
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00); this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
@ -200,10 +213,13 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->messages.backgrounds.regular = splits.background; this->messages.backgrounds.regular = splits.background;
this->messages.backgrounds.alternate = getColor(0, sat, 0.93); this->messages.backgrounds.alternate = getColor(0, sat, 0.93);
if (isLight_) { if (isLight_)
{
this->messages.backgrounds.highlighted = this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8); blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
} else { }
else
{
this->messages.backgrounds.highlighted = this->messages.backgrounds.highlighted =
QColor(getSettings()->highlightColor); QColor(getSettings()->highlightColor);
} }
@ -247,25 +263,32 @@ QColor Theme::blendColors(const QColor &color1, const QColor &color2,
void Theme::normalizeColor(QColor &color) void Theme::normalizeColor(QColor &color)
{ {
if (this->isLight_) { if (this->isLight_)
if (color.lightnessF() > 0.5) { {
if (color.lightnessF() > 0.5)
{
color.setHslF(color.hueF(), color.saturationF(), 0.5); color.setHslF(color.hueF(), color.saturationF(), 0.5);
} }
if (color.lightnessF() > 0.4 && color.hueF() > 0.1 && if (color.lightnessF() > 0.4 && color.hueF() > 0.1 &&
color.hueF() < 0.33333) { color.hueF() < 0.33333)
{
color.setHslF(color.hueF(), color.saturationF(), color.setHslF(color.hueF(), color.saturationF(),
color.lightnessF() - sin((color.hueF() - 0.1) / color.lightnessF() - sin((color.hueF() - 0.1) /
(0.3333 - 0.1) * 3.14159) * (0.3333 - 0.1) * 3.14159) *
color.saturationF() * 0.4); color.saturationF() * 0.4);
} }
} else { }
if (color.lightnessF() < 0.5) { else
{
if (color.lightnessF() < 0.5)
{
color.setHslF(color.hueF(), color.saturationF(), 0.5); color.setHslF(color.hueF(), color.saturationF(), 0.5);
} }
if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 && if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 &&
color.hueF() < 0.83333) { color.hueF() < 0.83333)
{
color.setHslF( color.setHslF(
color.hueF(), color.saturationF(), color.hueF(), color.saturationF(),
color.lightnessF() + sin((color.hueF() - 0.54444) / color.lightnessF() + sin((color.hueF() - 0.54444) /

View file

@ -45,12 +45,16 @@ void Toasts::sendChannelNotification(const QString &channelName, Platform p)
}; };
#endif #endif
// Fetch user profile avatar // Fetch user profile avatar
if (p == Platform::Twitch) { if (p == Platform::Twitch)
{
QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" + QFileInfo check_file(getPaths()->twitchProfileAvatars + "/twitch/" +
channelName + ".png"); channelName + ".png");
if (check_file.exists() && check_file.isFile()) { if (check_file.exists() && check_file.isFile())
{
sendChannelNotification(); sendChannelNotification();
} else { }
else
{
this->fetchChannelAvatar( this->fetchChannelAvatar(
channelName, channelName,
[channelName, sendChannelNotification](QString avatarLink) { [channelName, sendChannelNotification](QString avatarLink) {
@ -81,7 +85,8 @@ public:
void toastActivated() const void toastActivated() const
{ {
QString link; QString link;
if (platform_ == Platform::Twitch) { if (platform_ == Platform::Twitch)
{
link = "http://www.twitch.tv/" + channelName_; link = "http://www.twitch.tv/" + channelName_;
} }
QDesktopServices::openUrl(QUrl(link)); QDesktopServices::openUrl(QUrl(link));
@ -112,14 +117,16 @@ void Toasts::sendWindowsNotification(const QString &channelName, Platform p)
templ.setTextField(L"Click here to open in browser", templ.setTextField(L"Click here to open in browser",
WinToastLib::WinToastTemplate::SecondLine); WinToastLib::WinToastTemplate::SecondLine);
QString Path; QString Path;
if (p == Platform::Twitch) { if (p == Platform::Twitch)
{
Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName + Path = getPaths()->twitchProfileAvatars + "/twitch/" + channelName +
".png"; ".png";
} }
std::string temp_Utf8 = Path.toUtf8().constData(); std::string temp_Utf8 = Path.toUtf8().constData();
std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end()); std::wstring imagePath = std::wstring(temp_Utf8.begin(), temp_Utf8.end());
templ.setImagePath(imagePath); templ.setImagePath(imagePath);
if (getSettings()->notificationPlaySound) { if (getSettings()->notificationPlaySound)
{
templ.setAudioOption( templ.setAudioOption(
WinToastLib::WinToastTemplate::AudioOption::Silent); WinToastLib::WinToastTemplate::AudioOption::Silent);
} }
@ -151,19 +158,22 @@ void Toasts::fetchChannelAvatar(const QString channelName,
request.setTimeout(30000); request.setTimeout(30000);
request.onSuccess([successCallback](auto result) mutable -> Outcome { request.onSuccess([successCallback](auto result) mutable -> Outcome {
auto root = result.parseJson(); auto root = result.parseJson();
if (!root.value("users").isArray()) { if (!root.value("users").isArray())
{
// log("API Error while getting user id, users is not an array"); // log("API Error while getting user id, users is not an array");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
auto users = root.value("users").toArray(); auto users = root.value("users").toArray();
if (users.size() != 1) { if (users.size() != 1)
{
// log("API Error while getting user id, users array size is not // log("API Error while getting user id, users array size is not
// 1"); // 1");
successCallback(""); successCallback("");
return Failure; return Failure;
} }
if (!users[0].isObject()) { if (!users[0].isObject())
{
// log("API Error while getting user id, first user is not an // log("API Error while getting user id, first user is not an
// object"); // object");
successCallback(""); successCallback("");
@ -171,7 +181,8 @@ void Toasts::fetchChannelAvatar(const QString channelName,
} }
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto avatar = firstUser.value("logo"); auto avatar = firstUser.value("logo");
if (!avatar.isString()) { if (!avatar.isString())
{
// log("API Error: while getting user avatar, first user object " // log("API Error: while getting user avatar, first user object "
// "`avatar` key " // "`avatar` key "
// "is not a " // "is not a "

View file

@ -39,7 +39,8 @@ const QString &Updates::getOnlineVersion() const
void Updates::installUpdates() void Updates::installUpdates()
{ {
if (this->status_ != UpdateAvailable) { if (this->status_ != UpdateAvailable)
{
assert(false); assert(false);
return; return;
} }
@ -77,7 +78,8 @@ void Updates::installUpdates()
QFile file(filename); QFile file(filename);
file.open(QIODevice::Truncate | QIODevice::WriteOnly); file.open(QIODevice::Truncate | QIODevice::WriteOnly);
if (file.write(object) == -1) { if (file.write(object) == -1)
{
this->setStatus_(WriteFileFailed); this->setStatus_(WriteFileFailed);
return Failure; return Failure;
} }
@ -109,7 +111,8 @@ void Updates::checkForUpdates()
QJsonValue version_val = object.value("version"); QJsonValue version_val = object.value("version");
QJsonValue update_val = object.value("update"); QJsonValue update_val = object.value("update");
if (!version_val.isString() || !update_val.isString()) { if (!version_val.isString() || !update_val.isString())
{
this->setStatus_(SearchFailed); this->setStatus_(SearchFailed);
qDebug() << "error updating"; qDebug() << "error updating";
@ -129,7 +132,8 @@ void Updates::checkForUpdates()
this->onlineVersion_ = version_val.toString(); this->onlineVersion_ = version_val.toString();
this->updateUrl_ = update_val.toString(); this->updateUrl_ = update_val.toString();
if (this->currentVersion_ != this->onlineVersion_) { if (this->currentVersion_ != this->onlineVersion_)
{
this->setStatus_(UpdateAvailable); this->setStatus_(UpdateAvailable);
postToThread([this] { postToThread([this] {
QMessageBox *box = new QMessageBox( QMessageBox *box = new QMessageBox(
@ -140,11 +144,14 @@ void Updates::checkForUpdates()
box->setAttribute(Qt::WA_DeleteOnClose); box->setAttribute(Qt::WA_DeleteOnClose);
box->show(); box->show();
box->raise(); box->raise();
if (box->exec() == QMessageBox::Yes) { if (box->exec() == QMessageBox::Yes)
{
this->installUpdates(); this->installUpdates();
} }
}); });
} else { }
else
{
this->setStatus_(NoUpdateAvailable); this->setStatus_(NoUpdateAvailable);
} }
return Failure; return Failure;
@ -161,7 +168,8 @@ Updates::Status Updates::getStatus() const
bool Updates::shouldShowUpdateButton() const bool Updates::shouldShowUpdateButton() const
{ {
switch (this->getStatus()) { switch (this->getStatus())
{
case UpdateAvailable: case UpdateAvailable:
case SearchFailed: case SearchFailed:
case Downloading: case Downloading:
@ -176,7 +184,8 @@ bool Updates::shouldShowUpdateButton() const
bool Updates::isError() const bool Updates::isError() const
{ {
switch (this->getStatus()) { switch (this->getStatus())
{
case SearchFailed: case SearchFailed:
case DownloadFailed: case DownloadFailed:
case WriteFileFailed: case WriteFileFailed:
@ -189,7 +198,8 @@ bool Updates::isError() const
void Updates::setStatus_(Status status) void Updates::setStatus_(Status status)
{ {
if (this->status_ != status) { if (this->status_ != status)
{
this->status_ = status; this->status_ = status;
postToThread([this, status] { this->statusUpdated.invoke(status); }); postToThread([this, status] { this->statusUpdated.invoke(status); });
} }

View file

@ -45,7 +45,8 @@ void WindowManager::showAccountSelectPopup(QPoint point)
// static QWidget *lastFocusedWidget = nullptr; // static QWidget *lastFocusedWidget = nullptr;
static AccountSwitchPopupWidget *w = new AccountSwitchPopupWidget(); static AccountSwitchPopupWidget *w = new AccountSwitchPopupWidget();
if (w->hasFocus()) { if (w->hasFocus())
{
w->hide(); w->hide();
// if (lastFocusedWidget) { // if (lastFocusedWidget) {
// lastFocusedWidget->setFocus(); // lastFocusedWidget->setFocus();
@ -109,7 +110,8 @@ void WindowManager::updateWordTypeMask()
auto flags = MessageElementFlags(MEF::Text); auto flags = MessageElementFlags(MEF::Text);
// timestamp // timestamp
if (settings->showTimestamps) { if (settings->showTimestamps)
{
flags.set(MEF::Timestamp); flags.set(MEF::Timestamp);
} }
@ -152,7 +154,8 @@ void WindowManager::updateWordTypeMask()
// update flags // update flags
MessageElementFlags newFlags = static_cast<MessageElementFlags>(flags); MessageElementFlags newFlags = static_cast<MessageElementFlags>(flags);
if (newFlags != this->wordFlags_) { if (newFlags != this->wordFlags_)
{
this->wordFlags_ = newFlags; this->wordFlags_ = newFlags;
this->wordFlagsChanged.invoke(); this->wordFlagsChanged.invoke();
@ -172,7 +175,8 @@ void WindowManager::forceLayoutChannelViews()
void WindowManager::repaintVisibleChatWidgets(Channel *channel) void WindowManager::repaintVisibleChatWidgets(Channel *channel)
{ {
if (this->mainWindow_ != nullptr) { if (this->mainWindow_ != nullptr)
{
this->mainWindow_->repaintVisibleChatWidgets(channel); this->mainWindow_->repaintVisibleChatWidgets(channel);
} }
} }
@ -211,13 +215,16 @@ Window &WindowManager::createWindow(WindowType type)
this->windows_.push_back(window); this->windows_.push_back(window);
window->show(); window->show();
if (type != WindowType::Main) { if (type != WindowType::Main)
{
window->setAttribute(Qt::WA_DeleteOnClose); window->setAttribute(Qt::WA_DeleteOnClose);
QObject::connect(window, &QWidget::destroyed, [this, window] { QObject::connect(window, &QWidget::destroyed, [this, window] {
for (auto it = this->windows_.begin(); it != this->windows_.end(); for (auto it = this->windows_.begin(); it != this->windows_.end();
it++) { it++)
if (*it == window) { {
if (*it == window)
{
this->windows_.erase(it); this->windows_.erase(it);
break; break;
} }
@ -237,7 +244,8 @@ Window *WindowManager::windowAt(int index)
{ {
assertInGuiThread(); assertInGuiThread();
if (index < 0 || (size_t)index >= this->windows_.size()) { if (index < 0 || (size_t)index >= this->windows_.size())
{
return nullptr; return nullptr;
} }
log("getting window at bad index {}", index); log("getting window at bad index {}", index);
@ -263,7 +271,8 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
QJsonArray windows_arr = document.object().value("windows").toArray(); QJsonArray windows_arr = document.object().value("windows").toArray();
// "deserialize" // "deserialize"
for (QJsonValue window_val : windows_arr) { for (QJsonValue window_val : windows_arr)
{
QJsonObject window_obj = window_val.toObject(); QJsonObject window_obj = window_val.toObject();
// get type // get type
@ -271,13 +280,15 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
WindowType type = WindowType type =
type_val == "main" ? WindowType::Main : WindowType::Popup; type_val == "main" ? WindowType::Main : WindowType::Popup;
if (type == WindowType::Main && mainWindow_ != nullptr) { if (type == WindowType::Main && mainWindow_ != nullptr)
{
type = WindowType::Popup; type = WindowType::Popup;
} }
Window &window = createWindow(type); Window &window = createWindow(type);
if (type == WindowType::Main) { if (type == WindowType::Main)
{
mainWindow_ = &window; mainWindow_ = &window;
} }
@ -288,7 +299,8 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
int width = window_obj.value("width").toInt(-1); int width = window_obj.value("width").toInt(-1);
int height = window_obj.value("height").toInt(-1); int height = window_obj.value("height").toInt(-1);
if (x != -1 && y != -1 && width != -1 && height != -1) { if (x != -1 && y != -1 && width != -1 && height != -1)
{
// Have to offset x by one because qt moves the window 1px too // Have to offset x by one because qt moves the window 1px too
// far to the left // far to the left
window.setGeometry(x + 1, y, width, height); window.setGeometry(x + 1, y, width, height);
@ -297,19 +309,22 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
// load tabs // load tabs
QJsonArray tabs = window_obj.value("tabs").toArray(); QJsonArray tabs = window_obj.value("tabs").toArray();
for (QJsonValue tab_val : tabs) { for (QJsonValue tab_val : tabs)
{
SplitContainer *page = window.getNotebook().addPage(false); SplitContainer *page = window.getNotebook().addPage(false);
QJsonObject tab_obj = tab_val.toObject(); QJsonObject tab_obj = tab_val.toObject();
// set custom title // set custom title
QJsonValue title_val = tab_obj.value("title"); QJsonValue title_val = tab_obj.value("title");
if (title_val.isString()) { if (title_val.isString())
{
page->getTab()->setCustomTitle(title_val.toString()); page->getTab()->setCustomTitle(title_val.toString());
} }
// selected // selected
if (tab_obj.value("selected").toBool(false)) { if (tab_obj.value("selected").toBool(false))
{
window.getNotebook().select(page); window.getNotebook().select(page);
} }
@ -320,7 +335,8 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
// load splits // load splits
QJsonObject splitRoot = tab_obj.value("splits2").toObject(); QJsonObject splitRoot = tab_obj.value("splits2").toObject();
if (!splitRoot.isEmpty()) { if (!splitRoot.isEmpty())
{
page->decodeFromJson(splitRoot); page->decodeFromJson(splitRoot);
continue; continue;
@ -328,8 +344,10 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
// fallback load splits (old) // fallback load splits (old)
int colNr = 0; int colNr = 0;
for (QJsonValue column_val : tab_obj.value("splits").toArray()) { for (QJsonValue column_val : tab_obj.value("splits").toArray())
for (QJsonValue split_val : column_val.toArray()) { {
for (QJsonValue split_val : column_val.toArray())
{
Split *split = new Split(page); Split *split = new Split(page);
QJsonObject split_obj = split_val.toObject(); QJsonObject split_obj = split_val.toObject();
@ -342,7 +360,8 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
} }
} }
if (mainWindow_ == nullptr) { if (mainWindow_ == nullptr)
{
mainWindow_ = &createWindow(WindowType::Main); mainWindow_ = &createWindow(WindowType::Main);
mainWindow_->getNotebook().addPage(true); mainWindow_->getNotebook().addPage(true);
} }
@ -376,11 +395,13 @@ void WindowManager::save()
// "serialize" // "serialize"
QJsonArray window_arr; QJsonArray window_arr;
for (Window *window : this->windows_) { for (Window *window : this->windows_)
{
QJsonObject window_obj; QJsonObject window_obj;
// window type // window type
switch (window->getType()) { switch (window->getType())
{
case WindowType::Main: case WindowType::Main:
window_obj.insert("type", "main"); window_obj.insert("type", "main");
break; break;
@ -402,19 +423,22 @@ void WindowManager::save()
QJsonArray tabs_arr; QJsonArray tabs_arr;
for (int tab_i = 0; tab_i < window->getNotebook().getPageCount(); for (int tab_i = 0; tab_i < window->getNotebook().getPageCount();
tab_i++) { tab_i++)
{
QJsonObject tab_obj; QJsonObject tab_obj;
SplitContainer *tab = dynamic_cast<SplitContainer *>( SplitContainer *tab = dynamic_cast<SplitContainer *>(
window->getNotebook().getPageAt(tab_i)); window->getNotebook().getPageAt(tab_i));
assert(tab != nullptr); assert(tab != nullptr);
// custom tab title // custom tab title
if (tab->getTab()->hasCustomTitle()) { if (tab->getTab()->hasCustomTitle())
{
tab_obj.insert("title", tab->getTab()->getCustomTitle()); tab_obj.insert("title", tab->getTab()->getCustomTitle());
} }
// selected // selected
if (window->getNotebook().getSelectedPage() == tab) { if (window->getNotebook().getSelectedPage() == tab)
{
tab_obj.insert("selected", true); tab_obj.insert("selected", true);
} }
@ -459,7 +483,8 @@ void WindowManager::save()
void WindowManager::sendAlert() void WindowManager::sendAlert()
{ {
int flashDuration = 2500; int flashDuration = 2500;
if (getSettings()->longAlerts) { if (getSettings()->longAlerts)
{
flashDuration = 0; flashDuration = 0;
} }
QApplication::alert(this->getMainWindow().window(), flashDuration); QApplication::alert(this->getMainWindow().window(), flashDuration);
@ -474,29 +499,35 @@ void WindowManager::queueSave()
void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj) void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
{ {
switch (node->getType()) { switch (node->getType())
case SplitNode::_Split: { {
case SplitNode::_Split:
{
obj.insert("type", "split"); obj.insert("type", "split");
QJsonObject split; QJsonObject split;
encodeChannel(node->getSplit()->getIndirectChannel(), split); encodeChannel(node->getSplit()->getIndirectChannel(), split);
obj.insert("data", split); obj.insert("data", split);
obj.insert("flexh", node->getHorizontalFlex()); obj.insert("flexh", node->getHorizontalFlex());
obj.insert("flexv", node->getVerticalFlex()); obj.insert("flexv", node->getVerticalFlex());
} break; }
break;
case SplitNode::HorizontalContainer: case SplitNode::HorizontalContainer:
case SplitNode::VerticalContainer: { case SplitNode::VerticalContainer:
{
obj.insert("type", node->getType() == SplitNode::HorizontalContainer obj.insert("type", node->getType() == SplitNode::HorizontalContainer
? "horizontal" ? "horizontal"
: "vertical"); : "vertical");
QJsonArray items_arr; QJsonArray items_arr;
for (const std::unique_ptr<SplitNode> &n : node->getChildren()) { for (const std::unique_ptr<SplitNode> &n : node->getChildren())
{
QJsonObject subObj; QJsonObject subObj;
this->encodeNodeRecusively(n.get(), subObj); this->encodeNodeRecusively(n.get(), subObj);
items_arr.append(subObj); items_arr.append(subObj);
} }
obj.insert("items", items_arr); obj.insert("items", items_arr);
} break; }
break;
} }
} }
@ -504,20 +535,29 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
{ {
assertInGuiThread(); assertInGuiThread();
switch (channel.getType()) { switch (channel.getType())
case Channel::Type::Twitch: { {
case Channel::Type::Twitch:
{
obj.insert("type", "twitch"); obj.insert("type", "twitch");
obj.insert("name", channel.get()->getName()); obj.insert("name", channel.get()->getName());
} break; }
case Channel::Type::TwitchMentions: { break;
case Channel::Type::TwitchMentions:
{
obj.insert("type", "mentions"); obj.insert("type", "mentions");
} break; }
case Channel::Type::TwitchWatching: { break;
case Channel::Type::TwitchWatching:
{
obj.insert("type", "watching"); obj.insert("type", "watching");
} break; }
case Channel::Type::TwitchWhispers: { break;
case Channel::Type::TwitchWhispers:
{
obj.insert("type", "whispers"); obj.insert("type", "whispers");
} break; }
break;
} }
} }
@ -528,14 +568,21 @@ IndirectChannel WindowManager::decodeChannel(const QJsonObject &obj)
auto app = getApp(); auto app = getApp();
QString type = obj.value("type").toString(); QString type = obj.value("type").toString();
if (type == "twitch") { if (type == "twitch")
{
return app->twitch.server->getOrAddChannel( return app->twitch.server->getOrAddChannel(
obj.value("name").toString()); obj.value("name").toString());
} else if (type == "mentions") { }
else if (type == "mentions")
{
return app->twitch.server->mentionsChannel; return app->twitch.server->mentionsChannel;
} else if (type == "watching") { }
else if (type == "watching")
{
return app->twitch.server->watchingChannel; return app->twitch.server->watchingChannel;
} else if (type == "whispers") { }
else if (type == "whispers")
{
return app->twitch.server->whispersChannel; return app->twitch.server->whispersChannel;
} }
@ -546,7 +593,8 @@ void WindowManager::closeAll()
{ {
assertInGuiThread(); assertInGuiThread();
for (Window *window : windows_) { for (Window *window : windows_)
{
window->close(); window->close();
} }
} }
@ -573,7 +621,8 @@ float WindowManager::getUiScaleValue()
float WindowManager::getUiScaleValue(int scale) float WindowManager::getUiScaleValue(int scale)
{ {
switch (clampUiScale(scale)) { switch (clampUiScale(scale))
{
case -5: case -5:
return 0.5f; return 0.5f;
case -4: case -4:

View file

@ -16,11 +16,16 @@ QByteArray endline("\n");
LoggingChannel::LoggingChannel(const QString &_channelName) LoggingChannel::LoggingChannel(const QString &_channelName)
: channelName(_channelName) : channelName(_channelName)
{ {
if (this->channelName.startsWith("/whispers")) { if (this->channelName.startsWith("/whispers"))
{
this->subDirectory = "Whispers"; this->subDirectory = "Whispers";
} else if (channelName.startsWith("/mentions")) { }
else if (channelName.startsWith("/mentions"))
{
this->subDirectory = "Mentions"; this->subDirectory = "Mentions";
} else { }
else
{
this->subDirectory = this->subDirectory =
QStringLiteral("Channels") + QDir::separator() + channelName; QStringLiteral("Channels") + QDir::separator() + channelName;
} }
@ -33,9 +38,12 @@ LoggingChannel::LoggingChannel(const QString &_channelName)
getSettings()->logPath.connect([this](const QString &logPath, auto) { getSettings()->logPath.connect([this](const QString &logPath, auto) {
auto app = getApp(); auto app = getApp();
if (logPath.isEmpty()) { if (logPath.isEmpty())
{
this->baseDirectory = getPaths()->messageLogDirectory; this->baseDirectory = getPaths()->messageLogDirectory;
} else { }
else
{
this->baseDirectory = logPath; this->baseDirectory = logPath;
} }
@ -54,7 +62,8 @@ void LoggingChannel::openLogFile()
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
this->dateString = this->generateDateString(now); this->dateString = this->generateDateString(now);
if (this->fileHandle.isOpen()) { if (this->fileHandle.isOpen())
{
this->fileHandle.flush(); this->fileHandle.flush();
this->fileHandle.close(); this->fileHandle.close();
} }
@ -64,7 +73,8 @@ void LoggingChannel::openLogFile()
QString directory = QString directory =
this->baseDirectory + QDir::separator() + this->subDirectory; this->baseDirectory + QDir::separator() + this->subDirectory;
if (!QDir().mkpath(directory)) { if (!QDir().mkpath(directory))
{
log("Unable to create logging path"); log("Unable to create logging path");
return; return;
} }
@ -84,7 +94,8 @@ void LoggingChannel::addMessage(MessagePtr message)
QDateTime now = QDateTime::currentDateTime(); QDateTime now = QDateTime::currentDateTime();
QString messageDateString = this->generateDateString(now); QString messageDateString = this->generateDateString(now);
if (messageDateString != this->dateString) { if (messageDateString != this->dateString)
{
this->dateString = messageDateString; this->dateString = messageDateString;
this->openLogFile(); this->openLogFile();
} }

View file

@ -21,7 +21,8 @@ public:
QMutexLocker lock(&this->mutex_); QMutexLocker lock(&this->mutex_);
auto a = this->data_.find(name); auto a = this->data_.find(name);
if (a == this->data_.end()) { if (a == this->data_.end())
{
return false; return false;
} }
@ -35,7 +36,8 @@ public:
QMutexLocker lock(&this->mutex_); QMutexLocker lock(&this->mutex_);
auto a = this->data_.find(name); auto a = this->data_.find(name);
if (a == this->data_.end()) { if (a == this->data_.end())
{
TValue value = addLambda(); TValue value = addLambda();
this->data_.insert(name, value); this->data_.insert(name, value);
return value; return value;
@ -72,7 +74,8 @@ public:
QMapIterator<TKey, TValue> it(this->data_); QMapIterator<TKey, TValue> it(this->data_);
while (it.hasNext()) { while (it.hasNext())
{
it.next(); it.next();
func(it.key(), it.value()); func(it.key(), it.value());
} }
@ -84,7 +87,8 @@ public:
QMutableMapIterator<TKey, TValue> it(this->data_); QMutableMapIterator<TKey, TValue> it(this->data_);
while (it.hasNext()) { while (it.hasNext())
{
it.next(); it.next();
func(it.key(), it.value()); func(it.key(), it.value());
} }

View file

@ -18,9 +18,12 @@ public:
auto counts = counts_.access(); auto counts = counts_.access();
auto it = counts->find(name); auto it = counts->find(name);
if (it == counts->end()) { if (it == counts->end())
{
counts->insert(name, 1); counts->insert(name, 1);
} else { }
else
{
reinterpret_cast<int64_t &>(it.value())++; reinterpret_cast<int64_t &>(it.value())++;
} }
} }
@ -30,9 +33,12 @@ public:
auto counts = counts_.access(); auto counts = counts_.access();
auto it = counts->find(name); auto it = counts->find(name);
if (it == counts->end()) { if (it == counts->end())
{
counts->insert(name, -1); counts->insert(name, -1);
} else { }
else
{
reinterpret_cast<int64_t &>(it.value())--; reinterpret_cast<int64_t &>(it.value())--;
} }
} }
@ -42,7 +48,8 @@ public:
auto counts = counts_.access(); auto counts = counts_.access();
QString text; QString text;
for (auto it = counts->begin(); it != counts->end(); it++) { for (auto it = counts->begin(); it != counts->end(); it++)
{
text += it.key() + ": " + QString::number(it.value()) + "\n"; text += it.key() + ": " + QString::number(it.value()) + "\n";
} }
return text; return text;

View file

@ -19,23 +19,30 @@ QString formatTime(int totalSeconds)
int timeoutHours = timeoutMinutes / 60; int timeoutHours = timeoutMinutes / 60;
int hours = timeoutHours % 24; int hours = timeoutHours % 24;
int days = timeoutHours / 24; int days = timeoutHours / 24;
if (days > 0) { if (days > 0)
{
appendDuration(days, 'd', res); appendDuration(days, 'd', res);
} }
if (hours > 0) { if (hours > 0)
if (!res.isEmpty()) { {
if (!res.isEmpty())
{
res.append(" "); res.append(" ");
} }
appendDuration(hours, 'h', res); appendDuration(hours, 'h', res);
} }
if (minutes > 0) { if (minutes > 0)
if (!res.isEmpty()) { {
if (!res.isEmpty())
{
res.append(" "); res.append(" ");
} }
appendDuration(minutes, 'm', res); appendDuration(minutes, 'm', res);
} }
if (seconds > 0) { if (seconds > 0)
if (!res.isEmpty()) { {
if (!res.isEmpty())
{
res.append(" "); res.append(" ");
} }
appendDuration(seconds, 's', res); appendDuration(seconds, 's', res);

View file

@ -19,7 +19,8 @@ static QString CreateUUID()
static QString createLink(const QString &url, bool file = false) static QString createLink(const QString &url, bool file = false)
{ {
if (file) { if (file)
{
return QString("<a href=\"file:///" + url + return QString("<a href=\"file:///" + url +
"\"><span style=\"color: white;\">" + url + "\"><span style=\"color: white;\">" + url +
"</span></a>"); "</span></a>");
@ -29,9 +30,11 @@ static QString createLink(const QString &url, bool file = false)
url + "</span></a>"); url + "</span></a>");
} }
static QString createNamedLink(const QString &url, const QString &name, bool file = false) static QString createNamedLink(const QString &url, const QString &name,
bool file = false)
{ {
if (file) { if (file)
{
return QString("<a href=\"file:///" + url + return QString("<a href=\"file:///" + url +
"\"><span style=\"color: white;\">" + name + "\"><span style=\"color: white;\">" + name +
"</span></a>"); "</span></a>");
@ -43,7 +46,8 @@ static QString createNamedLink(const QString &url, const QString &name, bool fil
static QString shortenString(const QString &str, unsigned maxWidth = 50) static QString shortenString(const QString &str, unsigned maxWidth = 50)
{ {
if (str.size() <= maxWidth) { if (str.size() <= maxWidth)
{
return str; return str;
} }

View file

@ -21,7 +21,8 @@ namespace {
// transform into regex and replacement string // transform into regex and replacement string
std::vector<std::pair<QRegularExpression, QString>> replacers; std::vector<std::pair<QRegularExpression, QString>> replacers;
for (const auto &switch_ : switches) { for (const auto &switch_ : switches)
{
replacers.emplace_back( replacers.emplace_back(
QRegularExpression("(" + switch_.first + "\\.exe\"?).*", QRegularExpression("(" + switch_.first + "\\.exe\"?).*",
QRegularExpression::CaseInsensitiveOption), QRegularExpression::CaseInsensitiveOption),
@ -29,8 +30,10 @@ namespace {
} }
// try to find matching regex and apply it // try to find matching regex and apply it
for (const auto &replacement : replacers) { for (const auto &replacement : replacers)
if (replacement.first.match(command).hasMatch()) { {
if (replacement.first.match(command).hasMatch())
{
command.replace(replacement.first, replacement.second); command.replace(replacement.first, replacement.second);
return command; return command;
} }
@ -57,13 +60,15 @@ namespace {
QSettings::NativeFormat) QSettings::NativeFormat)
.value("Default") .value("Default")
.toString(); .toString();
if (command.isNull()) return QString(); if (command.isNull())
return QString();
log(command); log(command);
// inject switch to enable private browsing // inject switch to enable private browsing
command = injectPrivateSwitch(command); command = injectPrivateSwitch(command);
if (command.isNull()) return QString(); if (command.isNull())
return QString();
// link // link
command += " " + link; command += " " + link;

View file

@ -20,13 +20,18 @@ void initUpdateButton(Button &button,
dialog->raise(); dialog->raise();
dialog->buttonClicked.connect([&button](auto buttonType) { dialog->buttonClicked.connect([&button](auto buttonType) {
switch (buttonType) { switch (buttonType)
case UpdateDialog::Dismiss: { {
case UpdateDialog::Dismiss:
{
button.hide(); button.hide();
} break; }
case UpdateDialog::Install: { break;
case UpdateDialog::Install:
{
Updates::getInstance().installUpdates(); Updates::getInstance().installUpdates();
} break; }
break;
} }
}); });

View file

@ -11,34 +11,49 @@ inline QString parseTagString(const QString &input)
auto length = output.length(); auto length = output.length();
for (int i = 0; i < length - 1; i++) { for (int i = 0; i < length - 1; i++)
if (output[i] == '\\') { {
if (output[i] == '\\')
{
QChar c = output[i + 1]; QChar c = output[i + 1];
switch (c.cell()) { switch (c.cell())
case 'n': { {
case 'n':
{
output.replace(i, 2, '\n'); output.replace(i, 2, '\n');
} break; }
break;
case 'r': { case 'r':
{
output.replace(i, 2, '\r'); output.replace(i, 2, '\r');
} break; }
break;
case 's': { case 's':
{
output.replace(i, 2, ' '); output.replace(i, 2, ' ');
} break; }
break;
case '\\': { case '\\':
{
output.replace(i, 2, '\\'); output.replace(i, 2, '\\');
} break; }
break;
case ':': { case ':':
{
output.replace(i, 2, ';'); output.replace(i, 2, ';');
} break; }
break;
default: { default:
{
output.remove(i, 1); output.remove(i, 1);
} break; }
break;
} }
i++; i++;

View file

@ -153,7 +153,8 @@ private:
int>::type = 0> int>::type = 0>
QLayout *getOrCreateLayout() QLayout *getOrCreateLayout()
{ {
if (!this->item_->layout()) { if (!this->item_->layout())
{
this->item_->setLayout(new QHBoxLayout()); this->item_->setLayout(new QHBoxLayout());
} }

View file

@ -13,8 +13,10 @@ T *makeLayout(std::initializer_list<LayoutItem> items)
{ {
auto t = new T; auto t = new T;
for (auto &item : items) { for (auto &item : items)
switch (item.which()) { {
switch (item.which())
{
case 0: case 0:
t->addItem(new QWidgetItem(boost::get<QWidget *>(item))); t->addItem(new QWidgetItem(boost::get<QWidget *>(item)));
break; break;

View file

@ -19,19 +19,25 @@ namespace Settings {
struct Deserialize<QString> { struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr) static QString get(const rapidjson::Value &value, bool *error = nullptr)
{ {
if (!value.IsString()) { if (!value.IsString())
{
PAJLADA_REPORT_ERROR(error) PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION( PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is not a string"); "Deserialized rapidjson::Value is not a string");
return QString{}; return QString{};
} }
try { try
{
return QString::fromUtf8(value.GetString(), return QString::fromUtf8(value.GetString(),
value.GetStringLength()); value.GetStringLength());
} catch (const std::exception &) { }
catch (const std::exception &)
{
// int x = 5; // int x = 5;
} catch (...) { }
catch (...)
{
// int y = 5; // int y = 5;
} }

View file

@ -72,15 +72,18 @@ namespace rj {
template <typename Type> template <typename Type>
bool getSafe(const rapidjson::Value &obj, const char *key, Type &out) bool getSafe(const rapidjson::Value &obj, const char *key, Type &out)
{ {
if (!obj.IsObject()) { if (!obj.IsObject())
{
return false; return false;
} }
if (!obj.HasMember(key)) { if (!obj.HasMember(key))
{
return false; return false;
} }
if (obj.IsNull()) { if (obj.IsNull())
{
return false; return false;
} }

View file

@ -38,9 +38,12 @@ namespace {
{ {
auto app = getApp(); auto app = getApp();
if (getSettings()->streamlinkUseCustomPath) { if (getSettings()->streamlinkUseCustomPath)
{
return getSettings()->streamlinkPath + "/" + getBinaryName(); return getSettings()->streamlinkPath + "/" + getBinaryName();
} else { }
else
{
return getBinaryName(); return getBinaryName();
} }
} }
@ -49,7 +52,8 @@ namespace {
{ {
QFileInfo fileinfo(path); QFileInfo fileinfo(path);
if (!fileinfo.exists()) { if (!fileinfo.exists())
{
return false; return false;
// throw Exception(fS("Streamlink path ({}) is invalid, file does // throw Exception(fS("Streamlink path ({}) is invalid, file does
// not exist", path)); // not exist", path));
@ -63,13 +67,16 @@ namespace {
static QErrorMessage *msg = new QErrorMessage; static QErrorMessage *msg = new QErrorMessage;
auto app = getApp(); auto app = getApp();
if (getSettings()->streamlinkUseCustomPath) { if (getSettings()->streamlinkUseCustomPath)
{
msg->showMessage( msg->showMessage(
"Unable to find Streamlink executable\nMake sure your custom " "Unable to find Streamlink executable\nMake sure your custom "
"path " "path "
"is pointing " "is pointing "
"to the DIRECTORY where the streamlink executable is located"); "to the DIRECTORY where the streamlink executable is located");
} else { }
else
{
msg->showMessage( msg->showMessage(
"Unable to find Streamlink executable.\nIf you have Streamlink " "Unable to find Streamlink executable.\nIf you have Streamlink "
"installed, you might need to enable the custom path option"); "installed, you might need to enable the custom path option");
@ -82,9 +89,12 @@ namespace {
p->setProgram(getStreamlinkProgram()); p->setProgram(getStreamlinkProgram());
QObject::connect(p, &QProcess::errorOccurred, [=](auto err) { QObject::connect(p, &QProcess::errorOccurred, [=](auto err) {
if (err == QProcess::FailedToStart) { if (err == QProcess::FailedToStart)
{
showStreamlinkNotFoundError(); showStreamlinkNotFoundError();
} else { }
else
{
log("Error occured {}", err); log("Error occured {}", err);
} }
@ -110,20 +120,24 @@ void getStreamQualities(const QString &channelURL,
QObject::connect( QObject::connect(
p, static_cast<void (QProcess::*)(int)>(&QProcess::finished), p, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
[=](int res) { [=](int res) {
if (res != 0) { if (res != 0)
{
log("Got error code {}", res); log("Got error code {}", res);
// return; // return;
} }
QString lastLine = QString(p->readAllStandardOutput()); QString lastLine = QString(p->readAllStandardOutput());
lastLine = lastLine.trimmed().split('\n').last().trimmed(); lastLine = lastLine.trimmed().split('\n').last().trimmed();
if (lastLine.startsWith("Available streams: ")) { if (lastLine.startsWith("Available streams: "))
{
QStringList options; QStringList options;
QStringList split = QStringList split =
lastLine.right(lastLine.length() - 19).split(", "); lastLine.right(lastLine.length() - 19).split(", ");
for (int i = split.length() - 1; i >= 0; i--) { for (int i = split.length() - 1; i >= 0; i--)
{
QString option = split.at(i); QString option = split.at(i);
if (option == "best)") { if (option == "best)")
{
// As it turns out, sometimes, one quality option can // As it turns out, sometimes, one quality option can
// be the best and worst quality at the same time. // be the best and worst quality at the same time.
// Since we start loop from the end, we can check // Since we start loop from the end, we can check
@ -131,11 +145,17 @@ void getStreamQualities(const QString &channelURL,
option = split.at(--i); option = split.at(--i);
// "900p60 (worst" // "900p60 (worst"
options << option.left(option.length() - 7); options << option.left(option.length() - 7);
} else if (option.endsWith(" (worst)")) { }
else if (option.endsWith(" (worst)"))
{
options << option.left(option.length() - 8); options << option.left(option.length() - 8);
} else if (option.endsWith(" (best)")) { }
else if (option.endsWith(" (best)"))
{
options << option.left(option.length() - 7); options << option.left(option.length() - 7);
} else { }
else
{
options << option; options << option;
} }
} }
@ -157,7 +177,8 @@ void openStreamlink(const QString &channelURL, const QString &quality,
QStringList arguments; QStringList arguments;
QString additionalOptions = getSettings()->streamlinkOpts.getValue(); QString additionalOptions = getSettings()->streamlinkOpts.getValue();
if (!additionalOptions.isEmpty()) { if (!additionalOptions.isEmpty())
{
arguments << getSettings()->streamlinkOpts; arguments << getSettings()->streamlinkOpts;
} }
@ -165,14 +186,16 @@ void openStreamlink(const QString &channelURL, const QString &quality,
arguments << channelURL; arguments << channelURL;
if (!quality.isEmpty()) { if (!quality.isEmpty())
{
arguments << quality; arguments << quality;
} }
bool res = QProcess::startDetached(getStreamlinkProgram() + " " + bool res = QProcess::startDetached(getStreamlinkProgram() + " " +
QString(arguments.join(' '))); QString(arguments.join(' ')));
if (!res) { if (!res)
{
showStreamlinkNotFoundError(); showStreamlinkNotFoundError();
} }
} }
@ -186,7 +209,8 @@ void openStreamlinkForChannel(const QString &channel)
QString preferredQuality = getSettings()->preferredQuality; QString preferredQuality = getSettings()->preferredQuality;
preferredQuality = preferredQuality.toLower(); preferredQuality = preferredQuality.toLower();
if (preferredQuality == "choose") { if (preferredQuality == "choose")
{
getStreamQualities(channelURL, [=](QStringList qualityOptions) { getStreamQualities(channelURL, [=](QStringList qualityOptions) {
QualityPopup::showDialog(channel, qualityOptions); QualityPopup::showDialog(channel, qualityOptions);
}); });
@ -201,21 +225,31 @@ void openStreamlinkForChannel(const QString &channel)
// Streamlink qualities to exclude // Streamlink qualities to exclude
QString exclude; QString exclude;
if (preferredQuality == "high") { if (preferredQuality == "high")
{
exclude = ">720p30"; exclude = ">720p30";
quality = "high,best"; quality = "high,best";
} else if (preferredQuality == "medium") { }
else if (preferredQuality == "medium")
{
exclude = ">540p30"; exclude = ">540p30";
quality = "medium,best"; quality = "medium,best";
} else if (preferredQuality == "low") { }
else if (preferredQuality == "low")
{
exclude = ">360p30"; exclude = ">360p30";
quality = "low,best"; quality = "low,best";
} else if (preferredQuality == "audio only") { }
else if (preferredQuality == "audio only")
{
quality = "audio,audio_only"; quality = "audio,audio_only";
} else { }
else
{
quality = "best"; quality = "best";
} }
if (!exclude.isEmpty()) { if (!exclude.isEmpty())
{
args << "--stream-sorting-excludes" << exclude; args << "--stream-sorting-excludes" << exclude;
} }

View file

@ -17,9 +17,11 @@ typedef HRESULT(CALLBACK *GetDpiForMonitor_)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
boost::optional<UINT> getWindowDpi(HWND hwnd) boost::optional<UINT> getWindowDpi(HWND hwnd)
{ {
static HINSTANCE shcore = LoadLibrary(L"Shcore.dll"); static HINSTANCE shcore = LoadLibrary(L"Shcore.dll");
if (shcore != nullptr) { if (shcore != nullptr)
{
if (auto getDpiForMonitor = if (auto getDpiForMonitor =
GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor"))) { GetDpiForMonitor_(GetProcAddress(shcore, "GetDpiForMonitor")))
{
HMONITOR monitor = HMONITOR monitor =
MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
@ -39,8 +41,11 @@ typedef HRESULT(CALLBACK *OleFlushClipboard_)();
void flushClipboard() void flushClipboard()
{ {
static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll"); static HINSTANCE ole32 = LoadLibrary(L"Ole32.dll");
if (ole32 != nullptr) { if (ole32 != nullptr)
if (auto oleFlushClipboard = OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard"))) { {
if (auto oleFlushClipboard =
OleFlushClipboard_(GetProcAddress(ole32, "OleFlushClipboard")))
{
oleFlushClipboard(); oleFlushClipboard();
} }
} }

View file

@ -10,7 +10,6 @@ namespace chatterino {
boost::optional<UINT> getWindowDpi(HWND hwnd); boost::optional<UINT> getWindowDpi(HWND hwnd);
void flushClipboard(); void flushClipboard();
} // namespace chatterino } // namespace chatterino
#endif #endif

View file

@ -5,17 +5,18 @@
namespace chatterino { namespace chatterino {
namespace util { namespace util {
template <typename Container, typename UnaryPredicate> template <typename Container, typename UnaryPredicate>
typename Container::iterator find_if(Container &container, UnaryPredicate pred) typename Container::iterator find_if(Container &container,
{ UnaryPredicate pred)
return std::find_if(container.begin(), container.end(), pred); {
} return std::find_if(container.begin(), container.end(), pred);
}
template <typename Container, typename UnaryPredicate> template <typename Container, typename UnaryPredicate>
bool any_of(Container &container, UnaryPredicate pred) bool any_of(Container &container, UnaryPredicate pred)
{ {
return std::any_of(container.begin(), container.end(), pred); return std::any_of(container.begin(), container.end(), pred);
} }
} // namespace util } // namespace util
} // namespace chatterino } // namespace chatterino

View file

@ -15,7 +15,8 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
this->addItem(ANONYMOUS_USERNAME_LABEL); this->addItem(ANONYMOUS_USERNAME_LABEL);
for (const auto &userName : app->accounts->twitch.getUsernames()) { for (const auto &userName : app->accounts->twitch.getUsernames())
{
this->addItem(userName); this->addItem(userName);
} }
@ -26,7 +27,8 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
this->addItem(ANONYMOUS_USERNAME_LABEL); this->addItem(ANONYMOUS_USERNAME_LABEL);
for (const auto &userName : app->accounts->twitch.getUsernames()) { for (const auto &userName : app->accounts->twitch.getUsernames())
{
this->addItem(userName); this->addItem(userName);
} }
@ -38,12 +40,16 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
this->refreshSelection(); this->refreshSelection();
QObject::connect(this, &QListWidget::clicked, [=] { QObject::connect(this, &QListWidget::clicked, [=] {
if (!this->selectedItems().isEmpty()) { if (!this->selectedItems().isEmpty())
{
QString newUsername = this->currentItem()->text(); QString newUsername = this->currentItem()->text();
if (newUsername.compare(ANONYMOUS_USERNAME_LABEL, if (newUsername.compare(ANONYMOUS_USERNAME_LABEL,
Qt::CaseInsensitive) == 0) { Qt::CaseInsensitive) == 0)
{
app->accounts->twitch.currentUsername = ""; app->accounts->twitch.currentUsername = "";
} else { }
else
{
app->accounts->twitch.currentUsername = app->accounts->twitch.currentUsername =
newUsername.toStdString(); newUsername.toStdString();
} }
@ -61,20 +67,25 @@ void AccountSwitchWidget::refreshSelection()
this->blockSignals(true); this->blockSignals(true);
// Select the currently logged in user // Select the currently logged in user
if (this->count() > 0) { if (this->count() > 0)
{
auto app = getApp(); auto app = getApp();
auto currentUser = app->accounts->twitch.getCurrent(); auto currentUser = app->accounts->twitch.getCurrent();
if (currentUser->isAnon()) { if (currentUser->isAnon())
{
this->setCurrentRow(0); this->setCurrentRow(0);
} else { }
else
{
const QString &currentUsername = currentUser->getUserName(); const QString &currentUsername = currentUser->getUserName();
for (int i = 0; i < this->count(); ++i) { for (int i = 0; i < this->count(); ++i)
{
QString itemText = this->item(i)->text(); QString itemText = this->item(i)->text();
if (itemText.compare(currentUsername, Qt::CaseInsensitive) == if (itemText.compare(currentUsername, Qt::CaseInsensitive) == 0)
0) { {
this->setCurrentRow(i); this->setCurrentRow(i);
break; break;
} }

View file

@ -37,8 +37,10 @@ AttachedWindow::AttachedWindow(void *_target, int _yOffset)
AttachedWindow::~AttachedWindow() AttachedWindow::~AttachedWindow()
{ {
for (auto it = items.begin(); it != items.end(); it++) { for (auto it = items.begin(); it != items.end(); it++)
if (it->window == this) { {
if (it->window == this)
{
items.erase(it); items.erase(it);
break; break;
} }
@ -50,8 +52,10 @@ AttachedWindow::~AttachedWindow()
AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args) AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
{ {
AttachedWindow *window = [&]() { AttachedWindow *window = [&]() {
for (Item &item : items) { for (Item &item : items)
if (item.hwnd == target) { {
if (item.hwnd == target)
{
return item.window; return item.window;
} }
} }
@ -64,26 +68,35 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
bool show = true; bool show = true;
QSize size = window->size(); QSize size = window->size();
if (args.height != -1) { if (args.height != -1)
if (args.height == 0) { {
if (args.height == 0)
{
window->hide(); window->hide();
show = false; show = false;
} else { }
else
{
window->height_ = args.height; window->height_ = args.height;
size.setHeight(args.height); size.setHeight(args.height);
} }
} }
if (args.width != -1) { if (args.width != -1)
if (args.width == 0) { {
if (args.width == 0)
{
window->hide(); window->hide();
show = false; show = false;
} else { }
else
{
window->width_ = args.width; window->width_ = args.width;
size.setWidth(args.width); size.setWidth(args.width);
} }
} }
if (show) { if (show)
{
window->updateWindowRect(window->target_); window->updateWindowRect(window->target_);
window->show(); window->show();
} }
@ -93,8 +106,10 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
void AttachedWindow::detach(const QString &winId) void AttachedWindow::detach(const QString &winId)
{ {
for (Item &item : items) { for (Item &item : items)
if (item.winId == winId) { {
if (item.winId == winId)
{
item.window->deleteLater(); item.window->deleteLater();
} }
} }
@ -113,7 +128,8 @@ void AttachedWindow::showEvent(QShowEvent *)
void AttachedWindow::attachToHwnd(void *_attachedPtr) void AttachedWindow::attachToHwnd(void *_attachedPtr)
{ {
#ifdef USEWINSDK #ifdef USEWINSDK
if (this->attached_) { if (this->attached_)
{
return; return;
} }
@ -125,7 +141,8 @@ void AttachedWindow::attachToHwnd(void *_attachedPtr)
QObject::connect(&this->timer_, &QTimer::timeout, [this, hwnd, attached] { QObject::connect(&this->timer_, &QTimer::timeout, [this, hwnd, attached] {
// check process id // check process id
if (!this->validProcessName_) { if (!this->validProcessName_)
{
DWORD processId; DWORD processId;
::GetWindowThreadProcessId(attached, &processId); ::GetWindowThreadProcessId(attached, &processId);
@ -139,7 +156,8 @@ void AttachedWindow::attachToHwnd(void *_attachedPtr)
QString::fromWCharArray(filename.get(), filenameLength); QString::fromWCharArray(filename.get(), filenameLength);
if (!qfilename.endsWith("chrome.exe") && if (!qfilename.endsWith("chrome.exe") &&
!qfilename.endsWith("firefox.exe")) { !qfilename.endsWith("firefox.exe"))
{
qDebug() << "NM Illegal caller" << qfilename; qDebug() << "NM Illegal caller" << qfilename;
this->timer_.stop(); this->timer_.stop();
this->deleteLater(); this->deleteLater();
@ -167,7 +185,8 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr)
RECT rect; RECT rect;
::GetWindowRect(attached, &rect); ::GetWindowRect(attached, &rect);
if (::GetLastError() != 0) { if (::GetLastError() != 0)
{
qDebug() << "NM GetLastError()" << ::GetLastError(); qDebug() << "NM GetLastError()" << ::GetLastError();
this->timer_.stop(); this->timer_.stop();
@ -182,7 +201,8 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr)
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
float scale = 1.f; float scale = 1.f;
if (auto dpi = getWindowDpi(attached)) { if (auto dpi = getWindowDpi(attached))
{
scale = dpi.get() / 96.f; scale = dpi.get() / 96.f;
// for (auto w : this->ui_.split->findChildren<BaseWidget *>()) { // for (auto w : this->ui_.split->findChildren<BaseWidget *>()) {
@ -191,12 +211,15 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr)
// this->ui_.split->setOverrideScale(scale); // this->ui_.split->setOverrideScale(scale);
} }
if (this->height_ == -1) { if (this->height_ == -1)
{
// ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + // ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top +
// this->yOffset_ - 8, // this->yOffset_ - 8,
// this->width_, rect.bottom - rect.top - this->yOffset_, // this->width_, rect.bottom - rect.top - this->yOffset_,
// false); // false);
} else { }
else
{
::MoveWindow(hwnd, // ::MoveWindow(hwnd, //
int(rect.right - this->width_ * scale - 8), // int(rect.right - this->width_ * scale - 8), //
int(rect.bottom - this->height_ * scale - 8), // int(rect.bottom - this->height_ * scale - 8), //

View file

@ -28,13 +28,15 @@ BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f)
float BaseWidget::getScale() const float BaseWidget::getScale() const
{ {
if (this->overrideScale_) { if (this->overrideScale_)
{
return this->overrideScale_.get(); return this->overrideScale_.get();
} }
BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window()); BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window());
if (baseWidget == nullptr) { if (baseWidget == nullptr)
{
return 1.f; return 1.f;
} }
@ -87,10 +89,12 @@ void BaseWidget::setScaleIndependantSize(QSize size)
{ {
this->scaleIndependantSize_ = size; this->scaleIndependantSize_ = size;
if (size.width() > 0) { if (size.width() > 0)
{
this->setFixedWidth((int)(size.width() * this->getScale())); this->setFixedWidth((int)(size.width() * this->getScale()));
} }
if (size.height() > 0) { if (size.height() > 0)
{
this->setFixedHeight((int)(size.height() * this->getScale())); this->setFixedHeight((int)(size.height() * this->getScale()));
} }
} }
@ -109,16 +113,21 @@ void BaseWidget::setScaleIndependantHeight(int value)
void BaseWidget::childEvent(QChildEvent *event) void BaseWidget::childEvent(QChildEvent *event)
{ {
if (event->added()) { if (event->added())
{
BaseWidget *widget = dynamic_cast<BaseWidget *>(event->child()); BaseWidget *widget = dynamic_cast<BaseWidget *>(event->child());
if (widget != nullptr) { if (widget != nullptr)
{
this->widgets_.push_back(widget); this->widgets_.push_back(widget);
} }
} else if (event->removed()) { }
for (auto it = this->widgets_.begin(); it != this->widgets_.end(); else if (event->removed())
it++) { {
if (*it == event->child()) { for (auto it = this->widgets_.begin(); it != this->widgets_.end(); it++)
{
if (*it == event->child())
{
this->widgets_.erase(it); this->widgets_.erase(it);
break; break;
} }

Some files were not shown because too many files have changed in this diff Show more