mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
refactor: some Application & style things (#5561)
This commit is contained in:
parent
ac88730563
commit
627c735524
|
@ -73,3 +73,6 @@ CheckOptions:
|
||||||
|
|
||||||
- key: misc-const-correctness.AnalyzeValues
|
- key: misc-const-correctness.AnalyzeValues
|
||||||
value: false
|
value: false
|
||||||
|
|
||||||
|
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
|
||||||
|
value: true
|
||||||
|
|
|
@ -20,6 +20,7 @@ class BaseApplication : public EmptyApplication
|
||||||
public:
|
public:
|
||||||
BaseApplication()
|
BaseApplication()
|
||||||
: settings(this->args, this->settingsDir.path())
|
: settings(this->args, this->settingsDir.path())
|
||||||
|
, updates(this->paths_, this->settings)
|
||||||
, theme(this->paths_)
|
, theme(this->paths_)
|
||||||
, fonts(this->settings)
|
, fonts(this->settings)
|
||||||
{
|
{
|
||||||
|
@ -28,11 +29,17 @@ public:
|
||||||
explicit BaseApplication(const QString &settingsData)
|
explicit BaseApplication(const QString &settingsData)
|
||||||
: EmptyApplication(settingsData)
|
: EmptyApplication(settingsData)
|
||||||
, settings(this->args, this->settingsDir.path())
|
, settings(this->args, this->settingsDir.path())
|
||||||
|
, updates(this->paths_, this->settings)
|
||||||
, theme(this->paths_)
|
, theme(this->paths_)
|
||||||
, fonts(this->settings)
|
, fonts(this->settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Updates &getUpdates() override
|
||||||
|
{
|
||||||
|
return this->updates;
|
||||||
|
}
|
||||||
|
|
||||||
IStreamerMode *getStreamerMode() override
|
IStreamerMode *getStreamerMode() override
|
||||||
{
|
{
|
||||||
return &this->streamerMode;
|
return &this->streamerMode;
|
||||||
|
@ -60,6 +67,7 @@ public:
|
||||||
|
|
||||||
Args args;
|
Args args;
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
Updates updates;
|
||||||
DisabledStreamerMode streamerMode;
|
DisabledStreamerMode streamerMode;
|
||||||
Theme theme;
|
Theme theme;
|
||||||
Fonts fonts;
|
Fonts fonts;
|
||||||
|
|
|
@ -12,13 +12,9 @@ namespace chatterino::mock {
|
||||||
class EmptyApplication : public IApplication
|
class EmptyApplication : public IApplication
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
EmptyApplication()
|
EmptyApplication() = default;
|
||||||
: updates_(this->paths_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit EmptyApplication(const QString &settingsData)
|
explicit EmptyApplication(const QString &settingsData)
|
||||||
: EmptyApplication()
|
|
||||||
{
|
{
|
||||||
QFile settingsFile(this->settingsDir.filePath("settings.json"));
|
QFile settingsFile(this->settingsDir.filePath("settings.json"));
|
||||||
settingsFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
settingsFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
|
@ -212,11 +208,6 @@ public:
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Updates &getUpdates() override
|
|
||||||
{
|
|
||||||
return this->updates_;
|
|
||||||
}
|
|
||||||
|
|
||||||
BttvEmotes *getBttvEmotes() override
|
BttvEmotes *getBttvEmotes() override
|
||||||
{
|
{
|
||||||
assert(false && "EmptyApplication::getBttvEmotes was called without "
|
assert(false && "EmptyApplication::getBttvEmotes was called without "
|
||||||
|
@ -269,7 +260,6 @@ public:
|
||||||
QTemporaryDir settingsDir;
|
QTemporaryDir settingsDir;
|
||||||
Paths paths_;
|
Paths paths_;
|
||||||
Args args_;
|
Args args_;
|
||||||
Updates updates_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino::mock
|
} // namespace chatterino::mock
|
||||||
|
|
|
@ -61,10 +61,9 @@
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
#include <QApplication>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
@ -128,8 +127,6 @@ IApplication *INSTANCE = nullptr;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
static std::atomic<bool> isAppInitialized{false};
|
|
||||||
|
|
||||||
IApplication::IApplication()
|
IApplication::IApplication()
|
||||||
{
|
{
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
|
@ -194,8 +191,7 @@ Application::~Application()
|
||||||
|
|
||||||
void Application::initialize(Settings &settings, const Paths &paths)
|
void Application::initialize(Settings &settings, const Paths &paths)
|
||||||
{
|
{
|
||||||
assert(isAppInitialized == false);
|
assert(!this->initialized);
|
||||||
isAppInitialized = true;
|
|
||||||
|
|
||||||
// Show changelog
|
// Show changelog
|
||||||
if (!this->args_.isFramelessEmbed &&
|
if (!this->args_.isFramelessEmbed &&
|
||||||
|
@ -271,17 +267,19 @@ void Application::initialize(Settings &settings, const Paths &paths)
|
||||||
{
|
{
|
||||||
this->initNm(paths);
|
this->initNm(paths);
|
||||||
}
|
}
|
||||||
this->initPubSub();
|
this->twitchPubSub->initialize();
|
||||||
|
|
||||||
this->initBttvLiveUpdates();
|
this->initBttvLiveUpdates();
|
||||||
this->initSeventvEventAPI();
|
this->initSeventvEventAPI();
|
||||||
|
|
||||||
this->streamerMode->start();
|
this->streamerMode->start();
|
||||||
|
|
||||||
|
this->initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Application::run(QApplication &qtApp)
|
int Application::run()
|
||||||
{
|
{
|
||||||
assert(isAppInitialized);
|
assert(this->initialized);
|
||||||
|
|
||||||
this->twitch->connect();
|
this->twitch->connect();
|
||||||
|
|
||||||
|
@ -290,44 +288,23 @@ int Application::run(QApplication &qtApp)
|
||||||
this->windows->getMainWindow().show();
|
this->windows->getMainWindow().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSettings()->betaUpdates.connect(
|
|
||||||
[this] {
|
|
||||||
this->updates.checkForUpdates();
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
|
|
||||||
getSettings()->enableBTTVGlobalEmotes.connect(
|
|
||||||
[this] {
|
|
||||||
this->bttvEmotes->loadEmotes();
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
getSettings()->enableBTTVChannelEmotes.connect(
|
getSettings()->enableBTTVChannelEmotes.connect(
|
||||||
[this] {
|
[this] {
|
||||||
this->twitch->reloadAllBTTVChannelEmotes();
|
this->twitch->reloadAllBTTVChannelEmotes();
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
getSettings()->enableFFZGlobalEmotes.connect(
|
|
||||||
[this] {
|
|
||||||
this->ffzEmotes->loadEmotes();
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
getSettings()->enableFFZChannelEmotes.connect(
|
getSettings()->enableFFZChannelEmotes.connect(
|
||||||
[this] {
|
[this] {
|
||||||
this->twitch->reloadAllFFZChannelEmotes();
|
this->twitch->reloadAllFFZChannelEmotes();
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
getSettings()->enableSevenTVGlobalEmotes.connect(
|
|
||||||
[this] {
|
|
||||||
this->seventvEmotes->loadGlobalEmotes();
|
|
||||||
},
|
|
||||||
false);
|
|
||||||
getSettings()->enableSevenTVChannelEmotes.connect(
|
getSettings()->enableSevenTVChannelEmotes.connect(
|
||||||
[this] {
|
[this] {
|
||||||
this->twitch->reloadAllSevenTVChannelEmotes();
|
this->twitch->reloadAllSevenTVChannelEmotes();
|
||||||
},
|
},
|
||||||
false);
|
false);
|
||||||
|
|
||||||
return qtApp.exec();
|
return QApplication::exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
Theme *Application::getThemes()
|
Theme *Application::getThemes()
|
||||||
|
@ -597,455 +574,6 @@ void Application::initNm(const Paths &paths)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::initPubSub()
|
|
||||||
{
|
|
||||||
// We can safely ignore these signal connections since the twitch object will always
|
|
||||||
// be destroyed before the Application
|
|
||||||
std::ignore = this->twitchPubSub->moderation.chatCleared.connect(
|
|
||||||
[this](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text =
|
|
||||||
QString("%1 cleared the chat.").arg(action.source.login);
|
|
||||||
|
|
||||||
postToThread([chan, text] {
|
|
||||||
chan->addSystemMessage(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.modeChanged.connect(
|
|
||||||
[this](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text =
|
|
||||||
QString("%1 turned %2 %3 mode.")
|
|
||||||
.arg(action.source.login)
|
|
||||||
.arg(action.state == ModeChangedAction::State::On ? "on"
|
|
||||||
: "off")
|
|
||||||
.arg(action.getModeName());
|
|
||||||
|
|
||||||
if (action.duration > 0)
|
|
||||||
{
|
|
||||||
text += QString(" (%1 seconds)").arg(action.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([chan, text] {
|
|
||||||
chan->addSystemMessage(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.moderationStateChanged.connect(
|
|
||||||
[this](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text;
|
|
||||||
|
|
||||||
text = QString("%1 %2 %3.")
|
|
||||||
.arg(action.source.login,
|
|
||||||
(action.modded ? "modded" : "unmodded"),
|
|
||||||
action.target.login);
|
|
||||||
|
|
||||||
postToThread([chan, text] {
|
|
||||||
chan->addSystemMessage(text);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.userBanned.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([chan, action] {
|
|
||||||
MessageBuilder msg(action);
|
|
||||||
msg->flags.set(MessageFlag::PubSub);
|
|
||||||
chan->addOrReplaceTimeout(msg.release());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.userWarned.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Resolve the moderator's user ID into a full user here, so message can look better
|
|
||||||
postToThread([chan, action] {
|
|
||||||
MessageBuilder msg(action);
|
|
||||||
msg->flags.set(MessageFlag::PubSub);
|
|
||||||
chan->addMessage(msg.release(), MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.messageDeleted.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty() || getSettings()->hideDeletionActions)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto msg = MessageBuilder::makeDeletionMessageFromPubSub(action);
|
|
||||||
|
|
||||||
postToThread([chan, msg] {
|
|
||||||
auto replaced = false;
|
|
||||||
LimitedQueueSnapshot<MessagePtr> snapshot =
|
|
||||||
chan->getMessageSnapshot();
|
|
||||||
int snapshotLength = snapshot.size();
|
|
||||||
|
|
||||||
// without parens it doesn't build on windows
|
|
||||||
int end = (std::max)(0, snapshotLength - 200);
|
|
||||||
|
|
||||||
for (int i = snapshotLength - 1; i >= end; --i)
|
|
||||||
{
|
|
||||||
const auto &s = snapshot[i];
|
|
||||||
if (!s->flags.has(MessageFlag::PubSub) &&
|
|
||||||
s->timeoutUser == msg->timeoutUser)
|
|
||||||
{
|
|
||||||
chan->replaceMessage(s, msg);
|
|
||||||
replaced = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!replaced)
|
|
||||||
{
|
|
||||||
chan->addMessage(msg, MessageContext::Original);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.userUnbanned.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto msg = MessageBuilder(action).release();
|
|
||||||
|
|
||||||
postToThread([chan, msg] {
|
|
||||||
chan->addMessage(msg, MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore =
|
|
||||||
this->twitchPubSub->moderation.suspiciousMessageReceived.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
if (action.treatment ==
|
|
||||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
|
||||||
{
|
|
||||||
qCWarning(chatterinoTwitch)
|
|
||||||
<< "Received suspicious message with unknown "
|
|
||||||
"treatment:"
|
|
||||||
<< action.treatmentString;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitored chats are received over irc; in the future, we will use pubsub instead
|
|
||||||
if (action.treatment !=
|
|
||||||
PubSubLowTrustUsersMessage::Treatment::Restricted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSettings()->streamerModeHideModActions &&
|
|
||||||
this->getStreamerMode()->isEnabled())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto chan =
|
|
||||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto twitchChannel =
|
|
||||||
std::dynamic_pointer_cast<TwitchChannel>(chan);
|
|
||||||
if (!twitchChannel)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([twitchChannel, action] {
|
|
||||||
const auto p = MessageBuilder::makeLowTrustUserMessage(
|
|
||||||
action, twitchChannel->getName(), twitchChannel.get());
|
|
||||||
twitchChannel->addMessage(p.first,
|
|
||||||
MessageContext::Original);
|
|
||||||
twitchChannel->addMessage(p.second,
|
|
||||||
MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore =
|
|
||||||
this->twitchPubSub->moderation.suspiciousTreatmentUpdated.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
if (action.treatment ==
|
|
||||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
|
||||||
{
|
|
||||||
qCWarning(chatterinoTwitch)
|
|
||||||
<< "Received suspicious user update with unknown "
|
|
||||||
"treatment:"
|
|
||||||
<< action.treatmentString;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.updatedByUserLogin.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getSettings()->streamerModeHideModActions &&
|
|
||||||
this->getStreamerMode()->isEnabled())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto chan =
|
|
||||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([chan, action] {
|
|
||||||
auto msg =
|
|
||||||
MessageBuilder::makeLowTrustUpdateMessage(action);
|
|
||||||
chan->addMessage(msg, MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.autoModMessageCaught.connect(
|
|
||||||
[&](const auto &msg, const QString &channelID) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(channelID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (msg.type)
|
|
||||||
{
|
|
||||||
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
|
|
||||||
if (msg.status == "PENDING")
|
|
||||||
{
|
|
||||||
AutomodAction action(msg.data, channelID);
|
|
||||||
action.reason = QString("%1 level %2")
|
|
||||||
.arg(msg.contentCategory)
|
|
||||||
.arg(msg.contentLevel);
|
|
||||||
|
|
||||||
action.msgID = msg.messageID;
|
|
||||||
action.message = msg.messageText;
|
|
||||||
|
|
||||||
// this message also contains per-word automod data, which could be implemented
|
|
||||||
|
|
||||||
// extract sender data manually because Twitch loves not being consistent
|
|
||||||
QString senderDisplayName =
|
|
||||||
msg.senderUserDisplayName; // Might be transformed later
|
|
||||||
bool hasLocalizedName = false;
|
|
||||||
if (!msg.senderUserDisplayName.isEmpty())
|
|
||||||
{
|
|
||||||
// check for non-ascii display names
|
|
||||||
if (QString::compare(msg.senderUserDisplayName,
|
|
||||||
msg.senderUserLogin,
|
|
||||||
Qt::CaseInsensitive) != 0)
|
|
||||||
{
|
|
||||||
hasLocalizedName = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QColor senderColor = msg.senderUserChatColor;
|
|
||||||
QString senderColor_;
|
|
||||||
if (!senderColor.isValid() &&
|
|
||||||
getSettings()->colorizeNicknames)
|
|
||||||
{
|
|
||||||
// color may be not present if user is a grey-name
|
|
||||||
senderColor = getRandomColor(msg.senderUserID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle username style based on prefered setting
|
|
||||||
switch (getSettings()->usernameDisplayMode.getValue())
|
|
||||||
{
|
|
||||||
case UsernameDisplayMode::Username: {
|
|
||||||
if (hasLocalizedName)
|
|
||||||
{
|
|
||||||
senderDisplayName = msg.senderUserLogin;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UsernameDisplayMode::LocalizedName: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case UsernameDisplayMode::
|
|
||||||
UsernameAndLocalizedName: {
|
|
||||||
if (hasLocalizedName)
|
|
||||||
{
|
|
||||||
senderDisplayName = QString("%1(%2)").arg(
|
|
||||||
msg.senderUserLogin,
|
|
||||||
msg.senderUserDisplayName);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
action.target =
|
|
||||||
ActionUser{msg.senderUserID, msg.senderUserLogin,
|
|
||||||
senderDisplayName, senderColor};
|
|
||||||
postToThread([chan, action] {
|
|
||||||
const auto p = MessageBuilder::makeAutomodMessage(
|
|
||||||
action, chan->getName());
|
|
||||||
chan->addMessage(p.first, MessageContext::Original);
|
|
||||||
chan->addMessage(p.second,
|
|
||||||
MessageContext::Original);
|
|
||||||
|
|
||||||
getApp()
|
|
||||||
->getTwitch()
|
|
||||||
->getAutomodChannel()
|
|
||||||
->addMessage(p.first, MessageContext::Original);
|
|
||||||
getApp()
|
|
||||||
->getTwitch()
|
|
||||||
->getAutomodChannel()
|
|
||||||
->addMessage(p.second,
|
|
||||||
MessageContext::Original);
|
|
||||||
|
|
||||||
if (getSettings()->showAutomodInMentions)
|
|
||||||
{
|
|
||||||
getApp()
|
|
||||||
->getTwitch()
|
|
||||||
->getMentionsChannel()
|
|
||||||
->addMessage(p.first,
|
|
||||||
MessageContext::Original);
|
|
||||||
getApp()
|
|
||||||
->getTwitch()
|
|
||||||
->getMentionsChannel()
|
|
||||||
->addMessage(p.second,
|
|
||||||
MessageContext::Original);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// "ALLOWED" and "DENIED" statuses remain unimplemented
|
|
||||||
// They are versions of automod_message_(denied|approved) but for mods.
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PubSubAutoModQueueMessage::Type::INVALID:
|
|
||||||
default: {
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.autoModMessageBlocked.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([chan, action] {
|
|
||||||
const auto p =
|
|
||||||
MessageBuilder::makeAutomodMessage(action, chan->getName());
|
|
||||||
chan->addMessage(p.first, MessageContext::Original);
|
|
||||||
chan->addMessage(p.second, MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.automodUserMessage.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
if (getSettings()->streamerModeHideModActions &&
|
|
||||||
this->getStreamerMode()->isEnabled())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto msg = MessageBuilder(action).release();
|
|
||||||
|
|
||||||
postToThread([chan, msg] {
|
|
||||||
chan->addMessage(msg, MessageContext::Original);
|
|
||||||
});
|
|
||||||
chan->deleteMessage(msg->id);
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore = this->twitchPubSub->moderation.automodInfoMessage.connect(
|
|
||||||
[&](const auto &action) {
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
|
||||||
|
|
||||||
if (chan->isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
postToThread([chan, action] {
|
|
||||||
const auto p = MessageBuilder::makeAutomodInfoMessage(action);
|
|
||||||
chan->addMessage(p, MessageContext::Original);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
std::ignore =
|
|
||||||
this->twitchPubSub->pointReward.redeemed.connect([&](auto &data) {
|
|
||||||
QString channelId = data.value("channel_id").toString();
|
|
||||||
if (channelId.isEmpty())
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoApp)
|
|
||||||
<< "Couldn't find channel id of point reward";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto chan = this->twitch->getChannelOrEmptyByID(channelId);
|
|
||||||
|
|
||||||
auto reward = ChannelPointReward(data);
|
|
||||||
|
|
||||||
postToThread([chan, reward] {
|
|
||||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
|
||||||
{
|
|
||||||
channel->addChannelPointReward(reward);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this->twitchPubSub->start();
|
|
||||||
this->twitchPubSub->setAccount(this->accounts->twitch.getCurrent());
|
|
||||||
|
|
||||||
this->accounts->twitch.currentUserChanged.connect(
|
|
||||||
[this] {
|
|
||||||
this->twitchPubSub->unlistenChannelModerationActions();
|
|
||||||
this->twitchPubSub->unlistenAutomod();
|
|
||||||
this->twitchPubSub->unlistenLowTrustUsers();
|
|
||||||
this->twitchPubSub->unlistenChannelPointRewards();
|
|
||||||
|
|
||||||
this->twitchPubSub->setAccount(this->accounts->twitch.getCurrent());
|
|
||||||
},
|
|
||||||
boost::signals2::at_front);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::initBttvLiveUpdates()
|
void Application::initBttvLiveUpdates()
|
||||||
{
|
{
|
||||||
if (!this->bttvLiveUpdates)
|
if (!this->bttvLiveUpdates)
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
@ -133,7 +131,7 @@ public:
|
||||||
void load();
|
void load();
|
||||||
void save();
|
void save();
|
||||||
|
|
||||||
int run(QApplication &qtApp);
|
int run();
|
||||||
|
|
||||||
friend void test();
|
friend void test();
|
||||||
|
|
||||||
|
@ -219,13 +217,14 @@ public:
|
||||||
IStreamerMode *getStreamerMode() override;
|
IStreamerMode *getStreamerMode() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initPubSub();
|
|
||||||
void initBttvLiveUpdates();
|
void initBttvLiveUpdates();
|
||||||
void initSeventvEventAPI();
|
void initSeventvEventAPI();
|
||||||
void initNm(const Paths &paths);
|
void initNm(const Paths &paths);
|
||||||
|
|
||||||
NativeMessagingServer nmServer;
|
NativeMessagingServer nmServer;
|
||||||
Updates &updates;
|
Updates &updates;
|
||||||
|
|
||||||
|
bool initialized{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
IApplication *getApp();
|
IApplication *getApp();
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace {
|
||||||
{
|
{
|
||||||
// borrowed from
|
// borrowed from
|
||||||
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
|
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
|
||||||
auto dark = qApp->palette();
|
auto dark = QApplication::palette();
|
||||||
|
|
||||||
dark.setColor(QPalette::Window, QColor(22, 22, 22));
|
dark.setColor(QPalette::Window, QColor(22, 22, 22));
|
||||||
dark.setColor(QPalette::WindowText, Qt::white);
|
dark.setColor(QPalette::WindowText, Qt::white);
|
||||||
|
@ -71,7 +71,7 @@ namespace {
|
||||||
dark.setColor(QPalette::Disabled, QPalette::WindowText,
|
dark.setColor(QPalette::Disabled, QPalette::WindowText,
|
||||||
QColor(127, 127, 127));
|
QColor(127, 127, 127));
|
||||||
|
|
||||||
qApp->setPalette(dark);
|
QApplication::setPalette(dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initQt()
|
void initQt()
|
||||||
|
@ -263,7 +263,7 @@ void runGui(QApplication &a, const Paths &paths, Settings &settings,
|
||||||
|
|
||||||
Application app(settings, paths, args, updates);
|
Application app(settings, paths, args, updates);
|
||||||
app.initialize(settings, paths);
|
app.initialize(settings, paths);
|
||||||
app.run(a);
|
app.run();
|
||||||
app.save();
|
app.save();
|
||||||
|
|
||||||
settings.requestSave();
|
settings.requestSave();
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
ChatterSet::ChatterSet()
|
ChatterSet::ChatterSet()
|
||||||
: items(chatterLimit)
|
: items(ChatterSet::CHATTER_LIMIT)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +20,7 @@ void ChatterSet::updateOnlineChatters(
|
||||||
BenchmarkGuard bench("update online chatters");
|
BenchmarkGuard bench("update online chatters");
|
||||||
|
|
||||||
// Create a new lru cache without the users that are not present anymore.
|
// Create a new lru cache without the users that are not present anymore.
|
||||||
cache::lru_cache<QString, QString> tmp(chatterLimit);
|
cache::lru_cache<QString, QString> tmp(ChatterSet::CHATTER_LIMIT);
|
||||||
|
|
||||||
for (auto &&chatter : lowerCaseUsernames)
|
for (auto &&chatter : lowerCaseUsernames)
|
||||||
{
|
{
|
||||||
|
@ -32,7 +30,7 @@ void ChatterSet::updateOnlineChatters(
|
||||||
|
|
||||||
// Less chatters than the limit => try to preserve as many as possible.
|
// Less chatters than the limit => try to preserve as many as possible.
|
||||||
}
|
}
|
||||||
else if (lowerCaseUsernames.size() < chatterLimit)
|
else if (lowerCaseUsernames.size() < ChatterSet::CHATTER_LIMIT)
|
||||||
{
|
{
|
||||||
tmp.put(chatter, chatter);
|
tmp.put(chatter, chatter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
#include <lrucache/lrucache.hpp>
|
#include <lrucache/lrucache.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <set>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -19,7 +16,7 @@ class ChatterSet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// The limit of how many chatters can be saved for a channel.
|
/// The limit of how many chatters can be saved for a channel.
|
||||||
static constexpr size_t chatterLimit = 2000;
|
static constexpr size_t CHATTER_LIMIT = 2000;
|
||||||
|
|
||||||
ChatterSet();
|
ChatterSet();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "util/CombinePath.hpp"
|
#include "util/CombinePath.hpp"
|
||||||
#include "util/Variant.hpp"
|
#include "util/Variant.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
|
@ -89,7 +90,7 @@ void queueInsecureSave()
|
||||||
if (!isQueued)
|
if (!isQueued)
|
||||||
{
|
{
|
||||||
isQueued = true;
|
isQueued = true;
|
||||||
QTimer::singleShot(200, qApp, [] {
|
QTimer::singleShot(200, QApplication::instance(), [] {
|
||||||
storeInsecure(insecureInstance());
|
storeInsecure(insecureInstance());
|
||||||
isQueued = false;
|
isQueued = false;
|
||||||
});
|
});
|
||||||
|
@ -133,8 +134,8 @@ void runNextJob()
|
||||||
job->setAutoDelete(true);
|
job->setAutoDelete(true);
|
||||||
job->setKey(set.name);
|
job->setKey(set.name);
|
||||||
job->setTextData(set.credential);
|
job->setTextData(set.credential);
|
||||||
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
QObject::connect(job, &QKeychain::Job::finished,
|
||||||
[](auto) {
|
QApplication::instance(), [](auto) {
|
||||||
runNextJob();
|
runNextJob();
|
||||||
});
|
});
|
||||||
job->start();
|
job->start();
|
||||||
|
@ -143,8 +144,8 @@ void runNextJob()
|
||||||
auto *job = new QKeychain::DeletePasswordJob("chatterino");
|
auto *job = new QKeychain::DeletePasswordJob("chatterino");
|
||||||
job->setAutoDelete(true);
|
job->setAutoDelete(true);
|
||||||
job->setKey(erase.name);
|
job->setKey(erase.name);
|
||||||
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
QObject::connect(job, &QKeychain::Job::finished,
|
||||||
[](auto) {
|
QApplication::instance(), [](auto) {
|
||||||
runNextJob();
|
runNextJob();
|
||||||
});
|
});
|
||||||
job->start();
|
job->start();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
@ -44,7 +45,7 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
|
||||||
if (caller)
|
if (caller)
|
||||||
{
|
{
|
||||||
// Caller must be in gui thread
|
// Caller must be in gui thread
|
||||||
assert(caller->thread() == qApp->thread());
|
assert(caller->thread() == QApplication::instance()->thread());
|
||||||
|
|
||||||
this->data->caller = const_cast<QObject *>(caller);
|
this->data->caller = const_cast<QObject *>(caller);
|
||||||
this->data->hasCaller = true;
|
this->data->hasCaller = true;
|
||||||
|
|
|
@ -110,15 +110,15 @@ int main(int argc, char **argv)
|
||||||
<< QSslSocket::supportedProtocols();
|
<< QSslSocket::supportedProtocols();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Updates updates(*paths);
|
Settings settings(args, paths->settingsDirectory);
|
||||||
|
|
||||||
|
Updates updates(*paths, settings);
|
||||||
|
|
||||||
NetworkConfigurationProvider::applyFromEnv(Env::get());
|
NetworkConfigurationProvider::applyFromEnv(Env::get());
|
||||||
|
|
||||||
IvrApi::initialize();
|
IvrApi::initialize();
|
||||||
Helix::initialize();
|
Helix::initialize();
|
||||||
|
|
||||||
Settings settings(args, paths->settingsDirectory);
|
|
||||||
|
|
||||||
runGui(a, *paths, settings, args, updates);
|
runGui(a, *paths, settings, args, updates);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
||||||
#include <boost/variant.hpp>
|
#include <boost/variant.hpp>
|
||||||
|
#include <QApplication>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
|
@ -15,82 +15,57 @@
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const QString CHANNEL_HAS_NO_EMOTES(
|
using namespace chatterino;
|
||||||
"This channel has no BetterTTV channel emotes.");
|
|
||||||
|
|
||||||
QString emoteLinkFormat("https://betterttv.com/emotes/%1");
|
const QString CHANNEL_HAS_NO_EMOTES(
|
||||||
// BTTV doesn't provide any data on the size, so we assume an emote is 28x28
|
"This channel has no BetterTTV channel emotes.");
|
||||||
constexpr QSize EMOTE_BASE_SIZE(28, 28);
|
|
||||||
|
|
||||||
struct CreateEmoteResult {
|
/// The emote page template.
|
||||||
EmoteId id;
|
///
|
||||||
EmoteName name;
|
/// %1 being the emote ID (e.g. 566ca04265dbbdab32ec054a)
|
||||||
Emote emote;
|
constexpr QStringView EMOTE_LINK_FORMAT = u"https://betterttv.com/emotes/%1";
|
||||||
};
|
|
||||||
|
|
||||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
/// The emote CDN link template.
|
||||||
const QString &emoteScale)
|
///
|
||||||
|
/// %1 being the emote ID (e.g. 566ca04265dbbdab32ec054a)
|
||||||
|
///
|
||||||
|
/// %2 being the emote size (e.g. 3x)
|
||||||
|
constexpr QStringView EMOTE_CDN_FORMAT =
|
||||||
|
u"https://cdn.betterttv.net/emote/%1/%2";
|
||||||
|
|
||||||
|
// BTTV doesn't provide any data on the size, so we assume an emote is 28x28
|
||||||
|
constexpr QSize EMOTE_BASE_SIZE(28, 28);
|
||||||
|
|
||||||
|
struct CreateEmoteResult {
|
||||||
|
EmoteId id;
|
||||||
|
EmoteName name;
|
||||||
|
Emote emote;
|
||||||
|
};
|
||||||
|
|
||||||
|
Url getEmoteLinkV3(const EmoteId &id, const QString &emoteScale)
|
||||||
|
{
|
||||||
|
return {EMOTE_CDN_FORMAT.arg(id.string, emoteScale)};
|
||||||
|
}
|
||||||
|
|
||||||
|
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
||||||
|
{
|
||||||
|
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
||||||
|
static std::mutex mutex;
|
||||||
|
|
||||||
|
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonArray &jsonEmotes,
|
||||||
|
const EmoteMap ¤tEmotes)
|
||||||
|
{
|
||||||
|
auto emotes = EmoteMap();
|
||||||
|
|
||||||
|
for (auto jsonEmote : jsonEmotes)
|
||||||
{
|
{
|
||||||
urlTemplate.detach();
|
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||||
|
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||||
return {urlTemplate.replace("{{id}}", id.string)
|
|
||||||
.replace("{{image}}", emoteScale)};
|
|
||||||
}
|
|
||||||
|
|
||||||
Url getEmoteLinkV3(const EmoteId &id, const QString &emoteScale)
|
|
||||||
{
|
|
||||||
static const QString urlTemplate(
|
|
||||||
"https://cdn.betterttv.net/emote/%1/%2");
|
|
||||||
|
|
||||||
return {urlTemplate.arg(id.string, emoteScale)};
|
|
||||||
}
|
|
||||||
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
|
|
||||||
{
|
|
||||||
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
|
|
||||||
static std::mutex mutex;
|
|
||||||
|
|
||||||
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
|
|
||||||
}
|
|
||||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
|
||||||
const QJsonArray &jsonEmotes, const EmoteMap ¤tEmotes)
|
|
||||||
{
|
|
||||||
auto emotes = EmoteMap();
|
|
||||||
|
|
||||||
for (auto jsonEmote : jsonEmotes)
|
|
||||||
{
|
|
||||||
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
|
||||||
auto name =
|
|
||||||
EmoteName{jsonEmote.toObject().value("code").toString()};
|
|
||||||
|
|
||||||
auto emote = Emote({
|
|
||||||
name,
|
|
||||||
ImageSet{Image::fromUrl(getEmoteLinkV3(id, "1x"), 1,
|
|
||||||
EMOTE_BASE_SIZE),
|
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5,
|
|
||||||
EMOTE_BASE_SIZE * 2),
|
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25,
|
|
||||||
EMOTE_BASE_SIZE * 4)},
|
|
||||||
Tooltip{name.string + "<br>Global BetterTTV Emote"},
|
|
||||||
Url{emoteLinkFormat.arg(id.string)},
|
|
||||||
});
|
|
||||||
|
|
||||||
emotes[name] =
|
|
||||||
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {Success, std::move(emotes)};
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateEmoteResult createChannelEmote(const QString &channelDisplayName,
|
|
||||||
const QJsonObject &jsonEmote)
|
|
||||||
{
|
|
||||||
auto id = EmoteId{jsonEmote.value("id").toString()};
|
|
||||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
|
||||||
auto author = EmoteAuthor{
|
|
||||||
jsonEmote.value("user").toObject().value("displayName").toString()};
|
|
||||||
|
|
||||||
auto emote = Emote({
|
auto emote = Emote({
|
||||||
name,
|
name,
|
||||||
|
@ -99,58 +74,82 @@ namespace {
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5,
|
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5,
|
||||||
EMOTE_BASE_SIZE * 2),
|
EMOTE_BASE_SIZE * 2),
|
||||||
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25,
|
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25,
|
||||||
EMOTE_BASE_SIZE * 4),
|
EMOTE_BASE_SIZE * 4)},
|
||||||
},
|
Tooltip{name.string + "<br>Global BetterTTV Emote"},
|
||||||
Tooltip{
|
Url{EMOTE_LINK_FORMAT.arg(id.string)},
|
||||||
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
|
|
||||||
.arg(name.string)
|
|
||||||
// when author is empty, it is a channel emote created by the broadcaster
|
|
||||||
.arg(author.string.isEmpty() ? "Channel" : "Shared")
|
|
||||||
.arg(author.string.isEmpty() ? channelDisplayName
|
|
||||||
: author.string)},
|
|
||||||
Url{emoteLinkFormat.arg(id.string)},
|
|
||||||
false,
|
|
||||||
id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {id, name, emote};
|
emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool updateChannelEmote(Emote &emote, const QString &channelDisplayName,
|
return {Success, std::move(emotes)};
|
||||||
const QJsonObject &jsonEmote)
|
}
|
||||||
|
|
||||||
|
CreateEmoteResult createChannelEmote(const QString &channelDisplayName,
|
||||||
|
const QJsonObject &jsonEmote)
|
||||||
|
{
|
||||||
|
auto id = EmoteId{jsonEmote.value("id").toString()};
|
||||||
|
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||||
|
auto author = EmoteAuthor{
|
||||||
|
jsonEmote.value("user").toObject().value("displayName").toString()};
|
||||||
|
|
||||||
|
auto emote = Emote({
|
||||||
|
name,
|
||||||
|
ImageSet{
|
||||||
|
Image::fromUrl(getEmoteLinkV3(id, "1x"), 1, EMOTE_BASE_SIZE),
|
||||||
|
Image::fromUrl(getEmoteLinkV3(id, "2x"), 0.5, EMOTE_BASE_SIZE * 2),
|
||||||
|
Image::fromUrl(getEmoteLinkV3(id, "3x"), 0.25, EMOTE_BASE_SIZE * 4),
|
||||||
|
},
|
||||||
|
Tooltip{
|
||||||
|
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
|
||||||
|
.arg(name.string)
|
||||||
|
// when author is empty, it is a channel emote created by the broadcaster
|
||||||
|
.arg(author.string.isEmpty() ? "Channel" : "Shared")
|
||||||
|
.arg(author.string.isEmpty() ? channelDisplayName
|
||||||
|
: author.string)},
|
||||||
|
Url{EMOTE_LINK_FORMAT.arg(id.string)},
|
||||||
|
false,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {id, name, emote};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updateChannelEmote(Emote &emote, const QString &channelDisplayName,
|
||||||
|
const QJsonObject &jsonEmote)
|
||||||
|
{
|
||||||
|
bool anyModifications = false;
|
||||||
|
|
||||||
|
if (jsonEmote.contains("code"))
|
||||||
{
|
{
|
||||||
bool anyModifications = false;
|
emote.name = EmoteName{jsonEmote.value("code").toString()};
|
||||||
|
anyModifications = true;
|
||||||
if (jsonEmote.contains("code"))
|
|
||||||
{
|
|
||||||
emote.name = EmoteName{jsonEmote.value("code").toString()};
|
|
||||||
anyModifications = true;
|
|
||||||
}
|
|
||||||
if (jsonEmote.contains("user"))
|
|
||||||
{
|
|
||||||
emote.author = EmoteAuthor{jsonEmote.value("user")
|
|
||||||
.toObject()
|
|
||||||
.value("displayName")
|
|
||||||
.toString()};
|
|
||||||
anyModifications = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anyModifications)
|
|
||||||
{
|
|
||||||
emote.tooltip = Tooltip{
|
|
||||||
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
|
|
||||||
.arg(emote.name.string)
|
|
||||||
// when author is empty, it is a channel emote created by the broadcaster
|
|
||||||
.arg(emote.author.string.isEmpty() ? "Channel" : "Shared")
|
|
||||||
.arg(emote.author.string.isEmpty() ? channelDisplayName
|
|
||||||
: emote.author.string)};
|
|
||||||
}
|
|
||||||
|
|
||||||
return anyModifications;
|
|
||||||
}
|
}
|
||||||
|
if (jsonEmote.contains("user"))
|
||||||
|
{
|
||||||
|
emote.author = EmoteAuthor{
|
||||||
|
jsonEmote.value("user").toObject().value("displayName").toString()};
|
||||||
|
anyModifications = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyModifications)
|
||||||
|
{
|
||||||
|
emote.tooltip = Tooltip{
|
||||||
|
QString("%1<br>%2 BetterTTV Emote<br>By: %3")
|
||||||
|
.arg(emote.name.string)
|
||||||
|
// when author is empty, it is a channel emote created by the broadcaster
|
||||||
|
.arg(emote.author.string.isEmpty() ? "Channel" : "Shared")
|
||||||
|
.arg(emote.author.string.isEmpty() ? channelDisplayName
|
||||||
|
: emote.author.string)};
|
||||||
|
}
|
||||||
|
|
||||||
|
return anyModifications;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
using namespace bttv::detail;
|
using namespace bttv::detail;
|
||||||
|
|
||||||
EmoteMap bttv::detail::parseChannelEmotes(const QJsonObject &jsonRoot,
|
EmoteMap bttv::detail::parseChannelEmotes(const QJsonObject &jsonRoot,
|
||||||
|
@ -182,6 +181,11 @@ EmoteMap bttv::detail::parseChannelEmotes(const QJsonObject &jsonRoot,
|
||||||
BttvEmotes::BttvEmotes()
|
BttvEmotes::BttvEmotes()
|
||||||
: global_(std::make_shared<EmoteMap>())
|
: global_(std::make_shared<EmoteMap>())
|
||||||
{
|
{
|
||||||
|
getSettings()->enableBTTVGlobalEmotes.connect(
|
||||||
|
[this] {
|
||||||
|
this->loadEmotes();
|
||||||
|
},
|
||||||
|
this->managedConnections, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
|
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
|
||||||
|
@ -360,15 +364,4 @@ std::optional<EmotePtr> BttvEmotes::removeEmote(
|
||||||
return emote;
|
return emote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
|
||||||
const QString &emoteScale)
|
|
||||||
{
|
|
||||||
urlTemplate.detach();
|
|
||||||
|
|
||||||
return {urlTemplate.replace("{{id}}", id.string)
|
|
||||||
.replace("{{image}}", emoteScale)};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
|
|
||||||
|
#include <pajlada/signals/scoped-connection.hpp>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -81,6 +86,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||||
|
managedConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
|
@ -299,7 +299,7 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||||
QString::size_type lastParsedEmojiEndIndex = 0;
|
QString::size_type lastParsedEmojiEndIndex = 0;
|
||||||
|
|
||||||
for (auto i = 0; i < text.length(); ++i)
|
for (qsizetype i = 0; i < text.length(); ++i)
|
||||||
{
|
{
|
||||||
const QChar character = text.at(i);
|
const QChar character = text.at(i);
|
||||||
|
|
||||||
|
@ -401,7 +401,7 @@ QString Emojis::replaceShortCodes(const QString &text) const
|
||||||
QString ret(text);
|
QString ret(text);
|
||||||
auto it = this->findShortCodesRegex_.globalMatch(text);
|
auto it = this->findShortCodesRegex_.globalMatch(text);
|
||||||
|
|
||||||
int32_t offset = 0;
|
qsizetype offset = 0;
|
||||||
|
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,9 +12,6 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <shared_mutex>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
std::vector<FfzBadges::Badge> FfzBadges::getUserBadges(const UserId &id)
|
std::vector<FfzBadges::Badge> FfzBadges::getUserBadges(const UserId &id)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
|
||||||
#include "util/ThreadGuard.hpp"
|
#include "util/ThreadGuard.hpp"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
|
|
@ -206,6 +206,11 @@ FfzChannelBadgeMap ffz::detail::parseChannelBadges(const QJsonObject &badgeRoot)
|
||||||
FfzEmotes::FfzEmotes()
|
FfzEmotes::FfzEmotes()
|
||||||
: global_(std::make_shared<EmoteMap>())
|
: global_(std::make_shared<EmoteMap>())
|
||||||
{
|
{
|
||||||
|
getSettings()->enableFFZGlobalEmotes.connect(
|
||||||
|
[this] {
|
||||||
|
this->loadEmotes();
|
||||||
|
},
|
||||||
|
this->managedConnections, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
|
std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
#include "util/QStringHash.hpp"
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
#include <boost/unordered/unordered_flat_map.hpp>
|
#include <boost/unordered/unordered_flat_map.hpp>
|
||||||
|
#include <pajlada/signals/scoped-connection.hpp>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -51,6 +55,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||||
|
managedConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "providers/ffz/FfzUtil.hpp"
|
#include "providers/ffz/FfzUtil.hpp"
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Url parseFfzUrl(const QString &ffzUrl)
|
Url parseFfzUrl(const QString &ffzUrl)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "common/Aliases.hpp"
|
#include "common/Aliases.hpp"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
|
|
@ -188,6 +188,11 @@ EmoteMap seventv::detail::parseEmotes(const QJsonArray &emoteSetEmotes,
|
||||||
SeventvEmotes::SeventvEmotes()
|
SeventvEmotes::SeventvEmotes()
|
||||||
: global_(std::make_shared<EmoteMap>())
|
: global_(std::make_shared<EmoteMap>())
|
||||||
{
|
{
|
||||||
|
getSettings()->enableSevenTVGlobalEmotes.connect(
|
||||||
|
[this] {
|
||||||
|
this->loadGlobalEmotes();
|
||||||
|
},
|
||||||
|
this->managedConnections, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<const EmoteMap> SeventvEmotes::globalEmotes() const
|
std::shared_ptr<const EmoteMap> SeventvEmotes::globalEmotes() const
|
||||||
|
|
|
@ -4,11 +4,17 @@
|
||||||
#include "common/Atomic.hpp"
|
#include "common/Atomic.hpp"
|
||||||
#include "common/FlagsEnum.hpp"
|
#include "common/FlagsEnum.hpp"
|
||||||
|
|
||||||
|
#include <pajlada/signals/scoped-connection.hpp>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -21,7 +27,7 @@ namespace seventv::eventapi {
|
||||||
} // namespace seventv::eventapi
|
} // namespace seventv::eventapi
|
||||||
|
|
||||||
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
|
// https://github.com/SevenTV/API/blob/a84e884b5590dbb5d91a5c6b3548afabb228f385/data/model/emote-set.model.go#L29-L36
|
||||||
enum class SeventvActiveEmoteFlag : int64_t {
|
enum class SeventvActiveEmoteFlag : std::int64_t {
|
||||||
None = 0LL,
|
None = 0LL,
|
||||||
|
|
||||||
// Emote is zero-width
|
// Emote is zero-width
|
||||||
|
@ -152,6 +158,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||||
|
managedConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "providers/NetworkConfigurationProvider.hpp"
|
#include "providers/NetworkConfigurationProvider.hpp"
|
||||||
#include "providers/twitch/PubSubActions.hpp"
|
#include "providers/twitch/PubSubActions.hpp"
|
||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
|
@ -508,6 +510,23 @@ PubSub::~PubSub()
|
||||||
this->stop();
|
this->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PubSub::initialize()
|
||||||
|
{
|
||||||
|
this->start();
|
||||||
|
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||||
|
|
||||||
|
getApp()->getAccounts()->twitch.currentUserChanged.connect(
|
||||||
|
[this] {
|
||||||
|
this->unlistenChannelModerationActions();
|
||||||
|
this->unlistenAutomod();
|
||||||
|
this->unlistenLowTrustUsers();
|
||||||
|
this->unlistenChannelPointRewards();
|
||||||
|
|
||||||
|
this->setAccount(getApp()->getAccounts()->twitch.getCurrent());
|
||||||
|
},
|
||||||
|
boost::signals2::at_front);
|
||||||
|
}
|
||||||
|
|
||||||
void PubSub::setAccount(std::shared_ptr<TwitchAccount> account)
|
void PubSub::setAccount(std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
this->token_ = account->getOAuthToken();
|
this->token_ = account->getOAuthToken();
|
||||||
|
|
|
@ -3,15 +3,21 @@
|
||||||
#include "providers/twitch/PubSubClientOptions.hpp"
|
#include "providers/twitch/PubSubClientOptions.hpp"
|
||||||
#include "providers/twitch/PubSubWebsocket.hpp"
|
#include "providers/twitch/PubSubWebsocket.hpp"
|
||||||
#include "util/ExponentialBackoff.hpp"
|
#include "util/ExponentialBackoff.hpp"
|
||||||
#include "util/QStringHash.hpp"
|
|
||||||
|
|
||||||
|
#include <boost/asio/io_service.hpp>
|
||||||
|
#include <boost/asio/ssl/context.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <websocketpp/client.hpp>
|
#include <websocketpp/client.hpp>
|
||||||
|
#include <websocketpp/common/connection_hdl.hpp>
|
||||||
|
#include <websocketpp/common/memory.hpp>
|
||||||
|
#include <websocketpp/config/asio_client.hpp>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -19,6 +25,10 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#if __has_include(<gtest/gtest_prod.h>)
|
||||||
|
# include <gtest/gtest_prod.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchAccount;
|
class TwitchAccount;
|
||||||
|
@ -85,10 +95,8 @@ public:
|
||||||
PubSub &operator=(const PubSub &) = delete;
|
PubSub &operator=(const PubSub &) = delete;
|
||||||
PubSub &operator=(PubSub &&) = delete;
|
PubSub &operator=(PubSub &&) = delete;
|
||||||
|
|
||||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
/// Set up connections between itself & other parts of the application
|
||||||
|
void initialize();
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Signal<ClearChatAction> chatCleared;
|
Signal<ClearChatAction> chatCleared;
|
||||||
|
@ -192,6 +200,11 @@ public:
|
||||||
} diag;
|
} diag;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlistens to all topics matching the prefix in all clients
|
* Unlistens to all topics matching the prefix in all clients
|
||||||
*/
|
*/
|
||||||
|
@ -250,6 +263,22 @@ private:
|
||||||
const PubSubClientOptions clientOptions_;
|
const PubSubClientOptions clientOptions_;
|
||||||
|
|
||||||
bool stopping_{false};
|
bool stopping_{false};
|
||||||
|
|
||||||
|
#ifdef FRIEND_TEST
|
||||||
|
friend class FTest;
|
||||||
|
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ServerRespondsToPings);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ServerDoesntRespondToPings);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, DisconnectedAfter1s);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimit);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ReceivedWhisper);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, ModeratorActionsUserBanned);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, MissingToken);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, WrongToken);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, CorrectToken);
|
||||||
|
FRIEND_TEST(TwitchPubSubClient, AutoModMessageHeld);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1320,8 +1320,6 @@ void TwitchChannel::refreshPubSub()
|
||||||
|
|
||||||
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||||
|
|
||||||
getApp()->getTwitchPubSub()->setAccount(currentAccount);
|
|
||||||
|
|
||||||
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
||||||
if (this->hasModRights())
|
if (this->hasModRights())
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
#include "providers/seventv/SeventvEventAPI.hpp"
|
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||||
#include "providers/twitch/api/Helix.hpp"
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
|
#include "providers/twitch/PubSubActions.hpp"
|
||||||
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
|
#include "providers/twitch/pubsubmessages/AutoMod.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/StreamerMode.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
#include "util/RatelimitBucket.hpp"
|
#include "util/RatelimitBucket.hpp"
|
||||||
|
|
||||||
|
@ -230,6 +234,441 @@ void TwitchIrcServer::initialize()
|
||||||
this->connect();
|
this->connect();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.chatCleared,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text =
|
||||||
|
QString("%1 cleared the chat.").arg(action.source.login);
|
||||||
|
|
||||||
|
postToThread([chan, text] {
|
||||||
|
chan->addSystemMessage(text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.modeChanged,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text =
|
||||||
|
QString("%1 turned %2 %3 mode.")
|
||||||
|
.arg(action.source.login)
|
||||||
|
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||||
|
: "off")
|
||||||
|
.arg(action.getModeName());
|
||||||
|
|
||||||
|
if (action.duration > 0)
|
||||||
|
{
|
||||||
|
text += QString(" (%1 seconds)").arg(action.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([chan, text] {
|
||||||
|
chan->addSystemMessage(text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.moderationStateChanged,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
|
||||||
|
text = QString("%1 %2 %3.")
|
||||||
|
.arg(action.source.login,
|
||||||
|
(action.modded ? "modded" : "unmodded"),
|
||||||
|
action.target.login);
|
||||||
|
|
||||||
|
postToThread([chan, text] {
|
||||||
|
chan->addSystemMessage(text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.userBanned,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([chan, action] {
|
||||||
|
MessageBuilder msg(action);
|
||||||
|
msg->flags.set(MessageFlag::PubSub);
|
||||||
|
chan->addOrReplaceTimeout(msg.release());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.userWarned,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Resolve the moderator's user ID into a full user here, so message can look better
|
||||||
|
postToThread([chan, action] {
|
||||||
|
MessageBuilder msg(action);
|
||||||
|
msg->flags.set(MessageFlag::PubSub);
|
||||||
|
chan->addMessage(msg.release(), MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.messageDeleted,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty() || getSettings()->hideDeletionActions)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = MessageBuilder::makeDeletionMessageFromPubSub(action);
|
||||||
|
|
||||||
|
postToThread([chan, msg] {
|
||||||
|
auto replaced = false;
|
||||||
|
LimitedQueueSnapshot<MessagePtr> snapshot =
|
||||||
|
chan->getMessageSnapshot();
|
||||||
|
int snapshotLength = snapshot.size();
|
||||||
|
|
||||||
|
// without parens it doesn't build on windows
|
||||||
|
int end = (std::max)(0, snapshotLength - 200);
|
||||||
|
|
||||||
|
for (int i = snapshotLength - 1; i >= end; --i)
|
||||||
|
{
|
||||||
|
const auto &s = snapshot[i];
|
||||||
|
if (!s->flags.has(MessageFlag::PubSub) &&
|
||||||
|
s->timeoutUser == msg->timeoutUser)
|
||||||
|
{
|
||||||
|
chan->replaceMessage(s, msg);
|
||||||
|
replaced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!replaced)
|
||||||
|
{
|
||||||
|
chan->addMessage(msg, MessageContext::Original);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.userUnbanned,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = MessageBuilder(action).release();
|
||||||
|
|
||||||
|
postToThread([chan, msg] {
|
||||||
|
chan->addMessage(msg, MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.suspiciousMessageReceived,
|
||||||
|
[this](const auto &action) {
|
||||||
|
if (action.treatment ==
|
||||||
|
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoTwitch)
|
||||||
|
<< "Received suspicious message with unknown "
|
||||||
|
"treatment:"
|
||||||
|
<< action.treatmentString;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitored chats are received over irc; in the future, we will use pubsub instead
|
||||||
|
if (action.treatment !=
|
||||||
|
PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSettings()->streamerModeHideModActions &&
|
||||||
|
getApp()->getStreamerMode()->isEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||||
|
if (!twitchChannel)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([twitchChannel, action] {
|
||||||
|
const auto p = MessageBuilder::makeLowTrustUserMessage(
|
||||||
|
action, twitchChannel->getName(), twitchChannel.get());
|
||||||
|
twitchChannel->addMessage(p.first, MessageContext::Original);
|
||||||
|
twitchChannel->addMessage(p.second, MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.suspiciousTreatmentUpdated,
|
||||||
|
[this](const auto &action) {
|
||||||
|
if (action.treatment ==
|
||||||
|
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoTwitch)
|
||||||
|
<< "Received suspicious user update with unknown "
|
||||||
|
"treatment:"
|
||||||
|
<< action.treatmentString;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.updatedByUserLogin.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSettings()->streamerModeHideModActions &&
|
||||||
|
getApp()->getStreamerMode()->isEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.channelID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([chan, action] {
|
||||||
|
auto msg = MessageBuilder::makeLowTrustUpdateMessage(action);
|
||||||
|
chan->addMessage(msg, MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.autoModMessageCaught,
|
||||||
|
[this](const auto &msg, const QString &channelID) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(channelID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg.type)
|
||||||
|
{
|
||||||
|
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
|
||||||
|
if (msg.status == "PENDING")
|
||||||
|
{
|
||||||
|
AutomodAction action(msg.data, channelID);
|
||||||
|
action.reason = QString("%1 level %2")
|
||||||
|
.arg(msg.contentCategory)
|
||||||
|
.arg(msg.contentLevel);
|
||||||
|
|
||||||
|
action.msgID = msg.messageID;
|
||||||
|
action.message = msg.messageText;
|
||||||
|
|
||||||
|
// this message also contains per-word automod data, which could be implemented
|
||||||
|
|
||||||
|
// extract sender data manually because Twitch loves not being consistent
|
||||||
|
QString senderDisplayName =
|
||||||
|
msg.senderUserDisplayName; // Might be transformed later
|
||||||
|
bool hasLocalizedName = false;
|
||||||
|
if (!msg.senderUserDisplayName.isEmpty())
|
||||||
|
{
|
||||||
|
// check for non-ascii display names
|
||||||
|
if (QString::compare(msg.senderUserDisplayName,
|
||||||
|
msg.senderUserLogin,
|
||||||
|
Qt::CaseInsensitive) != 0)
|
||||||
|
{
|
||||||
|
hasLocalizedName = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QColor senderColor = msg.senderUserChatColor;
|
||||||
|
QString senderColor_;
|
||||||
|
if (!senderColor.isValid() &&
|
||||||
|
getSettings()->colorizeNicknames)
|
||||||
|
{
|
||||||
|
// color may be not present if user is a grey-name
|
||||||
|
senderColor = getRandomColor(msg.senderUserID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle username style based on prefered setting
|
||||||
|
switch (getSettings()->usernameDisplayMode.getValue())
|
||||||
|
{
|
||||||
|
case UsernameDisplayMode::Username: {
|
||||||
|
if (hasLocalizedName)
|
||||||
|
{
|
||||||
|
senderDisplayName = msg.senderUserLogin;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UsernameDisplayMode::LocalizedName: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UsernameDisplayMode::
|
||||||
|
UsernameAndLocalizedName: {
|
||||||
|
if (hasLocalizedName)
|
||||||
|
{
|
||||||
|
senderDisplayName = QString("%1(%2)").arg(
|
||||||
|
msg.senderUserLogin,
|
||||||
|
msg.senderUserDisplayName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action.target =
|
||||||
|
ActionUser{msg.senderUserID, msg.senderUserLogin,
|
||||||
|
senderDisplayName, senderColor};
|
||||||
|
postToThread([chan, action] {
|
||||||
|
const auto p = MessageBuilder::makeAutomodMessage(
|
||||||
|
action, chan->getName());
|
||||||
|
chan->addMessage(p.first, MessageContext::Original);
|
||||||
|
chan->addMessage(p.second,
|
||||||
|
MessageContext::Original);
|
||||||
|
|
||||||
|
getApp()
|
||||||
|
->getTwitch()
|
||||||
|
->getAutomodChannel()
|
||||||
|
->addMessage(p.first, MessageContext::Original);
|
||||||
|
getApp()
|
||||||
|
->getTwitch()
|
||||||
|
->getAutomodChannel()
|
||||||
|
->addMessage(p.second,
|
||||||
|
MessageContext::Original);
|
||||||
|
|
||||||
|
if (getSettings()->showAutomodInMentions)
|
||||||
|
{
|
||||||
|
getApp()
|
||||||
|
->getTwitch()
|
||||||
|
->getMentionsChannel()
|
||||||
|
->addMessage(p.first,
|
||||||
|
MessageContext::Original);
|
||||||
|
getApp()
|
||||||
|
->getTwitch()
|
||||||
|
->getMentionsChannel()
|
||||||
|
->addMessage(p.second,
|
||||||
|
MessageContext::Original);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// "ALLOWED" and "DENIED" statuses remain unimplemented
|
||||||
|
// They are versions of automod_message_(denied|approved) but for mods.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PubSubAutoModQueueMessage::Type::INVALID:
|
||||||
|
default: {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.autoModMessageBlocked,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([chan, action] {
|
||||||
|
const auto p =
|
||||||
|
MessageBuilder::makeAutomodMessage(action, chan->getName());
|
||||||
|
chan->addMessage(p.first, MessageContext::Original);
|
||||||
|
chan->addMessage(p.second, MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.automodUserMessage,
|
||||||
|
[this](const auto &action) {
|
||||||
|
if (getSettings()->streamerModeHideModActions &&
|
||||||
|
getApp()->getStreamerMode()->isEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = MessageBuilder(action).release();
|
||||||
|
|
||||||
|
postToThread([chan, msg] {
|
||||||
|
chan->addMessage(msg, MessageContext::Original);
|
||||||
|
});
|
||||||
|
chan->deleteMessage(msg->id);
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->moderation.automodInfoMessage,
|
||||||
|
[this](const auto &action) {
|
||||||
|
auto chan = this->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
|
if (chan->isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
postToThread([chan, action] {
|
||||||
|
const auto p = MessageBuilder::makeAutomodInfoMessage(action);
|
||||||
|
chan->addMessage(p, MessageContext::Original);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this->connections_.managedConnect(
|
||||||
|
getApp()->getTwitchPubSub()->pointReward.redeemed, [this](auto &data) {
|
||||||
|
QString channelId = data.value("channel_id").toString();
|
||||||
|
if (channelId.isEmpty())
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoApp)
|
||||||
|
<< "Couldn't find channel id of point reward";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chan = this->getChannelOrEmptyByID(channelId);
|
||||||
|
|
||||||
|
auto reward = ChannelPointReward(data);
|
||||||
|
|
||||||
|
postToThread([chan, reward] {
|
||||||
|
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||||
|
{
|
||||||
|
channel->addChannelPointReward(reward);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
||||||
|
|
|
@ -202,8 +202,6 @@ private:
|
||||||
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
||||||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||||
|
|
||||||
pajlada::Signals::SignalHolder signalHolder_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
# include <QStyleHints>
|
# include <QStyleHints>
|
||||||
#endif
|
#endif
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
@ -301,8 +302,9 @@ Theme::Theme(const Paths &paths)
|
||||||
this->loadAvailableThemes(paths);
|
this->loadAvailableThemes(paths);
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
QObject::connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged,
|
QObject::connect(QApplication::styleHints(),
|
||||||
&this->lifetime_, [this] {
|
&QStyleHints::colorSchemeChanged, &this->lifetime_,
|
||||||
|
[this] {
|
||||||
if (this->isSystemTheme())
|
if (this->isSystemTheme())
|
||||||
{
|
{
|
||||||
this->update();
|
this->update();
|
||||||
|
@ -320,7 +322,7 @@ void Theme::update()
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||||
if (this->isSystemTheme())
|
if (this->isSystemTheme())
|
||||||
{
|
{
|
||||||
switch (qApp->styleHints()->colorScheme())
|
switch (QApplication::styleHints()->colorScheme())
|
||||||
{
|
{
|
||||||
case Qt::ColorScheme::Light:
|
case Qt::ColorScheme::Light:
|
||||||
return this->lightSystemThemeName;
|
return this->lightSystemThemeName;
|
||||||
|
|
|
@ -46,12 +46,18 @@ const QString CHATTERINO_OS = u"unknown"_s;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Updates::Updates(const Paths &paths_)
|
Updates::Updates(const Paths &paths_, Settings &settings)
|
||||||
: paths(paths_)
|
: paths(paths_)
|
||||||
, currentVersion_(CHATTERINO_VERSION)
|
, currentVersion_(CHATTERINO_VERSION)
|
||||||
, updateGuideLink_("https://chatterino.com")
|
, updateGuideLink_("https://chatterino.com")
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoUpdate) << "init UpdateManager";
|
qCDebug(chatterinoUpdate) << "init UpdateManager";
|
||||||
|
|
||||||
|
settings.betaUpdates.connect(
|
||||||
|
[this] {
|
||||||
|
this->checkForUpdates();
|
||||||
|
},
|
||||||
|
this->managedConnections, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the online version is newer or older than the current version.
|
/// Checks if the online version is newer or older than the current version.
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <pajlada/signals/scoped-connection.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Paths;
|
class Paths;
|
||||||
|
class Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To check for updates, use the `checkForUpdates` method.
|
* To check for updates, use the `checkForUpdates` method.
|
||||||
|
@ -16,7 +21,7 @@ class Updates
|
||||||
const Paths &paths;
|
const Paths &paths;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Updates(const Paths &paths_);
|
Updates(const Paths &paths_, Settings &settings);
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
None,
|
None,
|
||||||
|
@ -59,6 +64,9 @@ private:
|
||||||
QString updateGuideLink_;
|
QString updateGuideLink_;
|
||||||
|
|
||||||
void setStatus_(Status status);
|
void setStatus_(Status status);
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||||
|
managedConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -664,8 +664,6 @@ IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor)
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
auto *app = getApp();
|
|
||||||
|
|
||||||
if (descriptor.type_ == "twitch")
|
if (descriptor.type_ == "twitch")
|
||||||
{
|
{
|
||||||
return getApp()->getTwitch()->getOrAddChannel(descriptor.channelName_);
|
return getApp()->getTwitch()->getOrAddChannel(descriptor.channelName_);
|
||||||
|
@ -753,7 +751,7 @@ void WindowManager::applyWindowLayout(const WindowLayout &layout)
|
||||||
// get geometry
|
// get geometry
|
||||||
{
|
{
|
||||||
// out of bounds windows
|
// out of bounds windows
|
||||||
auto screens = qApp->screens();
|
auto screens = QApplication::screens();
|
||||||
bool outOfBounds =
|
bool outOfBounds =
|
||||||
!qEnvironmentVariableIsSet("I3SOCK") &&
|
!qEnvironmentVariableIsSet("I3SOCK") &&
|
||||||
std::none_of(screens.begin(), screens.end(),
|
std::none_of(screens.begin(), screens.end(),
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
void GIFTimer::initialize()
|
void GIFTimer::initialize()
|
||||||
|
@ -24,7 +26,7 @@ void GIFTimer::initialize()
|
||||||
|
|
||||||
QObject::connect(&this->timer, &QTimer::timeout, [this] {
|
QObject::connect(&this->timer, &QTimer::timeout, [this] {
|
||||||
if (getSettings()->animationsWhenFocused &&
|
if (getSettings()->animationsWhenFocused &&
|
||||||
qApp->activeWindow() == nullptr)
|
QApplication::activeWindow() == nullptr)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ QString formatTime(int totalSeconds)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString formatTime(QString totalSecondsString)
|
QString formatTime(const QString &totalSecondsString)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
int totalSeconds(totalSecondsString.toInt(&ok));
|
int totalSeconds(totalSecondsString.toInt(&ok));
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
||||||
|
|
||||||
// format: 1h 23m 42s
|
// format: 1h 23m 42s
|
||||||
QString formatTime(int totalSeconds);
|
QString formatTime(int totalSeconds);
|
||||||
QString formatTime(QString totalSecondsString);
|
QString formatTime(const QString &totalSecondsString);
|
||||||
QString formatTime(std::chrono::seconds totalSeconds);
|
QString formatTime(std::chrono::seconds totalSeconds);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace chatterino {
|
||||||
// https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style
|
// https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style
|
||||||
// Qt 5/4 - preferred, has least allocations
|
// Qt 5/4 - preferred, has least allocations
|
||||||
template <typename F>
|
template <typename F>
|
||||||
static void postToThread(F &&fun, QObject *obj = qApp)
|
static void postToThread(F &&fun, QObject *obj = QCoreApplication::instance())
|
||||||
{
|
{
|
||||||
struct Event : public QEvent {
|
struct Event : public QEvent {
|
||||||
using Fun = typename std::decay<F>::type;
|
using Fun = typename std::decay<F>::type;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "widgets/splits/Split.hpp"
|
#include "widgets/splits/Split.hpp"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
@ -75,7 +76,7 @@ void FramelessEmbedWindow::showEvent(QShowEvent *)
|
||||||
auto handle = reinterpret_cast<HWND>(this->winId());
|
auto handle = reinterpret_cast<HWND>(this->winId());
|
||||||
if (!::SetParent(handle, parentHwnd))
|
if (!::SetParent(handle, parentHwnd))
|
||||||
{
|
{
|
||||||
qApp->exit(1);
|
QApplication::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
||||||
#include <magic_enum/magic_enum_flags.hpp>
|
#include <magic_enum/magic_enum_flags.hpp>
|
||||||
|
#include <QApplication>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
TEST(ChatterSet, insert)
|
TEST(ChatterSet, insert)
|
||||||
{
|
{
|
||||||
chatterino::ChatterSet set;
|
ChatterSet set;
|
||||||
|
|
||||||
EXPECT_FALSE(set.contains("pajlada"));
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
EXPECT_FALSE(set.contains("Pajlada"));
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
@ -26,7 +28,7 @@ TEST(ChatterSet, insert)
|
||||||
|
|
||||||
TEST(ChatterSet, MaxSize)
|
TEST(ChatterSet, MaxSize)
|
||||||
{
|
{
|
||||||
chatterino::ChatterSet set;
|
ChatterSet set;
|
||||||
|
|
||||||
EXPECT_FALSE(set.contains("pajlada"));
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
EXPECT_FALSE(set.contains("Pajlada"));
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
@ -36,7 +38,7 @@ TEST(ChatterSet, MaxSize)
|
||||||
EXPECT_TRUE(set.contains("Pajlada"));
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
||||||
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
for (auto i = 0; i < ChatterSet::CHATTER_LIMIT - 1; ++i)
|
||||||
{
|
{
|
||||||
set.addRecentChatter(QString("%1").arg(i));
|
set.addRecentChatter(QString("%1").arg(i));
|
||||||
}
|
}
|
||||||
|
@ -53,7 +55,7 @@ TEST(ChatterSet, MaxSize)
|
||||||
|
|
||||||
TEST(ChatterSet, MaxSizeLastUsed)
|
TEST(ChatterSet, MaxSizeLastUsed)
|
||||||
{
|
{
|
||||||
chatterino::ChatterSet set;
|
ChatterSet set;
|
||||||
|
|
||||||
EXPECT_FALSE(set.contains("pajlada"));
|
EXPECT_FALSE(set.contains("pajlada"));
|
||||||
EXPECT_FALSE(set.contains("Pajlada"));
|
EXPECT_FALSE(set.contains("Pajlada"));
|
||||||
|
@ -63,7 +65,7 @@ TEST(ChatterSet, MaxSizeLastUsed)
|
||||||
EXPECT_TRUE(set.contains("Pajlada"));
|
EXPECT_TRUE(set.contains("Pajlada"));
|
||||||
|
|
||||||
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
// After adding CHATTER_LIMIT-1 additional chatters, pajlada should still be in the set
|
||||||
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
for (auto i = 0; i < ChatterSet::CHATTER_LIMIT - 1; ++i)
|
||||||
{
|
{
|
||||||
set.addRecentChatter(QString("%1").arg(i));
|
set.addRecentChatter(QString("%1").arg(i));
|
||||||
}
|
}
|
||||||
|
@ -75,7 +77,7 @@ TEST(ChatterSet, MaxSizeLastUsed)
|
||||||
set.addRecentChatter("pajlada");
|
set.addRecentChatter("pajlada");
|
||||||
|
|
||||||
// After another CHATTER_LIMIT-1 additional chatters, pajlada should still be there
|
// After another CHATTER_LIMIT-1 additional chatters, pajlada should still be there
|
||||||
for (auto i = 0; i < chatterino::ChatterSet::chatterLimit - 1; ++i)
|
for (auto i = 0; i < ChatterSet::CHATTER_LIMIT - 1; ++i)
|
||||||
{
|
{
|
||||||
set.addRecentChatter(QString("new-%1").arg(i));
|
set.addRecentChatter(QString("new-%1").arg(i));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
@ -33,6 +32,8 @@ using namespace std::chrono_literals;
|
||||||
|
|
||||||
#ifdef RUN_PUBSUB_TESTS
|
#ifdef RUN_PUBSUB_TESTS
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class ReceivedMessage
|
class ReceivedMessage
|
||||||
{
|
{
|
||||||
|
@ -451,4 +452,6 @@ TEST(TwitchPubSubClient, AutoModMessageHeld)
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue