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
|
||||
value: false
|
||||
|
||||
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
|
||||
value: true
|
||||
|
|
|
@ -20,6 +20,7 @@ class BaseApplication : public EmptyApplication
|
|||
public:
|
||||
BaseApplication()
|
||||
: settings(this->args, this->settingsDir.path())
|
||||
, updates(this->paths_, this->settings)
|
||||
, theme(this->paths_)
|
||||
, fonts(this->settings)
|
||||
{
|
||||
|
@ -28,11 +29,17 @@ public:
|
|||
explicit BaseApplication(const QString &settingsData)
|
||||
: EmptyApplication(settingsData)
|
||||
, settings(this->args, this->settingsDir.path())
|
||||
, updates(this->paths_, this->settings)
|
||||
, theme(this->paths_)
|
||||
, fonts(this->settings)
|
||||
{
|
||||
}
|
||||
|
||||
Updates &getUpdates() override
|
||||
{
|
||||
return this->updates;
|
||||
}
|
||||
|
||||
IStreamerMode *getStreamerMode() override
|
||||
{
|
||||
return &this->streamerMode;
|
||||
|
@ -60,6 +67,7 @@ public:
|
|||
|
||||
Args args;
|
||||
Settings settings;
|
||||
Updates updates;
|
||||
DisabledStreamerMode streamerMode;
|
||||
Theme theme;
|
||||
Fonts fonts;
|
||||
|
|
|
@ -12,13 +12,9 @@ namespace chatterino::mock {
|
|||
class EmptyApplication : public IApplication
|
||||
{
|
||||
public:
|
||||
EmptyApplication()
|
||||
: updates_(this->paths_)
|
||||
{
|
||||
}
|
||||
EmptyApplication() = default;
|
||||
|
||||
explicit EmptyApplication(const QString &settingsData)
|
||||
: EmptyApplication()
|
||||
{
|
||||
QFile settingsFile(this->settingsDir.filePath("settings.json"));
|
||||
settingsFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
|
@ -212,11 +208,6 @@ public:
|
|||
}
|
||||
#endif
|
||||
|
||||
Updates &getUpdates() override
|
||||
{
|
||||
return this->updates_;
|
||||
}
|
||||
|
||||
BttvEmotes *getBttvEmotes() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getBttvEmotes was called without "
|
||||
|
@ -269,7 +260,6 @@ public:
|
|||
QTemporaryDir settingsDir;
|
||||
Paths paths_;
|
||||
Args args_;
|
||||
Updates updates_;
|
||||
};
|
||||
|
||||
} // namespace chatterino::mock
|
||||
|
|
|
@ -61,10 +61,9 @@
|
|||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <miniaudio.h>
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
@ -128,8 +127,6 @@ IApplication *INSTANCE = nullptr;
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
static std::atomic<bool> isAppInitialized{false};
|
||||
|
||||
IApplication::IApplication()
|
||||
{
|
||||
INSTANCE = this;
|
||||
|
@ -194,8 +191,7 @@ Application::~Application()
|
|||
|
||||
void Application::initialize(Settings &settings, const Paths &paths)
|
||||
{
|
||||
assert(isAppInitialized == false);
|
||||
isAppInitialized = true;
|
||||
assert(!this->initialized);
|
||||
|
||||
// Show changelog
|
||||
if (!this->args_.isFramelessEmbed &&
|
||||
|
@ -271,17 +267,19 @@ void Application::initialize(Settings &settings, const Paths &paths)
|
|||
{
|
||||
this->initNm(paths);
|
||||
}
|
||||
this->initPubSub();
|
||||
this->twitchPubSub->initialize();
|
||||
|
||||
this->initBttvLiveUpdates();
|
||||
this->initSeventvEventAPI();
|
||||
|
||||
this->streamerMode->start();
|
||||
|
||||
this->initialized = true;
|
||||
}
|
||||
|
||||
int Application::run(QApplication &qtApp)
|
||||
int Application::run()
|
||||
{
|
||||
assert(isAppInitialized);
|
||||
assert(this->initialized);
|
||||
|
||||
this->twitch->connect();
|
||||
|
||||
|
@ -290,44 +288,23 @@ int Application::run(QApplication &qtApp)
|
|||
this->windows->getMainWindow().show();
|
||||
}
|
||||
|
||||
getSettings()->betaUpdates.connect(
|
||||
[this] {
|
||||
this->updates.checkForUpdates();
|
||||
},
|
||||
false);
|
||||
|
||||
getSettings()->enableBTTVGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->bttvEmotes->loadEmotes();
|
||||
},
|
||||
false);
|
||||
getSettings()->enableBTTVChannelEmotes.connect(
|
||||
[this] {
|
||||
this->twitch->reloadAllBTTVChannelEmotes();
|
||||
},
|
||||
false);
|
||||
getSettings()->enableFFZGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->ffzEmotes->loadEmotes();
|
||||
},
|
||||
false);
|
||||
getSettings()->enableFFZChannelEmotes.connect(
|
||||
[this] {
|
||||
this->twitch->reloadAllFFZChannelEmotes();
|
||||
},
|
||||
false);
|
||||
getSettings()->enableSevenTVGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->seventvEmotes->loadGlobalEmotes();
|
||||
},
|
||||
false);
|
||||
getSettings()->enableSevenTVChannelEmotes.connect(
|
||||
[this] {
|
||||
this->twitch->reloadAllSevenTVChannelEmotes();
|
||||
},
|
||||
false);
|
||||
|
||||
return qtApp.exec();
|
||||
return QApplication::exec();
|
||||
}
|
||||
|
||||
Theme *Application::getThemes()
|
||||
|
@ -597,455 +574,6 @@ void Application::initNm(const Paths &paths)
|
|||
#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()
|
||||
{
|
||||
if (!this->bttvLiveUpdates)
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
#include "singletons/NativeMessaging.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
|
@ -133,7 +131,7 @@ public:
|
|||
void load();
|
||||
void save();
|
||||
|
||||
int run(QApplication &qtApp);
|
||||
int run();
|
||||
|
||||
friend void test();
|
||||
|
||||
|
@ -219,13 +217,14 @@ public:
|
|||
IStreamerMode *getStreamerMode() override;
|
||||
|
||||
private:
|
||||
void initPubSub();
|
||||
void initBttvLiveUpdates();
|
||||
void initSeventvEventAPI();
|
||||
void initNm(const Paths &paths);
|
||||
|
||||
NativeMessagingServer nmServer;
|
||||
Updates &updates;
|
||||
|
||||
bool initialized{false};
|
||||
};
|
||||
|
||||
IApplication *getApp();
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace {
|
|||
{
|
||||
// borrowed from
|
||||
// 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::WindowText, Qt::white);
|
||||
|
@ -71,7 +71,7 @@ namespace {
|
|||
dark.setColor(QPalette::Disabled, QPalette::WindowText,
|
||||
QColor(127, 127, 127));
|
||||
|
||||
qApp->setPalette(dark);
|
||||
QApplication::setPalette(dark);
|
||||
}
|
||||
|
||||
void initQt()
|
||||
|
@ -263,7 +263,7 @@ void runGui(QApplication &a, const Paths &paths, Settings &settings,
|
|||
|
||||
Application app(settings, paths, args, updates);
|
||||
app.initialize(settings, paths);
|
||||
app.run(a);
|
||||
app.run();
|
||||
app.save();
|
||||
|
||||
settings.requestSave();
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
#include "debug/Benchmark.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ChatterSet::ChatterSet()
|
||||
: items(chatterLimit)
|
||||
: items(ChatterSet::CHATTER_LIMIT)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -22,7 +20,7 @@ void ChatterSet::updateOnlineChatters(
|
|||
BenchmarkGuard bench("update online chatters");
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
@ -32,7 +30,7 @@ void ChatterSet::updateOnlineChatters(
|
|||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
#include <lrucache/lrucache.hpp>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
@ -19,7 +16,7 @@ class ChatterSet
|
|||
{
|
||||
public:
|
||||
/// 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();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "util/CombinePath.hpp"
|
||||
#include "util/Variant.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSaveFile>
|
||||
|
@ -89,7 +90,7 @@ void queueInsecureSave()
|
|||
if (!isQueued)
|
||||
{
|
||||
isQueued = true;
|
||||
QTimer::singleShot(200, qApp, [] {
|
||||
QTimer::singleShot(200, QApplication::instance(), [] {
|
||||
storeInsecure(insecureInstance());
|
||||
isQueued = false;
|
||||
});
|
||||
|
@ -133,8 +134,8 @@ void runNextJob()
|
|||
job->setAutoDelete(true);
|
||||
job->setKey(set.name);
|
||||
job->setTextData(set.credential);
|
||||
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
||||
[](auto) {
|
||||
QObject::connect(job, &QKeychain::Job::finished,
|
||||
QApplication::instance(), [](auto) {
|
||||
runNextJob();
|
||||
});
|
||||
job->start();
|
||||
|
@ -143,8 +144,8 @@ void runNextJob()
|
|||
auto *job = new QKeychain::DeletePasswordJob("chatterino");
|
||||
job->setAutoDelete(true);
|
||||
job->setKey(erase.name);
|
||||
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
||||
[](auto) {
|
||||
QObject::connect(job, &QKeychain::Job::finished,
|
||||
QApplication::instance(), [](auto) {
|
||||
runNextJob();
|
||||
});
|
||||
job->start();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "common/QLogging.hpp"
|
||||
#include "common/Version.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QtConcurrent>
|
||||
|
@ -44,7 +45,7 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
|
|||
if (caller)
|
||||
{
|
||||
// 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->hasCaller = true;
|
||||
|
|
|
@ -110,15 +110,15 @@ int main(int argc, char **argv)
|
|||
<< QSslSocket::supportedProtocols();
|
||||
#endif
|
||||
|
||||
Updates updates(*paths);
|
||||
Settings settings(args, paths->settingsDirectory);
|
||||
|
||||
Updates updates(*paths, settings);
|
||||
|
||||
NetworkConfigurationProvider::applyFromEnv(Env::get());
|
||||
|
||||
IvrApi::initialize();
|
||||
Helix::initialize();
|
||||
|
||||
Settings settings(args, paths->settingsDirectory);
|
||||
|
||||
runGui(a, *paths, settings, args, updates);
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <QApplication>
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
|
|
|
@ -15,82 +15,57 @@
|
|||
#include <QJsonArray>
|
||||
#include <QThread>
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
|
||||
const QString CHANNEL_HAS_NO_EMOTES(
|
||||
"This channel has no BetterTTV channel emotes.");
|
||||
using namespace chatterino;
|
||||
|
||||
QString emoteLinkFormat("https://betterttv.com/emotes/%1");
|
||||
// BTTV doesn't provide any data on the size, so we assume an emote is 28x28
|
||||
constexpr QSize EMOTE_BASE_SIZE(28, 28);
|
||||
const QString CHANNEL_HAS_NO_EMOTES(
|
||||
"This channel has no BetterTTV channel emotes.");
|
||||
|
||||
struct CreateEmoteResult {
|
||||
EmoteId id;
|
||||
EmoteName name;
|
||||
Emote emote;
|
||||
};
|
||||
/// The emote page template.
|
||||
///
|
||||
/// %1 being the emote ID (e.g. 566ca04265dbbdab32ec054a)
|
||||
constexpr QStringView EMOTE_LINK_FORMAT = u"https://betterttv.com/emotes/%1";
|
||||
|
||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||
const QString &emoteScale)
|
||||
/// The emote CDN link template.
|
||||
///
|
||||
/// %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();
|
||||
|
||||
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 id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||
|
||||
auto emote = Emote({
|
||||
name,
|
||||
|
@ -99,58 +74,82 @@ namespace {
|
|||
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{emoteLinkFormat.arg(id.string)},
|
||||
false,
|
||||
id,
|
||||
EMOTE_BASE_SIZE * 4)},
|
||||
Tooltip{name.string + "<br>Global BetterTTV Emote"},
|
||||
Url{EMOTE_LINK_FORMAT.arg(id.string)},
|
||||
});
|
||||
|
||||
return {id, name, emote};
|
||||
emotes[name] = cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
|
||||
}
|
||||
|
||||
bool updateChannelEmote(Emote &emote, const QString &channelDisplayName,
|
||||
const QJsonObject &jsonEmote)
|
||||
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({
|
||||
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;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using namespace bttv::detail;
|
||||
|
||||
EmoteMap bttv::detail::parseChannelEmotes(const QJsonObject &jsonRoot,
|
||||
|
@ -182,6 +181,11 @@ EmoteMap bttv::detail::parseChannelEmotes(const QJsonObject &jsonRoot,
|
|||
BttvEmotes::BttvEmotes()
|
||||
: global_(std::make_shared<EmoteMap>())
|
||||
{
|
||||
getSettings()->enableBTTVGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->loadEmotes();
|
||||
},
|
||||
this->managedConnections, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
|
||||
|
@ -360,15 +364,4 @@ std::optional<EmotePtr> BttvEmotes::removeEmote(
|
|||
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
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
#include "common/Aliases.hpp"
|
||||
#include "common/Atomic.hpp"
|
||||
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -81,6 +86,9 @@ public:
|
|||
|
||||
private:
|
||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||
|
||||
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||
managedConnections;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
|
|
@ -299,7 +299,7 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
|||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||
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);
|
||||
|
||||
|
@ -401,7 +401,7 @@ QString Emojis::replaceShortCodes(const QString &text) const
|
|||
QString ret(text);
|
||||
auto it = this->findShortCodesRegex_.globalMatch(text);
|
||||
|
||||
int32_t offset = 0;
|
||||
qsizetype offset = 0;
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
|
|
|
@ -12,9 +12,6 @@
|
|||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
#include <map>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
std::vector<FfzBadges::Badge> FfzBadges::getUserBadges(const UserId &id)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "util/QStringHash.hpp"
|
||||
#include "util/ThreadGuard.hpp"
|
||||
|
||||
#include <QColor>
|
||||
|
|
|
@ -206,6 +206,11 @@ FfzChannelBadgeMap ffz::detail::parseChannelBadges(const QJsonObject &badgeRoot)
|
|||
FfzEmotes::FfzEmotes()
|
||||
: global_(std::make_shared<EmoteMap>())
|
||||
{
|
||||
getSettings()->enableFFZGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->loadEmotes();
|
||||
},
|
||||
this->managedConnections, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
#include "util/QStringHash.hpp"
|
||||
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -51,6 +55,9 @@ public:
|
|||
|
||||
private:
|
||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||
|
||||
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||
managedConnections;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "providers/ffz/FfzUtil.hpp"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Url parseFfzUrl(const QString &ffzUrl)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "common/Aliases.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
|
|
@ -188,6 +188,11 @@ EmoteMap seventv::detail::parseEmotes(const QJsonArray &emoteSetEmotes,
|
|||
SeventvEmotes::SeventvEmotes()
|
||||
: global_(std::make_shared<EmoteMap>())
|
||||
{
|
||||
getSettings()->enableSevenTVGlobalEmotes.connect(
|
||||
[this] {
|
||||
this->loadGlobalEmotes();
|
||||
},
|
||||
this->managedConnections, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<const EmoteMap> SeventvEmotes::globalEmotes() const
|
||||
|
|
|
@ -4,11 +4,17 @@
|
|||
#include "common/Atomic.hpp"
|
||||
#include "common/FlagsEnum.hpp"
|
||||
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -21,7 +27,7 @@ namespace seventv::eventapi {
|
|||
} // namespace seventv::eventapi
|
||||
|
||||
// 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,
|
||||
|
||||
// Emote is zero-width
|
||||
|
@ -152,6 +158,9 @@ public:
|
|||
|
||||
private:
|
||||
Atomic<std::shared_ptr<const EmoteMap>> global_;
|
||||
|
||||
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||
managedConnections;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "providers/twitch/PubSubManager.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "providers/NetworkConfigurationProvider.hpp"
|
||||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubClient.hpp"
|
||||
|
@ -508,6 +510,23 @@ PubSub::~PubSub()
|
|||
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)
|
||||
{
|
||||
this->token_ = account->getOAuthToken();
|
||||
|
|
|
@ -3,15 +3,21 @@
|
|||
#include "providers/twitch/PubSubClientOptions.hpp"
|
||||
#include "providers/twitch/PubSubWebsocket.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 <QJsonObject>
|
||||
#include <QString>
|
||||
#include <websocketpp/client.hpp>
|
||||
#include <websocketpp/common/connection_hdl.hpp>
|
||||
#include <websocketpp/common/memory.hpp>
|
||||
#include <websocketpp/config/asio_client.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -19,6 +25,10 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include(<gtest/gtest_prod.h>)
|
||||
# include <gtest/gtest_prod.h>
|
||||
#endif
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchAccount;
|
||||
|
@ -85,10 +95,8 @@ public:
|
|||
PubSub &operator=(const PubSub &) = delete;
|
||||
PubSub &operator=(PubSub &&) = delete;
|
||||
|
||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
/// Set up connections between itself & other parts of the application
|
||||
void initialize();
|
||||
|
||||
struct {
|
||||
Signal<ClearChatAction> chatCleared;
|
||||
|
@ -192,6 +200,11 @@ public:
|
|||
} diag;
|
||||
|
||||
private:
|
||||
void setAccount(std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Unlistens to all topics matching the prefix in all clients
|
||||
*/
|
||||
|
@ -250,6 +263,22 @@ private:
|
|||
const PubSubClientOptions clientOptions_;
|
||||
|
||||
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
|
||||
|
|
|
@ -1320,8 +1320,6 @@ void TwitchChannel::refreshPubSub()
|
|||
|
||||
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||
|
||||
getApp()->getTwitchPubSub()->setAccount(currentAccount);
|
||||
|
||||
getApp()->getTwitchPubSub()->listenToChannelModerationActions(roomId);
|
||||
if (this->hasModRights())
|
||||
{
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
#include "providers/seventv/SeventvEventAPI.hpp"
|
||||
#include "providers/twitch/api/Helix.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/TwitchChannel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/StreamerMode.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "util/RatelimitBucket.hpp"
|
||||
|
||||
|
@ -230,6 +234,441 @@ void TwitchIrcServer::initialize()
|
|||
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,
|
||||
|
|
|
@ -202,8 +202,6 @@ private:
|
|||
std::queue<std::chrono::steady_clock::time_point> lastMessageMod_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeSpeed_;
|
||||
std::chrono::steady_clock::time_point lastErrorTimeAmount_;
|
||||
|
||||
pajlada::Signals::SignalHolder signalHolder_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
# include <QStyleHints>
|
||||
#endif
|
||||
#include <QApplication>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
@ -301,8 +302,9 @@ Theme::Theme(const Paths &paths)
|
|||
this->loadAvailableThemes(paths);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
QObject::connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged,
|
||||
&this->lifetime_, [this] {
|
||||
QObject::connect(QApplication::styleHints(),
|
||||
&QStyleHints::colorSchemeChanged, &this->lifetime_,
|
||||
[this] {
|
||||
if (this->isSystemTheme())
|
||||
{
|
||||
this->update();
|
||||
|
@ -320,7 +322,7 @@ void Theme::update()
|
|||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
if (this->isSystemTheme())
|
||||
{
|
||||
switch (qApp->styleHints()->colorScheme())
|
||||
switch (QApplication::styleHints()->colorScheme())
|
||||
{
|
||||
case Qt::ColorScheme::Light:
|
||||
return this->lightSystemThemeName;
|
||||
|
|
|
@ -46,12 +46,18 @@ const QString CHATTERINO_OS = u"unknown"_s;
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
Updates::Updates(const Paths &paths_)
|
||||
Updates::Updates(const Paths &paths_, Settings &settings)
|
||||
: paths(paths_)
|
||||
, currentVersion_(CHATTERINO_VERSION)
|
||||
, updateGuideLink_("https://chatterino.com")
|
||||
{
|
||||
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.
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Paths;
|
||||
class Settings;
|
||||
|
||||
/**
|
||||
* To check for updates, use the `checkForUpdates` method.
|
||||
|
@ -16,7 +21,7 @@ class Updates
|
|||
const Paths &paths;
|
||||
|
||||
public:
|
||||
explicit Updates(const Paths &paths_);
|
||||
Updates(const Paths &paths_, Settings &settings);
|
||||
|
||||
enum Status {
|
||||
None,
|
||||
|
@ -59,6 +64,9 @@ private:
|
|||
QString updateGuideLink_;
|
||||
|
||||
void setStatus_(Status status);
|
||||
|
||||
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||
managedConnections;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -664,8 +664,6 @@ IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor)
|
|||
{
|
||||
assertInGuiThread();
|
||||
|
||||
auto *app = getApp();
|
||||
|
||||
if (descriptor.type_ == "twitch")
|
||||
{
|
||||
return getApp()->getTwitch()->getOrAddChannel(descriptor.channelName_);
|
||||
|
@ -753,7 +751,7 @@ void WindowManager::applyWindowLayout(const WindowLayout &layout)
|
|||
// get geometry
|
||||
{
|
||||
// out of bounds windows
|
||||
auto screens = qApp->screens();
|
||||
auto screens = QApplication::screens();
|
||||
bool outOfBounds =
|
||||
!qEnvironmentVariableIsSet("I3SOCK") &&
|
||||
std::none_of(screens.begin(), screens.end(),
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void GIFTimer::initialize()
|
||||
|
@ -24,7 +26,7 @@ void GIFTimer::initialize()
|
|||
|
||||
QObject::connect(&this->timer, &QTimer::timeout, [this] {
|
||||
if (getSettings()->animationsWhenFocused &&
|
||||
qApp->activeWindow() == nullptr)
|
||||
QApplication::activeWindow() == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ QString formatTime(int totalSeconds)
|
|||
return res;
|
||||
}
|
||||
|
||||
QString formatTime(QString totalSecondsString)
|
||||
QString formatTime(const QString &totalSecondsString)
|
||||
{
|
||||
bool ok = true;
|
||||
int totalSeconds(totalSecondsString.toInt(&ok));
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
|
||||
// format: 1h 23m 42s
|
||||
QString formatTime(int totalSeconds);
|
||||
QString formatTime(QString totalSecondsString);
|
||||
QString formatTime(const QString &totalSecondsString);
|
||||
QString formatTime(std::chrono::seconds totalSeconds);
|
||||
|
||||
} // 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
|
||||
// Qt 5/4 - preferred, has least allocations
|
||||
template <typename F>
|
||||
static void postToThread(F &&fun, QObject *obj = qApp)
|
||||
static void postToThread(F &&fun, QObject *obj = QCoreApplication::instance())
|
||||
{
|
||||
struct Event : public QEvent {
|
||||
using Fun = typename std::decay<F>::type;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
|
@ -75,7 +76,7 @@ void FramelessEmbedWindow::showEvent(QShowEvent *)
|
|||
auto handle = reinterpret_cast<HWND>(this->winId());
|
||||
if (!::SetParent(handle, parentHwnd))
|
||||
{
|
||||
qApp->exit(1);
|
||||
QApplication::exit(1);
|
||||
}
|
||||
|
||||
QJsonDocument doc;
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "widgets/Window.hpp"
|
||||
|
||||
#include <magic_enum/magic_enum_flags.hpp>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QColor>
|
||||
#include <QDate>
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
#include <QStringList>
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
TEST(ChatterSet, insert)
|
||||
{
|
||||
chatterino::ChatterSet set;
|
||||
ChatterSet set;
|
||||
|
||||
EXPECT_FALSE(set.contains("pajlada"));
|
||||
EXPECT_FALSE(set.contains("Pajlada"));
|
||||
|
@ -26,7 +28,7 @@ TEST(ChatterSet, insert)
|
|||
|
||||
TEST(ChatterSet, MaxSize)
|
||||
{
|
||||
chatterino::ChatterSet set;
|
||||
ChatterSet set;
|
||||
|
||||
EXPECT_FALSE(set.contains("pajlada"));
|
||||
EXPECT_FALSE(set.contains("Pajlada"));
|
||||
|
@ -36,7 +38,7 @@ TEST(ChatterSet, MaxSize)
|
|||
EXPECT_TRUE(set.contains("Pajlada"));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
@ -53,7 +55,7 @@ TEST(ChatterSet, MaxSize)
|
|||
|
||||
TEST(ChatterSet, MaxSizeLastUsed)
|
||||
{
|
||||
chatterino::ChatterSet set;
|
||||
ChatterSet set;
|
||||
|
||||
EXPECT_FALSE(set.contains("pajlada"));
|
||||
EXPECT_FALSE(set.contains("Pajlada"));
|
||||
|
@ -63,7 +65,7 @@ TEST(ChatterSet, MaxSizeLastUsed)
|
|||
EXPECT_TRUE(set.contains("Pajlada"));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
@ -75,7 +77,7 @@ TEST(ChatterSet, MaxSizeLastUsed)
|
|||
set.addRecentChatter("pajlada");
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
using namespace chatterino;
|
||||
using namespace std::chrono_literals;
|
||||
|
@ -33,6 +32,8 @@ using namespace std::chrono_literals;
|
|||
|
||||
#ifdef RUN_PUBSUB_TESTS
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class ReceivedMessage
|
||||
{
|
||||
|
@ -451,4 +452,6 @@ TEST(TwitchPubSubClient, AutoModMessageHeld)
|
|||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue