mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
changed to 80 max column
This commit is contained in:
parent
defa7e41fa
commit
f71ff08e68
203 changed files with 3792 additions and 2405 deletions
|
@ -1,23 +1,14 @@
|
||||||
IndentCaseLabels: true
|
|
||||||
BasedOnStyle: Google
|
|
||||||
IndentWidth: 4
|
|
||||||
Standard: Auto
|
|
||||||
PointerBindsToType: false
|
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
SpacesBeforeTrailingComments: 2
|
|
||||||
AccessModifierOffset: -1
|
AccessModifierOffset: -1
|
||||||
AlignEscapedNewlinesLeft: true
|
|
||||||
AlwaysBreakAfterDefinitionReturnType: false
|
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
|
||||||
BreakConstructorInitializersBeforeComma: true
|
|
||||||
# BreakBeforeBraces: Linux
|
|
||||||
BreakBeforeBraces: Custom
|
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
AlignEscapedNewlinesLeft: true
|
||||||
AllowShortFunctionsOnASingleLine: false
|
AllowShortFunctionsOnASingleLine: false
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
AllowShortLoopsOnASingleLine: false
|
AllowShortLoopsOnASingleLine: false
|
||||||
DerivePointerBinding: false
|
AlwaysBreakAfterDefinitionReturnType: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
BasedOnStyle: Google
|
||||||
BraceWrapping: {
|
BraceWrapping: {
|
||||||
AfterNamespace: 'false'
|
AfterNamespace: 'false'
|
||||||
AfterClass: 'true'
|
AfterClass: 'true'
|
||||||
|
@ -26,5 +17,14 @@ BraceWrapping: {
|
||||||
AfterFunction: 'true'
|
AfterFunction: 'true'
|
||||||
BeforeCatch: 'false'
|
BeforeCatch: 'false'
|
||||||
}
|
}
|
||||||
ColumnLimit: 100
|
BreakBeforeBraces: Custom
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
DerivePointerBinding: false
|
||||||
FixNamespaceComments: true
|
FixNamespaceComments: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
PointerBindsToType: false
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
Standard: Auto
|
||||||
|
|
|
@ -30,7 +30,8 @@ static std::atomic<bool> isAppInitialized{false};
|
||||||
Application *Application::instance = nullptr;
|
Application *Application::instance = nullptr;
|
||||||
|
|
||||||
// this class is responsible for handling the workflow of Chatterino
|
// this class is responsible for handling the workflow of Chatterino
|
||||||
// It will create the instances of the major classes, and connect their signals to each other
|
// It will create the instances of the major classes, and connect their signals
|
||||||
|
// to each other
|
||||||
|
|
||||||
Application::Application(Settings &_settings, Paths &_paths)
|
Application::Application(Settings &_settings, Paths &_paths)
|
||||||
: settings(&_settings)
|
: settings(&_settings)
|
||||||
|
@ -53,7 +54,8 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
{
|
{
|
||||||
this->instance = this;
|
this->instance = this;
|
||||||
|
|
||||||
this->fonts->fontChanged.connect([this]() { this->windows->layoutChannelViews(); });
|
this->fonts->fontChanged.connect(
|
||||||
|
[this]() { this->windows->layoutChannelViews(); });
|
||||||
|
|
||||||
this->twitch.server = this->twitch2;
|
this->twitch.server = this->twitch2;
|
||||||
this->twitch.pubsub = this->twitch2->pubsub;
|
this->twitch.pubsub = this->twitch2->pubsub;
|
||||||
|
@ -117,40 +119,49 @@ void Application::initPubsub()
|
||||||
Log("WHISPER RECEIVED LOL"); //
|
Log("WHISPER RECEIVED LOL"); //
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.chatCleared.connect([this](const auto &action) {
|
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
||||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
[this](const auto &action) {
|
||||||
if (chan->isEmpty()) {
|
auto chan =
|
||||||
return;
|
this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||||
}
|
if (chan->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QString text = QString("%1 cleared the chat").arg(action.source.name);
|
QString text =
|
||||||
|
QString("%1 cleared the chat").arg(action.source.name);
|
||||||
|
|
||||||
auto msg = Message::createSystemMessage(text);
|
auto msg = Message::createSystemMessage(text);
|
||||||
postToThread([chan, msg] { chan->addMessage(msg); });
|
postToThread([chan, msg] { chan->addMessage(msg); });
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.modeChanged.connect([this](const auto &action) {
|
this->twitch.pubsub->signals_.moderation.modeChanged.connect(
|
||||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
[this](const auto &action) {
|
||||||
if (chan->isEmpty()) {
|
auto chan =
|
||||||
return;
|
this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||||
}
|
if (chan->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QString text = QString("%1 turned %2 %3 mode") //
|
QString text =
|
||||||
.arg(action.source.name)
|
QString("%1 turned %2 %3 mode") //
|
||||||
.arg(action.state == ModeChangedAction::State::On ? "on" : "off")
|
.arg(action.source.name)
|
||||||
.arg(action.getModeName());
|
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||||
|
: "off")
|
||||||
|
.arg(action.getModeName());
|
||||||
|
|
||||||
if (action.duration > 0) {
|
if (action.duration > 0) {
|
||||||
text.append(" (" + QString::number(action.duration) + " seconds)");
|
text.append(" (" + QString::number(action.duration) +
|
||||||
}
|
" seconds)");
|
||||||
|
}
|
||||||
|
|
||||||
auto msg = Message::createSystemMessage(text);
|
auto msg = Message::createSystemMessage(text);
|
||||||
postToThread([chan, msg] { chan->addMessage(msg); });
|
postToThread([chan, msg] { chan->addMessage(msg); });
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect(
|
this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect(
|
||||||
[this](const auto &action) {
|
[this](const auto &action) {
|
||||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
auto chan =
|
||||||
|
this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||||
if (chan->isEmpty()) {
|
if (chan->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -158,48 +169,55 @@ void Application::initPubsub()
|
||||||
QString text;
|
QString text;
|
||||||
|
|
||||||
if (action.modded) {
|
if (action.modded) {
|
||||||
text = QString("%1 modded %2").arg(action.source.name, action.target.name);
|
text = QString("%1 modded %2")
|
||||||
|
.arg(action.source.name, action.target.name);
|
||||||
} else {
|
} else {
|
||||||
text = QString("%1 unmodded %2").arg(action.source.name, action.target.name);
|
text = QString("%1 unmodded %2")
|
||||||
|
.arg(action.source.name, action.target.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = Message::createSystemMessage(text);
|
auto msg = Message::createSystemMessage(text);
|
||||||
postToThread([chan, msg] { chan->addMessage(msg); });
|
postToThread([chan, msg] { chan->addMessage(msg); });
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.userBanned.connect([&](const auto &action) {
|
this->twitch.pubsub->signals_.moderation.userBanned.connect(
|
||||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
[&](const auto &action) {
|
||||||
|
auto chan =
|
||||||
|
this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
if (chan->isEmpty()) {
|
if (chan->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = Message::createTimeoutMessage(action);
|
auto msg = Message::createTimeoutMessage(action);
|
||||||
msg->flags |= Message::PubSub;
|
msg->flags |= Message::PubSub;
|
||||||
|
|
||||||
postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
|
postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.userUnbanned.connect([&](const auto &action) {
|
this->twitch.pubsub->signals_.moderation.userUnbanned.connect(
|
||||||
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
[&](const auto &action) {
|
||||||
|
auto chan =
|
||||||
|
this->twitch.server->getChannelOrEmptyByID(action.roomID);
|
||||||
|
|
||||||
if (chan->isEmpty()) {
|
if (chan->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = Message::createUntimeoutMessage(action);
|
auto msg = Message::createUntimeoutMessage(action);
|
||||||
|
|
||||||
postToThread([chan, msg] { chan->addMessage(msg); });
|
postToThread([chan, msg] { chan->addMessage(msg); });
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->start();
|
this->twitch.pubsub->start();
|
||||||
|
|
||||||
auto RequestModerationActions = [=]() {
|
auto RequestModerationActions = [=]() {
|
||||||
this->twitch.server->pubsub->unlistenAllModerationActions();
|
this->twitch.server->pubsub->unlistenAllModerationActions();
|
||||||
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
|
// TODO(pajlada): Unlisten to all authed topics instead of only
|
||||||
// this->twitch.pubsub->UnlistenAllAuthedTopics();
|
// moderation topics this->twitch.pubsub->UnlistenAllAuthedTopics();
|
||||||
|
|
||||||
this->twitch.server->pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
|
this->twitch.server->pubsub->listenToWhispers(
|
||||||
|
this->accounts->twitch.getCurrent()); //
|
||||||
};
|
};
|
||||||
|
|
||||||
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
|
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
|
||||||
|
|
|
@ -77,7 +77,8 @@ private:
|
||||||
void initPubsub();
|
void initPubsub();
|
||||||
void initNm();
|
void initNm();
|
||||||
|
|
||||||
template <typename T, typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
|
template <typename T,
|
||||||
|
typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
|
||||||
T &emplace()
|
T &emplace()
|
||||||
{
|
{
|
||||||
auto t = new T;
|
auto t = new T;
|
||||||
|
|
|
@ -54,15 +54,16 @@ void runLoop(NativeMessagingClient &client)
|
||||||
std::cin.read(b.get(), size);
|
std::cin.read(b.get(), size);
|
||||||
*(b.get() + size) = '\0';
|
*(b.get() + size) = '\0';
|
||||||
|
|
||||||
client.sendMessage(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
|
client.sendMessage(
|
||||||
|
QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool shouldRunBrowserExtensionHost(const QStringList &args)
|
bool shouldRunBrowserExtensionHost(const QStringList &args)
|
||||||
{
|
{
|
||||||
return args.size() > 0 &&
|
return args.size() > 0 && (args[0].startsWith("chrome-extension://") ||
|
||||||
(args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"));
|
args[0].endsWith(".json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void runBrowserExtensionHost()
|
void runBrowserExtensionHost()
|
||||||
|
|
|
@ -32,23 +32,28 @@ void installCustomPalette()
|
||||||
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
|
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
|
||||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
|
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText,
|
||||||
|
QColor(127, 127, 127));
|
||||||
darkPalette.setColor(QPalette::Base, QColor("#333"));
|
darkPalette.setColor(QPalette::Base, QColor("#333"));
|
||||||
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
|
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
|
||||||
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
|
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
|
darkPalette.setColor(QPalette::Disabled, QPalette::Text,
|
||||||
|
QColor(127, 127, 127));
|
||||||
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
|
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
|
||||||
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
|
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
|
||||||
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
|
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
|
||||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
|
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText,
|
||||||
|
QColor(127, 127, 127));
|
||||||
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
||||||
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||||
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||||
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
|
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight,
|
||||||
|
QColor(80, 80, 80));
|
||||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||||
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
|
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText,
|
||||||
|
QColor(127, 127, 127));
|
||||||
|
|
||||||
qApp->setPalette(darkPalette);
|
qApp->setPalette(darkPalette);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +111,8 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Running file
|
// Running file
|
||||||
auto runningPath = paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
|
auto runningPath =
|
||||||
|
paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
|
||||||
|
|
||||||
if (QFile::exists(runningPath)) {
|
if (QFile::exists(runningPath)) {
|
||||||
showLastCrashDialog();
|
showLastCrashDialog();
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Resources2 : public Singleton {
|
class Resources2 : public Singleton
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Resources2();
|
Resources2();
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,10 @@ Channel::Channel(const QString &name, Type type)
|
||||||
, name_(name)
|
, name_(name)
|
||||||
, type_(type)
|
, type_(type)
|
||||||
{
|
{
|
||||||
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
|
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
|
||||||
this->completionModel.clearExpiredStrings(); //
|
[this]() {
|
||||||
});
|
this->completionModel.clearExpiredStrings(); //
|
||||||
|
});
|
||||||
this->clearCompletionModelTimer_.start(60 * 1000);
|
this->clearCompletionModelTimer_.start(60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,8 @@ void Channel::addMessage(MessagePtr message)
|
||||||
|
|
||||||
const QString &username = message->loginName;
|
const QString &username = message->loginName;
|
||||||
if (!username.isEmpty()) {
|
if (!username.isEmpty()) {
|
||||||
// TODO: Add recent chatters display name. This should maybe be a setting
|
// TODO: Add recent chatters display name. This should maybe be a
|
||||||
|
// setting
|
||||||
this->addRecentChatter(message);
|
this->addRecentChatter(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,17 +103,21 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
|
if (s->flags.HasFlag(Message::Untimeout) &&
|
||||||
|
s->timeoutUser == message->timeoutUser) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
|
if (s->flags.HasFlag(Message::Timeout) &&
|
||||||
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
|
s->timeoutUser == message->timeoutUser) {
|
||||||
|
if (message->flags.HasFlag(Message::PubSub) &&
|
||||||
|
!s->flags.HasFlag(Message::PubSub)) {
|
||||||
this->replaceMessage(s, message);
|
this->replaceMessage(s, message);
|
||||||
addMessage = false;
|
addMessage = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
|
if (!message->flags.HasFlag(Message::PubSub) &&
|
||||||
|
s->flags.HasFlag(Message::PubSub)) {
|
||||||
addMessage = false;
|
addMessage = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +125,8 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
||||||
int count = s->count + 1;
|
int count = s->count + 1;
|
||||||
|
|
||||||
MessagePtr replacement(Message::createSystemMessage(
|
MessagePtr replacement(Message::createSystemMessage(
|
||||||
message->searchText + QString(" (") + QString::number(count) + " times)"));
|
message->searchText + QString(" (") + QString::number(count) +
|
||||||
|
" times)"));
|
||||||
|
|
||||||
replacement->timeoutUser = message->timeoutUser;
|
replacement->timeoutUser = message->timeoutUser;
|
||||||
replacement->count = count;
|
replacement->count = count;
|
||||||
|
@ -164,7 +171,8 @@ void Channel::disableAllMessages()
|
||||||
|
|
||||||
void Channel::addMessagesAtStart(std::vector<MessagePtr> &_messages)
|
void Channel::addMessagesAtStart(std::vector<MessagePtr> &_messages)
|
||||||
{
|
{
|
||||||
std::vector<MessagePtr> addedMessages = this->messages_.pushFront(_messages);
|
std::vector<MessagePtr> addedMessages =
|
||||||
|
this->messages_.pushFront(_messages);
|
||||||
|
|
||||||
if (addedMessages.size() != 0) {
|
if (addedMessages.size() != 0) {
|
||||||
this->messagesAddedAtStart.invoke(addedMessages);
|
this->messagesAddedAtStart.invoke(addedMessages);
|
||||||
|
|
|
@ -32,7 +32,8 @@ public:
|
||||||
explicit Channel(const QString &name, Type type);
|
explicit Channel(const QString &name, Type type);
|
||||||
virtual ~Channel();
|
virtual ~Channel();
|
||||||
|
|
||||||
pajlada::Signals::Signal<const QString &, const QString &, bool &> sendMessageSignal;
|
pajlada::Signals::Signal<const QString &, const QString &, bool &>
|
||||||
|
sendMessageSignal;
|
||||||
|
|
||||||
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
|
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
|
||||||
pajlada::Signals::Signal<MessagePtr &> messageAppended;
|
pajlada::Signals::Signal<MessagePtr &> messageAppended;
|
||||||
|
@ -96,7 +97,8 @@ class IndirectChannel
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IndirectChannel(ChannelPtr channel, Channel::Type type = Channel::Type::Direct)
|
IndirectChannel(ChannelPtr channel,
|
||||||
|
Channel::Type type = Channel::Type::Direct)
|
||||||
: data_(new Data(channel, type))
|
: data_(new Data(channel, type))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,10 @@ inline QString qS(const std::string &string)
|
||||||
return QString::fromStdString(string);
|
return QString::fromStdString(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
|
const Qt::KeyboardModifiers showSplitOverlayModifiers =
|
||||||
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
|
Qt::ControlModifier | Qt::AltModifier;
|
||||||
|
const Qt::KeyboardModifiers showAddSplitRegions =
|
||||||
|
Qt::ControlModifier | Qt::AltModifier;
|
||||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||||
|
|
||||||
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
|
||||||
|
|
|
@ -111,26 +111,32 @@ void CompletionModel::refresh()
|
||||||
// User-specific: Twitch Emotes
|
// User-specific: Twitch Emotes
|
||||||
if (auto account = app->accounts->twitch.getCurrent()) {
|
if (auto account = app->accounts->twitch.getCurrent()) {
|
||||||
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
|
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
|
||||||
// XXX: No way to discern between a twitch global emote and sub emote right now
|
// XXX: No way to discern between a twitch global emote and sub
|
||||||
this->addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
// emote right now
|
||||||
|
this->addString(emote.string,
|
||||||
|
TaggedString::Type::TwitchGlobalEmote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Global: BTTV Global Emotes
|
// // Global: BTTV Global Emotes
|
||||||
// std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteNames_;
|
// std::vector<QString> &bttvGlobalEmoteCodes =
|
||||||
// for (const auto &m : bttvGlobalEmoteCodes) {
|
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
|
||||||
|
// bttvGlobalEmoteCodes) {
|
||||||
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
|
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Global: FFZ Global Emotes
|
// // Global: FFZ Global Emotes
|
||||||
// std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
|
// std::vector<QString> &ffzGlobalEmoteCodes =
|
||||||
// for (const auto &m : ffzGlobalEmoteCodes) {
|
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
|
||||||
|
// ffzGlobalEmoteCodes) {
|
||||||
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
|
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Channel emotes
|
// Channel emotes
|
||||||
if (auto channel = dynamic_cast<TwitchChannel *>(
|
if (auto channel = dynamic_cast<TwitchChannel *>(
|
||||||
getApp()->twitch2->getChannelOrEmptyByID(this->channelName_).get())) {
|
getApp()
|
||||||
|
->twitch2->getChannelOrEmptyByID(this->channelName_)
|
||||||
|
.get())) {
|
||||||
auto bttv = channel->accessBttvEmotes();
|
auto bttv = channel->accessBttvEmotes();
|
||||||
// auto it = bttv->begin();
|
// auto it = bttv->begin();
|
||||||
// for (const auto &emote : *bttv) {
|
// for (const auto &emote : *bttv) {
|
||||||
|
@ -143,7 +149,8 @@ void CompletionModel::refresh()
|
||||||
|
|
||||||
// Channel-specific: FFZ Channel Emotes
|
// Channel-specific: FFZ Channel Emotes
|
||||||
for (const auto &emote : *channel->accessFfzEmotes()) {
|
for (const auto &emote : *channel->accessFfzEmotes()) {
|
||||||
this->addString(emote.second->name.string, TaggedString::Type::FFZChannelEmote);
|
this->addString(emote.second->name.string,
|
||||||
|
TaggedString::Type::FFZChannelEmote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +171,8 @@ void CompletionModel::refresh()
|
||||||
|
|
||||||
// Channel-specific: Usernames
|
// Channel-specific: Usernames
|
||||||
// fourtf: only works with twitch chat
|
// fourtf: only works with twitch chat
|
||||||
// auto c = ChannelManager::getInstance().getTwitchChannel(this->channelName);
|
// auto c =
|
||||||
|
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
|
||||||
// auto usernames = c->getUsernamesForCompletions();
|
// auto usernames = c->getUsernamesForCompletions();
|
||||||
// for (const auto &name : usernames) {
|
// for (const auto &name : usernames) {
|
||||||
// assert(!name.displayName.isEmpty());
|
// assert(!name.displayName.isEmpty());
|
||||||
|
@ -191,9 +199,11 @@ void CompletionModel::addUser(const QString &username)
|
||||||
auto add = [this](const QString &str) {
|
auto add = [this](const QString &str) {
|
||||||
auto ts = this->createUser(str + " ");
|
auto ts = this->createUser(str + " ");
|
||||||
// Always add a space at the end of completions
|
// Always add a space at the end of completions
|
||||||
std::pair<std::set<TaggedString>::iterator, bool> p = this->emotes_.insert(ts);
|
std::pair<std::set<TaggedString>::iterator, bool> p =
|
||||||
|
this->emotes_.insert(ts);
|
||||||
if (!p.second) {
|
if (!p.second) {
|
||||||
// No inseration was made, figure out if we need to replace the username.
|
// No inseration was made, figure out if we need to replace the
|
||||||
|
// username.
|
||||||
|
|
||||||
if (p.first->str > ts.str) {
|
if (p.first->str > ts.str) {
|
||||||
// Replace lowercase version of name with mixed-case version
|
// Replace lowercase version of name with mixed-case version
|
||||||
|
|
|
@ -65,7 +65,8 @@ public:
|
||||||
this->data.insert(name, value);
|
this->data.insert(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void each(std::function<void(const TKey &name, const TValue &value)> func) const
|
void each(
|
||||||
|
std::function<void(const TKey &name, const TValue &value)> func) const
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&this->mutex);
|
QMutexLocker lock(&this->mutex);
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace chatterino {
|
||||||
// bool isValid() const;
|
// bool isValid() const;
|
||||||
// Image *getImage(float scale) const;
|
// Image *getImage(float scale) const;
|
||||||
|
|
||||||
// // Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
|
// // Link to the emote page i.e.
|
||||||
// QString pageLink;
|
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
|
||||||
|
|
||||||
// Image *image1x = nullptr;
|
// Image *image1x = nullptr;
|
||||||
// Image *image2x = nullptr;
|
// Image *image2x = nullptr;
|
||||||
|
|
|
@ -45,9 +45,11 @@ LinkParser::LinkParser(const QString &unparsedString)
|
||||||
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
|
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
|
||||||
"|"
|
"|"
|
||||||
// host name
|
// host name
|
||||||
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+)"
|
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+"
|
||||||
|
")"
|
||||||
// domain name
|
// domain name
|
||||||
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+)*"
|
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-"
|
||||||
|
"9]+)*"
|
||||||
// TLD identifier
|
// TLD identifier
|
||||||
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
|
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
|
||||||
"(?:[\\.](?:" +
|
"(?:[\\.](?:" +
|
||||||
|
@ -61,7 +63,8 @@ LinkParser::LinkParser(const QString &unparsedString)
|
||||||
"(?:[/?#]\\S*)?"
|
"(?:[/?#]\\S*)?"
|
||||||
"$";
|
"$";
|
||||||
|
|
||||||
return QRegularExpression(hyperlinkRegExp, QRegularExpression::CaseInsensitiveOption);
|
return QRegularExpression(hyperlinkRegExp,
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
this->match_ = linkRegex.match(unparsedString);
|
this->match_ = linkRegex.match(unparsedString);
|
||||||
|
|
|
@ -30,7 +30,8 @@ QString NetworkData::getHash()
|
||||||
bytes.append(header);
|
bytes.append(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray hashBytes(QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
|
QByteArray hashBytes(
|
||||||
|
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
|
||||||
|
|
||||||
this->hash_ = hashBytes.toHex();
|
this->hash_ = hashBytes.toHex();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
NetworkRequest::NetworkRequest(const std::string &url, NetworkRequestType requestType)
|
NetworkRequest::NetworkRequest(const std::string &url,
|
||||||
|
NetworkRequestType requestType)
|
||||||
: data(new NetworkData)
|
: data(new NetworkData)
|
||||||
, timer(new NetworkTimer)
|
, timer(new NetworkTimer)
|
||||||
{
|
{
|
||||||
|
@ -62,7 +63,8 @@ void NetworkRequest::setRawHeader(const char *headerName, const char *value)
|
||||||
this->data->request_.setRawHeader(headerName, value);
|
this->data->request_.setRawHeader(headerName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkRequest::setRawHeader(const char *headerName, const QByteArray &value)
|
void NetworkRequest::setRawHeader(const char *headerName,
|
||||||
|
const QByteArray &value)
|
||||||
{
|
{
|
||||||
this->data->request_.setRawHeader(headerName, value);
|
this->data->request_.setRawHeader(headerName, value);
|
||||||
}
|
}
|
||||||
|
@ -77,7 +79,8 @@ void NetworkRequest::setTimeout(int ms)
|
||||||
this->timer->timeoutMS_ = ms;
|
this->timer->timeoutMS_ = ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkRequest::makeAuthorizedV5(const QString &clientID, const QString &oauthToken)
|
void NetworkRequest::makeAuthorizedV5(const QString &clientID,
|
||||||
|
const QString &oauthToken)
|
||||||
{
|
{
|
||||||
this->setRawHeader("Client-ID", clientID);
|
this->setRawHeader("Client-ID", clientID);
|
||||||
this->setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
|
this->setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
|
||||||
|
@ -114,12 +117,14 @@ void NetworkRequest::execute()
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case NetworkRequestType::Put: {
|
case NetworkRequestType::Put: {
|
||||||
// Put requests cannot be cached, therefore the request is called immediately
|
// Put requests cannot be cached, therefore the request is called
|
||||||
|
// immediately
|
||||||
this->doRequest();
|
this->doRequest();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case NetworkRequestType::Delete: {
|
case NetworkRequestType::Delete: {
|
||||||
// Delete requests cannot be cached, therefore the request is called immediately
|
// Delete requests cannot be cached, therefore the request is called
|
||||||
|
// immediately
|
||||||
this->doRequest();
|
this->doRequest();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
@ -152,7 +157,8 @@ Outcome NetworkRequest::tryLoadCachedFile()
|
||||||
|
|
||||||
cachedFile.close();
|
cachedFile.close();
|
||||||
|
|
||||||
// XXX: If success is false, we should invalidate the cache file somehow/somewhere
|
// XXX: If success is false, we should invalidate the cache file
|
||||||
|
// somehow/somewhere
|
||||||
|
|
||||||
return outcome;
|
return outcome;
|
||||||
}
|
}
|
||||||
|
@ -166,14 +172,16 @@ void NetworkRequest::doRequest()
|
||||||
|
|
||||||
this->timer->start();
|
this->timer->start();
|
||||||
|
|
||||||
auto onUrlRequested = [data = this->data, timer = this->timer, worker]() mutable {
|
auto onUrlRequested = [data = this->data, timer = this->timer,
|
||||||
|
worker]() mutable {
|
||||||
auto reply = [&]() -> QNetworkReply * {
|
auto reply = [&]() -> QNetworkReply * {
|
||||||
switch (data->requestType_) {
|
switch (data->requestType_) {
|
||||||
case NetworkRequestType::Get:
|
case NetworkRequestType::Get:
|
||||||
return NetworkManager::NaM.get(data->request_);
|
return NetworkManager::NaM.get(data->request_);
|
||||||
|
|
||||||
case NetworkRequestType::Put:
|
case NetworkRequestType::Put:
|
||||||
return NetworkManager::NaM.put(data->request_, data->payload_);
|
return NetworkManager::NaM.put(data->request_,
|
||||||
|
data->payload_);
|
||||||
|
|
||||||
case NetworkRequestType::Delete:
|
case NetworkRequestType::Delete:
|
||||||
return NetworkManager::NaM.deleteResource(data->request_);
|
return NetworkManager::NaM.deleteResource(data->request_);
|
||||||
|
@ -221,12 +229,14 @@ void NetworkRequest::doRequest()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data->caller_ != nullptr) {
|
if (data->caller_ != nullptr) {
|
||||||
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, handleReply);
|
QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_,
|
||||||
QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable {
|
handleReply);
|
||||||
emit worker->doneUrl();
|
QObject::connect(reply, &QNetworkReply::finished, worker,
|
||||||
|
[worker]() mutable {
|
||||||
|
emit worker->doneUrl();
|
||||||
|
|
||||||
delete worker;
|
delete worker;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
QObject::connect(reply, &QNetworkReply::finished, worker,
|
QObject::connect(reply, &QNetworkReply::finished, worker,
|
||||||
[handleReply, worker]() mutable {
|
[handleReply, worker]() mutable {
|
||||||
|
@ -237,7 +247,8 @@ void NetworkRequest::doRequest()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker, onUrlRequested);
|
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
||||||
|
onUrlRequested);
|
||||||
|
|
||||||
emit requester.requestUrl();
|
emit requester.requestUrl();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,18 @@ namespace chatterino {
|
||||||
|
|
||||||
class NetworkRequest
|
class NetworkRequest
|
||||||
{
|
{
|
||||||
// Stores all data about the request that needs to be passed around to each part of the request
|
// Stores all data about the request that needs to be passed around to each
|
||||||
|
// part of the request
|
||||||
std::shared_ptr<NetworkData> data;
|
std::shared_ptr<NetworkData> data;
|
||||||
|
|
||||||
// Timer that tracks the timeout
|
// Timer that tracks the timeout
|
||||||
// By default, there's no explicit timeout for the request
|
// By default, there's no explicit timeout for the request
|
||||||
// to enable the timer, the "setTimeout" function needs to be called before execute is called
|
// to enable the timer, the "setTimeout" function needs to be called before
|
||||||
|
// execute is called
|
||||||
std::shared_ptr<NetworkTimer> timer;
|
std::shared_ptr<NetworkTimer> timer;
|
||||||
|
|
||||||
// The NetworkRequest destructor will assert if executed_ hasn't been set to true before dying
|
// The NetworkRequest destructor will assert if executed_ hasn't been set to
|
||||||
|
// true before dying
|
||||||
bool executed_ = false;
|
bool executed_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -31,9 +34,11 @@ public:
|
||||||
NetworkRequest(NetworkRequest &&other) = default;
|
NetworkRequest(NetworkRequest &&other) = default;
|
||||||
NetworkRequest &operator=(NetworkRequest &&other) = default;
|
NetworkRequest &operator=(NetworkRequest &&other) = default;
|
||||||
|
|
||||||
explicit NetworkRequest(const std::string &url,
|
explicit NetworkRequest(
|
||||||
NetworkRequestType requestType = NetworkRequestType::Get);
|
const std::string &url,
|
||||||
explicit NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
|
NetworkRequestType requestType = NetworkRequestType::Get);
|
||||||
|
explicit NetworkRequest(
|
||||||
|
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
|
||||||
|
|
||||||
~NetworkRequest();
|
~NetworkRequest();
|
||||||
|
|
||||||
|
@ -50,14 +55,15 @@ public:
|
||||||
void setRawHeader(const char *headerName, const QByteArray &value);
|
void setRawHeader(const char *headerName, const QByteArray &value);
|
||||||
void setRawHeader(const char *headerName, const QString &value);
|
void setRawHeader(const char *headerName, const QString &value);
|
||||||
void setTimeout(int ms);
|
void setTimeout(int ms);
|
||||||
void makeAuthorizedV5(const QString &clientID, const QString &oauthToken = QString());
|
void makeAuthorizedV5(const QString &clientID,
|
||||||
|
const QString &oauthToken = QString());
|
||||||
|
|
||||||
void execute();
|
void execute();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Returns true if the file was successfully loaded from cache
|
// Returns true if the file was successfully loaded from cache
|
||||||
// Returns false if the cache file either didn't exist, or it contained "invalid" data
|
// Returns false if the cache file either didn't exist, or it contained
|
||||||
// "invalid" is specified by the onSuccess callback
|
// "invalid" data "invalid" is specified by the onSuccess callback
|
||||||
Outcome tryLoadCachedFile();
|
Outcome tryLoadCachedFile();
|
||||||
|
|
||||||
void doRequest();
|
void doRequest();
|
||||||
|
|
|
@ -27,11 +27,12 @@ rapidjson::Document NetworkResult::parseRapidJson() const
|
||||||
{
|
{
|
||||||
rapidjson::Document ret(rapidjson::kObjectType);
|
rapidjson::Document ret(rapidjson::kObjectType);
|
||||||
|
|
||||||
rapidjson::ParseResult result = ret.Parse(this->data_.data(), this->data_.length());
|
rapidjson::ParseResult result =
|
||||||
|
ret.Parse(this->data_.data(), this->data_.length());
|
||||||
|
|
||||||
if (result.Code() != rapidjson::kParseErrorNone) {
|
if (result.Code() != rapidjson::kParseErrorNone) {
|
||||||
Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
|
Log("JSON parse error: {} ({})",
|
||||||
result.Offset());
|
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ bool NetworkTimer::isStarted() const
|
||||||
return this->started_;
|
return this->started_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTimer::onTimeout(NetworkWorker *worker, std::function<void()> cb) const
|
void NetworkTimer::onTimeout(NetworkWorker *worker,
|
||||||
|
std::function<void()> cb) const
|
||||||
{
|
{
|
||||||
assert(this->timer_ != nullptr);
|
assert(this->timer_ != nullptr);
|
||||||
assert(worker != nullptr);
|
assert(worker != nullptr);
|
||||||
|
|
|
@ -59,7 +59,8 @@ public:
|
||||||
return !this->hasElement();
|
return !this->hasElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename X = T, typename = std::enable_if_t<!std::is_const<X>::value>>
|
template <typename X = T,
|
||||||
|
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||||
operator NullablePtr<const T>() const
|
operator NullablePtr<const T>() const
|
||||||
{
|
{
|
||||||
return NullablePtr<const T>(this->element_);
|
return NullablePtr<const T>(this->element_);
|
||||||
|
|
|
@ -8,7 +8,8 @@ namespace Settings {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct Serialize<QString> {
|
struct Serialize<QString> {
|
||||||
static rapidjson::Value get(const QString &value, rapidjson::Document::AllocatorType &a)
|
static rapidjson::Value get(const QString &value,
|
||||||
|
rapidjson::Document::AllocatorType &a)
|
||||||
{
|
{
|
||||||
return rapidjson::Value(value.toUtf8(), a);
|
return rapidjson::Value(value.toUtf8(), a);
|
||||||
}
|
}
|
||||||
|
@ -20,12 +21,14 @@ struct Deserialize<QString> {
|
||||||
{
|
{
|
||||||
if (!value.IsString()) {
|
if (!value.IsString()) {
|
||||||
PAJLADA_REPORT_ERROR(error)
|
PAJLADA_REPORT_ERROR(error)
|
||||||
PAJLADA_THROW_EXCEPTION("Deserialized rapidjson::Value is not a string");
|
PAJLADA_THROW_EXCEPTION(
|
||||||
|
"Deserialized rapidjson::Value is not a string");
|
||||||
return QString{};
|
return QString{};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return QString::fromUtf8(value.GetString(), value.GetStringLength());
|
return QString::fromUtf8(value.GetString(),
|
||||||
|
value.GetStringLength());
|
||||||
} catch (const std::exception &) {
|
} catch (const std::exception &) {
|
||||||
// int x = 5;
|
// int x = 5;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
|
|
@ -88,7 +88,8 @@ template <typename TVectorItem>
|
||||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual int insertItem(const TVectorItem &item, int index = -1, void *caller = nullptr) override
|
virtual int insertItem(const TVectorItem &item, int index = -1,
|
||||||
|
void *caller = nullptr) override
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
|
@ -115,11 +116,13 @@ template <typename TVectorItem, typename Compare>
|
||||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual int insertItem(const TVectorItem &item, int = -1, void *caller = nullptr) override
|
virtual int insertItem(const TVectorItem &item, int = -1,
|
||||||
|
void *caller = nullptr) override
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
auto it = std::lower_bound(this->vector_.begin(), this->vector_.end(), item, Compare{});
|
auto it = std::lower_bound(this->vector_.begin(), this->vector_.end(),
|
||||||
|
item, Compare{});
|
||||||
int index = it - this->vector_.begin();
|
int index = it - this->vector_.begin();
|
||||||
this->vector_.insert(it, item);
|
this->vector_.insert(it, item);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename TVectorItem>
|
template <typename TVectorItem>
|
||||||
class SignalVectorModel : public QAbstractTableModel, pajlada::Signals::SignalHolder
|
class SignalVectorModel : public QAbstractTableModel,
|
||||||
|
pajlada::Signals::SignalHolder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
SignalVectorModel(int columnCount, QObject *parent = nullptr)
|
||||||
|
@ -43,7 +44,8 @@ public:
|
||||||
index = this->beforeInsert(args.item, row, index);
|
index = this->beforeInsert(args.item, row, index);
|
||||||
|
|
||||||
this->beginInsertRows(QModelIndex(), index, index);
|
this->beginInsertRows(QModelIndex(), index, index);
|
||||||
this->rows_.insert(this->rows_.begin() + index, Row(row, args.item));
|
this->rows_.insert(this->rows_.begin() + index,
|
||||||
|
Row(row, args.item));
|
||||||
this->endInsertRows();
|
this->endInsertRows();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,7 +67,8 @@ public:
|
||||||
assert(row >= 0 && row <= this->rows_.size());
|
assert(row >= 0 && row <= this->rows_.size());
|
||||||
|
|
||||||
// remove row
|
// remove row
|
||||||
std::vector<QStandardItem *> items = std::move(this->rows_[row].items);
|
std::vector<QStandardItem *> items =
|
||||||
|
std::move(this->rows_[row].items);
|
||||||
|
|
||||||
this->beginRemoveRows(QModelIndex(), row, row);
|
this->beginRemoveRows(QModelIndex(), row, row);
|
||||||
this->rows_.erase(this->rows_.begin() + row);
|
this->rows_.erase(this->rows_.begin() + row);
|
||||||
|
@ -103,15 +106,18 @@ public:
|
||||||
QVariant data(const QModelIndex &index, int role) const override
|
QVariant data(const QModelIndex &index, int role) const override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_);
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
|
column < this->columnCount_);
|
||||||
|
|
||||||
return rows_[row].items[column]->data(role);
|
return rows_[row].items[column]->data(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override
|
bool setData(const QModelIndex &index, const QVariant &value,
|
||||||
|
int role) override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_);
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
|
column < this->columnCount_);
|
||||||
|
|
||||||
Row &rowItem = this->rows_[row];
|
Row &rowItem = this->rows_[row];
|
||||||
|
|
||||||
|
@ -124,15 +130,16 @@ public:
|
||||||
this->vector_->removeItem(vecRow, this);
|
this->vector_->removeItem(vecRow, this);
|
||||||
|
|
||||||
assert(this->rows_[row].original);
|
assert(this->rows_[row].original);
|
||||||
TVectorItem item =
|
TVectorItem item = this->getItemFromRow(
|
||||||
this->getItemFromRow(this->rows_[row].items, this->rows_[row].original.get());
|
this->rows_[row].items, this->rows_[row].original.get());
|
||||||
this->vector_->insertItem(item, vecRow, this);
|
this->vector_->insertItem(item, vecRow, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
|
int role) const override
|
||||||
{
|
{
|
||||||
if (orientation != Qt::Horizontal) {
|
if (orientation != Qt::Horizontal) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
@ -146,7 +153,8 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value,
|
bool setHeaderData(int section, Qt::Orientation orientation,
|
||||||
|
const QVariant &value,
|
||||||
int role = Qt::DisplayRole) override
|
int role = Qt::DisplayRole) override
|
||||||
{
|
{
|
||||||
if (orientation != Qt::Horizontal) {
|
if (orientation != Qt::Horizontal) {
|
||||||
|
@ -162,14 +170,16 @@ public:
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||||
{
|
{
|
||||||
int row = index.row(), column = index.column();
|
int row = index.row(), column = index.column();
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_);
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
|
column < this->columnCount_);
|
||||||
|
|
||||||
return this->rows_[index.row()].items[index.column()]->flags();
|
return this->rows_[index.row()].items[index.column()]->flags();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStandardItem *getItem(int row, int column)
|
QStandardItem *getItem(int row, int column)
|
||||||
{
|
{
|
||||||
assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_);
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
|
column < this->columnCount_);
|
||||||
|
|
||||||
return rows_[row].items[column];
|
return rows_[row].items[column];
|
||||||
}
|
}
|
||||||
|
@ -204,20 +214,23 @@ protected:
|
||||||
const TVectorItem &original) = 0;
|
const TVectorItem &original) = 0;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0;
|
virtual void getRowFromItem(const TVectorItem &item,
|
||||||
|
std::vector<QStandardItem *> &row) = 0;
|
||||||
|
|
||||||
virtual int beforeInsert(const TVectorItem &item, std::vector<QStandardItem *> &row,
|
virtual int beforeInsert(const TVectorItem &item,
|
||||||
|
std::vector<QStandardItem *> &row,
|
||||||
int proposedIndex)
|
int proposedIndex)
|
||||||
{
|
{
|
||||||
return proposedIndex;
|
return proposedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void afterRemoved(const TVectorItem &item, std::vector<QStandardItem *> &row, int index)
|
virtual void afterRemoved(const TVectorItem &item,
|
||||||
|
std::vector<QStandardItem *> &row, int index)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
const QVariant &value, int role)
|
int column, const QVariant &value, int role)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +239,8 @@ protected:
|
||||||
assert(index >= 0 && index <= this->rows_.size());
|
assert(index >= 0 && index <= this->rows_.size());
|
||||||
|
|
||||||
this->beginInsertRows(QModelIndex(), index, index);
|
this->beginInsertRows(QModelIndex(), index, index);
|
||||||
this->rows_.insert(this->rows_.begin() + index, Row(std::move(row), true));
|
this->rows_.insert(this->rows_.begin() + index,
|
||||||
|
Row(std::move(row), true));
|
||||||
this->endInsertRows();
|
this->endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ namespace chatterino {
|
||||||
AccountController::AccountController()
|
AccountController::AccountController()
|
||||||
{
|
{
|
||||||
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
||||||
this->accounts_.insertItem(std::dynamic_pointer_cast<Account>(args.item));
|
this->accounts_.insertItem(
|
||||||
|
std::dynamic_pointer_cast<Account>(args.item));
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||||
|
|
|
@ -28,7 +28,8 @@ public:
|
||||||
TwitchAccountManager twitch;
|
TwitchAccountManager twitch;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>> accounts_;
|
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
|
||||||
|
accounts_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -10,8 +10,8 @@ AccountModel::AccountModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
|
std::shared_ptr<Account> AccountModel::getItemFromRow(
|
||||||
const std::shared_ptr<Account> &original)
|
std::vector<QStandardItem *> &, const std::shared_ptr<Account> &original)
|
||||||
{
|
{
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,8 @@ void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
|
||||||
}
|
}
|
||||||
|
|
||||||
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
|
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
|
||||||
std::vector<QStandardItem *> &row, int proposedIndex)
|
std::vector<QStandardItem *> &row,
|
||||||
|
int proposedIndex)
|
||||||
{
|
{
|
||||||
if (this->categoryCount_[item->getCategory()]++ == 0) {
|
if (this->categoryCount_[item->getCategory()]++ == 0) {
|
||||||
auto row = this->createRow();
|
auto row = this->createRow();
|
||||||
|
|
|
@ -18,17 +18,20 @@ public:
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual std::shared_ptr<Account> getItemFromRow(
|
virtual std::shared_ptr<Account> getItemFromRow(
|
||||||
std::vector<QStandardItem *> &row, const std::shared_ptr<Account> &original) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
const std::shared_ptr<Account> &original) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
|
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
virtual int beforeInsert(const std::shared_ptr<Account> &item,
|
virtual int beforeInsert(const std::shared_ptr<Account> &item,
|
||||||
std::vector<QStandardItem *> &row, int proposedIndex) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
int proposedIndex) override;
|
||||||
|
|
||||||
virtual void afterRemoved(const std::shared_ptr<Account> &item,
|
virtual void afterRemoved(const std::shared_ptr<Account> &item,
|
||||||
std::vector<QStandardItem *> &row, int index) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
int index) override;
|
||||||
|
|
||||||
friend class AccountController;
|
friend class AccountController;
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#define TWITCH_DEFAULT_COMMANDS \
|
#define TWITCH_DEFAULT_COMMANDS \
|
||||||
{ \
|
{ \
|
||||||
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", "/unban", "/timeout", \
|
"/help", "/w", "/me", "/disconnect", "/mods", "/color", "/ban", \
|
||||||
"/untimeout", "/slow", "/slowoff", "/r9kbeta", "/r9kbetaoff", "/emoteonly", \
|
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
|
||||||
"/emoteonlyoff", "/clear", "/subscribers", "/subscribersoff", "/followers", \
|
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
|
||||||
"/followersoff" \
|
"/clear", "/subscribers", "/subscribersoff", "/followers", \
|
||||||
|
"/followersoff" \
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -77,7 +78,8 @@ void CommandController::save()
|
||||||
{
|
{
|
||||||
QFile textFile(this->filePath_);
|
QFile textFile(this->filePath_);
|
||||||
if (!textFile.open(QIODevice::WriteOnly)) {
|
if (!textFile.open(QIODevice::WriteOnly)) {
|
||||||
Log("[CommandController::saveCommands] Unable to open {} for writing", this->filePath_);
|
Log("[CommandController::saveCommands] Unable to open {} for writing",
|
||||||
|
this->filePath_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +98,8 @@ CommandModel *CommandController::createModel(QObject *parent)
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CommandController::execCommand(const QString &text, ChannelPtr channel, bool dryRun)
|
QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
|
bool dryRun)
|
||||||
{
|
{
|
||||||
QStringList words = text.split(' ', QString::SkipEmptyParts);
|
QStringList words = text.split(' ', QString::SkipEmptyParts);
|
||||||
Command command;
|
Command command;
|
||||||
|
@ -122,11 +125,13 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
MessageBuilder b;
|
MessageBuilder b;
|
||||||
|
|
||||||
b.emplace<TimestampElement>();
|
b.emplace<TimestampElement>();
|
||||||
b.emplace<TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
|
b.emplace<TextElement>(
|
||||||
MessageElement::Text, MessageColor::Text,
|
app->accounts->twitch.getCurrent()->getUserName(),
|
||||||
FontStyle::ChatMediumBold);
|
MessageElement::Text, MessageColor::Text,
|
||||||
|
FontStyle::ChatMediumBold);
|
||||||
b.emplace<TextElement>("->", MessageElement::Text);
|
b.emplace<TextElement>("->", MessageElement::Text);
|
||||||
b.emplace<TextElement>(words[1] + ":", MessageElement::Text, MessageColor::Text,
|
b.emplace<TextElement>(words[1] + ":", MessageElement::Text,
|
||||||
|
MessageColor::Text,
|
||||||
FontStyle::ChatMediumBold);
|
FontStyle::ChatMediumBold);
|
||||||
|
|
||||||
QString rest = "";
|
QString rest = "";
|
||||||
|
@ -144,7 +149,9 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
|
|
||||||
if (getSettings()->inlineWhispers) {
|
if (getSettings()->inlineWhispers) {
|
||||||
app->twitch.server->forEachChannel(
|
app->twitch.server->forEachChannel(
|
||||||
[&b](ChannelPtr _channel) { _channel->addMessage(b.getMessage()); });
|
[&b](ChannelPtr _channel) {
|
||||||
|
_channel->addMessage(b.getMessage());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
@ -165,15 +172,17 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
} else if (commandName == "/uptime") {
|
} else if (commandName == "/uptime") {
|
||||||
const auto &streamStatus = twitchChannel->accessStreamStatus();
|
const auto &streamStatus = twitchChannel->accessStreamStatus();
|
||||||
|
|
||||||
QString messageText =
|
QString messageText = streamStatus->live
|
||||||
streamStatus->live ? streamStatus->uptime : "Channel is not live.";
|
? streamStatus->uptime
|
||||||
|
: "Channel is not live.";
|
||||||
|
|
||||||
channel->addMessage(Message::createSystemMessage(messageText));
|
channel->addMessage(Message::createSystemMessage(messageText));
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
} else if (commandName == "/ignore") {
|
} else if (commandName == "/ignore") {
|
||||||
if (words.size() < 2) {
|
if (words.size() < 2) {
|
||||||
channel->addMessage(Message::createSystemMessage("Usage: /ignore [user]"));
|
channel->addMessage(
|
||||||
|
Message::createSystemMessage("Usage: /ignore [user]"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -182,19 +191,21 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
auto target = words.at(1);
|
auto target = words.at(1);
|
||||||
|
|
||||||
if (user->isAnon()) {
|
if (user->isAnon()) {
|
||||||
channel->addMessage(
|
channel->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("You must be logged in to ignore someone"));
|
"You must be logged in to ignore someone"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
user->ignore(target, [channel](auto resultCode, const QString &message) {
|
user->ignore(target, [channel](auto resultCode,
|
||||||
|
const QString &message) {
|
||||||
channel->addMessage(Message::createSystemMessage(message));
|
channel->addMessage(Message::createSystemMessage(message));
|
||||||
});
|
});
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
} else if (commandName == "/unignore") {
|
} else if (commandName == "/unignore") {
|
||||||
if (words.size() < 2) {
|
if (words.size() < 2) {
|
||||||
channel->addMessage(Message::createSystemMessage("Usage: /unignore [user]"));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
|
"Usage: /unignore [user]"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -203,19 +214,21 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
auto target = words.at(1);
|
auto target = words.at(1);
|
||||||
|
|
||||||
if (user->isAnon()) {
|
if (user->isAnon()) {
|
||||||
channel->addMessage(
|
channel->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("You must be logged in to ignore someone"));
|
"You must be logged in to ignore someone"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
user->unignore(target, [channel](auto resultCode, const QString &message) {
|
user->unignore(target, [channel](auto resultCode,
|
||||||
|
const QString &message) {
|
||||||
channel->addMessage(Message::createSystemMessage(message));
|
channel->addMessage(Message::createSystemMessage(message));
|
||||||
});
|
});
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
} else if (commandName == "/follow") {
|
} else if (commandName == "/follow") {
|
||||||
if (words.size() < 2) {
|
if (words.size() < 2) {
|
||||||
channel->addMessage(Message::createSystemMessage("Usage: /follow [user]"));
|
channel->addMessage(
|
||||||
|
Message::createSystemMessage("Usage: /follow [user]"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -224,27 +237,29 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
auto target = words.at(1);
|
auto target = words.at(1);
|
||||||
|
|
||||||
if (user->isAnon()) {
|
if (user->isAnon()) {
|
||||||
channel->addMessage(
|
channel->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("You must be logged in to follow someone"));
|
"You must be logged in to follow someone"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchApi::findUserId(target, [user, channel, target](QString userId) {
|
TwitchApi::findUserId(
|
||||||
if (userId.isEmpty()) {
|
target, [user, channel, target](QString userId) {
|
||||||
channel->addMessage(Message::createSystemMessage(
|
if (userId.isEmpty()) {
|
||||||
"User " + target + " could not be followed!"));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
return;
|
"User " + target + " could not be followed!"));
|
||||||
}
|
return;
|
||||||
user->followUser(userId, [channel, target]() {
|
}
|
||||||
channel->addMessage(
|
user->followUser(userId, [channel, target]() {
|
||||||
Message::createSystemMessage("You successfully followed " + target));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
|
"You successfully followed " + target));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
} else if (commandName == "/unfollow") {
|
} else if (commandName == "/unfollow") {
|
||||||
if (words.size() < 2) {
|
if (words.size() < 2) {
|
||||||
channel->addMessage(Message::createSystemMessage("Usage: /unfollow [user]"));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
|
"Usage: /unfollow [user]"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -253,28 +268,29 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
auto target = words.at(1);
|
auto target = words.at(1);
|
||||||
|
|
||||||
if (user->isAnon()) {
|
if (user->isAnon()) {
|
||||||
channel->addMessage(
|
channel->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("You must be logged in to follow someone"));
|
"You must be logged in to follow someone"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchApi::findUserId(target, [user, channel, target](QString userId) {
|
TwitchApi::findUserId(
|
||||||
if (userId.isEmpty()) {
|
target, [user, channel, target](QString userId) {
|
||||||
channel->addMessage(Message::createSystemMessage(
|
if (userId.isEmpty()) {
|
||||||
"User " + target + " could not be followed!"));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
return;
|
"User " + target + " could not be followed!"));
|
||||||
}
|
return;
|
||||||
user->unfollowUser(userId, [channel, target]() {
|
}
|
||||||
channel->addMessage(
|
user->unfollowUser(userId, [channel, target]() {
|
||||||
Message::createSystemMessage("You successfully unfollowed " + target));
|
channel->addMessage(Message::createSystemMessage(
|
||||||
|
"You successfully unfollowed " + target));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
} else if (commandName == "/logs") {
|
} else if (commandName == "/logs") {
|
||||||
if (words.size() < 2) {
|
if (words.size() < 2) {
|
||||||
channel->addMessage(
|
channel->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("Usage: /logs [user] (channel)"));
|
"Usage: /logs [user] (channel)"));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -293,7 +309,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
if (words.at(2).at(0) == "#") {
|
if (words.at(2).at(0) == "#") {
|
||||||
channelName = words.at(2).mid(1);
|
channelName = words.at(2).mid(1);
|
||||||
}
|
}
|
||||||
auto logsChannel = app->twitch.server->getChannelOrEmpty(channelName);
|
auto logsChannel =
|
||||||
|
app->twitch.server->getChannelOrEmpty(channelName);
|
||||||
if (logsChannel == nullptr) {
|
if (logsChannel == nullptr) {
|
||||||
} else {
|
} else {
|
||||||
logs->setInfo(logsChannel, target);
|
logs->setInfo(logsChannel, target);
|
||||||
|
@ -319,7 +336,8 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||||
return this->execCustomCommand(words, command);
|
return this->execCustomCommand(words, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CommandController::execCustomCommand(const QStringList &words, const Command &command)
|
QString CommandController::execCustomCommand(const QStringList &words,
|
||||||
|
const Command &command)
|
||||||
{
|
{
|
||||||
QString result;
|
QString result;
|
||||||
|
|
||||||
|
@ -331,13 +349,15 @@ QString CommandController::execCustomCommand(const QStringList &words, const Com
|
||||||
int matchOffset = 0;
|
int matchOffset = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
QRegularExpressionMatch match = parseCommand.match(command.func, matchOffset);
|
QRegularExpressionMatch match =
|
||||||
|
parseCommand.match(command.func, matchOffset);
|
||||||
|
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result += command.func.mid(lastCaptureEnd, match.capturedStart() - lastCaptureEnd + 1);
|
result += command.func.mid(lastCaptureEnd,
|
||||||
|
match.capturedStart() - lastCaptureEnd + 1);
|
||||||
|
|
||||||
lastCaptureEnd = match.capturedEnd();
|
lastCaptureEnd = match.capturedEnd();
|
||||||
matchOffset = lastCaptureEnd - 1;
|
matchOffset = lastCaptureEnd - 1;
|
||||||
|
|
|
@ -22,7 +22,8 @@ class CommandController final : public Singleton
|
||||||
public:
|
public:
|
||||||
CommandController();
|
CommandController();
|
||||||
|
|
||||||
QString execCommand(const QString &text, std::shared_ptr<Channel> channel, bool dryRun);
|
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||||
|
bool dryRun);
|
||||||
QStringList getDefaultTwitchCommandList();
|
QStringList getDefaultTwitchCommandList();
|
||||||
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||||
|
|
|
@ -9,18 +9,23 @@ CommandModel::CommandModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, const Command &original)
|
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||||
|
const Command &original)
|
||||||
{
|
{
|
||||||
return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString());
|
return Command(row[0]->data(Qt::EditRole).toString(),
|
||||||
|
row[1]->data(Qt::EditRole).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void CommandModel::getRowFromItem(const Command &item, std::vector<QStandardItem *> &row)
|
void CommandModel::getRowFromItem(const Command &item,
|
||||||
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
row[0]->setData(item.name, Qt::DisplayRole);
|
row[0]->setData(item.name, Qt::DisplayRole);
|
||||||
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||||
|
Qt::ItemIsEditable);
|
||||||
row[1]->setData(item.func, Qt::DisplayRole);
|
row[1]->setData(item.func, Qt::DisplayRole);
|
||||||
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
||||||
|
Qt::ItemIsEditable);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -19,7 +19,8 @@ protected:
|
||||||
const Command &command) override;
|
const Command &command) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;
|
virtual void getRowFromItem(const Command &item,
|
||||||
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class CommandController;
|
friend class CommandController;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,8 +15,9 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual HighlightBlacklistUser getItemFromRow(std::vector<QStandardItem *> &row,
|
virtual HighlightBlacklistUser getItemFromRow(
|
||||||
const HighlightBlacklistUser &original) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
const HighlightBlacklistUser &original) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const HighlightBlacklistUser &item,
|
virtual void getRowFromItem(const HighlightBlacklistUser &item,
|
||||||
|
|
|
@ -16,14 +16,16 @@ class HighlightBlacklistUser
|
||||||
public:
|
public:
|
||||||
bool operator==(const HighlightBlacklistUser &other) const
|
bool operator==(const HighlightBlacklistUser &other) const
|
||||||
{
|
{
|
||||||
return std::tie(this->pattern_, this->isRegex_) == std::tie(other.pattern_, other.isRegex_);
|
return std::tie(this->pattern_, this->isRegex_) ==
|
||||||
|
std::tie(other.pattern_, other.isRegex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightBlacklistUser(const QString &pattern, bool isRegex = false)
|
HighlightBlacklistUser(const QString &pattern, bool isRegex = false)
|
||||||
: pattern_(pattern)
|
: pattern_(pattern)
|
||||||
, isRegex_(isRegex)
|
, isRegex_(isRegex)
|
||||||
, regex_(isRegex ? pattern : "", QRegularExpression::CaseInsensitiveOption |
|
, regex_(isRegex ? pattern : "",
|
||||||
QRegularExpression::UseUnicodePropertiesOption)
|
QRegularExpression::CaseInsensitiveOption |
|
||||||
|
QRegularExpression::UseUnicodePropertiesOption)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,14 @@ void HighlightController::initialize(Settings &settings, Paths &paths)
|
||||||
this->highlightsSetting_.setValue(this->phrases.getVector());
|
this->highlightsSetting_.setValue(this->phrases.getVector());
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const HighlightBlacklistUser &blacklistedUser : this->blacklistSetting_.getValue()) {
|
for (const HighlightBlacklistUser &blacklistedUser :
|
||||||
|
this->blacklistSetting_.getValue()) {
|
||||||
this->blacklistedUsers.appendItem(blacklistedUser);
|
this->blacklistedUsers.appendItem(blacklistedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->blacklistedUsers.delayedItemsChanged.connect(
|
this->blacklistedUsers.delayedItemsChanged.connect([this] {
|
||||||
[this] { this->blacklistSetting_.setValue(this->blacklistedUsers.getVector()); });
|
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightModel *HighlightController::createModel(QObject *parent)
|
HighlightModel *HighlightController::createModel(QObject *parent)
|
||||||
|
@ -61,7 +63,8 @@ bool HighlightController::isHighlightedUser(const QString &username)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightBlacklistModel *HighlightController::createBlacklistModel(QObject *parent)
|
HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
||||||
|
QObject *parent)
|
||||||
{
|
{
|
||||||
auto *model = new HighlightBlacklistModel(parent);
|
auto *model = new HighlightBlacklistModel(parent);
|
||||||
model->init(&this->blacklistedUsers);
|
model->init(&this->blacklistedUsers);
|
||||||
|
@ -71,7 +74,8 @@ HighlightBlacklistModel *HighlightController::createBlacklistModel(QObject *pare
|
||||||
|
|
||||||
bool HighlightController::blacklistContains(const QString &username)
|
bool HighlightController::blacklistContains(const QString &username)
|
||||||
{
|
{
|
||||||
std::vector<HighlightBlacklistUser> blacklistItems = this->blacklistedUsers.getVector();
|
std::vector<HighlightBlacklistUser> blacklistItems =
|
||||||
|
this->blacklistedUsers.getVector();
|
||||||
for (const auto &blacklistedUser : blacklistItems) {
|
for (const auto &blacklistedUser : blacklistItems) {
|
||||||
if (blacklistedUser.isMatch(username)) {
|
if (blacklistedUser.isMatch(username)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -13,18 +13,20 @@ HighlightModel::HighlightModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
HighlightPhrase HighlightModel::getItemFromRow(
|
||||||
const HighlightPhrase &original)
|
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||||
{
|
{
|
||||||
// key, alert, sound, regex
|
// key, alert, sound, regex
|
||||||
|
|
||||||
return HighlightPhrase{
|
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||||
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
|
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||||
row[2]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool()};
|
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[3]->data(Qt::CheckStateRole).toBool()};
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector<QStandardItem *> &row)
|
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||||
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getPattern());
|
setStringItem(row[0], item.getPattern());
|
||||||
setBoolItem(row[1], item.getAlert());
|
setBoolItem(row[1], item.getAlert());
|
||||||
|
@ -35,31 +37,38 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item, std::vector<QSt
|
||||||
void HighlightModel::afterInit()
|
void HighlightModel::afterInit()
|
||||||
{
|
{
|
||||||
std::vector<QStandardItem *> row = this->createRow();
|
std::vector<QStandardItem *> row = this->createRow();
|
||||||
setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true, false);
|
setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(),
|
||||||
|
true, false);
|
||||||
row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||||
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
|
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(),
|
||||||
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(), true, false);
|
true, false);
|
||||||
|
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
|
||||||
|
true, false);
|
||||||
row[3]->setFlags(0);
|
row[3]->setFlags(0);
|
||||||
this->insertCustomRow(row, 0);
|
this->insertCustomRow(row, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
const QVariant &value, int role)
|
int column, const QVariant &value,
|
||||||
|
int role)
|
||||||
{
|
{
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case 0: {
|
case 0: {
|
||||||
if (role == Qt::CheckStateRole) {
|
if (role == Qt::CheckStateRole) {
|
||||||
getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
|
getApp()->settings->enableHighlightsSelf.setValue(
|
||||||
|
value.toBool());
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 1: {
|
case 1: {
|
||||||
if (role == Qt::CheckStateRole) {
|
if (role == Qt::CheckStateRole) {
|
||||||
getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
|
getApp()->settings->enableHighlightTaskbar.setValue(
|
||||||
|
value.toBool());
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 2: {
|
case 2: {
|
||||||
if (role == Qt::CheckStateRole) {
|
if (role == Qt::CheckStateRole) {
|
||||||
getApp()->settings->enableHighlightSound.setValue(value.toBool());
|
getApp()->settings->enableHighlightSound.setValue(
|
||||||
|
value.toBool());
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 3: {
|
case 3: {
|
||||||
|
|
|
@ -15,8 +15,9 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
virtual HighlightPhrase getItemFromRow(
|
||||||
const HighlightPhrase &original) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
const HighlightPhrase &original) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||||
|
@ -24,8 +25,9 @@ protected:
|
||||||
|
|
||||||
virtual void afterInit() override;
|
virtual void afterInit() override;
|
||||||
|
|
||||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
const QVariant &value, int role) override;
|
int column, const QVariant &value,
|
||||||
|
int role) override;
|
||||||
|
|
||||||
friend class HighlightController;
|
friend class HighlightController;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,16 +14,20 @@ class HighlightPhrase
|
||||||
public:
|
public:
|
||||||
bool operator==(const HighlightPhrase &other) const
|
bool operator==(const HighlightPhrase &other) const
|
||||||
{
|
{
|
||||||
return std::tie(this->pattern_, this->sound_, this->alert_, this->isRegex_) ==
|
return std::tie(this->pattern_, this->sound_, this->alert_,
|
||||||
std::tie(other.pattern_, other.sound_, other.alert_, other.isRegex_);
|
this->isRegex_) == std::tie(other.pattern_,
|
||||||
|
other.sound_, other.alert_,
|
||||||
|
other.isRegex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightPhrase(const QString &pattern, bool alert, bool sound, bool isRegex)
|
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||||
|
bool isRegex)
|
||||||
: pattern_(pattern)
|
: pattern_(pattern)
|
||||||
, alert_(alert)
|
, alert_(alert)
|
||||||
, sound_(sound)
|
, sound_(sound)
|
||||||
, isRegex_(isRegex)
|
, isRegex_(isRegex)
|
||||||
, regex_(isRegex_ ? pattern : "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
, regex_(isRegex_ ? pattern
|
||||||
|
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||||
QRegularExpression::CaseInsensitiveOption |
|
QRegularExpression::CaseInsensitiveOption |
|
||||||
QRegularExpression::UseUnicodePropertiesOption)
|
QRegularExpression::UseUnicodePropertiesOption)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,14 +13,15 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn vector item into model row
|
// turn vector item into model row
|
||||||
HighlightPhrase UserHighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
HighlightPhrase UserHighlightModel::getItemFromRow(
|
||||||
const HighlightPhrase &original)
|
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||||
{
|
{
|
||||||
// key, regex
|
// key, regex
|
||||||
|
|
||||||
return HighlightPhrase{
|
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||||
row[0]->data(Qt::DisplayRole).toString(), row[1]->data(Qt::CheckStateRole).toBool(),
|
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||||
row[2]->data(Qt::CheckStateRole).toBool(), row[3]->data(Qt::CheckStateRole).toBool()};
|
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[3]->data(Qt::CheckStateRole).toBool()};
|
||||||
}
|
}
|
||||||
|
|
||||||
// row into vector item
|
// row into vector item
|
||||||
|
|
|
@ -15,8 +15,9 @@ class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// vector into model row
|
// vector into model row
|
||||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
virtual HighlightPhrase getItemFromRow(
|
||||||
const HighlightPhrase &original) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
const HighlightPhrase &original) override;
|
||||||
|
|
||||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
|
@ -25,7 +25,8 @@ public:
|
||||||
private:
|
private:
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
|
||||||
ChatterinoSetting<std::vector<IgnorePhrase>> ignoresSetting_ = {"/ignore/phrases"};
|
ChatterinoSetting<std::vector<IgnorePhrase>> ignoresSetting_ = {
|
||||||
|
"/ignore/phrases"};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -23,7 +23,8 @@ IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void IgnoreModel::getRowFromItem(const IgnorePhrase &item, std::vector<QStandardItem *> &row)
|
void IgnoreModel::getRowFromItem(const IgnorePhrase &item,
|
||||||
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getPattern());
|
setStringItem(row[0], item.getPattern());
|
||||||
setBoolItem(row[1], item.isRegex());
|
setBoolItem(row[1], item.isRegex());
|
||||||
|
|
|
@ -16,13 +16,15 @@ class IgnorePhrase
|
||||||
public:
|
public:
|
||||||
bool operator==(const IgnorePhrase &other) const
|
bool operator==(const IgnorePhrase &other) const
|
||||||
{
|
{
|
||||||
return std::tie(this->pattern_, this->isRegex_) == std::tie(other.pattern_, other.isRegex_);
|
return std::tie(this->pattern_, this->isRegex_) ==
|
||||||
|
std::tie(other.pattern_, other.isRegex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
IgnorePhrase(const QString &pattern, bool isRegex)
|
IgnorePhrase(const QString &pattern, bool isRegex)
|
||||||
: pattern_(pattern)
|
: pattern_(pattern)
|
||||||
, isRegex_(isRegex)
|
, isRegex_(isRegex)
|
||||||
, regex_(isRegex_ ? pattern : "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
, regex_(isRegex_ ? pattern
|
||||||
|
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||||
QRegularExpression::CaseInsensitiveOption |
|
QRegularExpression::CaseInsensitiveOption |
|
||||||
QRegularExpression::UseUnicodePropertiesOption)
|
QRegularExpression::UseUnicodePropertiesOption)
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace chatterino {
|
||||||
//{
|
//{
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// ModerationAction::ModerationAction(const QString &_line1, const QString &_line2,
|
// ModerationAction::ModerationAction(const QString &_line1, const QString
|
||||||
|
// &_line2,
|
||||||
// const QString &_action)
|
// const QString &_action)
|
||||||
// : _isImage(false)
|
// : _isImage(false)
|
||||||
// , image(nullptr)
|
// , image(nullptr)
|
||||||
|
@ -55,10 +56,12 @@ ModerationAction::ModerationAction(const QString &action)
|
||||||
// line1 = this->line1_;
|
// line1 = this->line1_;
|
||||||
// line2 = this->line2_;
|
// line2 = this->line2_;
|
||||||
// } else {
|
// } else {
|
||||||
// this->_moderationActions.emplace_back(app->resources->buttonTimeout, str);
|
// this->_moderationActions.emplace_back(app->resources->buttonTimeout,
|
||||||
|
// str);
|
||||||
// }
|
// }
|
||||||
} else if (action.startsWith("/ban ")) {
|
} else if (action.startsWith("/ban ")) {
|
||||||
this->image_ = Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
|
this->image_ =
|
||||||
|
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
|
||||||
} else {
|
} else {
|
||||||
QString xD = action;
|
QString xD = action;
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ ModerationActionModel ::ModerationActionModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
ModerationAction ModerationActionModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
ModerationAction ModerationActionModel::getItemFromRow(
|
||||||
const ModerationAction &original)
|
std::vector<QStandardItem *> &row, const ModerationAction &original)
|
||||||
{
|
{
|
||||||
return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
|
return ModerationAction(row[0]->data(Qt::DisplayRole).toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual ModerationAction getItemFromRow(std::vector<QStandardItem *> &row,
|
virtual ModerationAction getItemFromRow(
|
||||||
const ModerationAction &original) override;
|
std::vector<QStandardItem *> &row,
|
||||||
|
const ModerationAction &original) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const ModerationAction &item,
|
virtual void getRowFromItem(const ModerationAction &item,
|
||||||
|
|
|
@ -25,7 +25,8 @@ public:
|
||||||
ModerationActionModel *createModel(QObject *parent);
|
ModerationActionModel *createModel(QObject *parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {"/moderation/actions"};
|
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {
|
||||||
|
"/moderation/actions"};
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
TaggedUser::TaggedUser(ProviderId provider, const QString &name, const QString &id)
|
TaggedUser::TaggedUser(ProviderId provider, const QString &name,
|
||||||
|
const QString &id)
|
||||||
: providerId_(provider)
|
: providerId_(provider)
|
||||||
, name_(name)
|
, name_(name)
|
||||||
, id_(id)
|
, id_(id)
|
||||||
|
|
|
@ -19,7 +19,8 @@ TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row)
|
void TaggedUsersModel::getRowFromItem(const TaggedUser &item,
|
||||||
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getName());
|
setStringItem(row[0], item.getName());
|
||||||
}
|
}
|
||||||
|
@ -27,14 +28,18 @@ void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStand
|
||||||
void TaggedUsersModel::afterInit()
|
void TaggedUsersModel::afterInit()
|
||||||
{
|
{
|
||||||
// std::vector<QStandardItem *> row = this->createRow();
|
// std::vector<QStandardItem *> row = this->createRow();
|
||||||
// setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
|
// setBoolItem(row[0],
|
||||||
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
// getApp()->settings->enableHighlightsSelf.getValue(), true, false);
|
||||||
// setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
|
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||||
// false); setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
|
// setBoolItem(row[1],
|
||||||
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
|
// getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
|
||||||
|
// setBoolItem(row[2],
|
||||||
|
// getApp()->settings->enableHighlightSound.getValue(), true, false);
|
||||||
|
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *>
|
||||||
|
// &row, int column,
|
||||||
// const QVariant &value, int role)
|
// const QVariant &value, int role)
|
||||||
//{
|
//{
|
||||||
// switch (column) {
|
// switch (column) {
|
||||||
|
|
|
@ -17,12 +17,15 @@ protected:
|
||||||
const TaggedUser &original) override;
|
const TaggedUser &original) override;
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row) override;
|
virtual void getRowFromItem(const TaggedUser &item,
|
||||||
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
virtual void afterInit() override;
|
virtual void afterInit() override;
|
||||||
|
|
||||||
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
// virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
// const QVariant &value, int role) override;
|
// int column,
|
||||||
|
// const QVariant &value, int role)
|
||||||
|
// override;
|
||||||
|
|
||||||
friend class TaggedUsersController;
|
friend class TaggedUsersController;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,8 @@ public:
|
||||||
|
|
||||||
~BenchmarkGuard()
|
~BenchmarkGuard()
|
||||||
{
|
{
|
||||||
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f << "ms";
|
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f
|
||||||
|
<< "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal getElapsedMs()
|
qreal getElapsedMs()
|
||||||
|
|
|
@ -12,9 +12,10 @@ int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
// convert char[][] to QStringList
|
// convert char** to QStringList
|
||||||
auto args = QStringList();
|
auto args = QStringList();
|
||||||
std::transform(argv + 1, argv + argc, std::back_inserter(args), [&](auto s) { return s; });
|
std::transform(argv + 1, argv + argc, std::back_inserter(args),
|
||||||
|
[&](auto s) { return s; });
|
||||||
|
|
||||||
// run in gui mode or browser extension host mode
|
// run in gui mode or browser extension host mode
|
||||||
if (shouldRunBrowserExtensionHost(args)) {
|
if (shouldRunBrowserExtensionHost(args)) {
|
||||||
|
|
|
@ -85,7 +85,8 @@ std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
|
||||||
std::vector<Frame> frames;
|
std::vector<Frame> frames;
|
||||||
|
|
||||||
if (reader.imageCount() <= 0) {
|
if (reader.imageCount() <= 0) {
|
||||||
Log("Error while reading image {}: '{}'", url.string, reader.errorString());
|
Log("Error while reading image {}: '{}'", url.string,
|
||||||
|
reader.errorString());
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +101,8 @@ std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frames.size() != 0) {
|
if (frames.size() != 0) {
|
||||||
Log("Error while reading image {}: '{}'", url.string, reader.errorString());
|
Log("Error while reading image {}: '{}'", url.string,
|
||||||
|
reader.errorString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames;
|
return frames;
|
||||||
|
|
|
@ -47,7 +47,8 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
|
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
|
||||||
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale = 1);
|
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap,
|
||||||
|
qreal scale = 1);
|
||||||
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
|
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
|
||||||
static ImagePtr getEmpty();
|
static ImagePtr getEmpty();
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ ImageSet::ImageSet()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageSet::ImageSet(const ImagePtr &image1, const ImagePtr &image2, const ImagePtr &image3)
|
ImageSet::ImageSet(const ImagePtr &image1, const ImagePtr &image2,
|
||||||
|
const ImagePtr &image3)
|
||||||
: imageX1_(image1)
|
: imageX1_(image1)
|
||||||
, imageX2_(image2)
|
, imageX2_(image2)
|
||||||
, imageX3_(image3)
|
, imageX3_(image3)
|
||||||
|
|
|
@ -16,7 +16,8 @@ namespace chatterino {
|
||||||
//
|
//
|
||||||
// Explanation:
|
// Explanation:
|
||||||
// - messages can be appended until 'limit' is reached
|
// - messages can be appended until 'limit' is reached
|
||||||
// - when the limit is reached for every message added one will be removed at the start
|
// - when the limit is reached for every message added one will be removed at
|
||||||
|
// the start
|
||||||
// - messages can only be added to the start when there is space for them,
|
// - messages can only be added to the start when there is space for them,
|
||||||
// trying to add messages to the start when it's full will not add them
|
// trying to add messages to the start when it's full will not add them
|
||||||
// - you are able to get a "Snapshot" which captures the state of this object
|
// - you are able to get a "Snapshot" which captures the state of this object
|
||||||
|
@ -41,7 +42,8 @@ public:
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
this->chunks_ = std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
this->chunks_ =
|
||||||
|
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||||
Chunk chunk = std::make_shared<std::vector<T>>();
|
Chunk chunk = std::make_shared<std::vector<T>>();
|
||||||
chunk->resize(this->chunkSize_);
|
chunk->resize(this->chunkSize_);
|
||||||
this->chunks_->push_back(chunk);
|
this->chunks_->push_back(chunk);
|
||||||
|
@ -60,8 +62,8 @@ public:
|
||||||
// still space in the last chunk
|
// still space in the last chunk
|
||||||
if (lastChunk->size() <= this->lastChunkEnd_) {
|
if (lastChunk->size() <= this->lastChunkEnd_) {
|
||||||
// create new chunk vector
|
// create new chunk vector
|
||||||
ChunkVector newVector =
|
ChunkVector newVector = std::make_shared<
|
||||||
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||||
|
|
||||||
// copy chunks
|
// copy chunks
|
||||||
for (Chunk &chunk : *this->chunks_) {
|
for (Chunk &chunk : *this->chunks_) {
|
||||||
|
@ -93,8 +95,8 @@ public:
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
// create new vector to clone chunks into
|
// create new vector to clone chunks into
|
||||||
ChunkVector newChunks =
|
ChunkVector newChunks = std::make_shared<
|
||||||
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||||
|
|
||||||
newChunks->resize(this->chunks_->size());
|
newChunks->resize(this->chunks_->size());
|
||||||
|
|
||||||
|
@ -142,7 +144,8 @@ public:
|
||||||
Chunk &chunk = this->chunks_->at(i);
|
Chunk &chunk = this->chunks_->at(i);
|
||||||
|
|
||||||
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
|
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
|
||||||
size_t end = i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
|
size_t end =
|
||||||
|
i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
|
||||||
|
|
||||||
for (size_t j = start; j < end; j++) {
|
for (size_t j = start; j < end; j++) {
|
||||||
if (chunk->at(j) == item) {
|
if (chunk->at(j) == item) {
|
||||||
|
@ -176,7 +179,8 @@ public:
|
||||||
Chunk &chunk = this->chunks_->at(i);
|
Chunk &chunk = this->chunks_->at(i);
|
||||||
|
|
||||||
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
|
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
|
||||||
size_t end = i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
|
size_t end =
|
||||||
|
i == chunk->size() - 1 ? this->lastChunkEnd_ : chunk->size();
|
||||||
|
|
||||||
for (size_t j = start; j < end; j++) {
|
for (size_t j = start; j < end; j++) {
|
||||||
if (x == index) {
|
if (x == index) {
|
||||||
|
@ -204,8 +208,9 @@ public:
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
return LimitedQueueSnapshot<T>(this->chunks_, this->limit_ - this->space(),
|
return LimitedQueueSnapshot<T>(
|
||||||
this->firstChunkOffset_, this->lastChunkEnd_);
|
this->chunks_, this->limit_ - this->space(),
|
||||||
|
this->firstChunkOffset_, this->lastChunkEnd_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -238,8 +243,8 @@ private:
|
||||||
// need to delete the first chunk
|
// need to delete the first chunk
|
||||||
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1) {
|
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1) {
|
||||||
// copy the chunk vector
|
// copy the chunk vector
|
||||||
ChunkVector newVector =
|
ChunkVector newVector = std::make_shared<
|
||||||
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||||
|
|
||||||
// delete first chunk
|
// delete first chunk
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
|
@ -12,8 +12,9 @@ class LimitedQueueSnapshot
|
||||||
public:
|
public:
|
||||||
LimitedQueueSnapshot() = default;
|
LimitedQueueSnapshot() = default;
|
||||||
|
|
||||||
LimitedQueueSnapshot(std::shared_ptr<std::vector<std::shared_ptr<std::vector<T>>>> chunks,
|
LimitedQueueSnapshot(
|
||||||
size_t length, size_t firstChunkOffset, size_t lastChunkEnd)
|
std::shared_ptr<std::vector<std::shared_ptr<std::vector<T>>>> chunks,
|
||||||
|
size_t length, size_t firstChunkOffset, size_t lastChunkEnd)
|
||||||
: chunks_(chunks)
|
: chunks_(chunks)
|
||||||
, length_(length)
|
, length_(length)
|
||||||
, firstChunkOffset_(firstChunkOffset)
|
, firstChunkOffset_(firstChunkOffset)
|
||||||
|
|
|
@ -33,7 +33,8 @@ MessagePtr Message::createSystemMessage(const QString &text)
|
||||||
MessagePtr message(new Message);
|
MessagePtr message(new Message);
|
||||||
|
|
||||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
message->addElement(
|
||||||
|
new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||||
message->flags |= MessageFlags::System;
|
message->flags |= MessageFlags::System;
|
||||||
message->flags |= MessageFlags::DoNotTriggerNotification;
|
message->flags |= MessageFlags::DoNotTriggerNotification;
|
||||||
message->searchText = text;
|
message->searchText = text;
|
||||||
|
@ -46,7 +47,8 @@ MessagePtr Message::createMessage(const QString &text)
|
||||||
MessagePtr message(new Message);
|
MessagePtr message(new Message);
|
||||||
|
|
||||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::Text));
|
message->addElement(
|
||||||
|
new TextElement(text, MessageElement::Text, MessageColor::Text));
|
||||||
message->searchText = text;
|
message->searchText = text;
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
|
@ -96,8 +98,10 @@ QString makeDuration(int timeoutSeconds)
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MessagePtr Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
MessagePtr Message::createTimeoutMessage(const QString &username,
|
||||||
const QString &reason, bool multipleTimes)
|
const QString &durationInSeconds,
|
||||||
|
const QString &reason,
|
||||||
|
bool multipleTimes)
|
||||||
{
|
{
|
||||||
QString text;
|
QString text;
|
||||||
|
|
||||||
|
@ -135,7 +139,8 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessagePtr Message::createTimeoutMessage(const BanAction &action, uint32_t count)
|
MessagePtr Message::createTimeoutMessage(const BanAction &action,
|
||||||
|
uint32_t count)
|
||||||
{
|
{
|
||||||
MessagePtr msg(new Message);
|
MessagePtr msg(new Message);
|
||||||
|
|
||||||
|
@ -178,7 +183,8 @@ MessagePtr Message::createTimeoutMessage(const BanAction &action, uint32_t count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
msg->addElement(
|
||||||
|
new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||||
msg->searchText = text;
|
msg->searchText = text;
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
|
@ -206,7 +212,8 @@ MessagePtr Message::createUntimeoutMessage(const UnbanAction &action)
|
||||||
.arg(action.target.name);
|
.arg(action.target.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
msg->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
msg->addElement(
|
||||||
|
new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||||
msg->searchText = text;
|
msg->searchText = text;
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
|
|
|
@ -68,13 +68,14 @@ public:
|
||||||
static std::shared_ptr<Message> createSystemMessage(const QString &text);
|
static std::shared_ptr<Message> createSystemMessage(const QString &text);
|
||||||
static std::shared_ptr<Message> createMessage(const QString &text);
|
static std::shared_ptr<Message> createMessage(const QString &text);
|
||||||
|
|
||||||
static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
|
static std::shared_ptr<Message> createTimeoutMessage(
|
||||||
const QString &durationInSeconds,
|
const QString &username, const QString &durationInSeconds,
|
||||||
const QString &reason, bool multipleTimes);
|
const QString &reason, bool multipleTimes);
|
||||||
|
|
||||||
static std::shared_ptr<Message> createTimeoutMessage(const BanAction &action,
|
static std::shared_ptr<Message> createTimeoutMessage(
|
||||||
uint32_t count = 1);
|
const BanAction &action, uint32_t count = 1);
|
||||||
static std::shared_ptr<Message> createUntimeoutMessage(const UnbanAction &action);
|
static std::shared_ptr<Message> createUntimeoutMessage(
|
||||||
|
const UnbanAction &action);
|
||||||
};
|
};
|
||||||
|
|
||||||
using MessagePtr = std::shared_ptr<Message>;
|
using MessagePtr = std::shared_ptr<Message>;
|
||||||
|
|
|
@ -47,9 +47,12 @@ QString MessageBuilder::matchLink(const QString &string)
|
||||||
{
|
{
|
||||||
LinkParser linkParser(string);
|
LinkParser linkParser(string);
|
||||||
|
|
||||||
static QRegularExpression httpRegex("\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
|
static QRegularExpression httpRegex(
|
||||||
static QRegularExpression ftpRegex("\\bftps?://", QRegularExpression::CaseInsensitiveOption);
|
"\\bhttps?://", QRegularExpression::CaseInsensitiveOption);
|
||||||
static QRegularExpression spotifyRegex("\\bspotify:", QRegularExpression::CaseInsensitiveOption);
|
static QRegularExpression ftpRegex(
|
||||||
|
"\\bftps?://", QRegularExpression::CaseInsensitiveOption);
|
||||||
|
static QRegularExpression spotifyRegex(
|
||||||
|
"\\bspotify:", QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
if (!linkParser.hasMatch()) {
|
if (!linkParser.hasMatch()) {
|
||||||
return QString();
|
return QString();
|
||||||
|
@ -57,7 +60,8 @@ QString MessageBuilder::matchLink(const QString &string)
|
||||||
|
|
||||||
QString captured = linkParser.getCaptured();
|
QString captured = linkParser.getCaptured();
|
||||||
|
|
||||||
if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) && !captured.contains(spotifyRegex)) {
|
if (!captured.contains(httpRegex) && !captured.contains(ftpRegex) &&
|
||||||
|
!captured.contains(spotifyRegex)) {
|
||||||
captured.insert(0, "http://");
|
captured.insert(0, "http://");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ public:
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
T *emplace(Args &&... args)
|
T *emplace(Args &&... args)
|
||||||
{
|
{
|
||||||
static_assert(std::is_base_of<MessageElement, T>::value, "T must extend MessageElement");
|
static_assert(std::is_base_of<MessageElement, T>::value,
|
||||||
|
"T must extend MessageElement");
|
||||||
|
|
||||||
T *element = new T(std::forward<Args>(args)...);
|
T *element = new T(std::forward<Args>(args)...);
|
||||||
this->append(element);
|
this->append(element);
|
||||||
|
|
|
@ -67,14 +67,15 @@ ImageElement::ImageElement(ImagePtr image, MessageElement::Flags flags)
|
||||||
// this->setTooltip(image->getTooltip());
|
// this->setTooltip(image->getTooltip());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
|
void ImageElement::addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags)
|
||||||
{
|
{
|
||||||
if (flags & this->getFlags()) {
|
if (flags & this->getFlags()) {
|
||||||
auto size = QSize(this->image_->width() * container.getScale(),
|
auto size = QSize(this->image_->width() * container.getScale(),
|
||||||
this->image_->height() * container.getScale());
|
this->image_->height() * container.getScale());
|
||||||
|
|
||||||
container.addElement(
|
container.addElement((new ImageLayoutElement(*this, this->image_, size))
|
||||||
(new ImageLayoutElement(*this, this->image_, size))->setLink(this->getLink()));
|
->setLink(this->getLink()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,8 @@ EmoteElement::EmoteElement(const EmotePtr &emote, MessageElement::Flags flags)
|
||||||
: MessageElement(flags)
|
: MessageElement(flags)
|
||||||
, emote_(emote)
|
, emote_(emote)
|
||||||
{
|
{
|
||||||
this->textElement_.reset(new TextElement(emote->getCopyString(), MessageElement::Misc));
|
this->textElement_.reset(
|
||||||
|
new TextElement(emote->getCopyString(), MessageElement::Misc));
|
||||||
|
|
||||||
this->setTooltip(emote->tooltip.string);
|
this->setTooltip(emote->tooltip.string);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +95,8 @@ EmotePtr EmoteElement::getEmote() const
|
||||||
return this->emote_;
|
return this->emote_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
|
void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags)
|
||||||
{
|
{
|
||||||
if (flags & this->getFlags()) {
|
if (flags & this->getFlags()) {
|
||||||
if (flags & MessageElement::EmoteImages) {
|
if (flags & MessageElement::EmoteImages) {
|
||||||
|
@ -103,11 +106,12 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
|
||||||
auto size = QSize(int(container.getScale() * image->width()),
|
auto size = QSize(int(container.getScale() * image->width()),
|
||||||
int(container.getScale() * image->height()));
|
int(container.getScale() * image->height()));
|
||||||
|
|
||||||
container.addElement(
|
container.addElement((new ImageLayoutElement(*this, image, size))
|
||||||
(new ImageLayoutElement(*this, image, size))->setLink(this->getLink()));
|
->setLink(this->getLink()));
|
||||||
} else {
|
} else {
|
||||||
if (this->textElement_) {
|
if (this->textElement_) {
|
||||||
this->textElement_->addToContainer(container, MessageElement::Misc);
|
this->textElement_->addToContainer(container,
|
||||||
|
MessageElement::Misc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,20 +130,24 @@ TextElement::TextElement(const QString &text, MessageElement::Flags flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
|
void TextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
if (flags & this->getFlags()) {
|
if (flags & this->getFlags()) {
|
||||||
QFontMetrics metrics = app->fonts->getFontMetrics(this->style_, container.getScale());
|
QFontMetrics metrics =
|
||||||
|
app->fonts->getFontMetrics(this->style_, container.getScale());
|
||||||
|
|
||||||
for (Word &word : this->words_) {
|
for (Word &word : this->words_) {
|
||||||
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
|
auto getTextLayoutElement = [&](QString text, int width,
|
||||||
|
bool trailingSpace) {
|
||||||
QColor color = this->color_.getColor(*app->themes);
|
QColor color = this->color_.getColor(*app->themes);
|
||||||
app->themes->normalizeColor(color);
|
app->themes->normalizeColor(color);
|
||||||
|
|
||||||
auto e = (new TextLayoutElement(*this, text, QSize(width, metrics.height()), color,
|
auto e = (new TextLayoutElement(
|
||||||
this->style_, container.getScale()))
|
*this, text, QSize(width, metrics.height()),
|
||||||
|
color, this->style_, container.getScale()))
|
||||||
->setLink(this->getLink());
|
->setLink(this->getLink());
|
||||||
e->setTrailingSpace(trailingSpace);
|
e->setTrailingSpace(trailingSpace);
|
||||||
return e;
|
return e;
|
||||||
|
@ -152,8 +160,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
|
||||||
|
|
||||||
// see if the text fits in the current line
|
// see if the text fits in the current line
|
||||||
if (container.fitsInLine(word.width)) {
|
if (container.fitsInLine(word.width)) {
|
||||||
container.addElementNoLineBreak(
|
container.addElementNoLineBreak(getTextLayoutElement(
|
||||||
getTextLayoutElement(word.text, word.width, this->hasTrailingSpace()));
|
word.text, word.width, this->hasTrailingSpace()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +170,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
|
||||||
container.breakLine();
|
container.breakLine();
|
||||||
|
|
||||||
if (container.fitsInLine(word.width)) {
|
if (container.fitsInLine(word.width)) {
|
||||||
container.addElementNoLineBreak(
|
container.addElementNoLineBreak(getTextLayoutElement(
|
||||||
getTextLayoutElement(word.text, word.width, this->hasTrailingSpace()));
|
word.text, word.width, this->hasTrailingSpace()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,8 +186,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
|
||||||
int charWidth = metrics.width(text[i]);
|
int charWidth = metrics.width(text[i]);
|
||||||
|
|
||||||
if (!container.fitsInLine(width + charWidth)) {
|
if (!container.fitsInLine(width + charWidth)) {
|
||||||
container.addElementNoLineBreak(
|
container.addElementNoLineBreak(getTextLayoutElement(
|
||||||
getTextLayoutElement(text.mid(wordStart, i - wordStart), width, false));
|
text.mid(wordStart, i - wordStart), width, false));
|
||||||
container.breakLine();
|
container.breakLine();
|
||||||
|
|
||||||
wordStart = i;
|
wordStart = i;
|
||||||
|
@ -194,8 +202,8 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
|
||||||
width += charWidth;
|
width += charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.addElement(
|
container.addElement(getTextLayoutElement(
|
||||||
getTextLayoutElement(text.mid(wordStart), width, this->hasTrailingSpace()));
|
text.mid(wordStart), width, this->hasTrailingSpace()));
|
||||||
container.breakLine();
|
container.breakLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +238,8 @@ TextElement *TimestampElement::formatTime(const QTime &time)
|
||||||
|
|
||||||
QString format = locale.toString(time, getApp()->settings->timestampFormat);
|
QString format = locale.toString(time, getApp()->settings->timestampFormat);
|
||||||
|
|
||||||
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::ChatMedium);
|
return new TextElement(format, Flags::Timestamp, MessageColor::System,
|
||||||
|
FontStyle::ChatMedium);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TWITCH MODERATION
|
// TWITCH MODERATION
|
||||||
|
@ -243,15 +252,19 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElement::Flags flags)
|
MessageElement::Flags flags)
|
||||||
{
|
{
|
||||||
if (flags & MessageElement::ModeratorTools) {
|
if (flags & MessageElement::ModeratorTools) {
|
||||||
QSize size(int(container.getScale() * 16), int(container.getScale() * 16));
|
QSize size(int(container.getScale() * 16),
|
||||||
|
int(container.getScale() * 16));
|
||||||
|
|
||||||
for (const auto &action : getApp()->moderationActions->items.getVector()) {
|
for (const auto &action :
|
||||||
|
getApp()->moderationActions->items.getVector()) {
|
||||||
if (auto image = action.getImage()) {
|
if (auto image = action.getImage()) {
|
||||||
container.addElement((new ImageLayoutElement(*this, image.get(), size))
|
container.addElement(
|
||||||
->setLink(Link(Link::UserAction, action.getAction())));
|
(new ImageLayoutElement(*this, image.get(), size))
|
||||||
|
->setLink(Link(Link::UserAction, action.getAction())));
|
||||||
} else {
|
} else {
|
||||||
container.addElement(
|
container.addElement(
|
||||||
(new TextIconLayoutElement(*this, action.getLine1(), action.getLine2(),
|
(new TextIconLayoutElement(*this, action.getLine1(),
|
||||||
|
action.getLine2(),
|
||||||
container.getScale(), size))
|
container.getScale(), size))
|
||||||
->setLink(Link(Link::UserAction, action.getAction())));
|
->setLink(Link(Link::UserAction, action.getAction())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,10 +72,11 @@ public:
|
||||||
// - Chatterino top donator badge
|
// - Chatterino top donator badge
|
||||||
BadgeChatterino = (1 << 18),
|
BadgeChatterino = (1 << 18),
|
||||||
|
|
||||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge?
|
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke)
|
||||||
|
// custom badge?
|
||||||
|
|
||||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity |
|
Badges = BadgeGlobalAuthority | BadgeChannelAuthority |
|
||||||
BadgeChatterino,
|
BadgeSubscription | BadgeVanity | BadgeChatterino,
|
||||||
|
|
||||||
ChannelName = (1 << 19),
|
ChannelName = (1 << 19),
|
||||||
|
|
||||||
|
@ -89,7 +90,8 @@ public:
|
||||||
|
|
||||||
AlwaysShow = (1 << 25),
|
AlwaysShow = (1 << 25),
|
||||||
|
|
||||||
// used in the ChannelView class to make the collapse buttons visible if needed
|
// used in the ChannelView class to make the collapse buttons visible if
|
||||||
|
// needed
|
||||||
Collapsed = (1 << 26),
|
Collapsed = (1 << 26),
|
||||||
|
|
||||||
// used for dynamic bold usernames
|
// used for dynamic bold usernames
|
||||||
|
@ -100,8 +102,9 @@ public:
|
||||||
LowercaseLink = (1 << 29),
|
LowercaseLink = (1 << 29),
|
||||||
OriginalLink = (1 << 30),
|
OriginalLink = (1 << 30),
|
||||||
|
|
||||||
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage |
|
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage |
|
||||||
TwitchEmoteImage | BitsAmount | Text | AlwaysShow,
|
BttvEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
||||||
|
AlwaysShow,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum UpdateFlags : char {
|
enum UpdateFlags : char {
|
||||||
|
@ -121,7 +124,8 @@ public:
|
||||||
bool hasTrailingSpace() const;
|
bool hasTrailingSpace() const;
|
||||||
Flags getFlags() const;
|
Flags getFlags() const;
|
||||||
|
|
||||||
virtual void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) = 0;
|
virtual void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MessageElement(Flags flags);
|
MessageElement(Flags flags);
|
||||||
|
@ -139,7 +143,8 @@ class ImageElement : public MessageElement
|
||||||
public:
|
public:
|
||||||
ImageElement(ImagePtr image, MessageElement::Flags flags);
|
ImageElement(ImagePtr image, MessageElement::Flags flags);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImagePtr image_;
|
ImagePtr image_;
|
||||||
|
@ -154,7 +159,8 @@ public:
|
||||||
FontStyle style = FontStyle::ChatMedium);
|
FontStyle style = FontStyle::ChatMedium);
|
||||||
~TextElement() override = default;
|
~TextElement() override = default;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MessageColor color_;
|
MessageColor color_;
|
||||||
|
@ -175,7 +181,8 @@ class EmoteElement : public MessageElement
|
||||||
public:
|
public:
|
||||||
EmoteElement(const EmotePtr &data, MessageElement::Flags flags_);
|
EmoteElement(const EmotePtr &data, MessageElement::Flags flags_);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags_) override;
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags_) override;
|
||||||
EmotePtr getEmote() const;
|
EmotePtr getEmote() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -190,7 +197,8 @@ public:
|
||||||
TimestampElement(QTime time_ = QTime::currentTime());
|
TimestampElement(QTime time_ = QTime::currentTime());
|
||||||
~TimestampElement() override = default;
|
~TimestampElement() override = default;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags) override;
|
||||||
|
|
||||||
TextElement *formatTime(const QTime &time);
|
TextElement *formatTime(const QTime &time);
|
||||||
|
|
||||||
|
@ -200,14 +208,15 @@ private:
|
||||||
QString format_;
|
QString format_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// adds all the custom moderation buttons, adds a variable amount of items depending on settings
|
// adds all the custom moderation buttons, adds a variable amount of items
|
||||||
// fourtf: implement
|
// depending on settings fourtf: implement
|
||||||
class TwitchModerationElement : public MessageElement
|
class TwitchModerationElement : public MessageElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TwitchModerationElement();
|
TwitchModerationElement();
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
|
MessageElement::Flags flags) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -26,7 +26,8 @@ struct SelectionItem {
|
||||||
if (this->messageIndex < b.messageIndex) {
|
if (this->messageIndex < b.messageIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this->messageIndex == b.messageIndex && this->charIndex < b.charIndex) {
|
if (this->messageIndex == b.messageIndex &&
|
||||||
|
this->charIndex < b.charIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -39,7 +40,8 @@ struct SelectionItem {
|
||||||
|
|
||||||
bool operator==(const SelectionItem &b) const
|
bool operator==(const SelectionItem &b) const
|
||||||
{
|
{
|
||||||
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
|
return this->messageIndex == b.messageIndex &&
|
||||||
|
this->charIndex == b.charIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const SelectionItem &b) const
|
bool operator!=(const SelectionItem &b) const
|
||||||
|
@ -74,7 +76,8 @@ struct Selection {
|
||||||
|
|
||||||
bool isSingleMessage() const
|
bool isSingleMessage() const
|
||||||
{
|
{
|
||||||
return this->selectionMin.messageIndex == this->selectionMax.messageIndex;
|
return this->selectionMin.messageIndex ==
|
||||||
|
this->selectionMax.messageIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -98,12 +98,14 @@ void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
|
||||||
if (this->flags & MessageLayout::Expanded ||
|
if (this->flags & MessageLayout::Expanded ||
|
||||||
(_flags & MessageElement::ModeratorTools &&
|
(_flags & MessageElement::ModeratorTools &&
|
||||||
!(this->message_->flags & Message::MessageFlags::Disabled))) {
|
!(this->message_->flags & Message::MessageFlags::Disabled))) {
|
||||||
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
|
messageFlags = Message::MessageFlags(messageFlags &
|
||||||
|
~Message::MessageFlags::Collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->container_.begin(width, this->scale_, messageFlags);
|
this->container_.begin(width, this->scale_, messageFlags);
|
||||||
|
|
||||||
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
|
for (const std::unique_ptr<MessageElement> &element :
|
||||||
|
this->message_->getElements()) {
|
||||||
element->addToContainer(this->container_, _flags);
|
element->addToContainer(this->container_, _flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +125,8 @@ void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
Selection &selection, bool isLastReadMessage, bool isWindowFocused)
|
Selection &selection, bool isLastReadMessage,
|
||||||
|
bool isWindowFocused)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
QPixmap *pixmap = this->buffer_.get();
|
QPixmap *pixmap = this->buffer_.get();
|
||||||
|
@ -132,7 +135,8 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
if (!pixmap) {
|
if (!pixmap) {
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
||||||
int(container_.getHeight() * painter.device()->devicePixelRatioF()));
|
int(container_.getHeight() *
|
||||||
|
painter.device()->devicePixelRatioF()));
|
||||||
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||||
#else
|
#else
|
||||||
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
|
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
|
||||||
|
@ -149,14 +153,16 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
|
|
||||||
// draw on buffer
|
// draw on buffer
|
||||||
painter.drawPixmap(0, y, *pixmap);
|
painter.drawPixmap(0, y, *pixmap);
|
||||||
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
|
// painter.drawPixmap(0, y, this->container.width,
|
||||||
|
// this->container.getHeight(), *pixmap);
|
||||||
|
|
||||||
// draw gif emotes
|
// draw gif emotes
|
||||||
this->container_.paintAnimatedElements(painter, y);
|
this->container_.paintAnimatedElements(painter, y);
|
||||||
|
|
||||||
// draw disabled
|
// draw disabled
|
||||||
if (this->message_->flags.HasFlag(Message::Disabled)) {
|
if (this->message_->flags.HasFlag(Message::Disabled)) {
|
||||||
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
|
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
||||||
|
app->themes->messages.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw selection
|
// draw selection
|
||||||
|
@ -172,19 +178,23 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
|
|
||||||
// draw last read message line
|
// draw last read message line
|
||||||
if (isLastReadMessage) {
|
if (isLastReadMessage) {
|
||||||
QColor color = isWindowFocused ? app->themes->tabs.selected.backgrounds.regular.color()
|
QColor color =
|
||||||
: app->themes->tabs.selected.backgrounds.unfocused.color();
|
isWindowFocused
|
||||||
|
? app->themes->tabs.selected.backgrounds.regular.color()
|
||||||
|
: app->themes->tabs.selected.backgrounds.unfocused.color();
|
||||||
|
|
||||||
QBrush brush(color,
|
QBrush brush(color, static_cast<Qt::BrushStyle>(
|
||||||
static_cast<Qt::BrushStyle>(app->settings->lastMessagePattern.getValue()));
|
app->settings->lastMessagePattern.getValue()));
|
||||||
|
|
||||||
painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
|
painter.fillRect(0, y + this->container_.getHeight() - 1,
|
||||||
|
pixmap->width(), 1, brush);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->bufferValid_ = true;
|
this->bufferValid_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
|
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
||||||
|
Selection & /*selection*/)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
|
@ -212,8 +222,8 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selectio
|
||||||
#ifdef FOURTF
|
#ifdef FOURTF
|
||||||
// debug
|
// debug
|
||||||
painter.setPen(QColor(255, 0, 0));
|
painter.setPen(QColor(255, 0, 0));
|
||||||
painter.drawRect(buffer->rect().x(), buffer->rect().y(), buffer->rect().width() - 1,
|
painter.drawRect(buffer->rect().x(), buffer->rect().y(),
|
||||||
buffer->rect().height() - 1);
|
buffer->rect().width() - 1, buffer->rect().height() - 1);
|
||||||
|
|
||||||
QTextOption option;
|
QTextOption option;
|
||||||
option.setAlignment(Qt::AlignRight | Qt::AlignTop);
|
option.setAlignment(Qt::AlignRight | Qt::AlignTop);
|
||||||
|
|
|
@ -40,8 +40,9 @@ public:
|
||||||
bool layout(int width, float scale_, MessageElement::Flags flags);
|
bool layout(int width, float scale_, MessageElement::Flags flags);
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
|
void paint(QPainter &painter, int width, int y, int messageIndex,
|
||||||
bool isLastReadMessage, bool isWindowFocused);
|
Selection &selection, bool isLastReadMessage,
|
||||||
|
bool isWindowFocused);
|
||||||
void invalidateBuffer();
|
void invalidateBuffer();
|
||||||
void deleteBuffer();
|
void deleteBuffer();
|
||||||
void deleteCache();
|
void deleteCache();
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#define COMPACT_EMOTES_OFFSET 6
|
#define COMPACT_EMOTES_OFFSET 6
|
||||||
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
|
#define MAX_UNCOLLAPSED_LINES \
|
||||||
|
(getApp()->settings->collpseMessagesMinLines.getValue())
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -29,13 +30,15 @@ float MessageLayoutContainer::getScale() const
|
||||||
}
|
}
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
void MessageLayoutContainer::begin(int width, float scale, Message::MessageFlags flags)
|
void MessageLayoutContainer::begin(int width, float scale,
|
||||||
|
Message::MessageFlags flags)
|
||||||
{
|
{
|
||||||
this->clear();
|
this->clear();
|
||||||
this->width_ = width;
|
this->width_ = width;
|
||||||
this->scale_ = scale;
|
this->scale_ = scale;
|
||||||
this->flags_ = flags;
|
this->flags_ = flags;
|
||||||
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, scale);
|
auto mediumFontMetrics =
|
||||||
|
getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, scale);
|
||||||
this->textLineHeight_ = mediumFontMetrics.height();
|
this->textLineHeight_ = mediumFontMetrics.height();
|
||||||
this->spaceWidth_ = mediumFontMetrics.width(' ');
|
this->spaceWidth_ = mediumFontMetrics.width(' ');
|
||||||
this->dotdotdotWidth_ = mediumFontMetrics.width("...");
|
this->dotdotdotWidth_ = mediumFontMetrics.width("...");
|
||||||
|
@ -66,7 +69,8 @@ void MessageLayoutContainer::addElement(MessageLayoutElement *element)
|
||||||
this->_addElement(element);
|
this->_addElement(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element)
|
void MessageLayoutContainer::addElementNoLineBreak(
|
||||||
|
MessageLayoutElement *element)
|
||||||
{
|
{
|
||||||
this->_addElement(element);
|
this->_addElement(element);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +80,8 @@ bool MessageLayoutContainer::canAddElements()
|
||||||
return this->canAddMessages_;
|
return this->canAddMessages_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
|
void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
|
||||||
|
bool forceAdd)
|
||||||
{
|
{
|
||||||
if (!this->canAddElements() && !forceAdd) {
|
if (!this->canAddElements() && !forceAdd) {
|
||||||
delete element;
|
delete element;
|
||||||
|
@ -91,8 +96,9 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool for
|
||||||
int newLineHeight = element->getRect().height();
|
int newLineHeight = element->getRect().height();
|
||||||
|
|
||||||
// compact emote offset
|
// compact emote offset
|
||||||
bool isCompactEmote = !(this->flags_ & Message::DisableCompactEmotes) &&
|
bool isCompactEmote =
|
||||||
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
!(this->flags_ & Message::DisableCompactEmotes) &&
|
||||||
|
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
||||||
|
|
||||||
if (isCompactEmote) {
|
if (isCompactEmote) {
|
||||||
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale_;
|
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale_;
|
||||||
|
@ -102,7 +108,8 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool for
|
||||||
this->lineHeight_ = std::max(this->lineHeight_, newLineHeight);
|
this->lineHeight_ = std::max(this->lineHeight_, newLineHeight);
|
||||||
|
|
||||||
// set move element
|
// set move element
|
||||||
element->setPosition(QPoint(this->currentX_, this->currentY_ - element->getRect().height()));
|
element->setPosition(
|
||||||
|
QPoint(this->currentX_, this->currentY_ - element->getRect().height()));
|
||||||
|
|
||||||
// add element
|
// add element
|
||||||
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
this->elements_.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
||||||
|
@ -120,35 +127,42 @@ void MessageLayoutContainer::breakLine()
|
||||||
int xOffset = 0;
|
int xOffset = 0;
|
||||||
|
|
||||||
if (this->flags_ & Message::Centered && this->elements_.size() > 0) {
|
if (this->flags_ & Message::Centered && this->elements_.size() > 0) {
|
||||||
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1)->getRect().right()) / 2;
|
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1)
|
||||||
|
->getRect()
|
||||||
|
.right()) /
|
||||||
|
2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = lineStart_; i < this->elements_.size(); i++) {
|
for (size_t i = lineStart_; i < this->elements_.size(); i++) {
|
||||||
MessageLayoutElement *element = this->elements_.at(i).get();
|
MessageLayoutElement *element = this->elements_.at(i).get();
|
||||||
|
|
||||||
bool isCompactEmote = !(this->flags_ & Message::DisableCompactEmotes) &&
|
bool isCompactEmote =
|
||||||
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
!(this->flags_ & Message::DisableCompactEmotes) &&
|
||||||
|
element->getCreator().getFlags() & MessageElement::EmoteImages;
|
||||||
|
|
||||||
int yExtra = 0;
|
int yExtra = 0;
|
||||||
if (isCompactEmote) {
|
if (isCompactEmote) {
|
||||||
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
|
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (element->getCreator().getFlags() & MessageElement::Badges) {
|
// if (element->getCreator().getFlags() & MessageElement::Badges)
|
||||||
|
// {
|
||||||
if (element->getRect().height() < this->textLineHeight_) {
|
if (element->getRect().height() < this->textLineHeight_) {
|
||||||
yExtra -= (this->textLineHeight_ - element->getRect().height()) / 2;
|
yExtra -= (this->textLineHeight_ - element->getRect().height()) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
|
element->setPosition(
|
||||||
element->getRect().y() + this->lineHeight_ + yExtra));
|
QPoint(element->getRect().x() + xOffset + this->margin.left,
|
||||||
|
element->getRect().y() + this->lineHeight_ + yExtra));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->lines_.size() != 0) {
|
if (this->lines_.size() != 0) {
|
||||||
this->lines_.back().endIndex = this->lineStart_;
|
this->lines_.back().endIndex = this->lineStart_;
|
||||||
this->lines_.back().endCharIndex = this->charIndex_;
|
this->lines_.back().endCharIndex = this->charIndex_;
|
||||||
}
|
}
|
||||||
this->lines_.push_back({(int)lineStart_, 0, this->charIndex_, 0,
|
this->lines_.push_back(
|
||||||
QRect(-100000, this->currentY_, 200000, lineHeight_)});
|
{(int)lineStart_, 0, this->charIndex_, 0,
|
||||||
|
QRect(-100000, this->currentY_, 200000, lineHeight_)});
|
||||||
|
|
||||||
for (int i = this->lineStart_; i < this->elements_.size(); i++) {
|
for (int i = this->lineStart_; i < this->elements_.size(); i++) {
|
||||||
this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
|
this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
|
||||||
|
@ -178,17 +192,20 @@ bool MessageLayoutContainer::fitsInLine(int _width)
|
||||||
{
|
{
|
||||||
return this->currentX_ + _width <=
|
return this->currentX_ + _width <=
|
||||||
(this->width_ - this->margin.left - this->margin.right -
|
(this->width_ - this->margin.left - this->margin.right -
|
||||||
(this->line_ + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth_ : 0));
|
(this->line_ + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth_
|
||||||
|
: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayoutContainer::end()
|
void MessageLayoutContainer::end()
|
||||||
{
|
{
|
||||||
if (!this->canAddElements()) {
|
if (!this->canAddElements()) {
|
||||||
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
|
static TextElement dotdotdot("...", MessageElement::Collapsed,
|
||||||
|
MessageColor::Link);
|
||||||
static QString dotdotdotText("...");
|
static QString dotdotdotText("...");
|
||||||
|
|
||||||
auto *element = new TextLayoutElement(
|
auto *element = new TextLayoutElement(
|
||||||
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth_, this->textLineHeight_),
|
dotdotdot, dotdotdotText,
|
||||||
|
QSize(this->dotdotdotWidth_, this->textLineHeight_),
|
||||||
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale_);
|
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale_);
|
||||||
|
|
||||||
// getApp()->themes->messages.textColors.system
|
// getApp()->themes->messages.textColors.system
|
||||||
|
@ -235,7 +252,8 @@ MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
|
||||||
// painting
|
// painting
|
||||||
void MessageLayoutContainer::paintElements(QPainter &painter)
|
void MessageLayoutContainer::paintElements(QPainter &painter)
|
||||||
{
|
{
|
||||||
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_) {
|
for (const std::unique_ptr<MessageLayoutElement> &element :
|
||||||
|
this->elements_) {
|
||||||
#ifdef FOURTF
|
#ifdef FOURTF
|
||||||
painter.setPen(QColor(0, 255, 0));
|
painter.setPen(QColor(0, 255, 0));
|
||||||
painter.drawRect(element->getRect());
|
painter.drawRect(element->getRect());
|
||||||
|
@ -245,9 +263,11 @@ void MessageLayoutContainer::paintElements(QPainter &painter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayoutContainer::paintAnimatedElements(QPainter &painter, int yOffset)
|
void MessageLayoutContainer::paintAnimatedElements(QPainter &painter,
|
||||||
|
int yOffset)
|
||||||
{
|
{
|
||||||
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_) {
|
for (const std::unique_ptr<MessageLayoutElement> &element :
|
||||||
|
this->elements_) {
|
||||||
element->paintAnimated(painter, yOffset);
|
element->paintAnimated(painter, yOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +293,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
rect.setTop(std::max(0, rect.top()) + yOffset);
|
rect.setTop(std::max(0, rect.top()) + yOffset);
|
||||||
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
|
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
|
||||||
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
|
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
|
||||||
rect.setRight(this->elements_[line.endIndex - 1]->getRect().right());
|
rect.setRight(
|
||||||
|
this->elements_[line.endIndex - 1]->getRect().right());
|
||||||
|
|
||||||
painter.fillRect(rect, selectionColor);
|
painter.fillRect(rect, selectionColor);
|
||||||
}
|
}
|
||||||
|
@ -302,16 +323,19 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
int c = this->elements_[i]->getSelectionIndexCount();
|
int c = this->elements_[i]->getSelectionIndexCount();
|
||||||
|
|
||||||
if (index + c > selection.selectionMin.charIndex) {
|
if (index + c > selection.selectionMin.charIndex) {
|
||||||
x = this->elements_[i]->getXFromIndex(selection.selectionMin.charIndex - index);
|
x = this->elements_[i]->getXFromIndex(
|
||||||
|
selection.selectionMin.charIndex - index);
|
||||||
|
|
||||||
// ends in same line
|
// ends in same line
|
||||||
if (selection.selectionMax.messageIndex == messageIndex &&
|
if (selection.selectionMax.messageIndex == messageIndex &&
|
||||||
line.endCharIndex > /*=*/selection.selectionMax.charIndex) //
|
line.endCharIndex >
|
||||||
|
/*=*/selection.selectionMax.charIndex) //
|
||||||
{
|
{
|
||||||
returnAfter = true;
|
returnAfter = true;
|
||||||
index = line.startCharIndex;
|
index = line.startCharIndex;
|
||||||
for (int i = line.startIndex; i < line.endIndex; i++) {
|
for (int i = line.startIndex; i < line.endIndex; i++) {
|
||||||
int c = this->elements_[i]->getSelectionIndexCount();
|
int c =
|
||||||
|
this->elements_[i]->getSelectionIndexCount();
|
||||||
|
|
||||||
if (index + c > selection.selectionMax.charIndex) {
|
if (index + c > selection.selectionMax.charIndex) {
|
||||||
r = this->elements_[i]->getXFromIndex(
|
r = this->elements_[i]->getXFromIndex(
|
||||||
|
@ -330,9 +354,15 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
QRect rect = line.rect;
|
QRect rect = line.rect;
|
||||||
|
|
||||||
rect.setTop(std::max(0, rect.top()) + yOffset);
|
rect.setTop(std::max(0, rect.top()) + yOffset);
|
||||||
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
|
rect.setBottom(
|
||||||
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
|
std::min(this->height_, rect.bottom()) +
|
||||||
rect.setRight(this->elements_[line.endIndex - 1]->getRect().right());
|
yOffset);
|
||||||
|
rect.setLeft(this->elements_[line.startIndex]
|
||||||
|
->getRect()
|
||||||
|
.left());
|
||||||
|
rect.setRight(this->elements_[line.endIndex - 1]
|
||||||
|
->getRect()
|
||||||
|
.right());
|
||||||
|
|
||||||
painter.fillRect(rect, selectionColor);
|
painter.fillRect(rect, selectionColor);
|
||||||
}
|
}
|
||||||
|
@ -378,7 +408,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
rect.setTop(std::max(0, rect.top()) + yOffset);
|
rect.setTop(std::max(0, rect.top()) + yOffset);
|
||||||
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
|
rect.setBottom(std::min(this->height_, rect.bottom()) + yOffset);
|
||||||
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
|
rect.setLeft(this->elements_[line.startIndex]->getRect().left());
|
||||||
rect.setRight(this->elements_[line.endIndex - 1]->getRect().right());
|
rect.setRight(
|
||||||
|
this->elements_[line.endIndex - 1]->getRect().right());
|
||||||
|
|
||||||
painter.fillRect(rect, selectionColor);
|
painter.fillRect(rect, selectionColor);
|
||||||
continue;
|
continue;
|
||||||
|
@ -390,7 +421,8 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
int c = this->elements_[i]->getSelectionIndexCount();
|
int c = this->elements_[i]->getSelectionIndexCount();
|
||||||
|
|
||||||
if (index + c > selection.selectionMax.charIndex) {
|
if (index + c > selection.selectionMax.charIndex) {
|
||||||
r = this->elements_[i]->getXFromIndex(selection.selectionMax.charIndex - index);
|
r = this->elements_[i]->getXFromIndex(
|
||||||
|
selection.selectionMax.charIndex - index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,11 +456,13 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lineStart = line == this->lines_.end() ? this->lines_.back().startIndex : line->startIndex;
|
int lineStart = line == this->lines_.end() ? this->lines_.back().startIndex
|
||||||
|
: line->startIndex;
|
||||||
if (line != this->lines_.end()) {
|
if (line != this->lines_.end()) {
|
||||||
line++;
|
line++;
|
||||||
}
|
}
|
||||||
int lineEnd = line == this->lines_.end() ? this->elements_.size() : line->startIndex;
|
int lineEnd =
|
||||||
|
line == this->lines_.end() ? this->elements_.size() : line->startIndex;
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,8 @@ struct MessageLayoutContainer {
|
||||||
// painting
|
// painting
|
||||||
void paintElements(QPainter &painter);
|
void paintElements(QPainter &painter);
|
||||||
void paintAnimatedElements(QPainter &painter, int yOffset);
|
void paintAnimatedElements(QPainter &painter, int yOffset);
|
||||||
void paintSelection(QPainter &painter, int messageIndex, Selection &selection, int yOffset);
|
void paintSelection(QPainter &painter, int messageIndex,
|
||||||
|
Selection &selection, int yOffset);
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
int getSelectionIndex(QPoint point);
|
int getSelectionIndex(QPoint point);
|
||||||
|
|
|
@ -14,7 +14,8 @@ const QRect &MessageLayoutElement::getRect() const
|
||||||
return this->rect_;
|
return this->rect_;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageLayoutElement::MessageLayoutElement(MessageElement &creator, const QSize &size)
|
MessageLayoutElement::MessageLayoutElement(MessageElement &creator,
|
||||||
|
const QSize &size)
|
||||||
: creator_(creator)
|
: creator_(creator)
|
||||||
{
|
{
|
||||||
this->rect_.setSize(size);
|
this->rect_.setSize(size);
|
||||||
|
@ -63,14 +64,16 @@ const Link &MessageLayoutElement::getLink() const
|
||||||
// IMAGE
|
// IMAGE
|
||||||
//
|
//
|
||||||
|
|
||||||
ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size)
|
ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image,
|
||||||
|
const QSize &size)
|
||||||
: MessageLayoutElement(creator, size)
|
: MessageLayoutElement(creator, size)
|
||||||
, image_(image)
|
, image_(image)
|
||||||
{
|
{
|
||||||
this->trailingSpace = creator.hasTrailingSpace();
|
this->trailingSpace = creator.hasTrailingSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
|
void ImageLayoutElement::addCopyTextToString(QString &str, int from,
|
||||||
|
int to) const
|
||||||
{
|
{
|
||||||
// str += this->image_->getCopyString();
|
// str += this->image_->getCopyString();
|
||||||
str += "not implemented";
|
str += "not implemented";
|
||||||
|
@ -134,8 +137,9 @@ int ImageLayoutElement::getXFromIndex(int index)
|
||||||
// TEXT
|
// TEXT
|
||||||
//
|
//
|
||||||
|
|
||||||
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, const QSize &_size,
|
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text,
|
||||||
QColor _color, FontStyle _style, float _scale)
|
const QSize &_size, QColor _color,
|
||||||
|
FontStyle _style, float _scale)
|
||||||
: MessageLayoutElement(_creator, _size)
|
: MessageLayoutElement(_creator, _size)
|
||||||
, text(_text)
|
, text(_text)
|
||||||
, color(_color)
|
, color(_color)
|
||||||
|
@ -144,7 +148,8 @@ TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, c
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextLayoutElement::addCopyTextToString(QString &str, int from, int to) const
|
void TextLayoutElement::addCopyTextToString(QString &str, int from,
|
||||||
|
int to) const
|
||||||
{
|
{
|
||||||
str += this->text.mid(from, to - from);
|
str += this->text.mid(from, to - from);
|
||||||
|
|
||||||
|
@ -166,8 +171,9 @@ void TextLayoutElement::paint(QPainter &painter)
|
||||||
|
|
||||||
painter.setFont(app->fonts->getFont(this->style, this->scale));
|
painter.setFont(app->fonts->getFont(this->style, this->scale));
|
||||||
|
|
||||||
painter.drawText(QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->text,
|
painter.drawText(
|
||||||
QTextOption(Qt::AlignLeft | Qt::AlignTop));
|
QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000),
|
||||||
|
this->text, QTextOption(Qt::AlignLeft | Qt::AlignTop));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextLayoutElement::paintAnimated(QPainter &, int)
|
void TextLayoutElement::paintAnimated(QPainter &, int)
|
||||||
|
@ -219,8 +225,10 @@ int TextLayoutElement::getXFromIndex(int index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEXT ICON
|
// TEXT ICON
|
||||||
TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator, const QString &_line1,
|
TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator,
|
||||||
const QString &_line2, float _scale, const QSize &size)
|
const QString &_line1,
|
||||||
|
const QString &_line2,
|
||||||
|
float _scale, const QSize &size)
|
||||||
: MessageLayoutElement(creator, size)
|
: MessageLayoutElement(creator, size)
|
||||||
, scale(_scale)
|
, scale(_scale)
|
||||||
, line1(_line1)
|
, line1(_line1)
|
||||||
|
@ -228,7 +236,8 @@ TextIconLayoutElement::TextIconLayoutElement(MessageElement &creator, const QStr
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextIconLayoutElement::addCopyTextToString(QString &str, int from, int to) const
|
void TextIconLayoutElement::addCopyTextToString(QString &str, int from,
|
||||||
|
int to) const
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,11 +263,12 @@ void TextIconLayoutElement::paint(QPainter &painter)
|
||||||
painter.drawText(_rect, this->line1, option);
|
painter.drawText(_rect, this->line1, option);
|
||||||
} else {
|
} else {
|
||||||
painter.drawText(
|
painter.drawText(
|
||||||
QPoint(this->getRect().x(), this->getRect().y() + this->getRect().height() / 2),
|
QPoint(this->getRect().x(),
|
||||||
|
this->getRect().y() + this->getRect().height() / 2),
|
||||||
this->line1);
|
this->line1);
|
||||||
painter.drawText(
|
painter.drawText(QPoint(this->getRect().x(),
|
||||||
QPoint(this->getRect().x(), this->getRect().y() + this->getRect().height()),
|
this->getRect().y() + this->getRect().height()),
|
||||||
this->line2);
|
this->line2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ public:
|
||||||
MessageLayoutElement *setTrailingSpace(bool value);
|
MessageLayoutElement *setTrailingSpace(bool value);
|
||||||
MessageLayoutElement *setLink(const Link &link_);
|
MessageLayoutElement *setLink(const Link &link_);
|
||||||
|
|
||||||
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0;
|
virtual void addCopyTextToString(QString &str, int from = 0,
|
||||||
|
int to = INT_MAX) const = 0;
|
||||||
virtual int getSelectionIndexCount() = 0;
|
virtual int getSelectionIndexCount() = 0;
|
||||||
virtual void paint(QPainter &painter) = 0;
|
virtual void paint(QPainter &painter) = 0;
|
||||||
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
||||||
|
@ -52,10 +53,12 @@ private:
|
||||||
class ImageLayoutElement : public MessageLayoutElement
|
class ImageLayoutElement : public MessageLayoutElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size);
|
ImageLayoutElement(MessageElement &creator, ImagePtr image,
|
||||||
|
const QSize &size);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
|
void addCopyTextToString(QString &str, int from = 0,
|
||||||
|
int to = INT_MAX) const override;
|
||||||
int getSelectionIndexCount() override;
|
int getSelectionIndexCount() override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
|
@ -70,11 +73,13 @@ private:
|
||||||
class TextLayoutElement : public MessageLayoutElement
|
class TextLayoutElement : public MessageLayoutElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TextLayoutElement(MessageElement &creator_, QString &text, const QSize &size, QColor color,
|
TextLayoutElement(MessageElement &creator_, QString &text,
|
||||||
FontStyle style, float scale);
|
const QSize &size, QColor color, FontStyle style,
|
||||||
|
float scale);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
|
void addCopyTextToString(QString &str, int from = 0,
|
||||||
|
int to = INT_MAX) const override;
|
||||||
int getSelectionIndexCount() override;
|
int getSelectionIndexCount() override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
|
@ -93,11 +98,12 @@ private:
|
||||||
class TextIconLayoutElement : public MessageLayoutElement
|
class TextIconLayoutElement : public MessageLayoutElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TextIconLayoutElement(MessageElement &creator_, const QString &line1, const QString &line2,
|
TextIconLayoutElement(MessageElement &creator_, const QString &line1,
|
||||||
float scale, const QSize &size);
|
const QString &line2, float scale, const QSize &size);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
|
void addCopyTextToString(QString &str, int from = 0,
|
||||||
|
int to = INT_MAX) const override;
|
||||||
int getSelectionIndexCount() override;
|
int getSelectionIndexCount() override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
|
|
|
@ -13,11 +13,13 @@ namespace chatterino {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale)
|
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||||
|
const QString &emoteScale)
|
||||||
{
|
{
|
||||||
urlTemplate.detach();
|
urlTemplate.detach();
|
||||||
|
|
||||||
return {urlTemplate.replace("{{id}}", id.string).replace("{{image}}", emoteScale)};
|
return {urlTemplate.replace("{{id}}", id.string)
|
||||||
|
.replace("{{image}}", emoteScale)};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -76,23 +78,26 @@ void BttvEmotes::loadGlobalEmotes()
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot,
|
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
|
||||||
const EmoteMap ¤tEmotes)
|
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes)
|
||||||
{
|
{
|
||||||
auto emotes = EmoteMap();
|
auto emotes = EmoteMap();
|
||||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||||
auto urlTemplate = QString("https:" + jsonRoot.value("urlTemplate").toString());
|
auto urlTemplate =
|
||||||
|
QString("https:" + jsonRoot.value("urlTemplate").toString());
|
||||||
|
|
||||||
for (const QJsonValue &jsonEmote : jsonEmotes) {
|
for (const QJsonValue &jsonEmote : jsonEmotes) {
|
||||||
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
|
||||||
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
|
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
|
||||||
|
|
||||||
auto emote = Emote({name,
|
auto emote = Emote(
|
||||||
ImageSet{Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
{name,
|
||||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
ImageSet{
|
||||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||||
Tooltip{name.string + "<br />Global Bttv Emote"},
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||||
|
Tooltip{name.string + "<br />Global Bttv Emote"},
|
||||||
|
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||||
|
|
||||||
auto it = currentEmotes.find(name);
|
auto it = currentEmotes.find(name);
|
||||||
if (it != currentEmotes.end() && *it->second == emote) {
|
if (it != currentEmotes.end() && *it->second == emote) {
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace chatterino {
|
||||||
|
|
||||||
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes>
|
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes>
|
||||||
{
|
{
|
||||||
static constexpr const char *globalEmoteApiUrl = "https://api.betterttv.net/2/emotes";
|
static constexpr const char *globalEmoteApiUrl =
|
||||||
|
"https://api.betterttv.net/2/emotes";
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// BttvEmotes();
|
// BttvEmotes();
|
||||||
|
@ -22,8 +23,8 @@ public:
|
||||||
void loadGlobalEmotes();
|
void loadGlobalEmotes();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<Outcome, EmoteMap> parseGlobalEmotes(const QJsonObject &jsonRoot,
|
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
|
||||||
const EmoteMap ¤tEmotes);
|
const QJsonObject &jsonRoot, const EmoteMap ¤tEmotes);
|
||||||
|
|
||||||
UniqueAccess<EmoteMap> globalEmotes_;
|
UniqueAccess<EmoteMap> globalEmotes_;
|
||||||
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
|
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
|
||||||
|
|
|
@ -11,12 +11,16 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
static Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale);
|
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||||
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &jsonRoot);
|
const QString &emoteScale);
|
||||||
|
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
|
||||||
|
const QJsonObject &jsonRoot);
|
||||||
|
|
||||||
void loadBttvChannelEmotes(const QString &channelName, std::function<void(EmoteMap &&)> callback)
|
void loadBttvChannelEmotes(const QString &channelName,
|
||||||
|
std::function<void(EmoteMap &&)> callback)
|
||||||
{
|
{
|
||||||
auto request = NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
|
auto request =
|
||||||
|
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
|
||||||
|
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
request.setTimeout(3000);
|
request.setTimeout(3000);
|
||||||
|
@ -31,14 +35,17 @@ void loadBttvChannelEmotes(const QString &channelName, std::function<void(EmoteM
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &jsonRoot)
|
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
|
||||||
|
const QJsonObject &jsonRoot)
|
||||||
{
|
{
|
||||||
static UniqueAccess<std::unordered_map<EmoteId, std::weak_ptr<const Emote>>> cache_;
|
static UniqueAccess<std::unordered_map<EmoteId, std::weak_ptr<const Emote>>>
|
||||||
|
cache_;
|
||||||
|
|
||||||
auto cache = cache_.access();
|
auto cache = cache_.access();
|
||||||
auto emotes = EmoteMap();
|
auto emotes = EmoteMap();
|
||||||
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
auto jsonEmotes = jsonRoot.value("emotes").toArray();
|
||||||
auto urlTemplate = QString("https:" + jsonRoot.value("urlTemplate").toString());
|
auto urlTemplate =
|
||||||
|
QString("https:" + jsonRoot.value("urlTemplate").toString());
|
||||||
|
|
||||||
for (auto jsonEmote_ : jsonEmotes) {
|
for (auto jsonEmote_ : jsonEmotes) {
|
||||||
auto jsonEmote = jsonEmote_.toObject();
|
auto jsonEmote = jsonEmote_.toObject();
|
||||||
|
@ -47,30 +54,35 @@ static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(const QJsonObject &js
|
||||||
auto name = EmoteName{jsonEmote.value("code").toString()};
|
auto name = EmoteName{jsonEmote.value("code").toString()};
|
||||||
// emoteObject.value("imageType").toString();
|
// emoteObject.value("imageType").toString();
|
||||||
|
|
||||||
auto emote = Emote({name,
|
auto emote = Emote(
|
||||||
ImageSet{Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
{name,
|
||||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
ImageSet{
|
||||||
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
|
||||||
Tooltip{name.string + "<br />Channel Bttv Emote"},
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
|
||||||
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
|
||||||
|
Tooltip{name.string + "<br />Channel Bttv Emote"},
|
||||||
|
Url{"https://manage.betterttv.net/emotes/" + id.string}});
|
||||||
|
|
||||||
auto shared = (*cache)[id].lock();
|
auto shared = (*cache)[id].lock();
|
||||||
if (shared && *shared == emote) {
|
if (shared && *shared == emote) {
|
||||||
// reuse old shared_ptr if nothing changed
|
// reuse old shared_ptr if nothing changed
|
||||||
emotes[name] = shared;
|
emotes[name] = shared;
|
||||||
} else {
|
} else {
|
||||||
(*cache)[id] = emotes[name] = std::make_shared<Emote>(std::move(emote));
|
(*cache)[id] = emotes[name] =
|
||||||
|
std::make_shared<Emote>(std::move(emote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {Success, std::move(emotes)};
|
return {Success, std::move(emotes)};
|
||||||
}
|
}
|
||||||
|
|
||||||
static Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale)
|
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
|
||||||
|
const QString &emoteScale)
|
||||||
{
|
{
|
||||||
urlTemplate.detach();
|
urlTemplate.detach();
|
||||||
|
|
||||||
return {urlTemplate.replace("{{id}}", id.string).replace("{{image}}", emoteScale)};
|
return {urlTemplate.replace("{{id}}", id.string)
|
||||||
|
.replace("{{image}}", emoteScale)};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -7,8 +7,10 @@ class QString;
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class EmoteMap;
|
class EmoteMap;
|
||||||
constexpr const char *bttvChannelEmoteApiUrl = "https://api.betterttv.net/2/channels/";
|
constexpr const char *bttvChannelEmoteApiUrl =
|
||||||
|
"https://api.betterttv.net/2/channels/";
|
||||||
|
|
||||||
void loadBttvChannelEmotes(const QString &channelName, std::function<void(EmoteMap &&)> callback);
|
void loadBttvChannelEmotes(const QString &channelName,
|
||||||
|
std::function<void(EmoteMap &&)> callback);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -32,11 +32,13 @@ void ChatterinoBadges::loadChatterinoBadges()
|
||||||
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
|
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
|
||||||
auto jsonBadge = jsonBadge_.toObject();
|
auto jsonBadge = jsonBadge_.toObject();
|
||||||
|
|
||||||
auto emote = Emote{EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
|
auto emote = Emote{
|
||||||
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
|
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
|
||||||
|
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
|
||||||
|
|
||||||
for (auto jsonUser : jsonBadge.value("users").toArray()) {
|
for (auto jsonUser : jsonBadge.value("users").toArray()) {
|
||||||
replacement.add(UserName{jsonUser.toString()}, std::move(emote));
|
replacement.add(UserName{jsonUser.toString()},
|
||||||
|
std::move(emote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ namespace chatterino {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData, const rapidjson::Value &unparsedEmoji,
|
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
|
||||||
|
const rapidjson::Value &unparsedEmoji,
|
||||||
QString shortCode = QString())
|
QString shortCode = QString())
|
||||||
{
|
{
|
||||||
static uint unicodeBytes[4];
|
static uint unicodeBytes[4];
|
||||||
|
@ -80,7 +81,8 @@ void parseEmoji(const std::shared_ptr<EmojiData> &emojiData, const rapidjson::Va
|
||||||
int numUnicodeBytes = 0;
|
int numUnicodeBytes = 0;
|
||||||
|
|
||||||
for (const QString &unicodeCharacter : unicodeCharacters) {
|
for (const QString &unicodeCharacter : unicodeCharacters) {
|
||||||
unicodeBytes[numUnicodeBytes++] = QString(unicodeCharacter).toUInt(nullptr, 16);
|
unicodeBytes[numUnicodeBytes++] =
|
||||||
|
QString(unicodeCharacter).toUInt(nullptr, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
|
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
|
||||||
|
@ -116,8 +118,8 @@ void Emojis::loadEmojis()
|
||||||
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
|
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
|
||||||
|
|
||||||
if (result.Code() != rapidjson::kParseErrorNone) {
|
if (result.Code() != rapidjson::kParseErrorNone) {
|
||||||
Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
|
Log("JSON parse error: {} ({})",
|
||||||
result.Offset());
|
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +137,8 @@ void Emojis::loadEmojis()
|
||||||
this->emojis.insert(emojiData->unifiedCode, emojiData);
|
this->emojis.insert(emojiData->unifiedCode, emojiData);
|
||||||
|
|
||||||
if (unparsedEmoji.HasMember("skin_variations")) {
|
if (unparsedEmoji.HasMember("skin_variations")) {
|
||||||
for (const auto &skinVariation : unparsedEmoji["skin_variations"].GetObject()) {
|
for (const auto &skinVariation :
|
||||||
|
unparsedEmoji["skin_variations"].GetObject()) {
|
||||||
std::string tone = skinVariation.name.GetString();
|
std::string tone = skinVariation.name.GetString();
|
||||||
const auto &variation = skinVariation.value;
|
const auto &variation = skinVariation.value;
|
||||||
|
|
||||||
|
@ -143,20 +146,23 @@ void Emojis::loadEmojis()
|
||||||
|
|
||||||
auto toneNameIt = toneNames.find(tone);
|
auto toneNameIt = toneNames.find(tone);
|
||||||
if (toneNameIt == toneNames.end()) {
|
if (toneNameIt == toneNames.end()) {
|
||||||
Log("Tone with key {} does not exist in tone names map", tone);
|
Log("Tone with key {} does not exist in tone names map",
|
||||||
|
tone);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseEmoji(variationEmojiData, variation,
|
parseEmoji(variationEmojiData, variation,
|
||||||
emojiData->shortCodes[0] + "_" + toneNameIt->second);
|
emojiData->shortCodes[0] + "_" + toneNameIt->second);
|
||||||
|
|
||||||
this->emojiShortCodeToEmoji_.insert(variationEmojiData->shortCodes[0],
|
this->emojiShortCodeToEmoji_.insert(
|
||||||
variationEmojiData);
|
variationEmojiData->shortCodes[0], variationEmojiData);
|
||||||
this->shortCodes.push_back(variationEmojiData->shortCodes[0]);
|
this->shortCodes.push_back(variationEmojiData->shortCodes[0]);
|
||||||
|
|
||||||
this->emojiFirstByte_[variationEmojiData->value.at(0)].append(variationEmojiData);
|
this->emojiFirstByte_[variationEmojiData->value.at(0)].append(
|
||||||
|
variationEmojiData);
|
||||||
|
|
||||||
this->emojis.insert(variationEmojiData->unifiedCode, variationEmojiData);
|
this->emojis.insert(variationEmojiData->unifiedCode,
|
||||||
|
variationEmojiData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,14 +202,16 @@ void Emojis::loadEmojiOne2Capabilities()
|
||||||
void Emojis::sortEmojis()
|
void Emojis::sortEmojis()
|
||||||
{
|
{
|
||||||
for (auto &p : this->emojiFirstByte_) {
|
for (auto &p : this->emojiFirstByte_) {
|
||||||
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
|
std::stable_sort(p.begin(), p.end(),
|
||||||
return lhs->value.length() > rhs->value.length();
|
[](const auto &lhs, const auto &rhs) {
|
||||||
});
|
return lhs->value.length() > rhs->value.length();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &p = this->shortCodes;
|
auto &p = this->shortCodes;
|
||||||
std::stable_sort(p.begin(), p.end(),
|
std::stable_sort(p.begin(), p.end(), [](const auto &lhs, const auto &rhs) {
|
||||||
[](const auto &lhs, const auto &rhs) { return lhs < rhs; });
|
return lhs < rhs;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Emojis::loadEmojiSet()
|
void Emojis::loadEmojiSet()
|
||||||
|
@ -212,7 +220,8 @@ void Emojis::loadEmojiSet()
|
||||||
|
|
||||||
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
|
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
|
||||||
Log("Using emoji set {}", emojiSet);
|
Log("Using emoji set {}", emojiSet);
|
||||||
this->emojis.each([=](const auto &name, std::shared_ptr<EmojiData> &emoji) {
|
this->emojis.each([=](const auto &name,
|
||||||
|
std::shared_ptr<EmojiData> &emoji) {
|
||||||
QString emojiSetToUse = emojiSet;
|
QString emojiSetToUse = emojiSet;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static std::map<QString, QString> emojiSets = {
|
static std::map<QString, QString> emojiSets = {
|
||||||
|
@ -259,20 +268,22 @@ void Emojis::loadEmojiSet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code = code.toLower();
|
code = code.toLower();
|
||||||
QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/";
|
QString urlPrefix = "https://cdnjs.cloudflare.com/ajax/libs/"
|
||||||
|
"emojione/2.2.6/assets/png/";
|
||||||
auto it = emojiSets.find(emojiSetToUse);
|
auto it = emojiSets.find(emojiSetToUse);
|
||||||
if (it != emojiSets.end()) {
|
if (it != emojiSets.end()) {
|
||||||
urlPrefix = it->second;
|
urlPrefix = it->second;
|
||||||
}
|
}
|
||||||
QString url = urlPrefix + code + ".png";
|
QString url = urlPrefix + code + ".png";
|
||||||
emoji->emote = std::make_shared<Emote>(
|
emoji->emote = std::make_shared<Emote>(Emote{
|
||||||
Emote{EmoteName{emoji->value}, ImageSet{Image::fromUrl({url}, 0.35)},
|
EmoteName{emoji->value}, ImageSet{Image::fromUrl({url}, 0.35)},
|
||||||
Tooltip{":" + emoji->shortCodes[0] + ":<br/>Emoji"}, Url{}});
|
Tooltip{":" + emoji->shortCodes[0] + ":<br/>Emoji"}, Url{}});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(const QString &text)
|
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
|
const QString &text)
|
||||||
{
|
{
|
||||||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||||
int lastParsedEmojiEndIndex = 0;
|
int lastParsedEmojiEndIndex = 0;
|
||||||
|
@ -330,11 +341,13 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(const QString &text
|
||||||
int currentParsedEmojiFirstIndex = i;
|
int currentParsedEmojiFirstIndex = i;
|
||||||
int currentParsedEmojiEndIndex = i + (matchedEmojiLength);
|
int currentParsedEmojiEndIndex = i + (matchedEmojiLength);
|
||||||
|
|
||||||
int charactersFromLastParsedEmoji = currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
|
int charactersFromLastParsedEmoji =
|
||||||
|
currentParsedEmojiFirstIndex - lastParsedEmojiEndIndex;
|
||||||
|
|
||||||
if (charactersFromLastParsedEmoji > 0) {
|
if (charactersFromLastParsedEmoji > 0) {
|
||||||
// Add characters inbetween emojis
|
// Add characters inbetween emojis
|
||||||
result.emplace_back(text.mid(lastParsedEmojiEndIndex, charactersFromLastParsedEmoji));
|
result.emplace_back(text.mid(lastParsedEmojiEndIndex,
|
||||||
|
charactersFromLastParsedEmoji));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the emoji as a word to parsedWords
|
// Push the emoji as a word to parsedWords
|
||||||
|
@ -365,7 +378,8 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||||
|
|
||||||
auto capturedString = match.captured();
|
auto capturedString = match.captured();
|
||||||
|
|
||||||
QString matchString = capturedString.toLower().mid(1, capturedString.size() - 2);
|
QString matchString =
|
||||||
|
capturedString.toLower().mid(1, capturedString.size() - 2);
|
||||||
|
|
||||||
auto emojiIt = this->emojiShortCodeToEmoji_.constFind(matchString);
|
auto emojiIt = this->emojiShortCodeToEmoji_.constFind(matchString);
|
||||||
|
|
||||||
|
@ -375,7 +389,8 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||||
|
|
||||||
auto emojiData = emojiIt.value();
|
auto emojiData = emojiIt.value();
|
||||||
|
|
||||||
ret.replace(offset + match.capturedStart(), match.capturedLength(), emojiData->value);
|
ret.replace(offset + match.capturedStart(), match.capturedLength(),
|
||||||
|
emojiData->value);
|
||||||
|
|
||||||
offset += emojiData->value.size() - match.capturedLength();
|
offset += emojiData->value.size() - match.capturedLength();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct EmojiData {
|
struct EmojiData {
|
||||||
// actual byte-representation of the emoji (i.e. \154075\156150 which is :male:)
|
// actual byte-representation of the emoji (i.e. \154075\156150 which is
|
||||||
|
// :male:)
|
||||||
QString value;
|
QString value;
|
||||||
|
|
||||||
// i.e. 204e-50a2
|
// i.e. 204e-50a2
|
||||||
|
@ -57,7 +58,8 @@ private:
|
||||||
// shortCodeToEmoji maps strings like "sunglasses" to its emoji
|
// shortCodeToEmoji maps strings like "sunglasses" to its emoji
|
||||||
QMap<QString, std::shared_ptr<EmojiData>> emojiShortCodeToEmoji_;
|
QMap<QString, std::shared_ptr<EmojiData>> emojiShortCodeToEmoji_;
|
||||||
|
|
||||||
// Maps the first character of the emoji unicode string to a vector of possible emojis
|
// Maps the first character of the emoji unicode string to a vector of
|
||||||
|
// possible emojis
|
||||||
QMap<QChar, QVector<std::shared_ptr<EmojiData>>> emojiFirstByte_;
|
QMap<QChar, QVector<std::shared_ptr<EmojiData>>> emojiFirstByte_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@ Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
|
||||||
return {"https:" + emote.toString()};
|
return {"https:" + emote.toString()};
|
||||||
}
|
}
|
||||||
|
|
||||||
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, const QString &tooltip,
|
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
|
||||||
Emote &emoteData)
|
const QString &tooltip, Emote &emoteData)
|
||||||
{
|
{
|
||||||
auto url1x = getEmoteLink(urls, "1");
|
auto url1x = getEmoteLink(urls, "1");
|
||||||
auto url2x = getEmoteLink(urls, "2");
|
auto url2x = getEmoteLink(urls, "2");
|
||||||
|
@ -30,7 +30,8 @@ void fillInEmoteData(const QJsonObject &urls, const EmoteName &name, const QStri
|
||||||
//, code, tooltip
|
//, code, tooltip
|
||||||
emoteData.name = name;
|
emoteData.name = name;
|
||||||
emoteData.images =
|
emoteData.images =
|
||||||
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5), Image::fromUrl(url3x, 0.25)};
|
ImageSet{Image::fromUrl(url1x, 1), Image::fromUrl(url2x, 0.5),
|
||||||
|
Image::fromUrl(url3x, 0.25)};
|
||||||
emoteData.tooltip = {tooltip};
|
emoteData.tooltip = {tooltip};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -67,8 +68,9 @@ void FfzEmotes::loadGlobalEmotes()
|
||||||
NetworkRequest request(url);
|
NetworkRequest request(url);
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
request.setTimeout(30000);
|
request.setTimeout(30000);
|
||||||
request.onSuccess(
|
request.onSuccess([this](auto result) -> Outcome {
|
||||||
[this](auto result) -> Outcome { return this->parseGlobalEmotes(result.parseJson()); });
|
return this->parseGlobalEmotes(result.parseJson());
|
||||||
|
});
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
@ -90,10 +92,12 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
|
||||||
auto urls = jsonEmote.value("urls").toObject();
|
auto urls = jsonEmote.value("urls").toObject();
|
||||||
|
|
||||||
auto emote = Emote();
|
auto emote = Emote();
|
||||||
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote", emote);
|
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
|
||||||
emote.homePage = Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
emote);
|
||||||
.arg(id.string)
|
emote.homePage =
|
||||||
.arg(name.string)};
|
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
|
||||||
|
.arg(id.string)
|
||||||
|
.arg(name.string)};
|
||||||
|
|
||||||
replacement.add(name, emote);
|
replacement.add(name, emote);
|
||||||
}
|
}
|
||||||
|
@ -105,7 +109,8 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
|
||||||
void FfzEmotes::loadChannelEmotes(const QString &channelName,
|
void FfzEmotes::loadChannelEmotes(const QString &channelName,
|
||||||
std::function<void(EmoteMap &&)> callback)
|
std::function<void(EmoteMap &&)> callback)
|
||||||
{
|
{
|
||||||
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", qPrintable(channelName));
|
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n",
|
||||||
|
// qPrintable(channelName));
|
||||||
|
|
||||||
// QString url("https://api.frankerfacez.com/v1/room/" + channelName);
|
// QString url("https://api.frankerfacez.com/v1/room/" + channelName);
|
||||||
|
|
||||||
|
@ -145,10 +150,11 @@ Outcome parseChannelEmotes(const QJsonObject &jsonRoot)
|
||||||
|
|
||||||
// QJsonObject urls = emoteObject.value("urls").toObject();
|
// QJsonObject urls = emoteObject.value("urls").toObject();
|
||||||
|
|
||||||
// auto emote = this->channelEmoteCache_.getOrAdd(id, [id, &code, &urls] {
|
// auto emote = this->channelEmoteCache_.getOrAdd(id, [id, &code,
|
||||||
|
// &urls] {
|
||||||
// EmoteData emoteData;
|
// EmoteData emoteData;
|
||||||
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
|
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote",
|
||||||
// emoteData.pageLink =
|
// emoteData); emoteData.pageLink =
|
||||||
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
|
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
|
||||||
|
|
||||||
// return emoteData;
|
// return emoteData;
|
||||||
|
|
|
@ -10,8 +10,10 @@ namespace chatterino {
|
||||||
|
|
||||||
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes>
|
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes>
|
||||||
{
|
{
|
||||||
static constexpr const char *globalEmoteApiUrl = "https://api.frankerfacez.com/v1/set/global";
|
static constexpr const char *globalEmoteApiUrl =
|
||||||
static constexpr const char *channelEmoteApiUrl = "https://api.betterttv.net/2/channels/";
|
"https://api.frankerfacez.com/v1/set/global";
|
||||||
|
static constexpr const char *channelEmoteApiUrl =
|
||||||
|
"https://api.betterttv.net/2/channels/";
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// FfzEmotes();
|
// FfzEmotes();
|
||||||
|
@ -23,7 +25,8 @@ public:
|
||||||
boost::optional<EmotePtr> getEmote(const EmoteId &id);
|
boost::optional<EmotePtr> getEmote(const EmoteId &id);
|
||||||
|
|
||||||
void loadGlobalEmotes();
|
void loadGlobalEmotes();
|
||||||
void loadChannelEmotes(const QString &channelName, std::function<void(EmoteMap &&)> callback);
|
void loadChannelEmotes(const QString &channelName,
|
||||||
|
std::function<void(EmoteMap &&)> callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
|
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
|
||||||
|
|
|
@ -12,27 +12,35 @@ AbstractIrcServer::AbstractIrcServer()
|
||||||
{
|
{
|
||||||
// Initialize the connections
|
// Initialize the connections
|
||||||
this->writeConnection_.reset(new IrcConnection);
|
this->writeConnection_.reset(new IrcConnection);
|
||||||
this->writeConnection_->moveToThread(QCoreApplication::instance()->thread());
|
this->writeConnection_->moveToThread(
|
||||||
|
QCoreApplication::instance()->thread());
|
||||||
|
|
||||||
QObject::connect(this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
QObject::connect(
|
||||||
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
|
||||||
|
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
||||||
|
|
||||||
// Listen to read connection message signals
|
// Listen to read connection message signals
|
||||||
this->readConnection_.reset(new IrcConnection);
|
this->readConnection_.reset(new IrcConnection);
|
||||||
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
|
||||||
|
|
||||||
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
|
QObject::connect(this->readConnection_.get(),
|
||||||
|
&Communi::IrcConnection::messageReceived,
|
||||||
[this](auto msg) { this->messageReceived(msg); });
|
[this](auto msg) { this->messageReceived(msg); });
|
||||||
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::privateMessageReceived,
|
QObject::connect(this->readConnection_.get(),
|
||||||
|
&Communi::IrcConnection::privateMessageReceived,
|
||||||
[this](auto msg) { this->privateMessageReceived(msg); });
|
[this](auto msg) { this->privateMessageReceived(msg); });
|
||||||
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::connected,
|
QObject::connect(this->readConnection_.get(),
|
||||||
|
&Communi::IrcConnection::connected,
|
||||||
[this] { this->onConnected(); });
|
[this] { this->onConnected(); });
|
||||||
QObject::connect(this->readConnection_.get(), &Communi::IrcConnection::disconnected,
|
QObject::connect(this->readConnection_.get(),
|
||||||
|
&Communi::IrcConnection::disconnected,
|
||||||
[this] { this->onDisconnected(); });
|
[this] { this->onDisconnected(); });
|
||||||
|
|
||||||
// listen to reconnect request
|
// listen to reconnect request
|
||||||
this->readConnection_->reconnectRequested.connect([this] { this->connect(); });
|
this->readConnection_->reconnectRequested.connect(
|
||||||
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
|
[this] { this->connect(); });
|
||||||
|
// this->writeConnection->reconnectRequested.connect([this] {
|
||||||
|
// this->connect(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::connect()
|
void AbstractIrcServer::connect()
|
||||||
|
@ -75,7 +83,8 @@ void AbstractIrcServer::disconnect()
|
||||||
this->writeConnection_->close();
|
this->writeConnection_->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::sendMessage(const QString &channelName, const QString &message)
|
void AbstractIrcServer::sendMessage(const QString &channelName,
|
||||||
|
const QString &message)
|
||||||
{
|
{
|
||||||
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
|
||||||
}
|
}
|
||||||
|
@ -91,11 +100,13 @@ void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
void AbstractIrcServer::writeConnectionMessageReceived(
|
||||||
|
Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
|
||||||
|
const QString &dirtyChannelName)
|
||||||
{
|
{
|
||||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||||
|
|
||||||
|
@ -119,7 +130,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(const QString &dirty
|
||||||
chan->destroyed.connect([this, clojuresInCppAreShit] {
|
chan->destroyed.connect([this, clojuresInCppAreShit] {
|
||||||
// fourtf: issues when the server itself is destroyed
|
// fourtf: issues when the server itself is destroyed
|
||||||
|
|
||||||
Log("[AbstractIrcServer::addChannel] {} was destroyed", clojuresInCppAreShit);
|
Log("[AbstractIrcServer::addChannel] {} was destroyed",
|
||||||
|
clojuresInCppAreShit);
|
||||||
this->channels.remove(clojuresInCppAreShit);
|
this->channels.remove(clojuresInCppAreShit);
|
||||||
|
|
||||||
if (this->readConnection_) {
|
if (this->readConnection_) {
|
||||||
|
@ -147,7 +159,8 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(const QString &dirty
|
||||||
return chan;
|
return chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
|
std::shared_ptr<Channel> AbstractIrcServer::getChannelOrEmpty(
|
||||||
|
const QString &dirtyChannelName)
|
||||||
{
|
{
|
||||||
auto channelName = this->cleanChannelName(dirtyChannelName);
|
auto channelName = this->cleanChannelName(dirtyChannelName);
|
||||||
|
|
||||||
|
@ -187,9 +200,9 @@ void AbstractIrcServer::onConnected()
|
||||||
|
|
||||||
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
||||||
|
|
||||||
bool replaceMessage =
|
bool replaceMessage = snapshot.getLength() > 0 &&
|
||||||
snapshot.getLength() > 0 &&
|
snapshot[snapshot.getLength() - 1]->flags &
|
||||||
snapshot[snapshot.getLength() - 1]->flags & Message::DisconnectedMessage;
|
Message::DisconnectedMessage;
|
||||||
|
|
||||||
if (replaceMessage) {
|
if (replaceMessage) {
|
||||||
chan->replaceMessage(snapshot[snapshot.getLength() - 1], reconnMsg);
|
chan->replaceMessage(snapshot[snapshot.getLength() - 1], reconnMsg);
|
||||||
|
@ -217,7 +230,8 @@ void AbstractIrcServer::onDisconnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(const QString &channelName)
|
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
|
||||||
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -229,16 +243,19 @@ QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
|
||||||
|
|
||||||
void AbstractIrcServer::addFakeMessage(const QString &data)
|
void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||||
{
|
{
|
||||||
auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection_.get());
|
auto fakeMessage = Communi::IrcMessage::fromData(
|
||||||
|
data.toUtf8(), this->readConnection_.get());
|
||||||
|
|
||||||
if (fakeMessage->command() == "PRIVMSG") {
|
if (fakeMessage->command() == "PRIVMSG") {
|
||||||
this->privateMessageReceived(static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
this->privateMessageReceived(
|
||||||
|
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||||
} else {
|
} else {
|
||||||
this->messageReceived(fakeMessage);
|
this->messageReceived(fakeMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
void AbstractIrcServer::privateMessageReceived(
|
||||||
|
Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,8 @@ public:
|
||||||
// signals
|
// signals
|
||||||
pajlada::Signals::NoArgSignal connected;
|
pajlada::Signals::NoArgSignal connected;
|
||||||
pajlada::Signals::NoArgSignal disconnected;
|
pajlada::Signals::NoArgSignal disconnected;
|
||||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
|
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *>
|
||||||
|
// onPrivateMessage;
|
||||||
|
|
||||||
void addFakeMessage(const QString &data);
|
void addFakeMessage(const QString &data);
|
||||||
|
|
||||||
|
@ -40,8 +41,10 @@ public:
|
||||||
protected:
|
protected:
|
||||||
AbstractIrcServer();
|
AbstractIrcServer();
|
||||||
|
|
||||||
virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
|
virtual void initializeConnection(IrcConnection *connection, bool isRead,
|
||||||
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
|
bool isWrite) = 0;
|
||||||
|
virtual std::shared_ptr<Channel> createChannel(
|
||||||
|
const QString &channelName) = 0;
|
||||||
|
|
||||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||||
virtual void messageReceived(Communi::IrcMessage *message);
|
virtual void messageReceived(Communi::IrcMessage *message);
|
||||||
|
@ -50,7 +53,8 @@ protected:
|
||||||
virtual void onConnected();
|
virtual void onConnected();
|
||||||
virtual void onDisconnected();
|
virtual void onDisconnected();
|
||||||
|
|
||||||
virtual std::shared_ptr<Channel> getCustomChannel(const QString &channelName);
|
virtual std::shared_ptr<Channel> getCustomChannel(
|
||||||
|
const QString &channelName);
|
||||||
|
|
||||||
virtual bool hasSeparateWriteConnection() const = 0;
|
virtual bool hasSeparateWriteConnection() const = 0;
|
||||||
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
virtual QString cleanChannelName(const QString &dirtyChannelName);
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
// namespace chatterino {
|
// namespace chatterino {
|
||||||
//
|
//
|
||||||
// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName, const QString
|
// IrcAccount::IrcAccount(const QString &_userName, const QString &_nickName,
|
||||||
|
// const QString
|
||||||
// &_realName,
|
// &_realName,
|
||||||
// const QString &_password)
|
// const QString &_password)
|
||||||
// : userName(_userName)
|
// : userName(_userName)
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
// class IrcAccount
|
// class IrcAccount
|
||||||
//{
|
//{
|
||||||
// public:
|
// public:
|
||||||
// IrcAccount(const QString &userName, const QString &nickName, const QString &realName,
|
// IrcAccount(const QString &userName, const QString &nickName, const QString
|
||||||
|
// &realName,
|
||||||
// const QString &password);
|
// const QString &password);
|
||||||
|
|
||||||
// const QString &getUserName() const;
|
// const QString &getUserName() const;
|
||||||
|
|
|
@ -27,13 +27,14 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
|
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
||||||
this->recentlyReceivedMessage_ = true;
|
[this](Communi::IrcMessage *) {
|
||||||
|
this->recentlyReceivedMessage_ = true;
|
||||||
|
|
||||||
if (this->reconnectTimer_.isActive()) {
|
if (this->reconnectTimer_.isActive()) {
|
||||||
this->reconnectTimer_.stop();
|
this->reconnectTimer_.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -14,7 +14,8 @@ namespace chatterino {
|
||||||
// std::shared_ptr<IrcAccount> getAccount() const;
|
// std::shared_ptr<IrcAccount> getAccount() const;
|
||||||
|
|
||||||
// protected:
|
// protected:
|
||||||
// virtual void initializeConnection(Communi::IrcConnection *connection, bool isReadConnection);
|
// virtual void initializeConnection(Communi::IrcConnection *connection, bool
|
||||||
|
// isReadConnection);
|
||||||
|
|
||||||
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
// virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||||
// virtual void messageReceived(Communi::IrcMessage *message);
|
// virtual void messageReceived(Communi::IrcMessage *message);
|
||||||
|
|
|
@ -25,15 +25,17 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server)
|
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
|
TwitchServer &server)
|
||||||
{
|
{
|
||||||
this->addMessage(message, message->target(), message->content(), server, false,
|
this->addMessage(message, message->target(), message->content(), server,
|
||||||
message->isAction());
|
false, message->isAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, const QString &target,
|
void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||||
const QString &content, TwitchServer &server, bool isSub,
|
const QString &target,
|
||||||
bool isAction)
|
const QString &content, TwitchServer &server,
|
||||||
|
bool isSub, bool isAction)
|
||||||
{
|
{
|
||||||
QString channelName;
|
QString channelName;
|
||||||
if (!trimChannelName(target, channelName)) {
|
if (!trimChannelName(target, channelName)) {
|
||||||
|
@ -140,14 +142,17 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||||
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||||
|
|
||||||
if (chan->isEmpty()) {
|
if (chan->isEmpty()) {
|
||||||
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found", chanName);
|
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
|
||||||
|
"found",
|
||||||
|
chanName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the chat has been cleared by a moderator
|
// check if the chat has been cleared by a moderator
|
||||||
if (message->parameters().length() == 1) {
|
if (message->parameters().length() == 1) {
|
||||||
chan->disableAllMessages();
|
chan->disableAllMessages();
|
||||||
chan->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
|
chan->addMessage(Message::createSystemMessage(
|
||||||
|
"Chat has been cleared by a moderator."));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +170,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||||
reason = v.toString();
|
reason = v.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
|
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds,
|
||||||
|
reason, false);
|
||||||
chan->addOrReplaceTimeout(timeoutMsg);
|
chan->addOrReplaceTimeout(timeoutMsg);
|
||||||
|
|
||||||
// refresh all
|
// refresh all
|
||||||
|
@ -206,7 +212,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
auto c = app->twitch.server->whispersChannel.get();
|
auto c = app->twitch.server->whispersChannel.get();
|
||||||
|
|
||||||
TwitchMessageBuilder builder(c, message, args, message->parameter(1), false);
|
TwitchMessageBuilder builder(c, message, args, message->parameter(1),
|
||||||
|
false);
|
||||||
|
|
||||||
if (!builder.isIgnored()) {
|
if (!builder.isIgnored()) {
|
||||||
MessagePtr _message = builder.build();
|
MessagePtr _message = builder.build();
|
||||||
|
@ -229,7 +236,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server)
|
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
|
TwitchServer &server)
|
||||||
{
|
{
|
||||||
auto data = message->toData();
|
auto data = message->toData();
|
||||||
|
|
||||||
|
@ -244,7 +252,8 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, Tw
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
|
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
|
||||||
// Sub-specific message. I think it's only allowed for "resub" messages atm
|
// Sub-specific message. I think it's only allowed for "resub" messages
|
||||||
|
// atm
|
||||||
if (!content.isEmpty()) {
|
if (!content.isEmpty()) {
|
||||||
this->addMessage(message, target, content, server, true, false);
|
this->addMessage(message, target, content, server, true, false);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +262,8 @@ void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, Tw
|
||||||
auto it = tags.find("system-msg");
|
auto it = tags.find("system-msg");
|
||||||
|
|
||||||
if (it != tags.end()) {
|
if (it != tags.end()) {
|
||||||
auto newMessage = Message::createSystemMessage(parseTagString(it.value().toString()));
|
auto newMessage =
|
||||||
|
Message::createSystemMessage(parseTagString(it.value().toString()));
|
||||||
|
|
||||||
newMessage->flags |= Message::Subscription;
|
newMessage->flags |= Message::Subscription;
|
||||||
|
|
||||||
|
@ -279,7 +289,8 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
|
auto channel = app->twitch.server->getChannelOrEmpty(
|
||||||
|
message->parameter(0).remove(0, 1));
|
||||||
|
|
||||||
if (channel->isEmpty()) {
|
if (channel->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -299,10 +310,12 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||||
|
|
||||||
QString channelName;
|
QString channelName;
|
||||||
if (!trimChannelName(message->target(), channelName)) {
|
if (!trimChannelName(message->target(), channelName)) {
|
||||||
// Notice wasn't targeted at a single channel, send to all twitch channels
|
// Notice wasn't targeted at a single channel, send to all twitch
|
||||||
app->twitch.server->forEachChannelAndSpecialChannels([msg](const auto &c) {
|
// channels
|
||||||
c->addMessage(msg); //
|
app->twitch.server->forEachChannelAndSpecialChannels(
|
||||||
});
|
[msg](const auto &c) {
|
||||||
|
c->addMessage(msg); //
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -310,7 +323,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||||
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
|
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
|
||||||
|
|
||||||
if (channel->isEmpty()) {
|
if (channel->isEmpty()) {
|
||||||
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager ",
|
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
|
||||||
|
"manager ",
|
||||||
channelName);
|
channelName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -318,7 +332,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||||
channel->addMessage(msg);
|
channel->addMessage(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message)
|
void IrcMessageHandler::handleWriteConnectionNoticeMessage(
|
||||||
|
Communi::IrcNoticeMessage *message)
|
||||||
{
|
{
|
||||||
static std::unordered_set<std::string> readConnectionOnlyIDs{
|
static std::unordered_set<std::string> readConnectionOnlyIDs{
|
||||||
"host_on",
|
"host_on",
|
||||||
|
@ -333,8 +348,9 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
|
||||||
"r9k_on",
|
"r9k_on",
|
||||||
"r9k_off",
|
"r9k_off",
|
||||||
|
|
||||||
// Display for user who times someone out. This implies you're a moderator, at which point
|
// Display for user who times someone out. This implies you're a
|
||||||
// you will be connected to PubSub and receive a better message from there
|
// moderator, at which point you will be connected to PubSub and receive
|
||||||
|
// a better message from there
|
||||||
"timeout_success",
|
"timeout_success",
|
||||||
"ban_success",
|
"ban_success",
|
||||||
};
|
};
|
||||||
|
@ -347,7 +363,8 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log("Showing notice message from write connection with message id '{}'", msgID);
|
Log("Showing notice message from write connection with message id '{}'",
|
||||||
|
msgID);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->handleNoticeMessage(message);
|
this->handleNoticeMessage(message);
|
||||||
|
@ -356,9 +373,11 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
|
||||||
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
|
auto channel = app->twitch.server->getChannelOrEmpty(
|
||||||
|
message->parameter(0).remove(0, 1));
|
||||||
|
|
||||||
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
if (TwitchChannel *twitchChannel =
|
||||||
|
dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||||
twitchChannel->addJoinedUser(message->nick());
|
twitchChannel->addJoinedUser(message->nick());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,9 +385,11 @@ void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||||
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
|
auto channel = app->twitch.server->getChannelOrEmpty(
|
||||||
|
message->parameter(0).remove(0, 1));
|
||||||
|
|
||||||
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
if (TwitchChannel *twitchChannel =
|
||||||
|
dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||||
twitchChannel->addPartedUser(message->nick());
|
twitchChannel->addPartedUser(message->nick());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,15 @@ class IrcMessageHandler
|
||||||
public:
|
public:
|
||||||
static IrcMessageHandler &getInstance();
|
static IrcMessageHandler &getInstance();
|
||||||
|
|
||||||
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
|
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
|
TwitchServer &server);
|
||||||
|
|
||||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||||
void handleWhisperMessage(Communi::IrcMessage *message);
|
void handleWhisperMessage(Communi::IrcMessage *message);
|
||||||
void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server);
|
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
|
TwitchServer &server);
|
||||||
void handleModeMessage(Communi::IrcMessage *message);
|
void handleModeMessage(Communi::IrcMessage *message);
|
||||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||||
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||||
|
@ -28,8 +30,9 @@ public:
|
||||||
void handlePartMessage(Communi::IrcMessage *message);
|
void handlePartMessage(Communi::IrcMessage *message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
|
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||||
TwitchServer &server, bool isResub, bool isAction);
|
const QString &content, TwitchServer &server, bool isResub,
|
||||||
|
bool isAction);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -25,7 +25,8 @@ PartialTwitchUser PartialTwitchUser::byId(const QString &id)
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback, const QObject *caller)
|
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||||
|
const QObject *caller)
|
||||||
{
|
{
|
||||||
assert(!this->username_.isEmpty());
|
assert(!this->username_.isEmpty());
|
||||||
|
|
||||||
|
@ -33,7 +34,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback, cons
|
||||||
caller = QThread::currentThread();
|
caller = QThread::currentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest request("https://api.twitch.tv/kraken/users?login=" + this->username_);
|
NetworkRequest request("https://api.twitch.tv/kraken/users?login=" +
|
||||||
|
this->username_);
|
||||||
request.setCaller(caller);
|
request.setCaller(caller);
|
||||||
request.makeAuthorizedV5(getDefaultClientID());
|
request.makeAuthorizedV5(getDefaultClientID());
|
||||||
|
|
||||||
|
@ -56,7 +58,8 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback, cons
|
||||||
auto firstUser = users[0].toObject();
|
auto firstUser = users[0].toObject();
|
||||||
auto id = firstUser.value("_id");
|
auto id = firstUser.value("_id");
|
||||||
if (!id.isString()) {
|
if (!id.isString()) {
|
||||||
Log("API Error: while getting user id, first user object `_id` key is not a "
|
Log("API Error: while getting user id, first user object `_id` key "
|
||||||
|
"is not a "
|
||||||
"string");
|
"string");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ public:
|
||||||
static PartialTwitchUser byName(const QString &username);
|
static PartialTwitchUser byName(const QString &username);
|
||||||
static PartialTwitchUser byId(const QString &id);
|
static PartialTwitchUser byId(const QString &id);
|
||||||
|
|
||||||
void getId(std::function<void(QString)> successCallback, const QObject *caller = nullptr);
|
void getId(std::function<void(QString)> successCallback,
|
||||||
|
const QObject *caller = nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -21,7 +21,8 @@ struct PubSubAction {
|
||||||
QString roomID;
|
QString roomID;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used when a chat mode (i.e. slowmode, subscribers only mode) is enabled or disabled
|
// Used when a chat mode (i.e. slowmode, subscribers only mode) is enabled or
|
||||||
|
// disabled
|
||||||
struct ModeChangedAction : PubSubAction {
|
struct ModeChangedAction : PubSubAction {
|
||||||
using PubSubAction::PubSubAction;
|
using PubSubAction::PubSubAction;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,8 @@ static std::map<QString, std::string> sentMessages;
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
PubSubClient::PubSubClient(WebsocketClient &websocketClient, WebsocketHandle handle)
|
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
|
||||||
|
WebsocketHandle handle)
|
||||||
: websocketClient_(websocketClient)
|
: websocketClient_(websocketClient)
|
||||||
, handle_(handle)
|
, handle_(handle)
|
||||||
{
|
{
|
||||||
|
@ -58,7 +59,8 @@ bool PubSubClient::listen(rapidjson::Document &message)
|
||||||
this->numListens_ += numRequestedListens;
|
this->numListens_ += numRequestedListens;
|
||||||
|
|
||||||
for (const auto &topic : message["data"]["topics"].GetArray()) {
|
for (const auto &topic : message["data"]["topics"].GetArray()) {
|
||||||
this->listeners_.emplace_back(Listener{topic.GetString(), false, false, false});
|
this->listeners_.emplace_back(
|
||||||
|
Listener{topic.GetString(), false, false, false});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto uuid = CreateUUID();
|
auto uuid = CreateUUID();
|
||||||
|
@ -135,34 +137,38 @@ void PubSubClient::ping()
|
||||||
|
|
||||||
auto self = this->shared_from_this();
|
auto self = this->shared_from_this();
|
||||||
|
|
||||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15), [self](auto timer) {
|
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
|
||||||
if (!self->started_) {
|
[self](auto timer) {
|
||||||
return;
|
if (!self->started_) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (self->awaitingPong_) {
|
if (self->awaitingPong_) {
|
||||||
Log("No pong respnose, disconnect!");
|
Log("No pong respnose, disconnect!");
|
||||||
// TODO(pajlada): Label this connection as "disconnect me"
|
// TODO(pajlada): Label this connection as "disconnect me"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5), [self](auto timer) {
|
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
|
||||||
if (!self->started_) {
|
[self](auto timer) {
|
||||||
return;
|
if (!self->started_) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self->ping(); //
|
self->ping(); //
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSubClient::send(const char *payload)
|
bool PubSubClient::send(const char *payload)
|
||||||
{
|
{
|
||||||
WebsocketErrorCode ec;
|
WebsocketErrorCode ec;
|
||||||
this->websocketClient_.send(this->handle_, payload, websocketpp::frame::opcode::text, ec);
|
this->websocketClient_.send(this->handle_, payload,
|
||||||
|
websocketpp::frame::opcode::text, ec);
|
||||||
|
|
||||||
if (ec) {
|
if (ec) {
|
||||||
Log("Error sending message {}: {}", payload, ec.message());
|
Log("Error sending message {}: {}", payload, ec.message());
|
||||||
// TODO(pajlada): Check which error code happened and maybe gracefully handle it
|
// TODO(pajlada): Check which error code happened and maybe gracefully
|
||||||
|
// handle it
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -176,13 +182,15 @@ PubSub::PubSub()
|
||||||
{
|
{
|
||||||
qDebug() << "init PubSub";
|
qDebug() << "init PubSub";
|
||||||
|
|
||||||
this->moderationActionHandlers["clear"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["clear"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ClearChatAction action(data, roomID);
|
ClearChatAction action(data, roomID);
|
||||||
|
|
||||||
this->signals_.moderation.chatCleared.invoke(action);
|
this->signals_.moderation.chatCleared.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["slowoff"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["slowoff"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::Slow;
|
action.mode = ModeChangedAction::Mode::Slow;
|
||||||
|
@ -191,7 +199,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["slow"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["slow"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::Slow;
|
action.mode = ModeChangedAction::Mode::Slow;
|
||||||
|
@ -228,7 +237,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["r9kbetaoff"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::R9K;
|
action.mode = ModeChangedAction::Mode::R9K;
|
||||||
|
@ -237,7 +247,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["r9kbeta"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["r9kbeta"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::R9K;
|
action.mode = ModeChangedAction::Mode::R9K;
|
||||||
|
@ -246,17 +257,18 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["subscribersoff"] = [this](const auto &data,
|
this->moderationActionHandlers["subscribersoff"] =
|
||||||
const auto &roomID) {
|
[this](const auto &data, const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||||
action.state = ModeChangedAction::State::Off;
|
action.state = ModeChangedAction::State::Off;
|
||||||
|
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["subscribers"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["subscribers"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
action.mode = ModeChangedAction::Mode::SubscribersOnly;
|
||||||
|
@ -265,16 +277,18 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["emoteonlyoff"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["emoteonlyoff"] =
|
||||||
ModeChangedAction action(data, roomID);
|
[this](const auto &data, const auto &roomID) {
|
||||||
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||||
action.state = ModeChangedAction::State::Off;
|
action.state = ModeChangedAction::State::Off;
|
||||||
|
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["emoteonly"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["emoteonly"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModeChangedAction action(data, roomID);
|
ModeChangedAction action(data, roomID);
|
||||||
|
|
||||||
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
action.mode = ModeChangedAction::Mode::EmoteOnly;
|
||||||
|
@ -283,7 +297,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.modeChanged.invoke(action);
|
this->signals_.moderation.modeChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["unmod"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["unmod"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModerationStateAction action(data, roomID);
|
ModerationStateAction action(data, roomID);
|
||||||
|
|
||||||
getTargetUser(data, action.target);
|
getTargetUser(data, action.target);
|
||||||
|
@ -307,7 +322,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.moderationStateChanged.invoke(action);
|
this->signals_.moderation.moderationStateChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["mod"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["mod"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
ModerationStateAction action(data, roomID);
|
ModerationStateAction action(data, roomID);
|
||||||
|
|
||||||
getTargetUser(data, action.target);
|
getTargetUser(data, action.target);
|
||||||
|
@ -331,7 +347,8 @@ PubSub::PubSub()
|
||||||
this->signals_.moderation.moderationStateChanged.invoke(action);
|
this->signals_.moderation.moderationStateChanged.invoke(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["timeout"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["timeout"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
BanAction action(data, roomID);
|
BanAction action(data, roomID);
|
||||||
|
|
||||||
getCreatedByUser(data, action.source);
|
getCreatedByUser(data, action.source);
|
||||||
|
@ -367,7 +384,8 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["ban"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["ban"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
BanAction action(data, roomID);
|
BanAction action(data, roomID);
|
||||||
|
|
||||||
getCreatedByUser(data, action.source);
|
getCreatedByUser(data, action.source);
|
||||||
|
@ -396,7 +414,8 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["unban"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["unban"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
UnbanAction action(data, roomID);
|
UnbanAction action(data, roomID);
|
||||||
|
|
||||||
getCreatedByUser(data, action.source);
|
getCreatedByUser(data, action.source);
|
||||||
|
@ -421,7 +440,8 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this->moderationActionHandlers["untimeout"] = [this](const auto &data, const auto &roomID) {
|
this->moderationActionHandlers["untimeout"] = [this](const auto &data,
|
||||||
|
const auto &roomID) {
|
||||||
UnbanAction action(data, roomID);
|
UnbanAction action(data, roomID);
|
||||||
|
|
||||||
getCreatedByUser(data, action.source);
|
getCreatedByUser(data, action.source);
|
||||||
|
@ -447,16 +467,21 @@ PubSub::PubSub()
|
||||||
};
|
};
|
||||||
|
|
||||||
this->websocketClient.set_access_channels(websocketpp::log::alevel::all);
|
this->websocketClient.set_access_channels(websocketpp::log::alevel::all);
|
||||||
this->websocketClient.clear_access_channels(websocketpp::log::alevel::frame_payload);
|
this->websocketClient.clear_access_channels(
|
||||||
|
websocketpp::log::alevel::frame_payload);
|
||||||
|
|
||||||
this->websocketClient.init_asio();
|
this->websocketClient.init_asio();
|
||||||
|
|
||||||
// SSL Handshake
|
// SSL Handshake
|
||||||
this->websocketClient.set_tls_init_handler(bind(&PubSub::onTLSInit, this, ::_1));
|
this->websocketClient.set_tls_init_handler(
|
||||||
|
bind(&PubSub::onTLSInit, this, ::_1));
|
||||||
|
|
||||||
this->websocketClient.set_message_handler(bind(&PubSub::onMessage, this, ::_1, ::_2));
|
this->websocketClient.set_message_handler(
|
||||||
this->websocketClient.set_open_handler(bind(&PubSub::onConnectionOpen, this, ::_1));
|
bind(&PubSub::onMessage, this, ::_1, ::_2));
|
||||||
this->websocketClient.set_close_handler(bind(&PubSub::onConnectionClose, this, ::_1));
|
this->websocketClient.set_open_handler(
|
||||||
|
bind(&PubSub::onConnectionOpen, this, ::_1));
|
||||||
|
this->websocketClient.set_close_handler(
|
||||||
|
bind(&PubSub::onConnectionClose, this, ::_1));
|
||||||
|
|
||||||
// Add an initial client
|
// Add an initial client
|
||||||
this->addClient();
|
this->addClient();
|
||||||
|
@ -477,7 +502,8 @@ void PubSub::addClient()
|
||||||
|
|
||||||
void PubSub::start()
|
void PubSub::start()
|
||||||
{
|
{
|
||||||
this->mainThread.reset(new std::thread(std::bind(&PubSub::runThread, this)));
|
this->mainThread.reset(
|
||||||
|
new std::thread(std::bind(&PubSub::runThread, this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
||||||
|
@ -507,8 +533,8 @@ void PubSub::unlistenAllModerationActions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::listenToChannelModerationActions(const QString &channelID,
|
void PubSub::listenToChannelModerationActions(
|
||||||
std::shared_ptr<TwitchAccount> account)
|
const QString &channelID, std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
assert(!channelID.isEmpty());
|
assert(!channelID.isEmpty());
|
||||||
assert(account != nullptr);
|
assert(account != nullptr);
|
||||||
|
@ -527,7 +553,8 @@ void PubSub::listenToChannelModerationActions(const QString &channelID,
|
||||||
this->listenToTopic(topic, account);
|
this->listenToTopic(topic, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::listenToTopic(const std::string &topic, std::shared_ptr<TwitchAccount> account)
|
void PubSub::listenToTopic(const std::string &topic,
|
||||||
|
std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
auto message = createListenMessage({topic}, account);
|
auto message = createListenMessage({topic}, account);
|
||||||
|
|
||||||
|
@ -542,7 +569,8 @@ void PubSub::listen(rapidjson::Document &&msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log("Added to the back of the queue");
|
Log("Added to the back of the queue");
|
||||||
this->requests.emplace_back(std::make_unique<rapidjson::Document>(std::move(msg)));
|
this->requests.emplace_back(
|
||||||
|
std::make_unique<rapidjson::Document>(std::move(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSub::tryListen(rapidjson::Document &msg)
|
bool PubSub::tryListen(rapidjson::Document &msg)
|
||||||
|
@ -570,7 +598,8 @@ bool PubSub::isListeningToTopic(const std::string &topic)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr websocketMessage)
|
void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||||
|
WebsocketMessagePtr websocketMessage)
|
||||||
{
|
{
|
||||||
const std::string &payload = websocketMessage->get_payload();
|
const std::string &payload = websocketMessage->get_payload();
|
||||||
|
|
||||||
|
@ -585,7 +614,9 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!msg.IsObject()) {
|
if (!msg.IsObject()) {
|
||||||
Log("Error parsing message '{}' from PubSub. Root object is not an object", payload);
|
Log("Error parsing message '{}' from PubSub. Root object is not an "
|
||||||
|
"object",
|
||||||
|
payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,8 +646,8 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
|
||||||
} else if (type == "PONG") {
|
} else if (type == "PONG") {
|
||||||
auto clientIt = this->clients.find(hdl);
|
auto clientIt = this->clients.find(hdl);
|
||||||
|
|
||||||
// If this assert goes off, there's something wrong with the connection creation/preserving
|
// If this assert goes off, there's something wrong with the connection
|
||||||
// code KKona
|
// creation/preserving code KKona
|
||||||
assert(clientIt != this->clients.end());
|
assert(clientIt != this->clients.end());
|
||||||
|
|
||||||
auto &client = *clientIt;
|
auto &client = *clientIt;
|
||||||
|
@ -629,9 +660,11 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr webs
|
||||||
|
|
||||||
void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
||||||
{
|
{
|
||||||
auto client = std::make_shared<detail::PubSubClient>(this->websocketClient, hdl);
|
auto client =
|
||||||
|
std::make_shared<detail::PubSubClient>(this->websocketClient, hdl);
|
||||||
|
|
||||||
// We separate the starting from the constructor because we will want to use shared_from_this
|
// We separate the starting from the constructor because we will want to use
|
||||||
|
// shared_from_this
|
||||||
client->start();
|
client->start();
|
||||||
|
|
||||||
this->clients.emplace(hdl, client);
|
this->clients.emplace(hdl, client);
|
||||||
|
@ -643,8 +676,8 @@ void PubSub::onConnectionClose(WebsocketHandle hdl)
|
||||||
{
|
{
|
||||||
auto clientIt = this->clients.find(hdl);
|
auto clientIt = this->clients.find(hdl);
|
||||||
|
|
||||||
// If this assert goes off, there's something wrong with the connection creation/preserving
|
// If this assert goes off, there's something wrong with the connection
|
||||||
// code KKona
|
// creation/preserving code KKona
|
||||||
assert(clientIt != this->clients.end());
|
assert(clientIt != this->clients.end());
|
||||||
|
|
||||||
auto &client = clientIt->second;
|
auto &client = clientIt->second;
|
||||||
|
@ -658,7 +691,8 @@ void PubSub::onConnectionClose(WebsocketHandle hdl)
|
||||||
|
|
||||||
PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
||||||
{
|
{
|
||||||
WebsocketContextPtr ctx(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
|
WebsocketContextPtr ctx(
|
||||||
|
new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ctx->set_options(boost::asio::ssl::context::default_workarounds |
|
ctx->set_options(boost::asio::ssl::context::default_workarounds |
|
||||||
|
|
|
@ -22,7 +22,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
using WebsocketClient = websocketpp::client<websocketpp::config::asio_tls_client>;
|
using WebsocketClient =
|
||||||
|
websocketpp::client<websocketpp::config::asio_tls_client>;
|
||||||
using WebsocketHandle = websocketpp::connection_hdl;
|
using WebsocketHandle = websocketpp::connection_hdl;
|
||||||
using WebsocketErrorCode = websocketpp::lib::error_code;
|
using WebsocketErrorCode = websocketpp::lib::error_code;
|
||||||
|
|
||||||
|
@ -71,11 +72,14 @@ private:
|
||||||
|
|
||||||
class PubSub
|
class PubSub
|
||||||
{
|
{
|
||||||
using WebsocketMessagePtr = websocketpp::config::asio_tls_client::message_type::ptr;
|
using WebsocketMessagePtr =
|
||||||
using WebsocketContextPtr = websocketpp::lib::shared_ptr<boost::asio::ssl::context>;
|
websocketpp::config::asio_tls_client::message_type::ptr;
|
||||||
|
using WebsocketContextPtr =
|
||||||
|
websocketpp::lib::shared_ptr<boost::asio::ssl::context>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using Signal = pajlada::Signals::Signal<T>; // type-id is vector<T, Alloc<T>>
|
using Signal =
|
||||||
|
pajlada::Signals::Signal<T>; // type-id is vector<T, Alloc<T>>
|
||||||
|
|
||||||
WebsocketClient websocketClient;
|
WebsocketClient websocketClient;
|
||||||
std::unique_ptr<std::thread> mainThread;
|
std::unique_ptr<std::thread> mainThread;
|
||||||
|
@ -121,13 +125,14 @@ public:
|
||||||
|
|
||||||
void unlistenAllModerationActions();
|
void unlistenAllModerationActions();
|
||||||
|
|
||||||
void listenToChannelModerationActions(const QString &channelID,
|
void listenToChannelModerationActions(
|
||||||
std::shared_ptr<TwitchAccount> account);
|
const QString &channelID, std::shared_ptr<TwitchAccount> account);
|
||||||
|
|
||||||
std::vector<std::unique_ptr<rapidjson::Document>> requests;
|
std::vector<std::unique_ptr<rapidjson::Document>> requests;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void listenToTopic(const std::string &topic, std::shared_ptr<TwitchAccount> account);
|
void listenToTopic(const std::string &topic,
|
||||||
|
std::shared_ptr<TwitchAccount> account);
|
||||||
|
|
||||||
void listen(rapidjson::Document &&msg);
|
void listen(rapidjson::Document &&msg);
|
||||||
bool tryListen(rapidjson::Document &msg);
|
bool tryListen(rapidjson::Document &msg);
|
||||||
|
@ -142,7 +147,8 @@ private:
|
||||||
std::owner_less<WebsocketHandle>>
|
std::owner_less<WebsocketHandle>>
|
||||||
clients;
|
clients;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::function<void(const rapidjson::Value &, const QString &)>>
|
std::unordered_map<std::string, std::function<void(const rapidjson::Value &,
|
||||||
|
const QString &)>>
|
||||||
moderationActionHandlers;
|
moderationActionHandlers;
|
||||||
|
|
||||||
void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
||||||
|
|
|
@ -31,8 +31,9 @@ bool getTargetUser(const rapidjson::Value &data, ActionUser &user)
|
||||||
return rj::getSafe(data, "target_user_id", user.id);
|
return rj::getSafe(data, "target_user_id", user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVec,
|
rapidjson::Document createListenMessage(
|
||||||
std::shared_ptr<TwitchAccount> account)
|
const std::vector<std::string> &topicsVec,
|
||||||
|
std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
rapidjson::Document msg(rapidjson::kObjectType);
|
rapidjson::Document msg(rapidjson::kObjectType);
|
||||||
auto &a = msg.GetAllocator();
|
auto &a = msg.GetAllocator();
|
||||||
|
@ -57,7 +58,8 @@ rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVe
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document createUnlistenMessage(const std::vector<std::string> &topicsVec)
|
rapidjson::Document createUnlistenMessage(
|
||||||
|
const std::vector<std::string> &topicsVec)
|
||||||
{
|
{
|
||||||
rapidjson::Document msg(rapidjson::kObjectType);
|
rapidjson::Document msg(rapidjson::kObjectType);
|
||||||
auto &a = msg.GetAllocator();
|
auto &a = msg.GetAllocator();
|
||||||
|
|
|
@ -19,13 +19,16 @@ bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user);
|
||||||
|
|
||||||
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
|
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
|
||||||
|
|
||||||
rapidjson::Document createListenMessage(const std::vector<std::string> &topicsVec,
|
rapidjson::Document createListenMessage(
|
||||||
std::shared_ptr<TwitchAccount> account);
|
const std::vector<std::string> &topicsVec,
|
||||||
rapidjson::Document createUnlistenMessage(const std::vector<std::string> &topicsVec);
|
std::shared_ptr<TwitchAccount> account);
|
||||||
|
rapidjson::Document createUnlistenMessage(
|
||||||
|
const std::vector<std::string> &topicsVec);
|
||||||
|
|
||||||
// Create timer using given ioService
|
// Create timer using given ioService
|
||||||
template <typename Duration, typename Callback>
|
template <typename Duration, typename Callback>
|
||||||
void runAfter(boost::asio::io_service &ioService, Duration duration, Callback cb)
|
void runAfter(boost::asio::io_service &ioService, Duration duration,
|
||||||
|
Callback cb)
|
||||||
{
|
{
|
||||||
auto timer = std::make_shared<boost::asio::steady_timer>(ioService);
|
auto timer = std::make_shared<boost::asio::steady_timer>(ioService);
|
||||||
timer->expires_from_now(duration);
|
timer->expires_from_now(duration);
|
||||||
|
@ -42,7 +45,8 @@ void runAfter(boost::asio::io_service &ioService, Duration duration, Callback cb
|
||||||
|
|
||||||
// Use provided timer
|
// Use provided timer
|
||||||
template <typename Duration, typename Callback>
|
template <typename Duration, typename Callback>
|
||||||
void runAfter(std::shared_ptr<boost::asio::steady_timer> timer, Duration duration, Callback cb)
|
void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
|
||||||
|
Duration duration, Callback cb)
|
||||||
{
|
{
|
||||||
timer->expires_from_now(duration);
|
timer->expires_from_now(duration);
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,12 @@ EmoteName cleanUpCode(const EmoteName &dirtyEmoteCode)
|
||||||
cleanCode.detach();
|
cleanCode.detach();
|
||||||
|
|
||||||
static QMap<QString, QString> emoteNameReplacements{
|
static QMap<QString, QString> emoteNameReplacements{
|
||||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, {"\\<\\;3", "<3"},
|
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("},
|
||||||
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
{"\\<\\;3", "<3"}, {"\\:-?(o|O)", ":O"},
|
||||||
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
|
{"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
||||||
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("},
|
||||||
|
{"\\:-?\\)", ":)"}, {"\\:-?D", ":D"},
|
||||||
|
{"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
||||||
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
|
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,7 +107,8 @@ bool TwitchAccount::isAnon() const
|
||||||
|
|
||||||
void TwitchAccount::loadIgnores()
|
void TwitchAccount::loadIgnores()
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/blocks");
|
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
|
"/blocks");
|
||||||
|
|
||||||
NetworkRequest req(url);
|
NetworkRequest req(url);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
@ -140,7 +143,8 @@ void TwitchAccount::loadIgnores()
|
||||||
}
|
}
|
||||||
TwitchUser ignoredUser;
|
TwitchUser ignoredUser;
|
||||||
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
||||||
Log("Error parsing twitch user JSON {}", rj::stringify(userIt->value));
|
Log("Error parsing twitch user JSON {}",
|
||||||
|
rj::stringify(userIt->value));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,28 +158,32 @@ void TwitchAccount::loadIgnores()
|
||||||
req.execute();
|
req.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::ignore(const QString &targetName,
|
void TwitchAccount::ignore(
|
||||||
std::function<void(IgnoreResult, const QString &)> onFinished)
|
const QString &targetName,
|
||||||
|
std::function<void(IgnoreResult, const QString &)> onFinished)
|
||||||
{
|
{
|
||||||
const auto onIdFetched = [this, targetName, onFinished](QString targetUserId) {
|
const auto onIdFetched = [this, targetName,
|
||||||
|
onFinished](QString targetUserId) {
|
||||||
this->ignoreByID(targetUserId, targetName, onFinished); //
|
this->ignoreByID(targetUserId, targetName, onFinished); //
|
||||||
};
|
};
|
||||||
|
|
||||||
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targetName,
|
void TwitchAccount::ignoreByID(
|
||||||
std::function<void(IgnoreResult, const QString &)> onFinished)
|
const QString &targetUserID, const QString &targetName,
|
||||||
|
std::function<void(IgnoreResult, const QString &)> onFinished)
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/blocks/" +
|
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
targetUserID);
|
"/blocks/" + targetUserID);
|
||||||
NetworkRequest req(url, NetworkRequestType::Put);
|
NetworkRequest req(url, NetworkRequestType::Put);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
|
req.makeAuthorizedV5(this->getOAuthClient(), this->getOAuthToken());
|
||||||
|
|
||||||
req.onError([=](int errorCode) {
|
req.onError([=](int errorCode) {
|
||||||
onFinished(IgnoreResult_Failed, "An unknown error occured while trying to ignore user " +
|
onFinished(IgnoreResult_Failed,
|
||||||
targetName + " (" + QString::number(errorCode) + ")");
|
"An unknown error occured while trying to ignore user " +
|
||||||
|
targetName + " (" + QString::number(errorCode) + ")");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -183,21 +191,24 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
|
||||||
req.onSuccess([=](auto result) -> Outcome {
|
req.onSuccess([=](auto result) -> Outcome {
|
||||||
auto document = result.parseRapidJson();
|
auto document = result.parseRapidJson();
|
||||||
if (!document.IsObject()) {
|
if (!document.IsObject()) {
|
||||||
onFinished(IgnoreResult_Failed, "Bad JSON data while ignoring user " + targetName);
|
onFinished(IgnoreResult_Failed,
|
||||||
|
"Bad JSON data while ignoring user " + targetName);
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto userIt = document.FindMember("user");
|
auto userIt = document.FindMember("user");
|
||||||
if (userIt == document.MemberEnd()) {
|
if (userIt == document.MemberEnd()) {
|
||||||
onFinished(IgnoreResult_Failed,
|
onFinished(IgnoreResult_Failed,
|
||||||
"Bad JSON data while ignoring user (missing user) " + targetName);
|
"Bad JSON data while ignoring user (missing user) " +
|
||||||
|
targetName);
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchUser ignoredUser;
|
TwitchUser ignoredUser;
|
||||||
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
if (!rj::getSafe(userIt->value, ignoredUser)) {
|
||||||
onFinished(IgnoreResult_Failed,
|
onFinished(IgnoreResult_Failed,
|
||||||
"Bad JSON data while ignoring user (invalid user) " + targetName);
|
"Bad JSON data while ignoring user (invalid user) " +
|
||||||
|
targetName);
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -212,7 +223,8 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onFinished(IgnoreResult_Success, "Successfully ignored user " + targetName);
|
onFinished(IgnoreResult_Success,
|
||||||
|
"Successfully ignored user " + targetName);
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
});
|
});
|
||||||
|
@ -220,10 +232,12 @@ void TwitchAccount::ignoreByID(const QString &targetUserID, const QString &targe
|
||||||
req.execute();
|
req.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::unignore(const QString &targetName,
|
void TwitchAccount::unignore(
|
||||||
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
const QString &targetName,
|
||||||
|
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
||||||
{
|
{
|
||||||
const auto onIdFetched = [this, targetName, onFinished](QString targetUserId) {
|
const auto onIdFetched = [this, targetName,
|
||||||
|
onFinished](QString targetUserId) {
|
||||||
this->unignoreByID(targetUserId, targetName, onFinished); //
|
this->unignoreByID(targetUserId, targetName, onFinished); //
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,8 +248,8 @@ void TwitchAccount::unignoreByID(
|
||||||
const QString &targetUserID, const QString &targetName,
|
const QString &targetUserID, const QString &targetName,
|
||||||
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/blocks/" +
|
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
targetUserID);
|
"/blocks/" + targetUserID);
|
||||||
|
|
||||||
NetworkRequest req(url, NetworkRequestType::Delete);
|
NetworkRequest req(url, NetworkRequestType::Delete);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
@ -243,8 +257,8 @@ void TwitchAccount::unignoreByID(
|
||||||
|
|
||||||
req.onError([=](int errorCode) {
|
req.onError([=](int errorCode) {
|
||||||
onFinished(UnignoreResult_Failed,
|
onFinished(UnignoreResult_Failed,
|
||||||
"An unknown error occured while trying to unignore user " + targetName + " (" +
|
"An unknown error occured while trying to unignore user " +
|
||||||
QString::number(errorCode) + ")");
|
targetName + " (" + QString::number(errorCode) + ")");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -258,7 +272,8 @@ void TwitchAccount::unignoreByID(
|
||||||
|
|
||||||
this->ignores_.erase(ignoredUser);
|
this->ignores_.erase(ignoredUser);
|
||||||
}
|
}
|
||||||
onFinished(UnignoreResult_Success, "Successfully unignored user " + targetName);
|
onFinished(UnignoreResult_Success,
|
||||||
|
"Successfully unignored user " + targetName);
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
});
|
});
|
||||||
|
@ -269,8 +284,8 @@ void TwitchAccount::unignoreByID(
|
||||||
void TwitchAccount::checkFollow(const QString targetUserID,
|
void TwitchAccount::checkFollow(const QString targetUserID,
|
||||||
std::function<void(FollowResult)> onFinished)
|
std::function<void(FollowResult)> onFinished)
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/follows/channels/" +
|
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
targetUserID);
|
"/follows/channels/" + targetUserID);
|
||||||
|
|
||||||
NetworkRequest req(url);
|
NetworkRequest req(url);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
@ -295,7 +310,8 @@ void TwitchAccount::checkFollow(const QString targetUserID,
|
||||||
req.execute();
|
req.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::followUser(const QString userID, std::function<void()> successCallback)
|
void TwitchAccount::followUser(const QString userID,
|
||||||
|
std::function<void()> successCallback)
|
||||||
{
|
{
|
||||||
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
"/follows/channels/" + userID);
|
"/follows/channels/" + userID);
|
||||||
|
@ -315,7 +331,8 @@ void TwitchAccount::followUser(const QString userID, std::function<void()> succe
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::unfollowUser(const QString userID, std::function<void()> successCallback)
|
void TwitchAccount::unfollowUser(const QString userID,
|
||||||
|
std::function<void()> successCallback)
|
||||||
{
|
{
|
||||||
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
QUrl requestUrl("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
"/follows/channels/" + userID);
|
"/follows/channels/" + userID);
|
||||||
|
@ -361,7 +378,8 @@ void TwitchAccount::loadEmotes()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() + "/emotes");
|
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
||||||
|
"/emotes");
|
||||||
|
|
||||||
NetworkRequest req(url);
|
NetworkRequest req(url);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
@ -387,7 +405,8 @@ void TwitchAccount::loadEmotes()
|
||||||
req.execute();
|
req.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchAccount::TwitchAccountEmoteData> TwitchAccount::accessEmotes() const
|
AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
|
||||||
|
TwitchAccount::accessEmotes() const
|
||||||
{
|
{
|
||||||
return this->emotes_.accessConst();
|
return this->emotes_.accessConst();
|
||||||
}
|
}
|
||||||
|
@ -412,7 +431,8 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||||
|
|
||||||
this->loadEmoteSetData(emoteSet);
|
this->loadEmoteSetData(emoteSet);
|
||||||
|
|
||||||
for (const rapidjson::Value &emoteJSON : emoteSetJSON.value.GetArray()) {
|
for (const rapidjson::Value &emoteJSON :
|
||||||
|
emoteSetJSON.value.GetArray()) {
|
||||||
if (!emoteJSON.IsObject()) {
|
if (!emoteJSON.IsObject()) {
|
||||||
Log("Emote value was invalid");
|
Log("Emote value was invalid");
|
||||||
return;
|
return;
|
||||||
|
@ -459,8 +479,9 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest req("https://braize.pajlada.com/chatterino/twitchemotes/set/" + emoteSet->key +
|
NetworkRequest req(
|
||||||
"/");
|
"https://braize.pajlada.com/chatterino/twitchemotes/set/" +
|
||||||
|
emoteSet->key + "/");
|
||||||
req.setUseQuickLoadCache(true);
|
req.setUseQuickLoadCache(true);
|
||||||
|
|
||||||
req.onError([](int errorCode) -> bool {
|
req.onError([](int errorCode) -> bool {
|
||||||
|
@ -488,9 +509,11 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
Log("Loaded twitch emote set data for {}!", emoteSet->key);
|
Log("Loaded twitch emote set data for {}!", emoteSet->key);
|
||||||
|
|
||||||
if (type == "sub") {
|
if (type == "sub") {
|
||||||
emoteSet->text = QString("Twitch Subscriber Emote (%1)").arg(channelName);
|
emoteSet->text =
|
||||||
|
QString("Twitch Subscriber Emote (%1)").arg(channelName);
|
||||||
} else {
|
} else {
|
||||||
emoteSet->text = QString("Twitch Account Emote (%1)").arg(channelName);
|
emoteSet->text =
|
||||||
|
QString("Twitch Account Emote (%1)").arg(channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
emoteSet->channelName = channelName;
|
emoteSet->channelName = channelName;
|
||||||
|
|
|
@ -57,8 +57,8 @@ public:
|
||||||
EmoteMap emotes;
|
EmoteMap emotes;
|
||||||
};
|
};
|
||||||
|
|
||||||
TwitchAccount(const QString &username, const QString &oauthToken_, const QString &oauthClient_,
|
TwitchAccount(const QString &username, const QString &oauthToken_,
|
||||||
const QString &_userID);
|
const QString &oauthClient_, const QString &_userID);
|
||||||
|
|
||||||
virtual QString toString() const override;
|
virtual QString toString() const override;
|
||||||
|
|
||||||
|
@ -81,16 +81,22 @@ public:
|
||||||
void loadIgnores();
|
void loadIgnores();
|
||||||
void ignore(const QString &targetName,
|
void ignore(const QString &targetName,
|
||||||
std::function<void(IgnoreResult, const QString &)> onFinished);
|
std::function<void(IgnoreResult, const QString &)> onFinished);
|
||||||
void ignoreByID(const QString &targetUserID, const QString &targetName,
|
void ignoreByID(
|
||||||
std::function<void(IgnoreResult, const QString &)> onFinished);
|
const QString &targetUserID, const QString &targetName,
|
||||||
void unignore(const QString &targetName,
|
std::function<void(IgnoreResult, const QString &)> onFinished);
|
||||||
std::function<void(UnignoreResult, const QString &)> onFinished);
|
void unignore(
|
||||||
void unignoreByID(const QString &targetUserID, const QString &targetName,
|
const QString &targetName,
|
||||||
std::function<void(UnignoreResult, const QString &message)> onFinished);
|
std::function<void(UnignoreResult, const QString &)> onFinished);
|
||||||
|
void unignoreByID(
|
||||||
|
const QString &targetUserID, const QString &targetName,
|
||||||
|
std::function<void(UnignoreResult, const QString &message)> onFinished);
|
||||||
|
|
||||||
void checkFollow(const QString targetUserID, std::function<void(FollowResult)> onFinished);
|
void checkFollow(const QString targetUserID,
|
||||||
void followUser(const QString userID, std::function<void()> successCallback);
|
std::function<void(FollowResult)> onFinished);
|
||||||
void unfollowUser(const QString userID, std::function<void()> successCallback);
|
void followUser(const QString userID,
|
||||||
|
std::function<void()> successCallback);
|
||||||
|
void unfollowUser(const QString userID,
|
||||||
|
std::function<void()> successCallback);
|
||||||
|
|
||||||
std::set<TwitchUser> getIgnores() const;
|
std::set<TwitchUser> getIgnores() const;
|
||||||
|
|
||||||
|
|
|
@ -72,16 +72,17 @@ void TwitchAccountManager::reloadUsers()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string username =
|
std::string username = pajlada::Settings::Setting<std::string>::get(
|
||||||
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/username");
|
"/accounts/" + uid + "/username");
|
||||||
std::string userID =
|
std::string userID = pajlada::Settings::Setting<std::string>::get(
|
||||||
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/userID");
|
"/accounts/" + uid + "/userID");
|
||||||
std::string clientID =
|
std::string clientID = pajlada::Settings::Setting<std::string>::get(
|
||||||
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/clientID");
|
"/accounts/" + uid + "/clientID");
|
||||||
std::string oauthToken =
|
std::string oauthToken = pajlada::Settings::Setting<std::string>::get(
|
||||||
pajlada::Settings::Setting<std::string>::get("/accounts/" + uid + "/oauthToken");
|
"/accounts/" + uid + "/oauthToken");
|
||||||
|
|
||||||
if (username.empty() || userID.empty() || clientID.empty() || oauthToken.empty()) {
|
if (username.empty() || userID.empty() || clientID.empty() ||
|
||||||
|
oauthToken.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +97,11 @@ void TwitchAccountManager::reloadUsers()
|
||||||
// Do nothing
|
// Do nothing
|
||||||
} break;
|
} break;
|
||||||
case AddUserResponse::UserValuesUpdated: {
|
case AddUserResponse::UserValuesUpdated: {
|
||||||
Log("User {} already exists, and values updated!", userData.username);
|
Log("User {} already exists, and values updated!",
|
||||||
|
userData.username);
|
||||||
if (userData.username == this->getCurrent()->getUserName()) {
|
if (userData.username == this->getCurrent()->getUserName()) {
|
||||||
Log("It was the current user, so we need to reconnect stuff!");
|
Log("It was the current user, so we need to reconnect "
|
||||||
|
"stuff!");
|
||||||
this->currentUserChanged.invoke();
|
this->currentUserChanged.invoke();
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
@ -122,11 +125,13 @@ void TwitchAccountManager::load()
|
||||||
QString newUsername(QString::fromStdString(newValue));
|
QString newUsername(QString::fromStdString(newValue));
|
||||||
auto user = this->findUserByUsername(newUsername);
|
auto user = this->findUserByUsername(newUsername);
|
||||||
if (user) {
|
if (user) {
|
||||||
Log("[AccountManager:currentUsernameChanged] User successfully updated to {}",
|
Log("[AccountManager:currentUsernameChanged] User successfully "
|
||||||
|
"updated to {}",
|
||||||
newUsername);
|
newUsername);
|
||||||
this->currentUser_ = user;
|
this->currentUser_ = user;
|
||||||
} else {
|
} else {
|
||||||
Log("[AccountManager:currentUsernameChanged] User successfully updated to anonymous");
|
Log("[AccountManager:currentUsernameChanged] User successfully "
|
||||||
|
"updated to anonymous");
|
||||||
this->currentUser_ = this->anonymousUser_;
|
this->currentUser_ = this->anonymousUser_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +145,8 @@ bool TwitchAccountManager::isLoggedIn() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once `TwitchAccount` class has a way to check, we should also return false if the credentials
|
// Once `TwitchAccount` class has a way to check, we should also return
|
||||||
// are incorrect
|
// false if the credentials are incorrect
|
||||||
return !this->currentUser_->isAnon();
|
return !this->currentUser_->isAnon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,11 +156,13 @@ bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||||
|
|
||||||
std::string userID(account->getUserId().toStdString());
|
std::string userID(account->getUserId().toStdString());
|
||||||
if (!userID.empty()) {
|
if (!userID.empty()) {
|
||||||
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
|
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" +
|
||||||
|
userID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account->getUserName() == qS(this->currentUsername.getValue())) {
|
if (account->getUserName() == qS(this->currentUsername.getValue())) {
|
||||||
// The user that was removed is the current user, log into the anonymous user
|
// The user that was removed is the current user, log into the anonymous
|
||||||
|
// user
|
||||||
this->currentUsername = "";
|
this->currentUsername = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +193,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
auto newUser =
|
||||||
userData.clientID, userData.userID);
|
std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||||
|
userData.clientID, userData.userID);
|
||||||
|
|
||||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
|
|
||||||
//
|
//
|
||||||
// Warning: This class is not supposed to be created directly.
|
// Warning: This class is not supposed to be created directly.
|
||||||
// Get yourself an instance from our friends over at AccountManager.hpp
|
// Get yourself an instance from our friends over at
|
||||||
|
// AccountManager.hpp
|
||||||
//
|
//
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -30,12 +31,14 @@ public:
|
||||||
QString oauthToken;
|
QString oauthToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the current twitchUsers, or the anonymous user if we're not currently logged in
|
// Returns the current twitchUsers, or the anonymous user if we're not
|
||||||
|
// currently logged in
|
||||||
std::shared_ptr<TwitchAccount> getCurrent();
|
std::shared_ptr<TwitchAccount> getCurrent();
|
||||||
|
|
||||||
std::vector<QString> getUsernames() const;
|
std::vector<QString> getUsernames() const;
|
||||||
|
|
||||||
std::shared_ptr<TwitchAccount> findUserByUsername(const QString &username) const;
|
std::shared_ptr<TwitchAccount> findUserByUsername(
|
||||||
|
const QString &username) const;
|
||||||
bool userExists(const QString &username) const;
|
bool userExists(const QString &username) const;
|
||||||
|
|
||||||
void reloadUsers();
|
void reloadUsers();
|
||||||
|
@ -43,11 +46,13 @@ public:
|
||||||
|
|
||||||
bool isLoggedIn() const;
|
bool isLoggedIn() const;
|
||||||
|
|
||||||
pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""};
|
pajlada::Settings::Setting<std::string> currentUsername = {
|
||||||
|
"/accounts/current", ""};
|
||||||
pajlada::Signals::NoArgSignal currentUserChanged;
|
pajlada::Signals::NoArgSignal currentUserChanged;
|
||||||
pajlada::Signals::NoArgSignal userListUpdated;
|
pajlada::Signals::NoArgSignal userListUpdated;
|
||||||
|
|
||||||
SortedSignalVector<std::shared_ptr<TwitchAccount>, SharedPtrElementLess<TwitchAccount>>
|
SortedSignalVector<std::shared_ptr<TwitchAccount>,
|
||||||
|
SharedPtrElementLess<TwitchAccount>>
|
||||||
accounts;
|
accounts;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
void TwitchApi::findUserId(const QString user, std::function<void(QString)> successCallback)
|
void TwitchApi::findUserId(const QString user,
|
||||||
|
std::function<void(QString)> successCallback)
|
||||||
{
|
{
|
||||||
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
|
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
|
||||||
|
|
||||||
|
@ -37,7 +38,8 @@ void TwitchApi::findUserId(const QString user, std::function<void(QString)> succ
|
||||||
auto firstUser = users[0].toObject();
|
auto firstUser = users[0].toObject();
|
||||||
auto id = firstUser.value("_id");
|
auto id = firstUser.value("_id");
|
||||||
if (!id.isString()) {
|
if (!id.isString()) {
|
||||||
Log("API Error: while getting user id, first user object `_id` key is not a "
|
Log("API Error: while getting user id, first user object `_id` key "
|
||||||
|
"is not a "
|
||||||
"string");
|
"string");
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
|
|
|
@ -7,7 +7,8 @@ namespace chatterino {
|
||||||
class TwitchApi
|
class TwitchApi
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void findUserId(const QString user, std::function<void(QString)> callback);
|
static void findUserId(const QString user,
|
||||||
|
std::function<void(QString)> callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,8 @@ void TwitchBadges::initialize(Settings &settings, Paths &paths)
|
||||||
|
|
||||||
void TwitchBadges::loadTwitchBadges()
|
void TwitchBadges::loadTwitchBadges()
|
||||||
{
|
{
|
||||||
static QString url("https://badges.twitch.tv/v1/badges/global/display?language=en");
|
static QString url(
|
||||||
|
"https://badges.twitch.tv/v1/badges/global/display?language=en");
|
||||||
|
|
||||||
NetworkRequest req(url);
|
NetworkRequest req(url);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
@ -28,24 +29,29 @@ void TwitchBadges::loadTwitchBadges()
|
||||||
QJsonObject sets = root.value("badge_sets").toObject();
|
QJsonObject sets = root.value("badge_sets").toObject();
|
||||||
|
|
||||||
for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) {
|
for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) {
|
||||||
QJsonObject versions = it.value().toObject().value("versions").toObject();
|
QJsonObject versions =
|
||||||
|
it.value().toObject().value("versions").toObject();
|
||||||
|
|
||||||
for (auto versionIt = std::begin(versions); versionIt != std::end(versions);
|
for (auto versionIt = std::begin(versions);
|
||||||
++versionIt) {
|
versionIt != std::end(versions); ++versionIt) {
|
||||||
auto emote =
|
auto emote = Emote{
|
||||||
Emote{{""},
|
{""},
|
||||||
ImageSet{
|
ImageSet{
|
||||||
Image::fromUrl({root.value("image_url_1x").toString()}, 1),
|
Image::fromUrl({root.value("image_url_1x").toString()},
|
||||||
Image::fromUrl({root.value("image_url_2x").toString()}, 0.5),
|
1),
|
||||||
Image::fromUrl({root.value("image_url_4x").toString()}, 0.25),
|
Image::fromUrl({root.value("image_url_2x").toString()},
|
||||||
},
|
0.5),
|
||||||
Tooltip{root.value("description").toString()},
|
Image::fromUrl({root.value("image_url_4x").toString()},
|
||||||
Url{root.value("clickURL").toString()}};
|
0.25),
|
||||||
|
},
|
||||||
|
Tooltip{root.value("description").toString()},
|
||||||
|
Url{root.value("clickURL").toString()}};
|
||||||
// "title"
|
// "title"
|
||||||
// "clickAction"
|
// "clickAction"
|
||||||
|
|
||||||
QJsonObject versionObj = versionIt.value().toObject();
|
QJsonObject versionObj = versionIt.value().toObject();
|
||||||
this->badges.emplace(versionIt.key(), std::make_shared<Emote>(emote));
|
this->badges.emplace(versionIt.key(),
|
||||||
|
std::make_shared<Emote>(emote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,8 @@ TwitchChannel::TwitchChannel(const QString &name)
|
||||||
[=] { this->refreshViewerList(); });
|
[=] { this->refreshViewerList(); });
|
||||||
this->chattersListTimer_.start(5 * 60 * 1000);
|
this->chattersListTimer_.start(5 * 60 * 1000);
|
||||||
|
|
||||||
QObject::connect(&this->liveStatusTimer_, &QTimer::timeout, [=] { this->refreshLiveStatus(); });
|
QObject::connect(&this->liveStatusTimer_, &QTimer::timeout,
|
||||||
|
[=] { this->refreshLiveStatus(); });
|
||||||
this->liveStatusTimer_.start(60 * 1000);
|
this->liveStatusTimer_.start(60 * 1000);
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
@ -84,15 +85,16 @@ bool TwitchChannel::canSendMessage() const
|
||||||
|
|
||||||
void TwitchChannel::refreshChannelEmotes()
|
void TwitchChannel::refreshChannelEmotes()
|
||||||
{
|
{
|
||||||
loadBttvChannelEmotes(this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
loadBttvChannelEmotes(
|
||||||
if (auto shared = weak.lock()) //
|
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||||
*this->bttvEmotes_.access() = emoteMap;
|
if (auto shared = weak.lock()) //
|
||||||
});
|
*this->bttvEmotes_.access() = emoteMap;
|
||||||
getApp()->emotes->ffz.loadChannelEmotes(this->getName(),
|
});
|
||||||
[this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
getApp()->emotes->ffz.loadChannelEmotes(
|
||||||
if (auto shared = weak.lock())
|
this->getName(), [this, weak = weakOf<Channel>(this)](auto &&emoteMap) {
|
||||||
*this->ffzEmotes_.access() = emoteMap;
|
if (auto shared = weak.lock())
|
||||||
});
|
*this->ffzEmotes_.access() = emoteMap;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::sendMessage(const QString &message)
|
void TwitchChannel::sendMessage(const QString &message)
|
||||||
|
@ -100,11 +102,11 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
if (!app->accounts->twitch.isLoggedIn()) {
|
if (!app->accounts->twitch.isLoggedIn()) {
|
||||||
// XXX: It would be nice if we could add a link here somehow that opened the "account
|
// XXX: It would be nice if we could add a link here somehow that opened
|
||||||
// manager" dialog
|
// the "account manager" dialog
|
||||||
this->addMessage(
|
this->addMessage(Message::createSystemMessage(
|
||||||
Message::createSystemMessage("You need to log in to send messages. You can "
|
"You need to log in to send messages. You can "
|
||||||
"link your Twitch account in the settings."));
|
"link your Twitch account in the settings."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +183,8 @@ void TwitchChannel::addJoinedUser(const QString &user)
|
||||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||||
auto joinedUsers = this->joinedUsers_.access();
|
auto joinedUsers = this->joinedUsers_.access();
|
||||||
|
|
||||||
auto message = Message::createSystemMessage("Users joined: " + joinedUsers->join(", "));
|
auto message = Message::createSystemMessage(
|
||||||
|
"Users joined: " + joinedUsers->join(", "));
|
||||||
message->flags |= Message::Collapsed;
|
message->flags |= Message::Collapsed;
|
||||||
joinedUsers->clear();
|
joinedUsers->clear();
|
||||||
this->addMessage(message);
|
this->addMessage(message);
|
||||||
|
@ -208,7 +211,8 @@ void TwitchChannel::addPartedUser(const QString &user)
|
||||||
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
QTimer::singleShot(500, &this->lifetimeGuard_, [this] {
|
||||||
auto partedUsers = this->partedUsers_.access();
|
auto partedUsers = this->partedUsers_.access();
|
||||||
|
|
||||||
auto message = Message::createSystemMessage("Users parted: " + partedUsers->join(", "));
|
auto message = Message::createSystemMessage(
|
||||||
|
"Users parted: " + partedUsers->join(", "));
|
||||||
message->flags |= Message::Collapsed;
|
message->flags |= Message::Collapsed;
|
||||||
this->addMessage(message);
|
this->addMessage(message);
|
||||||
partedUsers->clear();
|
partedUsers->clear();
|
||||||
|
@ -230,7 +234,8 @@ void TwitchChannel::setRoomId(const QString &id)
|
||||||
this->loadRecentMessages();
|
this->loadRecentMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchChannel::RoomModes> TwitchChannel::accessRoomModes() const
|
AccessGuard<const TwitchChannel::RoomModes> TwitchChannel::accessRoomModes()
|
||||||
|
const
|
||||||
{
|
{
|
||||||
return this->roomModes_.accessConst();
|
return this->roomModes_.accessConst();
|
||||||
}
|
}
|
||||||
|
@ -247,12 +252,14 @@ bool TwitchChannel::isLive() const
|
||||||
return this->streamStatus_.access()->live;
|
return this->streamStatus_.access()->live;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchChannel::StreamStatus> TwitchChannel::accessStreamStatus() const
|
AccessGuard<const TwitchChannel::StreamStatus>
|
||||||
|
TwitchChannel::accessStreamStatus() const
|
||||||
{
|
{
|
||||||
return this->streamStatus_.accessConst();
|
return this->streamStatus_.accessConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<EmotePtr> TwitchChannel::getBttvEmote(const EmoteName &name) const
|
boost::optional<EmotePtr> TwitchChannel::getBttvEmote(
|
||||||
|
const EmoteName &name) const
|
||||||
{
|
{
|
||||||
auto emotes = this->bttvEmotes_.access();
|
auto emotes = this->bttvEmotes_.access();
|
||||||
auto it = emotes->find(name);
|
auto it = emotes->find(name);
|
||||||
|
@ -261,7 +268,8 @@ boost::optional<EmotePtr> TwitchChannel::getBttvEmote(const EmoteName &name) con
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<EmotePtr> TwitchChannel::getFfzEmote(const EmoteName &name) const
|
boost::optional<EmotePtr> TwitchChannel::getFfzEmote(
|
||||||
|
const EmoteName &name) const
|
||||||
{
|
{
|
||||||
auto emotes = this->bttvEmotes_.access();
|
auto emotes = this->bttvEmotes_.access();
|
||||||
auto it = emotes->find(name);
|
auto it = emotes->find(name);
|
||||||
|
@ -316,7 +324,8 @@ void TwitchChannel::refreshLiveStatus()
|
||||||
auto roomID = this->getRoomId();
|
auto roomID = this->getRoomId();
|
||||||
|
|
||||||
if (roomID.isEmpty()) {
|
if (roomID.isEmpty()) {
|
||||||
Log("[TwitchChannel:{}] Refreshing live status (Missing ID)", this->getName());
|
Log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
||||||
|
this->getName());
|
||||||
this->setLive(false);
|
this->setLive(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -332,12 +341,13 @@ void TwitchChannel::refreshLiveStatus()
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
//>>>>>>> 9bfbdefd2f0972a738230d5b95a009f73b1dd933
|
//>>>>>>> 9bfbdefd2f0972a738230d5b95a009f73b1dd933
|
||||||
|
|
||||||
request.onSuccess([this, weak = this->weak_from_this()](auto result) -> Outcome {
|
request.onSuccess(
|
||||||
ChannelPtr shared = weak.lock();
|
[this, weak = this->weak_from_this()](auto result) -> Outcome {
|
||||||
if (!shared) return Failure;
|
ChannelPtr shared = weak.lock();
|
||||||
|
if (!shared) return Failure;
|
||||||
|
|
||||||
return this->parseLiveStatus(result.parseRapidJson());
|
return this->parseLiveStatus(result.parseRapidJson());
|
||||||
});
|
});
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
@ -362,8 +372,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stream.HasMember("viewers") || !stream.HasMember("game") || !stream.HasMember("channel") ||
|
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
||||||
!stream.HasMember("created_at")) {
|
!stream.HasMember("channel") || !stream.HasMember("created_at")) {
|
||||||
Log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
|
Log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
|
||||||
this->setLive(false);
|
this->setLive(false);
|
||||||
return Failure;
|
return Failure;
|
||||||
|
@ -372,7 +382,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
const rapidjson::Value &streamChannel = stream["channel"];
|
const rapidjson::Value &streamChannel = stream["channel"];
|
||||||
|
|
||||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) {
|
if (!streamChannel.IsObject() || !streamChannel.HasMember("status")) {
|
||||||
Log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in channel");
|
Log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
|
||||||
|
"channel");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,10 +395,11 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
status->viewerCount = stream["viewers"].GetUint();
|
status->viewerCount = stream["viewers"].GetUint();
|
||||||
status->game = stream["game"].GetString();
|
status->game = stream["game"].GetString();
|
||||||
status->title = streamChannel["status"].GetString();
|
status->title = streamChannel["status"].GetString();
|
||||||
QDateTime since = QDateTime::fromString(stream["created_at"].GetString(), Qt::ISODate);
|
QDateTime since = QDateTime::fromString(
|
||||||
|
stream["created_at"].GetString(), Qt::ISODate);
|
||||||
auto diff = since.secsTo(QDateTime::currentDateTime());
|
auto diff = since.secsTo(QDateTime::currentDateTime());
|
||||||
status->uptime =
|
status->uptime = QString::number(diff / 3600) + "h " +
|
||||||
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
|
QString::number(diff % 3600 / 60) + "m";
|
||||||
|
|
||||||
status->rerun = false;
|
status->rerun = false;
|
||||||
if (stream.HasMember("stream_type")) {
|
if (stream.HasMember("stream_type")) {
|
||||||
|
@ -400,7 +412,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
||||||
|
|
||||||
if (broadcastPlatformValue.IsString()) {
|
if (broadcastPlatformValue.IsString()) {
|
||||||
const char *broadcastPlatform = stream["broadcast_platform"].GetString();
|
const char *broadcastPlatform =
|
||||||
|
stream["broadcast_platform"].GetString();
|
||||||
if (strcmp(broadcastPlatform, "rerun") == 0) {
|
if (strcmp(broadcastPlatform, "rerun") == 0) {
|
||||||
status->rerun = true;
|
status->rerun = true;
|
||||||
}
|
}
|
||||||
|
@ -417,18 +430,20 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
void TwitchChannel::loadRecentMessages()
|
void TwitchChannel::loadRecentMessages()
|
||||||
{
|
{
|
||||||
static QString genericURL =
|
static QString genericURL =
|
||||||
"https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" + getDefaultClientID();
|
"https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" +
|
||||||
|
getDefaultClientID();
|
||||||
|
|
||||||
NetworkRequest request(genericURL.arg(this->getRoomId()));
|
NetworkRequest request(genericURL.arg(this->getRoomId()));
|
||||||
request.makeAuthorizedV5(getDefaultClientID());
|
request.makeAuthorizedV5(getDefaultClientID());
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
|
|
||||||
request.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
request.onSuccess(
|
||||||
ChannelPtr shared = weak.lock();
|
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||||
if (!shared) return Failure;
|
ChannelPtr shared = weak.lock();
|
||||||
|
if (!shared) return Failure;
|
||||||
|
|
||||||
return this->parseRecentMessages(result.parseJson());
|
return this->parseRecentMessages(result.parseJson());
|
||||||
});
|
});
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
@ -442,8 +457,8 @@ Outcome TwitchChannel::parseRecentMessages(const QJsonObject &jsonRoot)
|
||||||
|
|
||||||
for (const auto jsonMessage : jsonMessages) {
|
for (const auto jsonMessage : jsonMessages) {
|
||||||
auto content = jsonMessage.toString().toUtf8();
|
auto content = jsonMessage.toString().toUtf8();
|
||||||
// passing nullptr as the channel makes the message invalid but we don't check for that
|
// passing nullptr as the channel makes the message invalid but we don't
|
||||||
// anyways
|
// check for that anyways
|
||||||
auto message = Communi::IrcMessage::fromData(content, nullptr);
|
auto message = Communi::IrcMessage::fromData(content, nullptr);
|
||||||
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
|
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
|
||||||
assert(privMsg);
|
assert(privMsg);
|
||||||
|
@ -468,7 +483,8 @@ void TwitchChannel::refreshPubsub()
|
||||||
if (roomId.isEmpty()) return;
|
if (roomId.isEmpty()) return;
|
||||||
|
|
||||||
auto account = getApp()->accounts->twitch.getCurrent();
|
auto account = getApp()->accounts->twitch.getCurrent();
|
||||||
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId, account);
|
getApp()->twitch2->pubsub->listenToChannelModerationActions(roomId,
|
||||||
|
account);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::refreshViewerList()
|
void TwitchChannel::refreshViewerList()
|
||||||
|
@ -477,35 +493,40 @@ void TwitchChannel::refreshViewerList()
|
||||||
const auto streamStatus = this->accessStreamStatus();
|
const auto streamStatus = this->accessStreamStatus();
|
||||||
|
|
||||||
if (getSettings()->onlyFetchChattersForSmallerStreamers) {
|
if (getSettings()->onlyFetchChattersForSmallerStreamers) {
|
||||||
if (streamStatus->live && streamStatus->viewerCount > getSettings()->smallStreamerLimit) {
|
if (streamStatus->live &&
|
||||||
|
streamStatus->viewerCount > getSettings()->smallStreamerLimit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get viewer list
|
// get viewer list
|
||||||
NetworkRequest request("https://tmi.twitch.tv/group/user/" + this->getName() + "/chatters");
|
NetworkRequest request("https://tmi.twitch.tv/group/user/" +
|
||||||
|
this->getName() + "/chatters");
|
||||||
|
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
request.onSuccess([this, weak = this->weak_from_this()](auto result) -> Outcome {
|
request.onSuccess(
|
||||||
// channel still exists?
|
[this, weak = this->weak_from_this()](auto result) -> Outcome {
|
||||||
auto shared = weak.lock();
|
// channel still exists?
|
||||||
if (!shared) return Failure;
|
auto shared = weak.lock();
|
||||||
|
if (!shared) return Failure;
|
||||||
|
|
||||||
return this->parseViewerList(result.parseJson());
|
return this->parseViewerList(result.parseJson());
|
||||||
});
|
});
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot)
|
Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot)
|
||||||
{
|
{
|
||||||
static QStringList categories = {"moderators", "staff", "admins", "global_mods", "viewers"};
|
static QStringList categories = {"moderators", "staff", "admins",
|
||||||
|
"global_mods", "viewers"};
|
||||||
|
|
||||||
// parse json
|
// parse json
|
||||||
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
QJsonObject jsonCategories = jsonRoot.value("chatters").toObject();
|
||||||
|
|
||||||
for (const auto &category : categories) {
|
for (const auto &category : categories) {
|
||||||
for (const auto jsonCategory : jsonCategories.value(category).toArray()) {
|
for (const auto jsonCategory :
|
||||||
|
jsonCategories.value(category).toArray()) {
|
||||||
this->completionModel.addUser(jsonCategory.toString());
|
this->completionModel.addUser(jsonCategory.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,8 +536,8 @@ Outcome TwitchChannel::parseViewerList(const QJsonObject &jsonRoot)
|
||||||
|
|
||||||
void TwitchChannel::loadBadges()
|
void TwitchChannel::loadBadges()
|
||||||
{
|
{
|
||||||
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" + this->getRoomId() +
|
auto url = Url{"https://badges.twitch.tv/v1/badges/channels/" +
|
||||||
"/display?language=en"};
|
this->getRoomId() + "/display?language=en"};
|
||||||
NetworkRequest req(url.string);
|
NetworkRequest req(url.string);
|
||||||
req.setCaller(QThread::currentThread());
|
req.setCaller(QThread::currentThread());
|
||||||
|
|
||||||
|
@ -529,19 +550,24 @@ void TwitchChannel::loadBadges()
|
||||||
auto jsonRoot = result.parseJson();
|
auto jsonRoot = result.parseJson();
|
||||||
|
|
||||||
auto _ = jsonRoot["badge_sets"].toObject();
|
auto _ = jsonRoot["badge_sets"].toObject();
|
||||||
for (auto jsonBadgeSet = _.begin(); jsonBadgeSet != _.end(); jsonBadgeSet++) {
|
for (auto jsonBadgeSet = _.begin(); jsonBadgeSet != _.end();
|
||||||
|
jsonBadgeSet++) {
|
||||||
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
|
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
|
||||||
|
|
||||||
auto _ = jsonBadgeSet->toObject()["versions"].toObject();
|
auto _ = jsonBadgeSet->toObject()["versions"].toObject();
|
||||||
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end(); jsonVersion_++) {
|
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
|
||||||
|
jsonVersion_++) {
|
||||||
auto jsonVersion = jsonVersion_->toObject();
|
auto jsonVersion = jsonVersion_->toObject();
|
||||||
auto emote = std::make_shared<Emote>(
|
auto emote = std::make_shared<Emote>(Emote{
|
||||||
Emote{EmoteName{},
|
EmoteName{},
|
||||||
ImageSet{Image::fromUrl({jsonVersion["image_url_1x"].toString()}),
|
ImageSet{Image::fromUrl(
|
||||||
Image::fromUrl({jsonVersion["image_url_2x"].toString()}),
|
{jsonVersion["image_url_1x"].toString()}),
|
||||||
Image::fromUrl({jsonVersion["image_url_4x"].toString()})},
|
Image::fromUrl(
|
||||||
Tooltip{jsonRoot["description"].toString()},
|
{jsonVersion["image_url_2x"].toString()}),
|
||||||
Url{jsonVersion["clickURL"].toString()}});
|
Image::fromUrl(
|
||||||
|
{jsonVersion["image_url_4x"].toString()})},
|
||||||
|
Tooltip{jsonRoot["description"].toString()},
|
||||||
|
Url{jsonVersion["clickURL"].toString()}});
|
||||||
|
|
||||||
versions.emplace(jsonVersion_.key(), emote);
|
versions.emplace(jsonVersion_.key(), emote);
|
||||||
};
|
};
|
||||||
|
@ -555,63 +581,67 @@ void TwitchChannel::loadBadges()
|
||||||
|
|
||||||
void TwitchChannel::loadCheerEmotes()
|
void TwitchChannel::loadCheerEmotes()
|
||||||
{
|
{
|
||||||
auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" + this->getRoomId()};
|
auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" +
|
||||||
|
this->getRoomId()};
|
||||||
auto request = NetworkRequest::twitchRequest(url.string);
|
auto request = NetworkRequest::twitchRequest(url.string);
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
|
|
||||||
request.onSuccess([this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
request.onSuccess(
|
||||||
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||||
|
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
||||||
|
|
||||||
for (auto &set : cheerEmoteSets) {
|
for (auto &set : cheerEmoteSets) {
|
||||||
auto cheerEmoteSet = CheerEmoteSet();
|
auto cheerEmoteSet = CheerEmoteSet();
|
||||||
cheerEmoteSet.regex = QRegularExpression("^" + set.prefix.toLower() + "([1-9][0-9]*)$");
|
cheerEmoteSet.regex = QRegularExpression(
|
||||||
|
"^" + set.prefix.toLower() + "([1-9][0-9]*)$");
|
||||||
|
|
||||||
for (auto &tier : set.tiers) {
|
for (auto &tier : set.tiers) {
|
||||||
CheerEmote cheerEmote;
|
CheerEmote cheerEmote;
|
||||||
|
|
||||||
cheerEmote.color = QColor(tier.color);
|
cheerEmote.color = QColor(tier.color);
|
||||||
cheerEmote.minBits = tier.minBits;
|
cheerEmote.minBits = tier.minBits;
|
||||||
|
|
||||||
// TODO(pajlada): We currently hardcode dark here :|
|
// TODO(pajlada): We currently hardcode dark here :|
|
||||||
// We will continue to do so for now since we haven't had to
|
// We will continue to do so for now since we haven't had to
|
||||||
// solve that anywhere else
|
// solve that anywhere else
|
||||||
|
|
||||||
cheerEmote.animatedEmote =
|
cheerEmote.animatedEmote = std::make_shared<Emote>(
|
||||||
std::make_shared<Emote>(Emote{EmoteName{"cheer emote"},
|
Emote{EmoteName{"cheer emote"},
|
||||||
ImageSet{
|
ImageSet{
|
||||||
tier.images["dark"]["animated"]["1"],
|
tier.images["dark"]["animated"]["1"],
|
||||||
tier.images["dark"]["animated"]["2"],
|
tier.images["dark"]["animated"]["2"],
|
||||||
tier.images["dark"]["animated"]["4"],
|
tier.images["dark"]["animated"]["4"],
|
||||||
},
|
},
|
||||||
Tooltip{}, Url{}});
|
Tooltip{}, Url{}});
|
||||||
cheerEmote.staticEmote =
|
cheerEmote.staticEmote = std::make_shared<Emote>(
|
||||||
std::make_shared<Emote>(Emote{EmoteName{"cheer emote"},
|
Emote{EmoteName{"cheer emote"},
|
||||||
ImageSet{
|
ImageSet{
|
||||||
tier.images["dark"]["static"]["1"],
|
tier.images["dark"]["static"]["1"],
|
||||||
tier.images["dark"]["static"]["2"],
|
tier.images["dark"]["static"]["2"],
|
||||||
tier.images["dark"]["static"]["4"],
|
tier.images["dark"]["static"]["4"],
|
||||||
},
|
},
|
||||||
Tooltip{}, Url{}});
|
Tooltip{}, Url{}});
|
||||||
|
|
||||||
cheerEmoteSet.cheerEmotes.emplace_back(cheerEmote);
|
cheerEmoteSet.cheerEmotes.emplace_back(cheerEmote);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(cheerEmoteSet.cheerEmotes.begin(),
|
||||||
|
cheerEmoteSet.cheerEmotes.end(),
|
||||||
|
[](const auto &lhs, const auto &rhs) {
|
||||||
|
return lhs.minBits < rhs.minBits; //
|
||||||
|
});
|
||||||
|
|
||||||
|
this->cheerEmoteSets_.emplace_back(cheerEmoteSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(cheerEmoteSet.cheerEmotes.begin(), cheerEmoteSet.cheerEmotes.end(),
|
return Success;
|
||||||
[](const auto &lhs, const auto &rhs) {
|
});
|
||||||
return lhs.minBits < rhs.minBits; //
|
|
||||||
});
|
|
||||||
|
|
||||||
this->cheerEmoteSets_.emplace_back(cheerEmoteSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
});
|
|
||||||
|
|
||||||
request.execute();
|
request.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<EmotePtr> TwitchChannel::getTwitchBadge(const QString &set,
|
boost::optional<EmotePtr> TwitchChannel::getTwitchBadge(
|
||||||
const QString &version) const
|
const QString &set, const QString &version) const
|
||||||
{
|
{
|
||||||
auto badgeSets = this->badgeSets_.access();
|
auto badgeSets = this->badgeSets_.access();
|
||||||
auto it = badgeSets->find(set);
|
auto it = badgeSets->find(set);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue