changed to 80 max column

This commit is contained in:
fourtf 2018-08-06 21:17:03 +02:00
parent defa7e41fa
commit f71ff08e68
203 changed files with 3792 additions and 2405 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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()

View file

@ -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();

View file

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

View file

@ -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);

View file

@ -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))
{ {
} }

View file

@ -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 - ";

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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();
} }

View file

@ -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();
} }

View file

@ -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();

View file

@ -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;
} }

View file

@ -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);

View file

@ -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_);

View file

@ -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 (...) {

View file

@ -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);

View file

@ -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();
} }

View file

@ -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) {

View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;
}; };

View file

@ -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,

View file

@ -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)
{ {
} }

View file

@ -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;

View file

@ -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: {

View file

@ -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;
}; };

View file

@ -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)
{ {

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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());

View file

@ -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)
{ {

View file

@ -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;

View file

@ -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());
} }

View file

@ -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,

View file

@ -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;
}; };

View file

@ -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)

View file

@ -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) {

View file

@ -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;
}; };

View file

@ -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()

View file

@ -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)) {

View file

@ -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;

View file

@ -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();

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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>;

View file

@ -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://");
} }

View file

@ -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);

View file

@ -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())));
} }

View file

@ -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

View file

@ -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;
} }
}; };

View file

@ -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);

View file

@ -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();

View file

@ -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;

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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 &currentEmotes) const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{ {
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) {

View file

@ -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 &currentEmotes); const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_; UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_; // UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;

View file

@ -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

View file

@ -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

View file

@ -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));
} }
} }

View file

@ -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();
} }

View file

@ -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_;
}; };

View file

@ -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;

View file

@ -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);

View file

@ -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)
{ {
} }

View file

@ -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);

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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());
} }
} }

View file

@ -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

View file

@ -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;
} }

View file

@ -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

View file

@ -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;

View file

@ -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 |

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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"}, {"\\&gt\\;\\(", "&gt;("}, {"\\&lt\\;3", "&lt;3"}, {"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("},
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"}, {"\\&lt\\;3", "&lt;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;

View file

@ -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;

View file

@ -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);

View file

@ -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:

View file

@ -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;

View file

@ -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:
}; };

View file

@ -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));
} }
} }

View file

@ -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