mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
added brace wrapping after if and for
This commit is contained in:
parent
c6e1ec3c71
commit
e259b9e39f
138 changed files with 4738 additions and 2237 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Resources2 : public Singleton {
|
class Resources2 : public Singleton
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Resources2();
|
Resources2();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace chatterino {
|
||||||
|
|
||||||
void NetworkTimer::start()
|
void NetworkTimer::start()
|
||||||
{
|
{
|
||||||
if (this->timeoutMS_ <= 0) {
|
if (this->timeoutMS_ <= 0)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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, "");
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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://");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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:
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ®ex = phrase.getRegex();
|
const auto ®ex = 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}},
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) /
|
||||||
|
|
|
@ -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 "
|
||||||
|
|
|
@ -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); });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ¤tUsername = currentUser->getUserName();
|
const QString ¤tUsername = 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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), //
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue